From 80594731e63ef65741dff490fdf644854b07e29c Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Wed, 10 Jul 2024 03:24:56 +0800 Subject: [PATCH] I don't want to waste time on resolving conflicts --- README.md | 2 +- api/build.gradle.kts | 47 +- .../api/BukkitCustomFishingPlugin.java | 215 +++ .../api/CustomFishingPlugin.java | 162 -- .../customfishing/api/common/Tuple.java | 59 - .../api/data/DataStorageInterface.java | 99 -- .../customfishing/api/data/PlayerData.java | 106 -- .../api/data/user/OfflineUser.java | 77 - .../api/event/CustomFishingReloadEvent.java | 8 +- .../api/event/FishHookLandEvent.java | 105 -- ...nEvent.java => FishingHookStateEvent.java} | 62 +- .../api/event/FishingLootSpawnEvent.java | 67 +- .../api/event/FishingResultEvent.java | 98 +- .../api/event/LavaFishingEvent.java | 97 -- .../customfishing/api/event/RodCastEvent.java | 42 +- .../api/integration/BlockProvider.java | 54 + ...nterface.java => EnchantmentProvider.java} | 8 +- .../api/integration/EntityProvider.java | 43 + .../ExternalProvider.java} | 12 +- .../api/integration/IntegrationManager.java | 121 ++ .../api/integration/ItemProvider.java | 49 + ...velInterface.java => LevelerProvider.java} | 7 +- ...asonInterface.java => SeasonProvider.java} | 6 +- .../api/manager/ActionManager.java | 128 -- .../api/manager/AdventureManager.java | 139 -- .../customfishing/api/manager/BagManager.java | 104 -- .../api/manager/BlockManager.java | 105 -- .../api/manager/CompetitionManager.java | 86 - .../api/manager/EffectManager.java | 115 -- .../api/manager/EntityManager.java | 50 - .../api/manager/FishingManager.java | 113 -- .../api/manager/HookManager.java | 78 - .../api/manager/IntegrationManager.java | 101 -- .../api/manager/ItemManager.java | 207 --- .../api/manager/LootManager.java | 96 -- .../api/manager/MarketManager.java | 136 -- .../api/manager/PlaceholderManager.java | 109 -- .../api/manager/RequirementManager.java | 124 -- .../api/manager/StatisticsManager.java | 43 - .../api/manager/StorageManager.java | 131 -- .../api/manager/TotemManager.java | 32 - .../api/mechanic/GlobalSettings.java | 137 -- .../api/mechanic/MechanicType.java | 101 ++ .../api/mechanic/TempFishingState.java | 80 - .../api/mechanic/action/Action.java | 12 +- .../api/mechanic/action/ActionExpansion.java | 30 +- .../api/mechanic/action/ActionFactory.java | 15 +- .../api/mechanic/action/ActionManager.java | 159 ++ .../api/mechanic/action/ActionTrigger.java | 4 +- .../api/mechanic/action/EmptyAction.java | 19 +- .../bag/BagManager.java} | 35 +- .../api/mechanic/bag/FishingBagHolder.java | 24 + .../api/mechanic/block/BlockConfig.java | 149 +- .../api/mechanic/block/BlockConfigImpl.java | 87 + .../api/mechanic/block/BlockDataModifier.java | 5 +- ...der.java => BlockDataModifierFactory.java} | 5 +- .../{BlockLibrary.java => BlockManager.java} | 19 +- .../mechanic/block/BlockStateModifier.java | 5 +- ...er.java => BlockStateModifierFactory.java} | 5 +- .../block/EmptyBlockDataModifier.java | 14 + .../block/EmptyBlockStateModifier.java | 14 + .../mechanic/competition/ActionBarConfig.java | 54 - .../mechanic/competition/BossBarConfig.java | 85 - .../competition/CompetitionConfig.java | 339 ++-- .../competition/CompetitionConfigImpl.java | 196 +++ .../mechanic/competition/CompetitionGoal.java | 114 +- .../competition/CompetitionManager.java | 40 + .../competition/CompetitionPlayer.java | 70 +- .../competition/FishingCompetition.java | 29 +- .../{Ranking.java => RankingProvider.java} | 8 +- .../{ => info}/AbstractCompetitionInfo.java | 28 +- .../competition/info/ActionBarConfig.java | 117 ++ .../competition/info/ActionBarConfigImpl.java | 62 + .../competition/info/BossBarConfig.java | 151 ++ .../competition/info/BossBarConfigImpl.java | 91 ++ .../api/mechanic/condition/Condition.java | 148 -- .../condition/FishingPreparation.java | 69 - .../api/mechanic/config/BaitConfigParser.java | 147 ++ .../mechanic/config/BlockConfigParser.java | 123 ++ .../api/mechanic/config/ConfigManager.java | 386 +++++ .../api/mechanic/config/ConfigType.java | 169 ++ .../mechanic/config/EnchantConfigParser.java | 92 ++ .../mechanic/config/EntityConfigParser.java | 123 ++ .../api/mechanic/config/GUIItemParser.java | 75 + .../api/mechanic/config/HookConfigParser.java | 163 ++ .../api/mechanic/config/ItemConfigParser.java | 135 ++ .../mechanic/config/MiniGameConfigParser.java | 47 + .../api/mechanic/config/RodConfigParser.java | 148 ++ .../mechanic/config/TotemConfigParser.java | 109 ++ .../api/mechanic/config/UtilConfigParser.java | 146 ++ .../function/BaseEffectParserFunction.java | 41 + .../config/function/BlockParserFunction.java | 41 + .../config/function/ConfigParserFunction.java | 23 + .../EffectModifierParserFunction.java | 41 + .../config/function/EntityParserFunction.java | 41 + .../config/function/EventParserFunction.java | 41 + .../config/function/HookParserFunction.java | 41 + .../config/function/ItemParserFunction.java | 50 + .../config/function/LootParserFunction.java | 41 + .../function/ParserType.java} | 19 +- .../config/function/PriorityFunction.java | 24 +- .../config/function/TotemParserFunction.java | 41 + .../api/mechanic/context/Context.java | 102 ++ .../api/mechanic/context/ContextKeys.java | 119 ++ .../mechanic/context/PlayerContextImpl.java | 109 ++ .../api/mechanic/effect/BaseEffect.java | 36 - .../api/mechanic/effect/Effect.java | 251 ++- .../api/mechanic/effect/EffectCarrier.java | 114 -- .../api/mechanic/effect/EffectImpl.java | 234 +++ .../api/mechanic/effect/EffectManager.java | 30 + .../api/mechanic/effect/EffectModifier.java | 95 +- .../mechanic/effect/EffectModifierImpl.java | 99 ++ .../api/mechanic/effect/EffectProperties.java | 65 + .../api/mechanic/effect/FishingEffect.java | 373 ----- .../api/mechanic/effect/LootBaseEffect.java | 149 ++ .../mechanic/effect/LootBaseEffectImpl.java | 133 ++ .../api/mechanic/entity/EntityConfig.java | 151 +- .../api/mechanic/entity/EntityConfigImpl.java | 105 ++ .../api/mechanic/entity/EntityManager.java | 45 + .../api/mechanic/event/EventCarrier.java | 128 ++ .../api/mechanic/event/EventCarrierImpl.java | 131 ++ .../api/mechanic/event/EventManager.java | 138 ++ .../mechanic/fishing/BaitAnimationTask.java | 55 + .../mechanic/fishing/CustomFishingHook.java | 470 ++++++ .../api/mechanic/fishing/FishingGears.java | 425 +++++ .../fishing/FishingManager.java} | 23 +- .../mechanic/fishing/FishingPreparation.java | 4 +- .../hook/HookMechanic.java} | 23 +- .../fishing/hook/LavaFishingMechanic.java | 234 +++ .../fishing/hook/VanillaMechanic.java | 133 ++ .../fishing/hook/VoidFishingMechanic.java | 218 +++ .../api/mechanic/game/AbstractGame.java | 29 + .../mechanic/game/AbstractGamingPlayer.java | 121 +- .../api/mechanic/game/BasicGameConfig.java | 86 - .../game/{GameInstance.java => Game.java} | 10 +- .../api/mechanic/game/GameBasics.java | 35 + .../api/mechanic/game/GameBasicsImpl.java | 67 + .../api/mechanic/game/GameFactory.java | 5 +- .../game}/GameManager.java | 38 +- .../api/mechanic/game/GameSetting.java | 6 +- .../api/mechanic/game/GamingPlayer.java | 44 +- .../api/mechanic/hook/HookConfig.java | 40 + .../{HookSetting.java => HookConfigImpl.java} | 56 +- .../api/mechanic/hook/HookManager.java | 34 + .../api/mechanic/item/CustomFishingItem.java | 94 ++ .../mechanic/item/CustomFishingItemImpl.java | 90 ++ .../api/mechanic/item/ItemBuilder.java | 99 -- .../{ItemLibrary.java => ItemEditor.java} | 12 +- .../api/mechanic/item/ItemManager.java | 69 + .../api/mechanic/item/tag/TagMap.java | 32 + .../api/mechanic/item/tag/TagMapImpl.java | 191 +++ .../api/mechanic/item/tag/TagValueType.java | 30 + .../api/mechanic/loot/CFLoot.java | 328 ---- .../customfishing/api/mechanic/loot/Loot.java | 242 ++- .../api/mechanic/loot/LootImpl.java | 214 +++ .../api/mechanic/loot/LootManager.java | 38 +- .../api/mechanic/loot/LootType.java | 8 +- .../MarketManager.java} | 24 +- .../api/mechanic/misc/WeightModifier.java | 25 - .../misc/cooldown}/CoolDownManager.java | 24 +- .../placeholder/BukkitPlaceholderManager.java | 111 ++ .../misc/placeholder/PlaceholderAPIUtils.java | 51 + .../misc/placeholder/PlaceholderManager.java | 78 + .../api/mechanic/misc/season/Season.java | 26 + .../api/mechanic/misc/value}/DynamicText.java | 14 +- .../misc/value/ExpressionMathValueImpl.java | 43 + .../api/mechanic/misc/value/MathValue.java | 90 ++ .../misc/value/PlaceholderTextValueImpl.java | 42 + .../misc/value/PlainMathValueImpl.java | 13 +- .../value/PlainTextValueImpl.java} | 23 +- .../misc/value/RangedMathValueImpl.java | 48 + .../api/mechanic/misc/value/TextValue.java | 82 + .../requirement/ConditionalElement.java | 45 + .../requirement/EmptyRequirement.java | 12 +- .../api/mechanic/requirement/Requirement.java | 20 +- .../requirement/RequirementExpansion.java | 4 +- .../requirement/RequirementFactory.java | 20 +- .../requirement/RequirementManager.java | 126 ++ .../mechanic/statistic/FishingStatistics.java | 155 ++ .../statistic/FishingStatisticsImpl.java | 126 ++ .../api/mechanic/statistic/Statistics.java | 176 --- .../api/mechanic/statistic/StatisticsKey.java | 20 - .../mechanic/statistic/StatisticsKeys.java | 21 + .../StatisticsManager.java} | 19 +- .../api/mechanic/totem/TotemConfig.java | 188 +-- .../api/mechanic/totem/TotemConfigImpl.java | 119 ++ .../api/mechanic/totem/TotemManager.java | 35 + .../api/mechanic/totem/TotemParticle.java | 23 +- .../mechanic/totem/block/type/EqualType.java | 4 +- .../api/scheduler/Scheduler.java | 93 -- .../api/storage/DataStorageProvider.java | 49 + .../api/storage/StorageManager.java | 81 + .../api/{data => storage}/StorageType.java | 3 +- .../api/storage/data/EarningData.java | 76 + .../api/{ => storage}/data/InventoryData.java | 18 +- .../api/storage/data/PlayerData.java | 188 +++ .../api/{ => storage}/data/StatisticData.java | 22 +- .../api/storage/user/UserData.java | 159 ++ .../api/storage/user/UserDataImpl.java | 164 ++ .../util/{FontUtils.java => EventUtils.java} | 27 +- .../api/util/InventoryUtils.java | 107 +- .../customfishing/api/util/LogUtils.java | 80 - .../customfishing/api}/util/MoonPhase.java | 2 +- .../customfishing/api/util/OffsetUtils.java | 16 +- .../api/util/ReflectionUtils.java | 90 -- .../api/{common => util}/SimpleLocation.java | 10 +- .../customfishing/api/util/TagUtils.java | 22 + build.gradle.kts | 97 +- common/build.gradle.kts | 38 + .../command/AbstractCommandFeature.java | 85 + .../command/AbstractCommandManager.java | 172 ++ .../common/command/CommandBuilder.java | 58 + .../common/command/CommandConfig.java | 77 + .../common/command/CommandFeature.java | 43 + .../command/CustomFishingCommandManager.java | 56 + .../common/config/ConfigLoader.java | 35 + .../common/config/node/Node.java | 48 +- .../common/dependency/Dependency.java | 357 +++++ .../DependencyDownloadException.java | 2 +- .../common/dependency}/DependencyManager.java | 2 +- .../dependency}/DependencyManagerImpl.java | 96 +- .../dependency/DependencyProperties.java | 61 + .../dependency}/DependencyRegistry.java | 2 +- .../dependency}/DependencyRepository.java | 51 +- .../classloader/IsolatedClassLoader.java | 2 +- .../dependency}/relocation/Relocation.java | 2 +- .../relocation/RelocationHandler.java | 13 +- .../relocation/RelocationHelper.java | 3 +- .../common/helper/AdventureHelper.java | 183 +++ .../common/helper/ExpressionHelper.java | 13 +- .../common/helper/GsonHelper.java | 43 + .../common/helper/VersionHelper.java | 157 +- .../common/item/AbstractItem.java | 185 +++ .../common/item/ComponentKeys.java | 32 + .../customfishing/common/item/Item.java | 79 + .../common/item/ItemFactory.java | 96 ++ .../locale/CustomFishingCaptionFormatter.java | 35 + .../locale/CustomFishingCaptionKeys.java | 27 + .../locale/CustomFishingCaptionProvider.java | 37 + .../common/locale/MessageConstants.java | 170 ++ .../MiniMessageTranslationRegistry.java | 73 + .../MiniMessageTranslationRegistryImpl.java | 235 +++ .../common/locale/MiniMessageTranslator.java | 47 + .../locale/MiniMessageTranslatorImpl.java | 92 ++ .../common/locale/TranslationManager.java | 185 +++ .../common/plugin/CustomFishingPlugin.java | 55 + .../plugin}/classpath/ClassPathAppender.java | 2 +- .../ReflectionClassPathAppender.java | 8 +- .../classpath/URLClassLoaderAccess.java | 12 +- .../common/plugin/feature/Reloadable.java | 22 +- .../plugin/logging/JavaPluginLogger.java | 52 +- .../plugin/logging/Log4jPluginLogger.java | 61 + .../common/plugin/logging/PluginLogger.java | 23 +- .../plugin/logging/Slf4jPluginLogger.java | 61 + .../scheduler/AbstractJavaScheduler.java | 132 ++ .../plugin/scheduler/RegionExecutor.java | 18 +- .../plugin/scheduler/SchedulerAdapter.java | 106 ++ .../plugin/scheduler/SchedulerTask.java | 15 +- .../common/sender/AbstractSender.java | 116 ++ .../common/sender/DummyConsoleSender.java | 62 + .../customfishing/common/sender/Sender.java | 121 ++ .../common/sender/SenderFactory.java | 82 + .../customfishing/common/util/ArrayUtils.java | 67 + .../common}/util/ClassUtils.java | 2 +- .../common}/util/CompletableFutures.java | 2 +- .../customfishing/common/util/Either.java | 60 + .../customfishing/common/util/EitherImpl.java | 130 ++ .../customfishing/common/util/FileUtils.java | 82 + .../customfishing/common/util}/Key.java | 13 +- .../customfishing/common/util/ListUtils.java | 25 +- .../customfishing/common/util}/Pair.java | 2 +- .../common/util/RandomUtils.java | 80 + .../common/util/TriConsumer.java | 11 +- .../common/util/TriFunction.java | 32 + .../customfishing/common/util/Tristate.java | 98 ++ .../customfishing/common/util/Tuple.java | 11 +- .../customfishing/common/util/UUIDUtils.java | 49 + .../common}/util/WeightUtils.java | 8 +- .../main/resources/library-version.properties | 27 + compatibility/build.gradle.kts | 75 + .../libs/AdvancedEnchantments-api.jar | Bin compatibility/libs/AdvancedSeasons-API.jar | Bin 0 -> 64703 bytes .../libs/BattlePass-4.0.6-api.jar | Bin .../libs/ClueScrolls-4.8.7-api.jar | Bin .../libs/RealisticSeasons-api.jar | Bin {plugin => compatibility}/libs/mcMMO-api.jar | Bin .../libs/notquests-5.17.1.jar | Bin .../libs/zaphkiel-2.0.24.jar | Bin .../bukkit/integration}/VaultHook.java | 19 +- .../block/ItemsAdderBlockProvider.java | 16 +- .../block/OraxenBlockProvider.java | 31 +- .../enchant/AdvancedEnchantmentsProvider.java | 19 +- .../enchant/VanillaEnchantmentsProvider.java | 20 +- .../entity/ItemsAdderEntityProvider.java | 14 +- .../entity/MythicEntityProvider.java | 19 +- .../entity/VanillaEntityProvider.java | 12 +- .../item/CustomFishingItemProvider.java | 56 + .../item/ItemsAdderItemProvider.java | 29 +- .../item/MMOItemsItemProvider.java | 22 +- .../item/McMMOTreasureProvider.java | 18 +- .../item/MythicMobsItemProvider.java | 25 +- .../item/NeigeItemsItemProvider.java | 18 +- .../integration/item/OraxenItemProvider.java | 14 +- .../item/ZaphkielItemProvider.java | 20 +- .../level/AuraSkillsLevelerProvider.java | 16 +- .../level/AureliumSkillsProvider.java | 18 +- .../level/EcoJobsLevelerProvider.java | 16 +- .../level/EcoSkillsLevelerProvider.java | 16 +- .../level/JobsRebornLevelerProvider.java | 16 +- .../level/MMOCoreLevelerProvider.java | 16 +- .../level/McMMOLevelerProvider.java | 16 +- .../integration}/papi/CompetitionPapi.java | 40 +- .../integration/papi/CustomFishingPapi.java | 38 +- .../integration/papi/StatisticsPapi.java | 117 ++ .../integration/quest/BattlePassQuest.java | 16 +- .../integration/quest/BetonQuestQuest.java | 13 +- .../integration/quest/ClueScrollsQuest.java | 20 +- .../season/AdvancedSeasonsProvider.java | 50 + .../season/CustomCropsSeasonProvider.java | 19 +- .../season/RealisticSeasonsProvider.java | 43 + {plugin => core}/.gitignore | 0 core/build.gradle.kts | 101 ++ core/libs/Sparrow-Heart-0.29.jar | Bin 0 -> 214942 bytes .../customfishing/bukkit/BukkitBootstrap.java | 42 + .../bukkit/BukkitCustomFishingPluginImpl.java | 246 +++ .../bukkit/action/BukkitActionManager.java | 938 +++++++++++ .../bukkit/adventure}/Languages.java | 2 +- .../ShadedAdventureComponentUtils.java | 2 +- .../ShadedAdventureComponentWrapper.java | 2 +- ...adedAdventureShadedComponentLocalizer.java | 2 +- .../adventure}/ShadedComponentLocalizer.java | 2 +- .../bukkit/bag/BukkitBagManager.java | 252 +++ .../bukkit/block/BukkitBlockManager.java | 344 ++++ .../bukkit/command/BukkitCommandFeature.java | 61 + .../bukkit/command/BukkitCommandManager.java | 87 + .../command/feature/AddStatisticsCommand.java | 69 + .../feature/EditOfflineBagCommand.java | 63 + .../command/feature/EditOnlineBagCommand.java | 51 + .../feature/EndCompetitionCommand.java | 54 + .../command/feature/ExportDataCommand.java | 125 ++ .../command/feature/FishingBagCommand.java | 52 + .../command/feature/GetItemCommand.java | 91 ++ .../command/feature/GiveItemCommand.java | 92 ++ .../command/feature/ImportDataCommand.java | 133 ++ .../command/feature/ImportItemCommand.java | 84 + .../command/feature/OpenBagCommand.java | 58 + .../command/feature/OpenMarketCommand.java | 56 + .../feature/QueryStatisticsCommand.java | 62 + .../bukkit/command/feature/ReloadCommand.java | 50 + .../feature/ResetStatisticsCommand.java | 55 + .../command/feature/SellFishCommand.java | 50 + .../command/feature/SetStatisticsCommand.java | 70 + .../feature/StartCompetitionCommand.java | 69 + .../feature/StopCompetitionCommand.java | 54 + .../command/feature/UnlockDataCommand.java | 54 + .../competition/BukkitCompetitionManager.java | 222 ++- .../bukkit/competition/Competition.java | 279 ++++ .../competition/CompetitionSchedule.java | 2 +- .../actionbar/ActionBarManager.java | 19 +- .../actionbar/ActionBarSender.java | 106 ++ .../competition/bossbar/BossBarManager.java | 19 +- .../competition/bossbar/BossBarSender.java | 119 ++ .../ranking/LocalRankingProvider.java | 14 +- .../ranking/RedisRankingProvider.java | 48 +- .../bukkit/config/BukkitConfigManager.java | 1081 +++++++++++++ .../bukkit/effect/BukkitEffectManager.java | 58 + .../bukkit/entity/BukkitEntityManager.java | 127 ++ .../bukkit/event/BukkitEventManager.java | 95 ++ .../bukkit/fishing/BukkitFishingManager.java | 354 +++++ .../bukkit/game/BukkitGameManager.java | 1057 +++++++++++++ .../bukkit/hook/BukkitHookManager.java | 272 ++++ .../integration/BukkitIntegrationManager.java | 254 +++ .../bukkit/item/BukkitItemFactory.java | 98 ++ .../bukkit/item/BukkitItemManager.java | 484 ++++++ .../item/damage/CustomDurabilityItem.java | 76 + .../bukkit/item/damage/DurabilityItem.java | 27 + .../item/damage/VanillaDurabilityItem.java | 28 +- .../item/impl/ComponentItemFactory.java | 209 +++ .../item/impl/UniversalItemFactory.java | 205 +++ .../bukkit/loot/BukkitLootManager.java | 183 +++ .../bukkit/market/BukkitMarketManager.java | 528 +++++++ .../market/MarketDynamicGUIElement.java | 2 +- .../bukkit/market/MarketGUI.java | 198 +++ .../bukkit}/market/MarketGUIElement.java | 2 +- .../requirement/BukkitRequirementManager.java | 1210 ++++++++++++++ .../scheduler/BukkitSchedulerAdapter.java | 52 + .../bukkit/scheduler/impl/BukkitExecutor.java | 64 + .../bukkit/scheduler/impl/FoliaExecutor.java | 74 + .../bukkit/sender/BukkitSenderFactory.java | 112 ++ .../statistic/BukkitStatisticsManager.java | 58 +- .../bukkit/storage/BukkitStorageManager.java | 365 +++++ .../storage/method/AbstractStorage.java | 32 +- .../database/nosql/MongoDBProvider.java | 107 +- .../method/database/nosql/RedisManager.java | 107 +- .../database/sql/AbstractHikariDatabase.java | 121 ++ .../database/sql/AbstractSQLDatabase.java | 131 +- .../method/database/sql/H2Provider.java | 23 +- .../method/database/sql/MariaDBProvider.java | 10 +- .../method/database/sql/MySQLProvider.java | 10 +- .../method/database/sql/SQLiteProvider.java | 98 +- .../storage/method/file/JsonProvider.java | 17 +- .../storage/method/file/YAMLProvider.java | 144 ++ .../bukkit/totem/ActivatedTotem.java | 88 ++ .../bukkit/totem/BukkitTotemManager.java | 224 +++ .../totem/particle/DustParticleSetting.java | 12 +- .../totem/particle/ParticleSetting.java | 12 +- .../bukkit/util/ItemStackUtils.java | 435 +++++ .../bukkit}/util/LocationUtils.java | 2 +- .../bukkit/util/PlayerUtils.java | 157 ++ core/src/main/resources/commands.yml | 186 +++ .../src/main/resources/config.yml | 237 ++- .../main/resources/contents/bait/default.yml | 0 .../main/resources/contents/block/default.yml | 0 .../resources/contents/category/default.yml | 0 .../contents/competition/default.yml | 0 .../resources/contents/enchant/default.yml | 41 +- .../resources/contents/entity/default.yml | 0 .../main/resources/contents/hook/default.yml | 0 .../main/resources/contents/item/default.yml | 279 ++-- .../resources/contents/minigame/default.yml | 6 +- .../main/resources/contents/rod/default.yml | 16 +- .../main/resources/contents/totem/default.yml | 0 .../main/resources/contents/util/default.yml | 3 +- .../src/main/resources/database.yml | 2 + .../src/main/resources/game-conditions.yml | 4 +- .../src/main/resources/loot-conditions.yml | 6 +- .../src/main/resources/plugin.yml | 6 +- .../src/main/resources/schema/h2.sql | 0 .../src/main/resources/schema/mariadb.sql | 0 .../src/main/resources/schema/mysql.sql | 0 .../src/main/resources/schema/sqlite.sql | 0 core/src/main/resources/translations/en.yml | 90 ++ gradle.properties | 49 +- plugin/build.gradle.kts | 110 -- .../CustomFishingPluginImpl.java | 309 ---- .../adventure/AdventureHelper.java | 250 --- .../command/CommandManagerImpl.java | 173 -- .../command/sub/CompetitionCommand.java | 113 -- .../command/sub/DataCommand.java | 284 ---- .../command/sub/DebugCommand.java | 206 --- .../command/sub/FishingBagCommand.java | 99 -- .../command/sub/GUIEditorCommand.java | 19 - .../command/sub/ItemCommand.java | 164 -- .../command/sub/StatisticsCommand.java | 138 -- .../compatibility/IntegrationManagerImpl.java | 277 ---- .../item/CustomFishingItemImpl.java | 42 - .../papi/PlaceholderManagerImpl.java | 220 --- .../compatibility/papi/StatisticsPapi.java | 114 -- .../compatibility/quest/NotQuestHook.java | 180 --- .../customfishing/gui/SectionPage.java | 25 - .../gui/icon/BackToFolderItem.java | 65 - .../gui/icon/BackToPageItem.java | 53 - .../customfishing/gui/icon/NextPageItem.java | 51 - .../gui/icon/PreviousPageItem.java | 51 - .../gui/icon/ScrollDownItem.java | 48 - .../customfishing/gui/icon/ScrollUpItem.java | 48 - .../gui/icon/property/item/AmountItem.java | 77 - .../gui/icon/property/item/CMDItem.java | 76 - .../icon/property/item/DisplayNameItem.java | 76 - .../icon/property/item/DurabilityItem.java | 76 - .../icon/property/item/EnchantmentItem.java | 86 - .../gui/icon/property/item/Head64Item.java | 94 -- .../gui/icon/property/item/ItemFlagItem.java | 81 - .../gui/icon/property/item/LoreItem.java | 81 - .../gui/icon/property/item/MaterialItem.java | 76 - .../gui/icon/property/item/NBTItem.java | 82 - .../icon/property/item/PreventGrabItem.java | 63 - .../gui/icon/property/item/PriceItem.java | 82 - .../property/item/RandomDurabilityItem.java | 65 - .../gui/icon/property/item/SizeItem.java | 76 - .../gui/icon/property/item/StackableItem.java | 63 - .../property/item/StoredEnchantmentItem.java | 86 - .../gui/icon/property/item/TagItem.java | 63 - .../icon/property/item/UnbreakableItem.java | 63 - .../icon/property/loot/DisableGameItem.java | 63 - .../icon/property/loot/DisableStatsItem.java | 63 - .../icon/property/loot/InstantGameItem.java | 63 - .../gui/icon/property/loot/NickItem.java | 76 - .../gui/icon/property/loot/ScoreItem.java | 76 - .../icon/property/loot/ShowInFinderItem.java | 63 - .../gui/page/file/FileSelector.java | 148 -- .../gui/page/item/AbstractSectionEditor.java | 124 -- .../gui/page/item/BaitEditor.java | 54 - .../gui/page/item/HookEditor.java | 54 - .../gui/page/item/ItemSelector.java | 302 ---- .../gui/page/item/RodEditor.java | 51 - .../gui/page/item/SectionEditor.java | 64 - .../gui/page/property/AmountEditor.java | 138 -- .../page/property/CustomModelDataEditor.java | 155 -- .../gui/page/property/DisplayNameEditor.java | 118 -- .../gui/page/property/DurabilityEditor.java | 141 -- .../gui/page/property/EnchantmentEditor.java | 232 --- .../gui/page/property/ItemFlagEditor.java | 140 -- .../gui/page/property/LoreEditor.java | 200 --- .../gui/page/property/MaterialEditor.java | 155 -- .../gui/page/property/NBTEditor.java | 699 --------- .../gui/page/property/NickEditor.java | 118 -- .../gui/page/property/PriceEditor.java | 189 --- .../gui/page/property/ScoreEditor.java | 134 -- .../gui/page/property/SizeEditor.java | 190 --- .../libraries/dependencies/Dependency.java | 244 --- .../libraries/loader/JarInJarClassLoader.java | 155 -- .../mechanic/action/ActionManagerImpl.java | 1054 ------------- .../mechanic/action/EmptyAction.java | 17 - .../mechanic/bag/BagManagerImpl.java | 250 --- .../mechanic/block/BlockManagerImpl.java | 529 ------- .../mechanic/competition/Competition.java | 381 ----- .../actionbar/ActionBarSender.java | 135 -- .../competition/bossbar/BossBarSender.java | 198 --- .../mechanic/effect/EffectManagerImpl.java | 395 ----- .../mechanic/entity/EntityManagerImpl.java | 181 --- .../mechanic/fishing/BaitAnimationTask.java | 84 - .../mechanic/fishing/FishingManagerImpl.java | 883 ----------- .../fishing/FishingPreparationImpl.java | 193 --- .../mechanic/fishing/HookCheckTimerTask.java | 362 ----- .../mechanic/fishing/LavaEffectTask.java | 89 -- .../mechanic/game/GameManagerImpl.java | 1209 -------------- .../mechanic/hook/HookManagerImpl.java | 336 ---- .../mechanic/item/ItemManagerImpl.java | 1124 ------------- .../mechanic/loot/LootManagerImpl.java | 281 ---- .../mechanic/market/MarketGUI.java | 351 ----- .../mechanic/market/MarketManagerImpl.java | 637 -------- .../mechanic/misc/ChatCatcherManager.java | 77 - .../requirement/ConditionalElement.java | 64 - .../requirement/RequirementManagerImpl.java | 1394 ----------------- .../mechanic/totem/ActivatedTotem.java | 88 -- .../mechanic/totem/TotemManagerImpl.java | 439 ------ .../scheduler/BukkitSchedulerImpl.java | 107 -- .../scheduler/FoliaSchedulerImpl.java | 112 -- .../scheduler/SchedulerImpl.java | 200 --- .../scheduler/SyncScheduler.java | 53 - .../customfishing/setting/CFConfig.java | 176 --- .../customfishing/setting/CFLocale.java | 309 ---- .../storage/StorageManagerImpl.java | 451 ------ .../database/sql/AbstractHikariDatabase.java | 198 --- .../storage/method/file/YAMLImpl.java | 192 --- .../storage/user/OfflineUserImpl.java | 123 -- .../storage/user/OnlineUserImpl.java | 46 - .../customfishing/util/ArmorStandUtils.java | 222 --- .../customfishing/util/ConfigUtils.java | 388 ----- .../customfishing/util/FakeItemUtils.java | 151 -- .../customfishing/util/ItemUtils.java | 505 ------ .../customfishing/util/NBTUtils.java | 281 ---- plugin/src/main/resources/market.yml | 158 -- plugin/src/main/resources/messages/en.yml | 125 -- plugin/src/main/resources/messages/es.yml | 125 -- plugin/src/main/resources/messages/fr.yml | 125 -- plugin/src/main/resources/messages/hu.yml | 126 -- plugin/src/main/resources/messages/kr.yml | 125 -- plugin/src/main/resources/messages/zh_cn.yml | 125 -- settings.gradle.kts | 4 +- 551 files changed, 29876 insertions(+), 31172 deletions(-) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/BukkitCustomFishingPlugin.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/CustomFishingPlugin.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/common/Tuple.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/data/DataStorageInterface.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/data/PlayerData.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/data/user/OfflineUser.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/event/FishHookLandEvent.java rename api/src/main/java/net/momirealms/customfishing/api/event/{FishingLootPreSpawnEvent.java => FishingHookStateEvent.java} (56%) delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/event/LavaFishingEvent.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/integration/BlockProvider.java rename api/src/main/java/net/momirealms/customfishing/api/integration/{EnchantmentInterface.java => EnchantmentProvider.java} (80%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/integration/EntityProvider.java rename api/src/main/java/net/momirealms/customfishing/api/{data/user/OnlineUser.java => integration/ExternalProvider.java} (74%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/integration/IntegrationManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/integration/ItemProvider.java rename api/src/main/java/net/momirealms/customfishing/api/integration/{LevelInterface.java => LevelerProvider.java} (82%) rename api/src/main/java/net/momirealms/customfishing/api/integration/{SeasonInterface.java => SeasonProvider.java} (83%) delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/ActionManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/AdventureManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/BagManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/BlockManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/CompetitionManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/EffectManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/EntityManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/FishingManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/HookManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/IntegrationManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/ItemManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/LootManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/MarketManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/PlaceholderManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/RequirementManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/StatisticsManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/StorageManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/manager/TotemManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/GlobalSettings.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/MechanicType.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/TempFishingState.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionManager.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/ParseUtils.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/action/EmptyAction.java (61%) rename api/src/main/java/net/momirealms/customfishing/api/{manager/VersionManager.java => mechanic/bag/BagManager.java} (56%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfigImpl.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/block/{BlockStateModifierBuilder.java => BlockDataModifierFactory.java} (87%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/block/{BlockLibrary.java => BlockManager.java} (63%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/block/{BlockDataModifierBuilder.java => BlockStateModifierFactory.java} (87%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockDataModifier.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockStateModifier.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/ActionBarConfig.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/BossBarConfig.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfigImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionManager.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/{Ranking.java => RankingProvider.java} (94%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/{ => info}/AbstractCompetitionInfo.java (71%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfig.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfigImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfig.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfigImpl.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/Condition.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/FishingPreparation.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BaitConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BlockConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigType.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EnchantConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EntityConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/GUIItemParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/HookConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ItemConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/MiniGameConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/RodConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/TotemConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/UtilConfigParser.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BaseEffectParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BlockParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ConfigParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EffectModifierParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EntityParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EventParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/HookParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ItemParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/LootParserFunction.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/{misc/Value.java => config/function/ParserType.java} (77%) rename plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/ExpressionValue.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/PriorityFunction.java (57%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/TotemParserFunction.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/context/Context.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/context/ContextKeys.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/context/PlayerContextImpl.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/BaseEffect.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectCarrier.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifierImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectProperties.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/FishingEffect.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffect.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffectImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfigImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrier.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrierImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/BaitAnimationTask.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/CustomFishingHook.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingGears.java rename api/src/main/java/net/momirealms/customfishing/api/{data/LegacyDataStorageInterface.java => mechanic/fishing/FishingManager.java} (62%) rename plugin/src/main/java/net/momirealms/customfishing/gui/Icon.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingPreparation.java (88%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/{entity/EntityLibrary.java => fishing/hook/HookMechanic.java} (65%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/LavaFishingMechanic.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VanillaMechanic.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VoidFishingMechanic.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGame.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/game/BasicGameConfig.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/game/{GameInstance.java => Game.java} (75%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasics.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasicsImpl.java rename api/src/main/java/net/momirealms/customfishing/api/{manager => mechanic/game}/GameManager.java (57%) rename plugin/src/main/java/net/momirealms/customfishing/gui/YamlPage.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSetting.java (85%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfig.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/{HookSetting.java => HookConfigImpl.java} (52%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItem.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItemImpl.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemBuilder.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/item/{ItemLibrary.java => ItemEditor.java} (79%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMap.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMapImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagValueType.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/CFLoot.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootImpl.java rename plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackGroundItem.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootManager.java (50%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/{item/BuildableItem.java => market/MarketManager.java} (60%) delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/WeightModifier.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic/misc => api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/cooldown}/CoolDownManager.java (80%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/BukkitPlaceholderManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderAPIUtils.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/season/Season.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic/misc => api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value}/DynamicText.java (75%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/ExpressionMathValueImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/MathValue.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlaceholderTextValueImpl.java rename plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/PlainValue.java => api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainMathValueImpl.java (71%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/{game/GameSettings.java => misc/value/PlainTextValueImpl.java} (64%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/RangedMathValueImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/TextValue.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/ConditionalElement.java rename {plugin/src/main/java/net/momirealms/customfishing => api/src/main/java/net/momirealms/customfishing/api}/mechanic/requirement/EmptyRequirement.java (68%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementManager.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatistics.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatisticsImpl.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/Statistics.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKey.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKeys.java rename api/src/main/java/net/momirealms/customfishing/api/mechanic/{block/BlockSettings.java => statistic/StatisticsManager.java} (69%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfigImpl.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemManager.java delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/scheduler/Scheduler.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/DataStorageProvider.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/StorageManager.java rename api/src/main/java/net/momirealms/customfishing/api/{data => storage}/StorageType.java (94%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/data/EarningData.java rename api/src/main/java/net/momirealms/customfishing/api/{ => storage}/data/InventoryData.java (62%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/data/PlayerData.java rename api/src/main/java/net/momirealms/customfishing/api/{ => storage}/data/StatisticData.java (64%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/user/UserData.java create mode 100644 api/src/main/java/net/momirealms/customfishing/api/storage/user/UserDataImpl.java rename api/src/main/java/net/momirealms/customfishing/api/util/{FontUtils.java => EventUtils.java} (56%) delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/util/LogUtils.java rename {plugin/src/main/java/net/momirealms/customfishing => api/src/main/java/net/momirealms/customfishing/api}/util/MoonPhase.java (96%) delete mode 100644 api/src/main/java/net/momirealms/customfishing/api/util/ReflectionUtils.java rename api/src/main/java/net/momirealms/customfishing/api/{common => util}/SimpleLocation.java (80%) create mode 100644 api/src/main/java/net/momirealms/customfishing/api/util/TagUtils.java create mode 100644 common/build.gradle.kts create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandFeature.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandManager.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/CommandBuilder.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/CommandConfig.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/CommandFeature.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/command/CustomFishingCommandManager.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/config/ConfigLoader.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/season/RealisticSeasonsImpl.java => common/src/main/java/net/momirealms/customfishing/common/config/node/Node.java (51%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/dependency/Dependency.java rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/DependencyDownloadException.java (96%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/DependencyManager.java (96%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/DependencyManagerImpl.java (68%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyProperties.java rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/DependencyRegistry.java (97%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/DependencyRepository.java (80%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/classloader/IsolatedClassLoader.java (96%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/relocation/Relocation.java (97%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/relocation/RelocationHandler.java (89%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies => common/src/main/java/net/momirealms/customfishing/common/dependency}/relocation/RelocationHelper.java (95%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/helper/AdventureHelper.java rename plugin/src/main/java/net/momirealms/customfishing/gui/ParentPage.java => common/src/main/java/net/momirealms/customfishing/common/helper/ExpressionHelper.java (72%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/helper/GsonHelper.java rename plugin/src/main/java/net/momirealms/customfishing/version/VersionManagerImpl.java => common/src/main/java/net/momirealms/customfishing/common/helper/VersionHelper.java (67%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/item/AbstractItem.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/item/ComponentKeys.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/item/Item.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/item/ItemFactory.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionFormatter.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionKeys.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionProvider.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/MessageConstants.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistry.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistryImpl.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslator.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslatorImpl.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/locale/TranslationManager.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/plugin/CustomFishingPlugin.java rename {plugin/src/main/java/net/momirealms/customfishing/libraries => common/src/main/java/net/momirealms/customfishing/common/plugin}/classpath/ClassPathAppender.java (96%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries => common/src/main/java/net/momirealms/customfishing/common/plugin}/classpath/ReflectionClassPathAppender.java (87%) rename {plugin/src/main/java/net/momirealms/customfishing/libraries => common/src/main/java/net/momirealms/customfishing/common/plugin}/classpath/URLClassLoaderAccess.java (95%) rename api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntitySettings.java => common/src/main/java/net/momirealms/customfishing/common/plugin/feature/Reloadable.java (71%) rename plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java => common/src/main/java/net/momirealms/customfishing/common/plugin/logging/JavaPluginLogger.java (54%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Log4jPluginLogger.java rename plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java => common/src/main/java/net/momirealms/customfishing/common/plugin/logging/PluginLogger.java (71%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Slf4jPluginLogger.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/AbstractJavaScheduler.java rename api/src/main/java/net/momirealms/customfishing/api/scheduler/CancellableTask.java => common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/RegionExecutor.java (71%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerAdapter.java rename plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java => common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerTask.java (84%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/sender/AbstractSender.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/sender/DummyConsoleSender.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/sender/Sender.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/sender/SenderFactory.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/ArrayUtils.java rename {plugin/src/main/java/net/momirealms/customfishing => common/src/main/java/net/momirealms/customfishing/common}/util/ClassUtils.java (98%) rename {plugin/src/main/java/net/momirealms/customfishing => common/src/main/java/net/momirealms/customfishing/common}/util/CompletableFutures.java (98%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/Either.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/EitherImpl.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/FileUtils.java rename {api/src/main/java/net/momirealms/customfishing/api/common => common/src/main/java/net/momirealms/customfishing/common/util}/Key.java (84%) rename api/src/main/java/net/momirealms/customfishing/api/data/EarningData.java => common/src/main/java/net/momirealms/customfishing/common/util/ListUtils.java (59%) rename {api/src/main/java/net/momirealms/customfishing/api/common => common/src/main/java/net/momirealms/customfishing/common/util}/Pair.java (94%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/RandomUtils.java rename api/src/main/java/net/momirealms/customfishing/api/manager/CommandManager.java => common/src/main/java/net/momirealms/customfishing/common/util/TriConsumer.java (84%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/TriFunction.java create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/Tristate.java rename plugin/src/main/java/net/momirealms/customfishing/util/NumberUtils.java => common/src/main/java/net/momirealms/customfishing/common/util/Tuple.java (74%) create mode 100644 common/src/main/java/net/momirealms/customfishing/common/util/UUIDUtils.java rename {api/src/main/java/net/momirealms/customfishing/api => common/src/main/java/net/momirealms/customfishing/common}/util/WeightUtils.java (96%) create mode 100644 common/src/main/resources/library-version.properties create mode 100644 compatibility/build.gradle.kts rename {plugin => compatibility}/libs/AdvancedEnchantments-api.jar (100%) create mode 100644 compatibility/libs/AdvancedSeasons-API.jar rename {plugin => compatibility}/libs/BattlePass-4.0.6-api.jar (100%) rename {plugin => compatibility}/libs/ClueScrolls-4.8.7-api.jar (100%) rename {plugin => compatibility}/libs/RealisticSeasons-api.jar (100%) rename {plugin => compatibility}/libs/mcMMO-api.jar (100%) rename {plugin => compatibility}/libs/notquests-5.17.1.jar (100%) rename {plugin => compatibility}/libs/zaphkiel-2.0.24.jar (100%) rename {plugin/src/main/java/net/momirealms/customfishing/compatibility => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration}/VaultHook.java (65%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/block/ItemsAdderBlockImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/ItemsAdderBlockProvider.java (70%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/block/VanillaBlockImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/OraxenBlockProvider.java (54%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/AdvancedEnchantmentsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/AdvancedEnchantmentsProvider.java (61%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/VanillaEnchantmentsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/VanillaEnchantmentsProvider.java (62%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/ItemsAdderEntityImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/ItemsAdderEntityProvider.java (74%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/MythicEntityImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/MythicEntityProvider.java (78%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/VanillaEntityImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/VanillaEntityProvider.java (71%) create mode 100644 compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/CustomFishingItemProvider.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ItemsAdderItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ItemsAdderItemProvider.java (55%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MMOItemsItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MMOItemsItemProvider.java (66%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/McMMOTreasureImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/McMMOTreasureProvider.java (79%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MythicMobsItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MythicMobsItemProvider.java (64%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/NeigeItemsItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/NeigeItemsItemProvider.java (68%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/OraxenItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/OraxenItemProvider.java (74%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ZaphkielItemImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ZaphkielItemProvider.java (67%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AuraSkillsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AuraSkillsLevelerProvider.java (71%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AureliumSkillsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AureliumSkillsProvider.java (69%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoJobsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoJobsLevelerProvider.java (71%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoSkillsImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoSkillsLevelerProvider.java (69%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/JobsRebornImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/JobsRebornLevelerProvider.java (77%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/MMOCoreImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/MMOCoreLevelerProvider.java (70%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/level/McMMOImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/McMMOLevelerProvider.java (68%) rename {plugin/src/main/java/net/momirealms/customfishing/compatibility => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration}/papi/CompetitionPapi.java (75%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CFPapi.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CustomFishingPapi.java (74%) create mode 100644 compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/StatisticsPapi.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BattlePassHook.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BattlePassQuest.java (84%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BetonQuestQuest.java (94%) rename plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/ClueScrollsQuest.java (73%) create mode 100644 compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/AdvancedSeasonsProvider.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/season/CustomCropsSeasonImpl.java => compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/CustomCropsSeasonProvider.java (60%) create mode 100644 compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/RealisticSeasonsProvider.java rename {plugin => core}/.gitignore (100%) create mode 100644 core/build.gradle.kts create mode 100644 core/libs/Sparrow-Heart-0.29.jar create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/BukkitBootstrap.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/BukkitCustomFishingPluginImpl.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/action/BukkitActionManager.java rename {plugin/src/main/java/net/momirealms/customfishing/adventure/component => core/src/main/java/net/momirealms/customfishing/bukkit/adventure}/Languages.java (97%) rename {plugin/src/main/java/net/momirealms/customfishing/adventure/component => core/src/main/java/net/momirealms/customfishing/bukkit/adventure}/ShadedAdventureComponentUtils.java (94%) rename {plugin/src/main/java/net/momirealms/customfishing/adventure/component => core/src/main/java/net/momirealms/customfishing/bukkit/adventure}/ShadedAdventureComponentWrapper.java (96%) rename {plugin/src/main/java/net/momirealms/customfishing/adventure/component => core/src/main/java/net/momirealms/customfishing/bukkit/adventure}/ShadedAdventureShadedComponentLocalizer.java (97%) rename {plugin/src/main/java/net/momirealms/customfishing/adventure/component => core/src/main/java/net/momirealms/customfishing/bukkit/adventure}/ShadedComponentLocalizer.java (97%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/bag/BukkitBagManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/block/BukkitBlockManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandFeature.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/AddStatisticsCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOfflineBagCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOnlineBagCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EndCompetitionCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ExportDataCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/FishingBagCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GetItemCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GiveItemCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportDataCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportItemCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenBagCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenMarketCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/QueryStatisticsCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ReloadCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ResetStatisticsCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SellFishCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SetStatisticsCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StartCompetitionCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StopCompetitionCommand.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/UnlockDataCommand.java rename plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/competition/BukkitCompetitionManager.java (51%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/competition/Competition.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/competition/CompetitionSchedule.java (98%) rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/competition/actionbar/ActionBarManager.java (86%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarSender.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/competition/bossbar/BossBarManager.java (86%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarSender.java rename plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/LocalRankingImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/LocalRankingProvider.java (95%) rename plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/RedisRankingImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/RedisRankingProvider.java (76%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/config/BukkitConfigManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/effect/BukkitEffectManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/entity/BukkitEntityManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/event/BukkitEventManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/fishing/BukkitFishingManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/game/BukkitGameManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/hook/BukkitHookManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/integration/BukkitIntegrationManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemFactory.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/CustomDurabilityItem.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/DurabilityItem.java rename plugin/src/main/java/net/momirealms/customfishing/compatibility/item/VanillaItemImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/VanillaDurabilityItem.java (58%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/ComponentItemFactory.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/UniversalItemFactory.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/loot/BukkitLootManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/market/BukkitMarketManager.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/market/MarketDynamicGUIElement.java (94%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUI.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/market/MarketGUIElement.java (96%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/requirement/BukkitRequirementManager.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/BukkitSchedulerAdapter.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/BukkitExecutor.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/FoliaExecutor.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/sender/BukkitSenderFactory.java rename plugin/src/main/java/net/momirealms/customfishing/mechanic/statistic/StatisticsManagerImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/statistic/BukkitStatisticsManager.java (59%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/storage/BukkitStorageManager.java rename {plugin/src/main/java/net/momirealms/customfishing => core/src/main/java/net/momirealms/customfishing/bukkit}/storage/method/AbstractStorage.java (65%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/MongoDBImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/MongoDBProvider.java (66%) rename {plugin/src/main/java/net/momirealms/customfishing => core/src/main/java/net/momirealms/customfishing/bukkit}/storage/method/database/nosql/RedisManager.java (73%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractHikariDatabase.java rename {plugin/src/main/java/net/momirealms/customfishing => core/src/main/java/net/momirealms/customfishing/bukkit}/storage/method/database/sql/AbstractSQLDatabase.java (70%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/H2Provider.java (76%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MariaDBImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MariaDBProvider.java (71%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MySQLImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MySQLProvider.java (71%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/SQLiteImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/SQLiteProvider.java (65%) rename plugin/src/main/java/net/momirealms/customfishing/storage/method/file/JsonImpl.java => core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/JsonProvider.java (89%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/YAMLProvider.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/totem/ActivatedTotem.java create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/totem/BukkitTotemManager.java rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/totem/particle/DustParticleSetting.java (84%) rename {plugin/src/main/java/net/momirealms/customfishing/mechanic => core/src/main/java/net/momirealms/customfishing/bukkit}/totem/particle/ParticleSetting.java (87%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/util/ItemStackUtils.java rename {plugin/src/main/java/net/momirealms/customfishing => core/src/main/java/net/momirealms/customfishing/bukkit}/util/LocationUtils.java (97%) create mode 100644 core/src/main/java/net/momirealms/customfishing/bukkit/util/PlayerUtils.java create mode 100644 core/src/main/resources/commands.yml rename {plugin => core}/src/main/resources/config.yml (54%) rename {plugin => core}/src/main/resources/contents/bait/default.yml (100%) rename {plugin => core}/src/main/resources/contents/block/default.yml (100%) rename {plugin => core}/src/main/resources/contents/category/default.yml (100%) rename {plugin => core}/src/main/resources/contents/competition/default.yml (100%) rename {plugin => core}/src/main/resources/contents/enchant/default.yml (51%) rename {plugin => core}/src/main/resources/contents/entity/default.yml (100%) rename {plugin => core}/src/main/resources/contents/hook/default.yml (100%) rename {plugin => core}/src/main/resources/contents/item/default.yml (83%) rename {plugin => core}/src/main/resources/contents/minigame/default.yml (99%) rename {plugin => core}/src/main/resources/contents/rod/default.yml (95%) rename {plugin => core}/src/main/resources/contents/totem/default.yml (100%) rename {plugin => core}/src/main/resources/contents/util/default.yml (98%) rename {plugin => core}/src/main/resources/database.yml (97%) rename {plugin => core}/src/main/resources/game-conditions.yml (99%) rename {plugin => core}/src/main/resources/loot-conditions.yml (98%) rename {plugin => core}/src/main/resources/plugin.yml (83%) rename {plugin => core}/src/main/resources/schema/h2.sql (100%) rename {plugin => core}/src/main/resources/schema/mariadb.sql (100%) rename {plugin => core}/src/main/resources/schema/mysql.sql (100%) rename {plugin => core}/src/main/resources/schema/sqlite.sql (100%) create mode 100644 core/src/main/resources/translations/en.yml delete mode 100644 plugin/build.gradle.kts delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/adventure/AdventureHelper.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/DataCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/DebugCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/FishingBagCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/GUIEditorCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/ItemCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/command/sub/StatisticsCommand.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/compatibility/item/CustomFishingItemImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/PlaceholderManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/StatisticsPapi.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/NotQuestHook.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/SectionPage.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToFolderItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToPageItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/NextPageItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/PreviousPageItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollDownItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollUpItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/AmountItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/CMDItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DisplayNameItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DurabilityItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/EnchantmentItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/Head64Item.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/ItemFlagItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/LoreItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/MaterialItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/NBTItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PreventGrabItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PriceItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/RandomDurabilityItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/SizeItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StackableItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StoredEnchantmentItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/TagItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/UnbreakableItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableGameItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableStatsItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/InstantGameItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/NickItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ScoreItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ShowInFinderItem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/AbstractSectionEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/BaitEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/HookEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/ItemSelector.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/RodEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/item/SectionEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/AmountEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/CustomModelDataEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DisplayNameEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DurabilityEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/EnchantmentEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ItemFlagEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/LoreEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/MaterialEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NBTEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NickEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/PriceEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ScoreEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/gui/page/property/SizeEditor.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/action/EmptyAction.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/bag/BagManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/block/BlockManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarSender.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarSender.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/effect/EffectManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/entity/EntityManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/BaitAnimationTask.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingPreparationImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/HookCheckTimerTask.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/LavaEffectTask.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/game/GameManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/hook/HookManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/item/ItemManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/loot/LootManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUI.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/ChatCatcherManager.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/ConditionalElement.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/RequirementManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/ActivatedTotem.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/scheduler/BukkitSchedulerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/scheduler/FoliaSchedulerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/scheduler/SyncScheduler.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/setting/CFConfig.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/setting/CFLocale.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/storage/StorageManagerImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/storage/method/file/YAMLImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/storage/user/OfflineUserImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/storage/user/OnlineUserImpl.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/util/ArmorStandUtils.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/util/ConfigUtils.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/util/FakeItemUtils.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/util/ItemUtils.java delete mode 100644 plugin/src/main/java/net/momirealms/customfishing/util/NBTUtils.java delete mode 100644 plugin/src/main/resources/market.yml delete mode 100644 plugin/src/main/resources/messages/en.yml delete mode 100644 plugin/src/main/resources/messages/es.yml delete mode 100644 plugin/src/main/resources/messages/fr.yml delete mode 100644 plugin/src/main/resources/messages/hu.yml delete mode 100644 plugin/src/main/resources/messages/kr.yml delete mode 100644 plugin/src/main/resources/messages/zh_cn.yml diff --git a/README.md b/README.md index 628c8ebf..848b560e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Gitbook -CustomFishing is a Paper plugin that provides minigames and a powerful condition & action library for fishing. +CustomFishing is a Paper plugin that provides minigames and a powerful condition & action system for fishing. With the new concept of weight system, CustomFishing brings unlimited customization possibilities and best performance. ## How to build diff --git a/api/build.gradle.kts b/api/build.gradle.kts index c4df5e65..7c3b1d7b 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,11 +1,48 @@ +plugins { + id("io.github.goooler.shadow") version "8.1.7" +} + +repositories { + maven("https://jitpack.io/") // rtag + maven("https://papermc.io/repo/repository/maven-public/") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi +} + dependencies { - compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") - compileOnly("de.tr7zw:item-nbt-api:2.12.4") + implementation(project(":common")) + implementation("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") { + exclude(module = "adventure-bom") + exclude(module = "checker-qual") + exclude(module = "annotations") + } + implementation("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}") + implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") + compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}") + compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}") + compileOnly("com.github.Xiao-MoMi:Sparrow-Heart:${rootProject.properties["sparrow_heart_version"]}") +// compileOnly(files("libs/Sparrow-Heart-${rootProject.properties["sparrow_heart_version"]}.jar")) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) } tasks { shadowJar { - relocate ("de.tr7zw.changeme", "net.momirealms.customfishing.libraries") + relocate("net.kyori", "net.momirealms.customfishing.libraries") + relocate("dev.dejvokep", "net.momirealms.customfishing.libraries") + relocate ("com.saicone.rtag", "net.momirealms.customfishing.libraries.rtag") } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/BukkitCustomFishingPlugin.java b/api/src/main/java/net/momirealms/customfishing/api/BukkitCustomFishingPlugin.java new file mode 100644 index 00000000..72d3e230 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/BukkitCustomFishingPlugin.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api; + +import net.momirealms.customfishing.api.integration.IntegrationManager; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.bag.BagManager; +import net.momirealms.customfishing.api.mechanic.block.BlockManager; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionManager; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.effect.EffectManager; +import net.momirealms.customfishing.api.mechanic.entity.EntityManager; +import net.momirealms.customfishing.api.mechanic.event.EventManager; +import net.momirealms.customfishing.api.mechanic.fishing.FishingManager; +import net.momirealms.customfishing.api.mechanic.game.GameManager; +import net.momirealms.customfishing.api.mechanic.hook.HookManager; +import net.momirealms.customfishing.api.mechanic.item.ItemManager; +import net.momirealms.customfishing.api.mechanic.loot.LootManager; +import net.momirealms.customfishing.api.mechanic.market.MarketManager; +import net.momirealms.customfishing.api.mechanic.misc.cooldown.CoolDownManager; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.PlaceholderManager; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.mechanic.statistic.StatisticsManager; +import net.momirealms.customfishing.api.mechanic.totem.TotemManager; +import net.momirealms.customfishing.api.storage.StorageManager; +import net.momirealms.customfishing.common.dependency.DependencyManager; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import net.momirealms.customfishing.common.plugin.scheduler.AbstractJavaScheduler; +import net.momirealms.customfishing.common.sender.SenderFactory; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.io.File; + +public abstract class BukkitCustomFishingPlugin implements CustomFishingPlugin, Reloadable { + + private static BukkitCustomFishingPlugin instance; + private final Plugin boostrap; + + protected EventManager eventManager; + protected ConfigManager configManager; + protected RequirementManager requirementManager; + protected ActionManager actionManager; + protected SenderFactory senderFactory; + protected PlaceholderManager placeholderManager; + protected AbstractJavaScheduler scheduler; + protected ItemManager itemManager; + protected IntegrationManager integrationManager; + protected CompetitionManager competitionManager; + protected MarketManager marketManager; + protected StorageManager storageManager; + protected LootManager lootManager; + protected CoolDownManager coolDownManager; + protected EntityManager entityManager; + protected BlockManager blockManager; + protected StatisticsManager statisticsManager; + protected EffectManager effectManager; + protected HookManager hookManager; + protected BagManager bagManager; + protected DependencyManager dependencyManager; + protected TranslationManager translationManager; + protected TotemManager totemManager; + protected FishingManager fishingManager; + protected GameManager gameManager; + + public BukkitCustomFishingPlugin(Plugin boostrap) { + if (!boostrap.getName().equals("CustomFishing")) { + throw new IllegalArgumentException("CustomFishing plugin requires custom fishing plugin"); + } + this.boostrap = boostrap; + instance = this; + } + + public static BukkitCustomFishingPlugin getInstance() { + if (instance == null) { + throw new IllegalArgumentException("Plugin not initialized"); + } + return instance; + } + + public EventManager getEventManager() { + return eventManager; + } + + @Override + public ConfigManager getConfigManager() { + return configManager; + } + + public RequirementManager getRequirementManager() { + return requirementManager; + } + + public ActionManager getActionManager() { + return actionManager; + } + + public SenderFactory getSenderFactory() { + return senderFactory; + } + + public File getDataFolder() { + return boostrap.getDataFolder(); + } + + public PlaceholderManager getPlaceholderManager() { + return placeholderManager; + } + + public ItemManager getItemManager() { + return itemManager; + } + + public IntegrationManager getIntegrationManager() { + return integrationManager; + } + + @Override + public AbstractJavaScheduler getScheduler() { + return scheduler; + } + + public CompetitionManager getCompetitionManager() { + return competitionManager; + } + + public MarketManager getMarketManager() { + return marketManager; + } + + public StorageManager getStorageManager() { + return storageManager; + } + + public LootManager getLootManager() { + return lootManager; + } + + public EntityManager getEntityManager() { + return entityManager; + } + + public HookManager getHookManager() { + return hookManager; + } + + public BlockManager getBlockManager() { + return blockManager; + } + + public CoolDownManager getCoolDownManager() { + return coolDownManager; + } + + public StatisticsManager getStatisticsManager() { + return statisticsManager; + } + + public EffectManager getEffectManager() { + return effectManager; + } + + public BagManager getBagManager() { + return bagManager; + } + + public TotemManager getTotemManager() { + return totemManager; + } + + public FishingManager getFishingManager() { + return fishingManager; + } + + public GameManager getGameManager() { + return gameManager; + } + + public Plugin getBoostrap() { + return boostrap; + } + + @Override + public DependencyManager getDependencyManager() { + return dependencyManager; + } + + @Override + public TranslationManager getTranslationManager() { + return translationManager; + } + + public abstract void enable(); + + public abstract void debug(Object message); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/CustomFishingPlugin.java b/api/src/main/java/net/momirealms/customfishing/api/CustomFishingPlugin.java deleted file mode 100644 index 649c0612..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/CustomFishingPlugin.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api; - -import net.momirealms.customfishing.api.manager.*; -import net.momirealms.customfishing.api.scheduler.Scheduler; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; - -public abstract class CustomFishingPlugin extends JavaPlugin { - - protected boolean initialized; - protected Scheduler scheduler; - protected CommandManager commandManager; - protected VersionManager versionManager; - protected ItemManager itemManager; - protected RequirementManager requirementManager; - protected ActionManager actionManager; - protected LootManager lootManager; - protected FishingManager fishingManager; - protected EffectManager effectManager; - protected EntityManager entityManager; - protected BlockManager blockManager; - protected AdventureManager adventure; - protected BagManager bagManager; - protected GameManager gameManager; - protected MarketManager marketManager; - protected IntegrationManager integrationManager; - protected CompetitionManager competitionManager; - protected StorageManager storageManager; - protected PlaceholderManager placeholderManager; - protected StatisticsManager statisticsManager; - protected TotemManager totemManager; - protected HookManager hookManager; - - private static CustomFishingPlugin instance; - - public CustomFishingPlugin() { - instance = this; - } - - public static CustomFishingPlugin get() { - return getInstance(); - } - - @NotNull - public static CustomFishingPlugin getInstance() { - return instance; - } - - public Scheduler getScheduler() { - return scheduler; - } - - public CommandManager getCommandManager() { - return commandManager; - } - - public VersionManager getVersionManager() { - return versionManager; - } - - public RequirementManager getRequirementManager() { - return requirementManager; - } - - public ActionManager getActionManager() { - return actionManager; - } - - public GameManager getGameManager() { - return gameManager; - } - - public BlockManager getBlockManager() { - return blockManager; - } - - public EntityManager getEntityManager() { - return entityManager; - } - - public ItemManager getItemManager() { - return itemManager; - } - - public EffectManager getEffectManager() { - return effectManager; - } - - public MarketManager getMarketManager() { - return marketManager; - } - - public FishingManager getFishingManager() { - return fishingManager; - } - - public AdventureManager getAdventure() { - return adventure; - } - - public BagManager getBagManager() { - return bagManager; - } - - public LootManager getLootManager() { - return lootManager; - } - - public StorageManager getStorageManager() { - return storageManager; - } - - public TotemManager getTotemManager() { - return totemManager; - } - - public HookManager getHookManager() { - return hookManager; - } - - public IntegrationManager getIntegrationManager() { - return integrationManager; - } - - public StatisticsManager getStatisticsManager() { - return statisticsManager; - } - - public PlaceholderManager getPlaceholderManager() { - return placeholderManager; - } - - public CompetitionManager getCompetitionManager() { - return competitionManager; - } - - public abstract void reload(); - - public abstract YamlConfiguration getConfig(String file); - - public abstract boolean isHookedPluginEnabled(String plugin); - - public abstract void debug(String message); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/common/Tuple.java b/api/src/main/java/net/momirealms/customfishing/api/common/Tuple.java deleted file mode 100644 index 2d50e10d..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/common/Tuple.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.common; - -public class Tuple { - - private L left; - private M mid; - private R right; - - public Tuple(L left, M mid, R right) { - this.left = left; - this.mid = mid; - this.right = right; - } - - public static Tuple of(final L left, final M mid, final R right) { - return new Tuple<>(left, mid, right); - } - - public L getLeft() { - return left; - } - - public void setLeft(L left) { - this.left = left; - } - - public M getMid() { - return mid; - } - - public void setMid(M mid) { - this.mid = mid; - } - - public R getRight() { - return right; - } - - public void setRight(R right) { - this.right = right; - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/DataStorageInterface.java b/api/src/main/java/net/momirealms/customfishing/api/data/DataStorageInterface.java deleted file mode 100644 index 31757898..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/data/DataStorageInterface.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.data; - -import net.momirealms.customfishing.api.data.user.OfflineUser; - -import java.util.Collection; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public interface DataStorageInterface { - - /** - * Initialize the data resource - */ - void initialize(); - - /** - * Close the data resource - */ - void disable(); - - /** - * Get the storage data source type - * - * @return {@link StorageType} - */ - StorageType getStorageType(); - - /** - * Retrieve a player's data - * - * @param uuid The UUID of the player. - * @param lock Whether to lock the player data during retrieval. - * @return A CompletableFuture containing the optional player data. - */ - CompletableFuture> getPlayerData(UUID uuid, boolean lock); - - /** - * Update a player's data - * - * @param uuid The UUID of the player. - * @param playerData The player data to update. - * @param unlock Whether to unlock the player data after updating. - * @return A CompletableFuture indicating the success of the update. - */ - CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock); - - /** - * Update or insert a player's data into the SQL database. - * - * @param uuid The UUID of the player. - * @param playerData The player data to update or insert. - * @param unlock Whether to unlock the player data after updating or inserting. - * @return A CompletableFuture indicating the success of the operation. - */ - CompletableFuture updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock); - - /** - * Update data for multiple players - * - * @param users A collection of OfflineUser objects representing players. - * @param unlock Whether to unlock the player data after updating. - */ - void updateManyPlayersData(Collection users, boolean unlock); - - /** - * Lock or unlock a player's data in the SQL database. - * - * @param uuid The UUID of the player. - * @param lock Whether to lock or unlock the player data. - */ - void lockOrUnlockPlayerData(UUID uuid, boolean lock); - - /** - * Get a set of unique user UUIDs - * - * @param legacy Whether to include legacy data in the retrieval. - * @return A set of unique user UUIDs. - */ - Set getUniqueUsers(boolean legacy); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/PlayerData.java b/api/src/main/java/net/momirealms/customfishing/api/data/PlayerData.java deleted file mode 100644 index 78e1c7b5..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/data/PlayerData.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.data; - -import com.google.gson.annotations.SerializedName; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class PlayerData { - - @SerializedName("name") - protected String name; - @SerializedName("stats") - protected StatisticData statisticsData; - @SerializedName("bag") - protected InventoryData bagData; - @SerializedName("trade") - protected EarningData earningData; - - public static PlayerData LOCKED = empty(); - - public static Builder builder() { - return new Builder(); - } - - public static PlayerData empty() { - return new Builder() - .setBagData(InventoryData.empty()) - .setEarningData(EarningData.empty()) - .setStats(StatisticData.empty()) - .build(); - } - - public static class Builder { - - private final PlayerData playerData; - - public Builder() { - this.playerData = new PlayerData(); - } - - @NotNull - public Builder setName(@Nullable String name) { - this.playerData.name = name; - return this; - } - - @NotNull - public Builder setStats(@Nullable StatisticData statisticsData) { - this.playerData.statisticsData = statisticsData; - return this; - } - - @NotNull - public Builder setBagData(@Nullable InventoryData inventoryData) { - this.playerData.bagData = inventoryData; - return this; - } - - @NotNull - public Builder setEarningData(@Nullable EarningData earningData) { - this.playerData.earningData = earningData; - return this; - } - - @NotNull - public PlayerData build() { - return this.playerData; - } - } - - public StatisticData getStatistics() { - return statisticsData; - } - - public InventoryData getBagData() { - return bagData; - } - - public EarningData getEarningData() { - return earningData; - } - - public String getName() { - return name; - } - - public boolean isLocked() { - return this == LOCKED; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/user/OfflineUser.java b/api/src/main/java/net/momirealms/customfishing/api/data/user/OfflineUser.java deleted file mode 100644 index 2322379c..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/data/user/OfflineUser.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.data.user; - -import net.momirealms.customfishing.api.data.EarningData; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; - -import java.util.UUID; - -public interface OfflineUser { - - /** - * Get the username - * - * @return user name - */ - String getName(); - - /** - * Get the user's uuid - * - * @return uuid - */ - UUID getUUID(); - - /** - * Get the fishing bag holder - * - * @return fishing bag holder - */ - FishingBagHolder getHolder(); - - /** - * Get the player's earning data - * - * @return earning data - */ - EarningData getEarningData(); - - /** - * Get the player's statistics - * - * @return statistics - */ - Statistics getStatistics(); - - /** - * If the user is online on current server - * - * @return online or not - */ - boolean isOnline(); - - /** - * Get the data in another minimized format that can be saved - * - * @return player data - */ - PlayerData getPlayerData(); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/CustomFishingReloadEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/CustomFishingReloadEvent.java index 1219d485..41defa68 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/CustomFishingReloadEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/CustomFishingReloadEvent.java @@ -17,7 +17,7 @@ package net.momirealms.customfishing.api.event; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; @@ -25,9 +25,9 @@ import org.jetbrains.annotations.NotNull; public class CustomFishingReloadEvent extends Event { private static final HandlerList handlerList = new HandlerList(); - private final CustomFishingPlugin plugin; + private final BukkitCustomFishingPlugin plugin; - public CustomFishingReloadEvent(CustomFishingPlugin plugin) { + public CustomFishingReloadEvent(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; } @@ -41,7 +41,7 @@ public class CustomFishingReloadEvent extends Event { return getHandlerList(); } - public CustomFishingPlugin getPluginInstance() { + public BukkitCustomFishingPlugin getPluginInstance() { return plugin; } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/FishHookLandEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/FishHookLandEvent.java deleted file mode 100644 index 8adeace1..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/event/FishHookLandEvent.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.event; - -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -import org.jetbrains.annotations.NotNull; - -/** - * This class represents an event that occurs when a fishing hook lands in either lava or water. - */ -public class FishHookLandEvent extends PlayerEvent { - - private static final HandlerList handlerList = new HandlerList(); - private final Target target; - private final FishHook fishHook; - private final Effect effect; - private boolean isFirst; - - /** - * Constructs a new FishHookLandEvent. - * - * @param who The player who triggered the event. - * @param target The target where the fishing hook has landed (LAVA or WATER). - * @param hook The fishing hook entity. - * @param initialEffect The initial effect - */ - public FishHookLandEvent(@NotNull Player who, Target target, FishHook hook, boolean isFirst, Effect initialEffect) { - super(who); - this.target = target; - this.fishHook = hook; - this.effect = initialEffect; - this.isFirst = isFirst; - } - - /** - * Gets the target where the fishing hook has landed. - * - * @return The target, which can be either LAVA or WATER. - */ - public Target getTarget() { - return target; - } - - /** - * Gets the fish hook bukkit entity - * - * @return fish hook - */ - public FishHook getFishHook() { - return fishHook; - } - - public static HandlerList getHandlerList() { - return handlerList; - } - - /** - * Is the first try of one fishing catch - * - * @return is first try - */ - public boolean isFirst() { - return isFirst; - } - - /** - * Get the fishing effect - * It's not advised to modify this value without checking "isFirst()" since this event can be trigger multiple times in one fishing catch - * - * @return fishing effect - */ - public Effect getEffect() { - return effect; - } - - @NotNull - @Override - public HandlerList getHandlers() { - return getHandlerList(); - } - - public enum Target { - LAVA, - WATER - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootPreSpawnEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/FishingHookStateEvent.java similarity index 56% rename from api/src/main/java/net/momirealms/customfishing/api/event/FishingLootPreSpawnEvent.java rename to api/src/main/java/net/momirealms/customfishing/api/event/FishingHookStateEvent.java index 41409832..cde4df5c 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootPreSpawnEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/FishingHookStateEvent.java @@ -17,52 +17,46 @@ package net.momirealms.customfishing.api.event; -import org.bukkit.Location; +import org.bukkit.entity.FishHook; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -public class FishingLootPreSpawnEvent extends PlayerEvent implements Cancellable { +public class FishingHookStateEvent extends PlayerEvent { private static final HandlerList handlerList = new HandlerList(); - private final Location location; - private final ItemStack itemStack; - private boolean isCancelled; + private final FishHook fishHook; + private final State state; - public FishingLootPreSpawnEvent(@NotNull Player who, Location location, ItemStack itemStack) { + public FishingHookStateEvent(@NotNull Player who, FishHook hook, State state) { super(who); - this.itemStack = itemStack; - this.location = location; - this.isCancelled = false; - } - - @Override - public boolean isCancelled() { - return isCancelled; - } - - @Override - public void setCancelled(boolean cancel) { - isCancelled = cancel; - } - - public Location getLocation() { - return location; - } - - public ItemStack getItemStack() { - return itemStack; - } - - @Override - public @NotNull HandlerList getHandlers() { - return handlerList; + this.fishHook = hook; + this.state = state; } public static HandlerList getHandlerList() { return handlerList; } + + public FishHook getFishHook() { + return fishHook; + } + + public State getState() { + return state; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } + + public enum State { + BITE, + ESCAPE, + LURE, + LAND + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootSpawnEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootSpawnEvent.java index 0e067137..ecaadf5f 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootSpawnEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/FishingLootSpawnEvent.java @@ -17,44 +17,73 @@ package net.momirealms.customfishing.api.event; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.loot.Loot; import org.bukkit.Location; -import org.bukkit.entity.Item; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class FishingLootSpawnEvent extends PlayerEvent implements Cancellable { +public class FishingLootSpawnEvent extends PlayerEvent { private static final HandlerList handlerList = new HandlerList(); private final Location location; - private final Item item; - private boolean isCancelled; + private final Entity entity; + private final Loot loot; + private final Context context; + private boolean skipActions; + private boolean summonEntity; - public FishingLootSpawnEvent(@NotNull Player who, Location location, Item item) { - super(who); - this.item = item; + public FishingLootSpawnEvent(@NotNull Context context, Location location, Loot loot, @Nullable Entity entity) { + super(context.getHolder()); + this.entity = entity; + this.loot = loot; this.location = location; - this.isCancelled = false; + this.skipActions = false; + this.summonEntity = true; + this.context = context; } - @Override - public boolean isCancelled() { - return isCancelled; - } - - @Override - public void setCancelled(boolean cancel) { - isCancelled = cancel; + public Context getContext() { + return context; } public Location getLocation() { return location; } - public Item getItem() { - return item; + /** + * Get the loot entity + * + * @return entity + */ + @Nullable + public Entity getEntity() { + return entity; + } + + @NotNull + public Loot getLoot() { + return loot; + } + + public boolean summonEntity() { + return summonEntity; + } + + public void summonEntity(boolean summonEntity) { + this.summonEntity = summonEntity; + } + + public boolean skipActions() { + return skipActions; + } + + public void skipActions(boolean skipActions) { + this.skipActions = skipActions; } @Override diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java index 2b2b96ee..2dd64e43 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java @@ -17,6 +17,8 @@ package net.momirealms.customfishing.api.event; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; import net.momirealms.customfishing.api.mechanic.loot.Loot; import org.bukkit.entity.FishHook; import org.bukkit.entity.Player; @@ -24,14 +26,9 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.Map; import java.util.Optional; -/** - * This class represents an event that occurs when a player gets a result from fishing. - */ public class FishingResultEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlerList = new HandlerList(); @@ -39,21 +36,12 @@ public class FishingResultEvent extends PlayerEvent implements Cancellable { private final Result result; private final Loot loot; private final FishHook fishHook; - private final Map args; + private Context context; - /** - * Constructs a new FishingResultEvent. - * - * @param who The player who triggered the event. - * @param result The result of the fishing action (SUCCESS or FAILURE). - * @param loot The loot received from fishing. - * @param args A map of placeholders and their corresponding values. - */ - public FishingResultEvent(@NotNull Player who, Result result, FishHook fishHook, Loot loot, Map args) { - super(who); + public FishingResultEvent(@NotNull Context context, Result result, FishHook fishHook, Loot loot) { + super(context.getHolder()); this.result = result; this.loot = loot; - this.args = args; this.fishHook = fishHook; } @@ -77,87 +65,31 @@ public class FishingResultEvent extends PlayerEvent implements Cancellable { isCancelled = cancel; } - /** - * Gets the value associated with a specific argument key. - * Usage example event.getArg("{x}") - * - * @param key The argument key enclosed in curly braces, e.g., "{amount}". - * @return The value associated with the argument key, or null if not found. - */ - public String getArg(String key) { - return args.get(key); - } - - /** - * Set the value associated with a specific argument key. - * @param key key - * @param value value - * @return previous value - */ - @Nullable - public String setArg(String key, String value) { - return args.put(key, value); - } - - /** - * Gets the result of the fishing action. - * - * @return The fishing result, which can be either SUCCESS or FAILURE. - */ public Result getResult() { return result; } - /** - * Get the fish hook entity. - * - * @return fish hook - */ public FishHook getFishHook() { return fishHook; } - /** - * Gets the loot received from fishing. - * - * @return The loot obtained from the fishing action. - */ public Loot getLoot() { return loot; } - /** - * Gets the amount of loot received. - * This value is determined by the "multiple-loot" effect. - * If you want to get the amount of item spawned, listen to FishingLootSpawnEvent - * - * @return The amount of loot received, or 1 if the loot is block or entity - */ - public int getAmount() { - return Integer.parseInt(Optional.ofNullable(getArg("{amount}")).orElse("1")); - } - - /** - * Set the loot amount (Only works for items) - * - * @param amount amount - */ - public void setAmount(int amount) { - setArg("{amount}", String.valueOf(amount)); - } - - /** - * Set the score to get in competition - * - * @param score score - */ public void setScore(double score) { - setArg("{SCORE}", String.valueOf(score)); + context.arg(ContextKeys.CUSTOM_SCORE, score); + } + + public Context getContext() { + return context; + } + + public int getAmount() { + if (result == Result.FAILURE) return 0; + return Optional.ofNullable(context.arg(ContextKeys.AMOUNT)).orElse(1); } - /** - * An enumeration representing possible fishing results (SUCCESS or FAILURE). - */ public enum Result { SUCCESS, FAILURE diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/LavaFishingEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/LavaFishingEvent.java deleted file mode 100644 index bc961c17..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/event/LavaFishingEvent.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.event; - -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.HandlerList; -import org.bukkit.event.player.PlayerEvent; -import org.jetbrains.annotations.NotNull; - -/** - * This class represents an event that occurs when a player fishes in lava. - */ -public class LavaFishingEvent extends PlayerEvent implements Cancellable { - - private static final HandlerList handlerList = new HandlerList(); - private final State state; - private boolean isCancelled; - private final FishHook hook; - - /** - * Constructs a new LavaFishingEvent. - * - * @param who The player who triggered the event. - * @param state The state of the fishing action (REEL_IN, CAUGHT_FISH, or BITE). - * @param hook The FishHook entity associated with the fishing action. - */ - public LavaFishingEvent(@NotNull Player who, State state, FishHook hook) { - super(who); - this.state = state; - this.isCancelled = false; - this.hook = hook; - } - - /** - * Gets the state of the fishing action. - * - * @return The fishing state, which can be REEL_IN, CAUGHT_FISH, or BITE. - */ - public State getState() { - return state; - } - - /** - * Gets the FishHook entity associated with the fishing action. - * - * @return The FishHook entity used in the fishing action. - */ - public FishHook getHook() { - return hook; - } - - public static HandlerList getHandlerList() { - return handlerList; - } - - @NotNull - @Override - public HandlerList getHandlers() { - return getHandlerList(); - } - - @Override - public boolean isCancelled() { - return isCancelled; - } - - @Override - public void setCancelled(boolean cancel) { - isCancelled = cancel; - } - - /** - * An enumeration representing possible states of the fishing action (REEL_IN, CAUGHT_FISH, BITE). - */ - public enum State { - REEL_IN, - CAUGHT_FISH, - BITE - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/RodCastEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/RodCastEvent.java index 7d496973..6ae4b1b2 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/RodCastEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/RodCastEvent.java @@ -17,8 +17,7 @@ package net.momirealms.customfishing.api.event; -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.fishing.FishingGears; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; @@ -30,24 +29,15 @@ import org.jetbrains.annotations.NotNull; */ public class RodCastEvent extends PlayerEvent implements Cancellable { - private final Effect effect; + private final FishingGears gears; private boolean isCancelled; private final PlayerFishEvent event; - private final FishingPreparation preparation; private static final HandlerList handlerList = new HandlerList(); - /** - * Constructs a new RodCastEvent. - * - * @param event The original PlayerFishEvent that triggered the rod cast. - * @param fishingPreparation The fishing preparation associated with the rod cast. - * @param effect The effect associated with the fishing rod cast. - */ - public RodCastEvent(PlayerFishEvent event, FishingPreparation fishingPreparation, Effect effect) { + public RodCastEvent(PlayerFishEvent event, FishingGears gears) { super(event.getPlayer()); - this.effect = effect; + this.gears = gears; this.event = event; - this.preparation = fishingPreparation; } @Override @@ -56,9 +46,10 @@ public class RodCastEvent extends PlayerEvent implements Cancellable { } /** - * Cancelling this event would not cancel the bukkit PlayerFishEvent + * Cancelling this event would disable CustomFishing mechanics + * If you want to prevent players from casting, use {@link #getBukkitPlayerFishEvent()} instead * - * @param cancel true if you wish to cancel this event + * @param cancel true if you want to cancel this event */ @Override public void setCancelled(boolean cancel) { @@ -69,15 +60,6 @@ public class RodCastEvent extends PlayerEvent implements Cancellable { return handlerList; } - /** - * Gets the fishing preparation associated with the rod cast. - * - * @return The FishingPreparation associated with the rod cast. - */ - public FishingPreparation getPreparation() { - return preparation; - } - @NotNull @Override public HandlerList getHandlers() { @@ -85,16 +67,16 @@ public class RodCastEvent extends PlayerEvent implements Cancellable { } /** - * Gets the effect associated with the fishing rod cast. + * Get the {@link FishingGears} * - * @return The Effect associated with the rod cast. + * @return fishing gears */ - public Effect getEffect() { - return effect; + public FishingGears getGears() { + return gears; } /** - * Gets the original PlayerFishEvent that triggered the rod cast. + * Gets the original PlayerFishEvent that triggered the {@link RodCastEvent}. * * @return The original PlayerFishEvent. */ diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/BlockProvider.java b/api/src/main/java/net/momirealms/customfishing/api/integration/BlockProvider.java new file mode 100644 index 00000000..f0bed0fd --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/BlockProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.integration; + +import net.momirealms.customfishing.api.mechanic.block.BlockDataModifier; +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Interface for providing custom block data and retrieving block IDs within the CustomFishing plugin. + * Extends the ExternalProvider interface. + */ +public interface BlockProvider extends ExternalProvider { + + /** + * Generates BlockData for a given player based on a block ID and a list of modifiers. + * + * @param context The player for whom the block data is generated. + * @param id The unique identifier for the block. + * @param modifiers A list of {@link BlockDataModifier} objects to apply to the block data. + * @return The generated {@link BlockData} for the specified block ID and modifiers. + */ + BlockData blockData(@NotNull Context context, @NotNull String id, List modifiers); + + /** + * Retrieves the unique block ID associated with a given block. + * + * @param block The block for which the ID is to be retrieved. + * @return The unique block ID as a string, or null if no ID is associated with the block. + */ + @Nullable + String blockID(@NotNull Block block); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentInterface.java b/api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentProvider.java similarity index 80% rename from api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentInterface.java rename to api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentProvider.java index 04a92876..d0daba40 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentInterface.java +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/EnchantmentProvider.java @@ -17,19 +17,19 @@ package net.momirealms.customfishing.api.integration; +import net.momirealms.customfishing.common.util.Pair; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.List; -public interface EnchantmentInterface { +public interface EnchantmentProvider extends ExternalProvider { /** * Get a list of enchantments with level for itemStack - * format: plugin:enchantment:level - * example: minecraft:sharpness:5 * * @param itemStack itemStack * @return enchantment list */ - List getEnchants(ItemStack itemStack); + List> getEnchants(@NotNull ItemStack itemStack); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/EntityProvider.java b/api/src/main/java/net/momirealms/customfishing/api/integration/EntityProvider.java new file mode 100644 index 00000000..9cfea439 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/EntityProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.integration; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public interface EntityProvider extends ExternalProvider { + + /** + * Spawns an entity at the specified location with the given properties. + * + * @param location The location where the entity will be spawned. + * @param id The identifier of the entity to be spawned. + * @param propertyMap A map containing additional properties for the entity. + * @return The spawned entity. + */ + @NotNull + Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map propertyMap); + + default Entity spawn(@NotNull Location location, @NotNull String id) { + return spawn(location, id, new HashMap<>()); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/user/OnlineUser.java b/api/src/main/java/net/momirealms/customfishing/api/integration/ExternalProvider.java similarity index 74% rename from api/src/main/java/net/momirealms/customfishing/api/data/user/OnlineUser.java rename to api/src/main/java/net/momirealms/customfishing/api/integration/ExternalProvider.java index a26fe65a..32cd5667 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/user/OnlineUser.java +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/ExternalProvider.java @@ -15,16 +15,14 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data.user; +package net.momirealms.customfishing.api.integration; -import org.bukkit.entity.Player; - -public interface OnlineUser extends OfflineUser { +public interface ExternalProvider { /** - * Get the bukkit player + * Gets the identification of the external provider. * - * @return player + * @return The identification string of the external provider. */ - Player getPlayer(); + String identifier(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/IntegrationManager.java b/api/src/main/java/net/momirealms/customfishing/api/integration/IntegrationManager.java new file mode 100644 index 00000000..6468d47b --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/IntegrationManager.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.integration; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Interface for managing integration providers in the custom fishing API. + * This allows for the registration and retrieval of various types of providers + * such as Leveler, Enchantment, and Season providers. + */ +public interface IntegrationManager extends Reloadable { + + /** + * Registers a LevelerProvider. + * + * @param levelerProvider the LevelerProvider to register + * @return true if registration is successful, false otherwise + */ + boolean registerLevelerProvider(@NotNull LevelerProvider levelerProvider); + + /** + * Unregisters a LevelerProvider by its ID. + * + * @param id the ID of the LevelerProvider to unregister + * @return true if unregistration is successful, false otherwise + */ + boolean unregisterLevelerProvider(@NotNull String id); + + /** + * Registers an EnchantmentProvider. + * + * @param enchantmentProvider the EnchantmentProvider to register + * @return true if registration is successful, false otherwise + */ + boolean registerEnchantmentProvider(@NotNull EnchantmentProvider enchantmentProvider); + + /** + * Unregisters an EnchantmentProvider by its ID. + * + * @param id the ID of the EnchantmentProvider to unregister + * @return true if unregistration is successful, false otherwise + */ + boolean unregisterEnchantmentProvider(@NotNull String id); + + /** + * Registers a SeasonProvider. + * + * @param seasonProvider the SeasonProvider to register + * @return true if registration is successful, false otherwise + */ + boolean registerSeasonProvider(@NotNull SeasonProvider seasonProvider); + + /** + * Unregisters the SeasonProvider. + * + * @return true if unregistration is successful, false otherwise + */ + boolean unregisterSeasonProvider(); + + boolean registerEntityProvider(@NotNull EntityProvider entityProvider); + + boolean unregisterEntityProvider(@NotNull String id); + + /** + * Retrieves a registered LevelerProvider by its ID. + * + * @param id the ID of the LevelerProvider to retrieve + * @return the LevelerProvider if found, or null if not found + */ + @Nullable + LevelerProvider getLevelerProvider(String id); + + /** + * Retrieves a registered EnchantmentProvider by its ID. + * + * @param id the ID of the EnchantmentProvider to retrieve + * @return the EnchantmentProvider if found, or null if not found + */ + @Nullable + EnchantmentProvider getEnchantmentProvider(String id); + + /** + * Retrieves a registered SeasonProvider by its ID. + * + * @return the SeasonProvider if found, or null if not found + */ + @Nullable + SeasonProvider getSeasonProvider(); + + List> getEnchantments(ItemStack itemStack); + + boolean registerItemProvider(@NotNull ItemProvider itemProvider); + + boolean unregisterItemProvider(@NotNull String id); + + boolean registerBlockProvider(@NotNull BlockProvider block); + + boolean unregisterBlockProvider(@NotNull String id); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/ItemProvider.java b/api/src/main/java/net/momirealms/customfishing/api/integration/ItemProvider.java new file mode 100644 index 00000000..e5a081d1 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/ItemProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.integration; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface representing a provider for custom items in the Custom Fishing plugin. + * This interface allows for building items for players and retrieving item IDs from item stacks. + */ +public interface ItemProvider extends ExternalProvider { + + /** + * Builds an ItemStack for a player based on a specified item ID. + * + * @param player the player for whom the item is being built. + * @param id the ID of the item to build. + * @return the built ItemStack. + */ + @NotNull + ItemStack buildItem(@NotNull Player player, @NotNull String id); + + /** + * Retrieves the item ID from a given ItemStack. + * + * @param itemStack the ItemStack from which to retrieve the item ID. + * @return the item ID as a string, or null if the item stack does not have an associated ID. + */ + @Nullable + String itemID(@NotNull ItemStack itemStack); +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/LevelInterface.java b/api/src/main/java/net/momirealms/customfishing/api/integration/LevelerProvider.java similarity index 82% rename from api/src/main/java/net/momirealms/customfishing/api/integration/LevelInterface.java rename to api/src/main/java/net/momirealms/customfishing/api/integration/LevelerProvider.java index da326178..0fbe15bc 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/integration/LevelInterface.java +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/LevelerProvider.java @@ -18,8 +18,9 @@ package net.momirealms.customfishing.api.integration; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public interface LevelInterface { +public interface LevelerProvider extends ExternalProvider { /** * Add exp to a certain skill or job @@ -28,7 +29,7 @@ public interface LevelInterface { * @param target the skill or job, for instance "Fishing" "fisherman" * @param amount the exp amount */ - void addXp(Player player, String target, double amount); + void addXp(@NotNull Player player, @NotNull String target, double amount); /** * Get a player's skill or job's level @@ -37,5 +38,5 @@ public interface LevelInterface { * @param target the skill or job, for instance "Fishing" "fisherman" * @return level */ - int getLevel(Player player, String target); + int getLevel(@NotNull Player player, @NotNull String target); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/integration/SeasonInterface.java b/api/src/main/java/net/momirealms/customfishing/api/integration/SeasonProvider.java similarity index 83% rename from api/src/main/java/net/momirealms/customfishing/api/integration/SeasonInterface.java rename to api/src/main/java/net/momirealms/customfishing/api/integration/SeasonProvider.java index bae8da60..526a0854 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/integration/SeasonInterface.java +++ b/api/src/main/java/net/momirealms/customfishing/api/integration/SeasonProvider.java @@ -17,10 +17,11 @@ package net.momirealms.customfishing.api.integration; +import net.momirealms.customfishing.api.mechanic.misc.season.Season; import org.bukkit.World; import org.jetbrains.annotations.NotNull; -public interface SeasonInterface { +public interface SeasonProvider extends ExternalProvider { /** * Get a world's season @@ -28,5 +29,6 @@ public interface SeasonInterface { * @param world world * @return spring, summer, autumn, winter or disabled */ - @NotNull String getSeason(World world); + @NotNull + Season getSeason(@NotNull World world); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/ActionManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/ActionManager.java deleted file mode 100644 index 334bd6bf..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/ActionManager.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionFactory; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import org.bukkit.configuration.ConfigurationSection; - -import java.util.HashMap; - -public interface ActionManager { - - /** - * Registers an ActionFactory for a specific action type. - * This method allows you to associate an ActionFactory with a custom action type. - * - * @param type The custom action type to register. - * @param actionFactory The ActionFactory responsible for creating actions of the specified type. - * @return True if the registration was successful (the action type was not already registered), false otherwise. - */ - boolean registerAction(String type, ActionFactory actionFactory); - - /** - * Unregisters an ActionFactory for a specific action type. - * This method allows you to remove the association between an action type and its ActionFactory. - * - * @param type The custom action type to unregister. - * @return True if the action type was successfully unregistered, false if it was not found. - */ - boolean unregisterAction(String type); - - /** - * Retrieves an Action object based on the configuration provided in a ConfigurationSection. - * This method reads the type of action from the section, obtains the corresponding ActionFactory, - * and builds an Action object using the specified values and chance. - *

- * events: - * success: - * action_1: <- section - * ... - * - * @param section The ConfigurationSection containing the action configuration. - * @return An Action object created based on the configuration, or an EmptyAction instance if the action type is invalid. - */ - Action getAction(ConfigurationSection section); - - /** - * Retrieves a mapping of ActionTriggers to arrays of Actions from a ConfigurationSection. - * This method iterates through the provided ConfigurationSection to extract action triggers - * and their associated arrays of Actions. - *

- * events: <- section - * success: - * action_1: - * ... - * - * @param section The ConfigurationSection containing action mappings. - * @return A HashMap where keys are ActionTriggers and values are arrays of Action objects. - */ - HashMap getActionMap(ConfigurationSection section); - - /** - * Retrieves an array of Action objects from a ConfigurationSection. - * This method iterates through the provided ConfigurationSection to extract Action configurations - * and build an array of Action objects. - *

- * events: - * success: <- section - * action_1: - * ... - * - * @param section The ConfigurationSection containing action configurations. - * @return An array of Action objects created based on the configurations in the section. - */ - Action[] getActions(ConfigurationSection section); - - /** - * Retrieves an ActionFactory associated with a specific action type. - * - * @param type The action type for which to retrieve the ActionFactory. - * @return The ActionFactory associated with the specified action type, or null if not found. - */ - ActionFactory getActionFactory(String type); - - /** - * Retrieves a mapping of success times to corresponding arrays of actions from a ConfigurationSection. - *

- * events: - * success-times: <- section - * 1: - * action_1: - * ... - * - * @param section The ConfigurationSection containing success times actions. - * @return A HashMap where success times associated with actions. - */ - HashMap getTimesActionMap(ConfigurationSection section); - - /** - * Triggers a list of actions with the given condition. - * If the list of actions is not null, each action in the list is triggered. - * - * @param actions The list of actions to trigger. - * @param condition The condition associated with the actions. - */ - static void triggerActions(Condition condition, Action... actions) { - if (actions != null) - for (Action action : actions) - action.trigger(condition); - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/AdventureManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/AdventureManager.java deleted file mode 100644 index 1f7c9c0b..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/AdventureManager.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public interface AdventureManager { - - /** - * Get component from text - * @param text text - * @return component - */ - Component getComponentFromMiniMessage(String text); - - /** - * Send a message to a command sender - * @param sender sender - * @param msg message - */ - void sendMessage(CommandSender sender, String msg); - - /** - * Send a message with prefix - * - * @param sender command sender - * @param s message - */ - void sendMessageWithPrefix(CommandSender sender, String s); - - /** - * Send a message to console - * @param msg message - */ - void sendConsoleMessage(String msg); - - /** - * Send a message to a player - * @param player player - * @param msg message - */ - void sendPlayerMessage(Player player, String msg); - - /** - * Send a title to a player - * @param player player - * @param title title - * @param subtitle subtitle - * @param in in (ms) - * @param duration duration (ms) - * @param out out (ms) - */ - void sendTitle(Player player, String title, String subtitle, int in, int duration, int out); - - /** - * Send a title to a player - * @param player player - * @param title title - * @param subtitle subtitle - * @param in in (ms) - * @param duration duration (ms) - * @param out out (ms) - */ - void sendTitle(Player player, Component title, Component subtitle, int in, int duration, int out); - - /** - * Send actionbar - * @param player player - * @param msg msg - */ - void sendActionbar(Player player, String msg); - - /** - * Play a sound to a player - * @param player player - * @param source sound source - * @param key sound key - * @param volume volume - * @param pitch pitch - */ - void sendSound(Player player, Sound.Source source, Key key, float volume, float pitch); - - void sendSound(Player player, Sound sound); - - /** - * Replace legacy color codes to MiniMessage format - * @param legacy legacy text - * @return MiniMessage format text - */ - String legacyToMiniMessage(String legacy); - - /** - * if a char is legacy color code - * @param c char - * @return is legacy color - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - boolean isColorCode(char c); - - /** - * Get legacy format text - * @param component component - * @return legacy format text - */ - String componentToLegacy(Component component); - - /** - * Get json - * @param component component - * @return json - */ - String componentToJson(Component component); - - /** - * Get paper component - * @param component shaded component - * @return paper component - */ - Object shadedComponentToOriginalComponent(Component component); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/BagManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/BagManager.java deleted file mode 100644 index f83c36d2..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/BagManager.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; - -import java.util.List; -import java.util.UUID; - -public interface BagManager { - - /** - * Is bag enabled - * - * @return enabled or not - */ - boolean isEnabled(); - - /** - * Retrieves the online bag inventory associated with a player's UUID. - * - * @param uuid The UUID of the player for whom the bag inventory is retrieved. - * @return The online bag inventory if the player is online, or null if not found. - */ - Inventory getOnlineBagInventory(UUID uuid); - - /** - * Get the rows of a player's fishing bag - * - * @param player player who owns the bag - * @return rows - */ - int getBagInventoryRows(Player player); - - /** - * Initiates the process of editing the bag inventory of an offline player by an admin. - * - * @param admin The admin player performing the edit. - * @param userData The OfflineUser data of the player whose bag is being edited. - */ - void editOfflinePlayerBag(Player admin, OfflineUser userData); - - /** - * Get the actions to perform when loot is automatically collected - * - * @return actions - */ - Action[] getCollectLootActions(); - - /** - * Get the actions to perform when bag is full - * - * @return actions - */ - Action[] getBagFullActions(); - - /** - * If bag can store fishing loots - * - * @return can store or not - */ - boolean doesBagStoreLoots(); - - /** - * Get the fishing bag's title - * - * @return title - */ - String getBagTitle(); - - /** - * Get a list of allowed items in bag - * - * @return whitelisted items - */ - List getBagWhiteListItems(); - - /** - * Get the requirements for automatically collecting loots - * - * @return requirements - */ - Requirement[] getCollectRequirements(); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/BlockManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/BlockManager.java deleted file mode 100644 index baa69366..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/BlockManager.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.block.BlockDataModifierBuilder; -import net.momirealms.customfishing.api.mechanic.block.BlockLibrary; -import net.momirealms.customfishing.api.mechanic.block.BlockStateModifierBuilder; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public interface BlockManager { - - /** - * Registers a BlockLibrary instance. - * This method associates a BlockLibrary with its unique identification and adds it to the registry. - * - * @param blockLibrary The BlockLibrary instance to register. - * @return True if the registration was successful (the identification is not already registered), false otherwise. - */ - boolean registerBlockLibrary(BlockLibrary blockLibrary); - - /** - * Unregisters a BlockLibrary instance by its identification. - * This method removes a BlockLibrary from the registry based on its unique identification. - * - * @param identification The unique identification of the BlockLibrary to unregister. - * @return True if the BlockLibrary was successfully unregistered, false if it was not found. - */ - boolean unregisterBlockLibrary(String identification); - - /** - * Registers a BlockDataModifierBuilder for a specific type. - * This method associates a BlockDataModifierBuilder with its type and adds it to the registry. - * - * @param type The type of the BlockDataModifierBuilder to register. - * @param builder The BlockDataModifierBuilder instance to register. - * @return True if the registration was successful (the type is not already registered), false otherwise. - */ - boolean registerBlockDataModifierBuilder(String type, BlockDataModifierBuilder builder); - - /** - * Registers a BlockStateModifierBuilder for a specific type. - * This method associates a BlockStateModifierBuilder with its type and adds it to the registry. - * - * @param type The type of the BlockStateModifierBuilder to register. - * @param builder The BlockStateModifierBuilder instance to register. - * @return True if the registration was successful (the type is not already registered), false otherwise. - */ - boolean registerBlockStateModifierBuilder(String type, BlockStateModifierBuilder builder); - - /** - * Unregisters a BlockDataModifierBuilder with the specified type. - * - * @param type The type of the BlockDataModifierBuilder to unregister. - * @return True if the BlockDataModifierBuilder was successfully unregistered, false otherwise. - */ - boolean unregisterBlockDataModifierBuilder(String type); - - /** - * Unregisters a BlockStateModifierBuilder with the specified type. - * - * @param type The type of the BlockStateModifierBuilder to unregister. - * @return True if the BlockStateModifierBuilder was successfully unregistered, false otherwise. - */ - boolean unregisterBlockStateModifierBuilder(String type); - - /** - * Summons a falling block at a specified location based on the provided loot. - * This method spawns a falling block at the given hookLocation with specific properties determined by the loot. - * - * @param player The player who triggered the action. - * @param hookLocation The location where the hook is positioned. - * @param playerLocation The location of the player. - * @param loot The loot to be associated with the summoned block. - */ - void summonBlock(Player player, Location hookLocation, Location playerLocation, Loot loot); - - /** - * Retrieves the block ID associated with a given Block instance using block detection order. - * This method iterates through the configured block detection order to find the block's ID - * by checking different BlockLibrary instances in the specified order. - * - * @param block The Block instance for which to retrieve the block ID. - * @return The block ID - */ - @NotNull String getAnyPluginBlockID(Block block); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/CompetitionManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/CompetitionManager.java deleted file mode 100644 index d78ade4a..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/CompetitionManager.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig; -import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; -import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; - -public interface CompetitionManager { - - /** - * Retrieves a set of all competition names. - * - * @return A set of competition names. - */ - @NotNull Set getAllCompetitionKeys(); - - /** - * Retrieves the localization key for a given competition goal. - * - * @param goal The competition goal to retrieve the localization key for. - * @return The localization key for the specified competition goal. - */ - @NotNull String getCompetitionGoalLocale(CompetitionGoal goal); - - /** - * Starts a competition with the specified name, allowing for the option to force start it or apply it to the entire server. - * - * @param competition The name of the competition to start. - * @param force Whether to force start the competition even if amount of the online players is lower than the requirement - * @param serverGroup Whether to apply the competition to the servers that connected to Redis. - * @return {@code true} if the competition was started successfully, {@code false} otherwise. - */ - boolean startCompetition(String competition, boolean force, @Nullable String serverGroup); - - /** - * Gets the ongoing fishing competition, if one is currently in progress. - * - * @return The ongoing fishing competition, or null if there is none. - */ - @Nullable FishingCompetition getOnGoingCompetition(); - - /** - * Starts a competition using the specified configuration. - * - * @param config The configuration of the competition to start. - * @param force Whether to force start the competition even if amount of the online players is lower than the requirement - * @param serverGroup Whether the competition should start across all servers that connected to Redis - * @return True if the competition was started successfully, false otherwise. - */ - boolean startCompetition(CompetitionConfig config, boolean force, @Nullable String serverGroup); - - /** - * Gets the number of seconds until the next competition. - * - * @return The number of seconds until the next competition. - */ - int getNextCompetitionSeconds(); - - /** - * Retrieves the configuration for a competition based on its key. - * - * @param key The key of the competition configuration to retrieve. - * @return The {@link CompetitionConfig} for the specified key, or {@code null} if no configuration exists with that key. - */ - @Nullable CompetitionConfig getConfig(String key); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/EffectManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/EffectManager.java deleted file mode 100644 index caccbfd4..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/EffectManager.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.mechanic.effect.BaseEffect; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import org.bukkit.configuration.ConfigurationSection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface EffectManager { - - /** - * Registers an EffectCarrier with a unique Key. - * - * @param key The unique Key associated with the EffectCarrier. - * @param effect The EffectCarrier to be registered. - * @return True if the registration was successful, false if the Key already exists. - */ - boolean registerEffectCarrier(Key key, EffectCarrier effect); - - /** - * Unregisters an EffectCarrier associated with the specified Key. - * - * @param key The unique Key of the EffectCarrier to unregister. - * @return True if the EffectCarrier was successfully unregistered, false if the Key does not exist. - */ - boolean unregisterEffectCarrier(Key key); - - /** - * Checks if an EffectCarrier with the specified namespace and id exists. - * - * @param namespace The namespace of the EffectCarrier. - * @param id The unique identifier of the EffectCarrier. - * @return True if an EffectCarrier with the given namespace and id exists, false otherwise. - */ - boolean hasEffectCarrier(String namespace, String id); - - /** - * Retrieves an EffectCarrier with the specified namespace and id. - * - * @param namespace The namespace of the EffectCarrier. - * @param id The unique identifier of the EffectCarrier. - * @return The EffectCarrier with the given namespace and id, or null if it doesn't exist. - */ - @Nullable EffectCarrier getEffectCarrier(String namespace, String id); - - /** - * Parses a ConfigurationSection to create an EffectCarrier based on the specified key and configuration. - *

- * xxx_item: <- section - * effects: - * ... - * events: - * ... - * - * @param key The key that uniquely identifies the EffectCarrier. - * @param section The ConfigurationSection containing the EffectCarrier configuration. - * @return An EffectCarrier instance based on the key and configuration, or null if the section is null. - */ - EffectCarrier getEffectCarrierFromSection(Key key, ConfigurationSection section); - - /** - * Retrieves the initial FishingEffect that represents no special effects. - * - * @return The initial FishingEffect. - */ - @NotNull FishingEffect getInitialEffect(); - - /** - * Parses a ConfigurationSection to retrieve an array of EffectModifiers. - *

- * effects: <- section - * effect_1: - * type: xxx - * value: xxx - * - * @param section The ConfigurationSection to parse. - * @return An array of EffectModifiers based on the values found in the section. - */ - @NotNull EffectModifier[] getEffectModifiers(ConfigurationSection section); - - BaseEffect getBaseEffect(ConfigurationSection section); - - /** - * Parses a ConfigurationSection to create an EffectModifier based on the specified type and configuration. - *

- * effects: - * effect_1: <- section - * type: xxx - * value: xxx - * - * @param section The ConfigurationSection containing the effect modifier configuration. - * @return An EffectModifier instance based on the type and configuration. - */ - @Nullable EffectModifier getEffectModifier(ConfigurationSection section); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/EntityManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/EntityManager.java deleted file mode 100644 index 25de6e97..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/EntityManager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.entity.EntityLibrary; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import org.bukkit.Location; - -public interface EntityManager { - - /** - * Registers an entity library for use in the plugin. - * - * @param entityLibrary The entity library to register. - * @return {@code true} if the entity library was successfully registered, {@code false} if it already exists. - */ - boolean registerEntityLibrary(EntityLibrary entityLibrary); - - /** - * Unregisters an entity library by its identification key. - * - * @param identification The identification key of the entity library to unregister. - * @return {@code true} if the entity library was successfully unregistered, {@code false} if it does not exist. - */ - boolean unregisterEntityLibrary(String identification); - - /** - * Summons an entity based on the given loot configuration to a specified location. - * - * @param hookLocation The location where the entity will be summoned, typically where the fishing hook is. - * @param playerLocation The location of the player who triggered the entity summoning. - * @param loot The loot configuration that defines the entity to be summoned. - */ - void summonEntity(Location hookLocation, Location playerLocation, Loot loot); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/FishingManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/FishingManager.java deleted file mode 100644 index bb9eabbe..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/FishingManager.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.TempFishingState; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.game.GameInstance; -import net.momirealms.customfishing.api.mechanic.game.GameSettings; -import net.momirealms.customfishing.api.mechanic.game.GamingPlayer; -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.util.UUID; - -public interface FishingManager { - - /** - * Removes a fishing hook entity associated with a given player's UUID. - * - * @param uuid The UUID of the player - * @return {@code true} if the fishing hook was successfully removed, {@code false} otherwise. - */ - boolean removeHook(UUID uuid); - - /** - * Retrieves a FishHook object associated with the provided player's UUID - * - * @param uuid The UUID of the player - * @return fishhook entity, null if not exists - */ - @Nullable FishHook getHook(UUID uuid); - - /** - * Sets the temporary fishing state for a player. - * - * @param player The player for whom to set the temporary fishing state. - * @param tempFishingState The temporary fishing state to set for the player. - */ - void setTempFishingState(Player player, TempFishingState tempFishingState); - - /** - * Gets the {@link TempFishingState} object associated with the given UUID. - * - * @param uuid The UUID of the player. - * @return The {@link TempFishingState} object if found, or {@code null} if not found. - */ - @Nullable TempFishingState getTempFishingState(UUID uuid); - - /** - * Removes the temporary fishing state associated with a player. - * - * @param player The player whose temporary fishing state should be removed. - */ - @Nullable TempFishingState removeTempFishingState(Player player); - - /** - * Processes the game result for a gaming player - * - * @param gamingPlayer The gaming player whose game result should be processed. - */ - void processGameResult(GamingPlayer gamingPlayer); - - /** - * Starts a fishing game for the specified player with the given condition and effect. - * - * @param player The player starting the fishing game. - * @param condition The condition used to determine the game. - * @param effect The effect applied to the game. - */ - boolean startFishingGame(Player player, Condition condition, Effect effect); - - /** - * Starts a fishing game for the specified player with the given settings and game instance. - * - * @param player The player starting the fishing game. - * @param settings The game settings for the fishing game. - * @param gameInstance The instance of the fishing game to start. - */ - boolean startFishingGame(Player player, GameSettings settings, GameInstance gameInstance); - - /** - * Checks if a player with the given UUID has cast their fishing hook. - * - * @param uuid The UUID of the player to check. - * @return {@code true} if the player has cast their fishing hook, {@code false} otherwise. - */ - boolean hasPlayerCastHook(UUID uuid); - - /** - * Gets the {@link GamingPlayer} object associated with the given UUID. - * - * @param uuid The UUID of the player. - * @return The {@link GamingPlayer} object if found, or {@code null} if not found. - */ - @Nullable GamingPlayer getGamingPlayer(UUID uuid); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/HookManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/HookManager.java deleted file mode 100644 index d0649926..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/HookManager.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.hook.HookSetting; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -public interface HookManager { - - /** - * Get the hook setting by its ID. - * - * @param id The ID of the hook setting to retrieve. - * @return The hook setting with the given ID, or null if not found. - */ - @Nullable HookSetting getHookSetting(String id); - - /** - * Decreases the durability of a fishing hook by a specified amount and optionally updates its lore. - * The hook would be removed if its durability is lower than 0 - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The amount by which to decrease the durability. - * @param updateLore Whether to update the lore of the fishing rod. - */ - void decreaseHookDurability(ItemStack rod, int amount, boolean updateLore); - - /** - * Increases the durability of a hook by a specified amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The amount by which to increase the durability. - * @param updateLore Whether to update the lore of the fishing rod. - */ - void increaseHookDurability(ItemStack rod, int amount, boolean updateLore); - - /** - * Sets the durability of a fishing hook to a specific amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The new durability value to set. - * @param updateLore Whether to update the lore of the fishing rod. - */ - void setHookDurability(ItemStack rod, int amount, boolean updateLore); - - /** - * Equips a fishing hook on a fishing rod. - * - * @param rod The fishing rod ItemStack. - * @param hook The fishing hook ItemStack. - * @return True if the hook was successfully equipped, false otherwise. - */ - boolean equipHookOnRod(ItemStack rod, ItemStack hook); - - /** - * Removes the fishing hook from a fishing rod. - * - * @param rod The fishing rod ItemStack. - * @return The removed fishing hook ItemStack, or null if no hook was found. - */ - @Nullable ItemStack removeHookFromRod(ItemStack rod); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/IntegrationManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/IntegrationManager.java deleted file mode 100644 index c78a91e9..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/IntegrationManager.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.integration.EnchantmentInterface; -import net.momirealms.customfishing.api.integration.LevelInterface; -import net.momirealms.customfishing.api.integration.SeasonInterface; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public interface IntegrationManager { - - /** - * Registers a level plugin with the specified name. - * - * @param plugin The name of the level plugin. - * @param level The implementation of the LevelInterface. - * @return true if the registration was successful, false if the plugin name is already registered. - */ - boolean registerLevelPlugin(String plugin, LevelInterface level); - - /** - * Unregisters a level plugin with the specified name. - * - * @param plugin The name of the level plugin to unregister. - * @return true if the unregistration was successful, false if the plugin name is not found. - */ - boolean unregisterLevelPlugin(String plugin); - - /** - * Registers an enchantment provided by a plugin. - * - * @param plugin The name of the plugin providing the enchantment. - * @param enchantment The enchantment to register. - * @return true if the registration was successful, false if the enchantment name is already in use. - */ - boolean registerEnchantment(String plugin, EnchantmentInterface enchantment); - - /** - * Unregisters an enchantment provided by a plugin. - * - * @param plugin The name of the plugin providing the enchantment. - * @return true if the enchantment was successfully unregistered, false if the enchantment was not found. - */ - boolean unregisterEnchantment(String plugin); - - /** - * Get the LevelInterface provided by a plugin. - * - * @param plugin The name of the plugin providing the LevelInterface. - * @return The LevelInterface provided by the specified plugin, or null if the plugin is not registered. - */ - @Nullable LevelInterface getLevelPlugin(String plugin); - - /** - * Get an enchantment plugin by its plugin name. - * - * @param plugin The name of the enchantment plugin. - * @return The enchantment plugin interface, or null if not found. - */ - @Nullable EnchantmentInterface getEnchantmentPlugin(String plugin); - - /** - * Get a list of enchantment keys with level applied to the given ItemStack. - * - * @param itemStack The ItemStack to check for enchantments. - * @return A list of enchantment names applied to the ItemStack. - */ - List getEnchantments(ItemStack itemStack); - - /** - * Get the current season interface, if available. - * - * @return The current season interface, or null if not available. - */ - @Nullable SeasonInterface getSeasonInterface(); - - /** - * Set the current season interface. - * - * @param season The season interface to set. - */ - void setSeasonInterface(SeasonInterface season); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/ItemManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/ItemManager.java deleted file mode 100644 index 25759e9c..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/ItemManager.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.item.BuildableItem; -import net.momirealms.customfishing.api.mechanic.item.ItemBuilder; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; -import org.bukkit.Location; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; -import java.util.Set; - -public interface ItemManager { - - /** - * Build an ItemStack with a specified namespace and value for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param namespace The namespace of the item. - * @param value The value of the item. - * @return The constructed ItemStack. - */ - @Nullable - ItemStack build(Player player, String namespace, String value); - - /** - * Build an ItemStack with a specified namespace and value, replacing placeholders, - * for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param namespace The namespace of the item. - * @param value The value of the item. - * @param placeholders The placeholders to replace in the item's attributes. - * @return The constructed ItemStack, or null if the item doesn't exist. - */ - @Nullable - ItemStack build(Player player, String namespace, String value, Map placeholders); - - /** - * Build an ItemStack using an ItemBuilder for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param builder The ItemBuilder used to construct the ItemStack. - * @return The constructed ItemStack. - */ - @NotNull ItemStack build(Player player, ItemBuilder builder); - - /** - * Build an ItemStack using the provided ItemBuilder, player, and placeholders. - * - * @param player The player for whom the item is being built. - * @param builder The ItemBuilder that defines the item's properties. - * @param placeholders A map of placeholders and their corresponding values to be applied to the item. - * @return The constructed ItemStack. - */ - @NotNull ItemStack build(Player player, ItemBuilder builder, Map placeholders); - - /** - * Build an ItemStack for a player based on the provided item ID. - * - * @param player The player for whom the ItemStack is being built. - * @param id The item ID, which include an identification (e.g., "Oraxen:id"). - * @return The constructed ItemStack or null if the ID is not valid. - */ - @Nullable ItemStack buildAnyPluginItemByID(Player player, String id); - - /** - * Get the item ID associated with the given ItemStack, if available. - * - * @param itemStack The ItemStack to retrieve the item ID from. - * @return The item ID without type or null if not found or if the ItemStack is null or empty. - */ - @Nullable String getCustomFishingItemID(ItemStack itemStack); - - /** - * Get the item ID associated with the given ItemStack by checking all available item libraries. - * The detection order is determined by the configuration. - * - * @param itemStack The ItemStack to retrieve the item ID from. - * @return The item ID or "AIR" if not found or if the ItemStack is null or empty. - */ - @NotNull String getAnyPluginItemID(ItemStack itemStack); - - /** - * Create a ItemBuilder instance for an item configuration section - *

- * xxx_item: <- section - * material: xxx - * custom-model-data: xxx - * - * @param section The configuration section containing item settings. - * @param type The type of the item (e.g., "rod", "bait"). - * @param id The unique identifier for the item. - * @return A CFBuilder instance representing the configured item, or null if the section is null. - */ - @Nullable ItemBuilder getItemBuilder(ConfigurationSection section, String type, String id); - - /** - * Get a set of all item keys in the CustomFishing plugin. - * - * @return A set of item keys. - */ - Set getAllItemsKey(); - - /** - * Retrieve a BuildableItem by its namespace and value. - * - * @param namespace The namespace of the BuildableItem. - * @param value The value of the BuildableItem. - * @return The BuildableItem with the specified namespace and value, or null if not found. - */ - @Nullable - BuildableItem getBuildableItem(String namespace, String value); - - /** - * Get an itemStack's appearance (material + custom model data) - * - * @param player player - * @param material id - * @return appearance - */ - ItemStack getItemStackAppearance(Player player, String material); - - /** - * Register an item library. - * - * @param itemLibrary The item library to register. - * @return True if the item library was successfully registered, false if it already exists. - */ - boolean registerItemLibrary(ItemLibrary itemLibrary); - - /** - * Unregister an item library. - * - * @param identification The item library to unregister. - * @return True if the item library was successfully unregistered, false if it doesn't exist. - */ - boolean unRegisterItemLibrary(String identification); - - /** - * Drops an item based on the provided loot, applying velocity from a hook location to a player location. - * - * @param player The player for whom the item is intended. - * @param hookLocation The location where the item will initially drop. - * @param playerLocation The target location towards which the item's velocity is applied. - * @param itemStack The loot to drop - * @param condition A map of placeholders for item customization. - */ - void dropItem(Player player, Location hookLocation, Location playerLocation, ItemStack itemStack, Condition condition); - - /** - * Checks if the provided ItemStack is a custom fishing item - * - * @param itemStack The ItemStack to check. - * @return True if the ItemStack is a custom fishing item; otherwise, false. - */ - boolean isCustomFishingItem(ItemStack itemStack); - - /** - * Decreases the durability of an ItemStack by a specified amount and optionally updates its lore. - * - * @param itemStack The ItemStack to modify. - * @param amount The amount by which to decrease the durability. - * @param updateLore Whether to update the lore of the ItemStack. - */ - void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean updateLore); - - /** - * Increases the durability of an ItemStack by a specified amount and optionally updates its lore. - * - * @param itemStack The ItemStack to modify. - * @param amount The amount by which to increase the durability. - * @param updateLore Whether to update the lore of the ItemStack. - */ - void increaseDurability(ItemStack itemStack, int amount, boolean updateLore); - - /** - * Sets the durability of an ItemStack to a specific amount and optionally updates its lore. - * - * @param itemStack The ItemStack to modify. - * @param amount The new durability value. - * @param updateLore Whether to update the lore of the ItemStack. - */ - void setDurability(ItemStack itemStack, int amount, boolean updateLore); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/LootManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/LootManager.java deleted file mode 100644 index 75acc6f8..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/LootManager.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public interface LootManager { - - /** - * Retrieves a list of loot IDs associated with a loot group key. - * - * @param key The key of the loot group. - * @return A list of loot IDs belonging to the specified loot group, or null if not found. - */ - @Nullable List getLootGroup(String key); - - /** - * Retrieves a loot configuration based on a provided loot key. - * - * @param key The key of the loot configuration. - * @return The Loot object associated with the specified loot key, or null if not found. - */ - @Nullable Loot getLoot(String key); - - /** - * Retrieves a collection of all loot configuration keys. - * - * @return A collection of all loot configuration keys. - */ - Collection getAllLootKeys(); - - /** - * Retrieves a collection of all loot configurations. - * - * @return A collection of all loot configurations. - */ - Collection getAllLoots(); - - /** - * Retrieves loot configurations with weights based on a given condition. - * - * @param condition The condition used to filter loot configurations. - * @return A mapping of loot configuration keys to their associated weights. - */ - HashMap getLootWithWeight(Condition condition); - - /** - * Get a collection of possible loot keys based on a given condition. - * - * @param condition The condition to determine possible loot. - * @return A collection of loot keys. - */ - Collection getPossibleLootKeys(Condition condition); - - /** - * Get a map of possible loot keys with their corresponding weights, considering fishing effect and condition. - * - * @param initialEffect The effect to apply weight modifiers. - * @param condition The condition to determine possible loot. - * @return A map of loot keys and their weights. - */ - @NotNull Map getPossibleLootKeysWithWeight(Effect initialEffect, Condition condition); - - /** - * Get the next loot item based on fishing effect and condition. - * - * @param effect The effect to apply weight modifiers. - * @param condition The condition to determine possible loot. - * @return The next loot item, or null if it doesn't exist. - */ - @Nullable Loot getNextLoot(Effect effect, Condition condition); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/MarketManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/MarketManager.java deleted file mode 100644 index 1388e503..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/MarketManager.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; - -import java.util.Map; - -public interface MarketManager { - - /** - * Open the market GUI for a player - * - * @param player player - */ - void openMarketGUI(Player player); - - /** - * Retrieves the current date as an integer in the format MMDD (e.g., September 21 as 0921). - * - * @return An integer representing the current date. - */ - int getCachedDate(); - - /** - * Retrieves the current date as an integer in the format MMDD (e.g., September 21 as 0921). - * - * @return An integer representing the current date. - */ - int getDate(); - - /** - * Calculates the price of an ItemStack based on custom data or a predefined price map. - * - * @param itemStack The ItemStack for which the price is calculated. - * @return The calculated price of the ItemStack. - */ - double getItemPrice(ItemStack itemStack); - - /** - * Retrieves the formula used for calculating prices. - * - * @return The pricing formula as a string. - */ - String getFormula(); - - /** - * Calculates the price based on a formula with provided variables. - * - * @return The calculated price based on the formula and provided variables. - */ - double getFishPrice(Player player, Map vars); - - /** - * Gets the character representing the item slot in the MarketGUI. - * - * @return The item slot character. - */ - char getItemSlot(); - - /** - * Gets the character representing the sell slot in the MarketGUI. - * - * @return The sell slot character. - */ - char getSellSlot(); - - /** - * Gets the character representing the sell-all slot in the MarketGUI. - * - * @return The sell-all slot character. - */ - char getSellAllSlot(); - - /** - * Gets the layout of the MarketGUI as an array of strings. - * - * @return The layout of the MarketGUI. - */ - String[] getLayout(); - - /** - * Gets the title of the MarketGUI. - * - * @return The title of the MarketGUI. - */ - String getTitle(); - - /** - * Gets the earning limit - * - * @return The earning limit - */ - double getEarningLimit(Player player); - - /** - * Is market enabled - * - * @return enable or not - */ - boolean isEnable(); - - /** - * Should fish in bag also be sold - * - * @return sell or not - */ - boolean sellFishingBag(); - - /** - * Get the total worth of the items in inventory - * - * @param inventory inventory - * @return total worth - */ - double getInventoryTotalWorth(Inventory inventory); - - int getInventorySellAmount(Inventory inventory); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/PlaceholderManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/PlaceholderManager.java deleted file mode 100644 index 922db18c..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/PlaceholderManager.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Map; - -public interface PlaceholderManager { - - /** - * Register a custom placeholder - * - * @param placeholder for instance {level} - * @param original for instance %player_level% - * @return success or not, it would fail if the placeholder has been registered - */ - boolean registerCustomPlaceholder(String placeholder, String original); - - /** - * Set placeholders in a text string for a player. - * - * @param player The player for whom the placeholders should be set. - * @param text The text string containing placeholders. - * @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text. - */ - String setPlaceholders(Player player, String text); - - /** - * Set placeholders in a text string for an offline player. - * - * @param player The offline player for whom the placeholders should be set. - * @param text The text string containing placeholders. - * @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text. - */ - String setPlaceholders(OfflinePlayer player, String text); - - /** - * Detect and extract placeholders from a text string. - * - * @param text The text string to search for placeholders. - * @return A list of detected placeholders in the text. - */ - List detectPlaceholders(String text); - - /** - * Get the value associated with a single placeholder. - * - * @param player The player for whom the placeholders are being resolved (nullable). - * @param placeholder The placeholder to look up. - * @param placeholders A map of placeholders to their corresponding values. - * @return The value associated with the placeholder, or the original placeholder if not found. - */ - String getSingleValue(@Nullable Player player, String placeholder, Map placeholders); - - /** - * Parse a text string by replacing placeholders with their corresponding values. - * - * @param player The offline player for whom the placeholders are being resolved (nullable). - * @param text The text string containing placeholders. - * @param placeholders A map of placeholders to their corresponding values. - * @return The text string with placeholders replaced by their values. - */ - String parse(@Nullable OfflinePlayer player, String text, Map placeholders); - - /** - * Parse a list of text strings by replacing placeholders with their corresponding values. - * - * @param player The player for whom the placeholders are being resolved (can be null for offline players). - * @param list The list of text strings containing placeholders. - * @param replacements A map of custom replacements for placeholders. - * @return The list of text strings with placeholders replaced by their values. - */ - List parse(@Nullable OfflinePlayer player, List list, Map replacements); - - /** - * Get an expression's value - * @param player player - * @param formula formula - * @param vars vars - * @return result - */ - double getExpressionValue(Player player, String formula, Map vars); - - /** - * Get an expression's value - * @param formula formula - * @return result - */ - double getExpressionValue(String formula); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/RequirementManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/RequirementManager.java deleted file mode 100644 index d18a6cd2..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/RequirementManager.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import net.momirealms.customfishing.api.mechanic.requirement.RequirementFactory; -import org.bukkit.configuration.ConfigurationSection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface RequirementManager { - - /** - * Legacy format support - * @param key key - * @param requirements requirements - * @param weight weight - * @return success or not - */ - @Deprecated - boolean putLegacyLootToMap(String key, Requirement[] requirements, double weight); - - /** - * Registers a custom requirement type with its corresponding factory. - * - * @param type The type identifier of the requirement. - * @param requirementFactory The factory responsible for creating instances of the requirement. - * @return True if registration was successful, false if the type is already registered. - */ - boolean registerRequirement(String type, RequirementFactory requirementFactory); - - /** - * Unregisters a custom requirement type. - * - * @param type The type identifier of the requirement to unregister. - * @return True if unregistration was successful, false if the type is not registered. - */ - boolean unregisterRequirement(String type); - - /** - * Retrieves an array of requirements based on a configuration section. - * - * @param section The configuration section containing requirement definitions. - * @param advanced A flag indicating whether to use advanced requirements. - * @return An array of Requirement objects based on the configuration section - */ - @Nullable Requirement[] getRequirements(ConfigurationSection section, boolean advanced); - - /** - * If a requirement type exists - * - * @param type type - * @return exists or not - */ - boolean hasRequirement(String type); - - /** - * Retrieves a Requirement object based on a configuration section and advanced flag. - *

- * requirement_1: <- section - * type: xxx - * value: xxx - * - * @param section The configuration section containing requirement definitions. - * @param advanced A flag indicating whether to use advanced requirements. - * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid. - */ - @NotNull Requirement getRequirement(ConfigurationSection section, boolean advanced); - - /** - * Gets a requirement based on the provided type and value. - * If a valid RequirementFactory is found for the type, it is used to create the requirement. - * If no factory is found, a warning is logged, and an empty requirement instance is returned. - *

- * world: <- type - * - world <- value - * - * @param type The type representing the requirement type. - * @param value The value associated with the requirement. - * @return A Requirement instance based on the type and value, or an EmptyRequirement if the type is invalid. - */ - @NotNull Requirement getRequirement(String type, Object value); - - /** - * Retrieves a RequirementFactory based on the specified requirement type. - * - * @param type The requirement type for which to retrieve a factory. - * @return A RequirementFactory for the specified type, or null if no factory is found. - */ - @Nullable RequirementFactory getRequirementFactory(String type); - - /** - * Checks if an array of requirements is met for a given condition. - * - * @param condition The Condition object to check against the requirements. - * @param requirements An array of Requirement instances to be evaluated. - * @return True if all requirements are met, false otherwise. Returns true if the requirements array is null. - */ - static boolean isRequirementMet(Condition condition, Requirement... requirements) { - if (requirements == null) return true; - for (Requirement requirement : requirements) { - if (!requirement.isConditionMet(condition)) { - return false; - } - } - return true; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/StatisticsManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/StatisticsManager.java deleted file mode 100644 index 64ad6544..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/StatisticsManager.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.UUID; - -public interface StatisticsManager { - - /** - * Get the statistics for a player with the given UUID. - * - * @param uuid The UUID of the player for whom statistics are retrieved. - * @return The player's statistics or null if the player is not found. - */ - @Nullable Statistics getStatistics(UUID uuid); - - /** - * Get a list of strings associated with a specific key in a category map. - * - * @param key The key to look up in the category map. - * @return A list of strings associated with the key or null if the key is not found. - */ - @Nullable List getCategory(String key); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/StorageManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/StorageManager.java deleted file mode 100644 index b84b5c1a..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/StorageManager.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.data.DataStorageInterface; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public interface StorageManager { - - /** - * Gets the unique server identifier. - * - * @return The unique server identifier. - */ - @NotNull String getUniqueID(); - - /** - * Gets an OnlineUser instance for the specified UUID. - * - * @param uuid The UUID of the player. - * @return An OnlineUser instance if the player is online, or null if not. - */ - @Nullable OnlineUser getOnlineUser(UUID uuid); - - /** - * Get all the online users - * - * @return online users - */ - Collection getOnlineUsers(); - - /** - * Asynchronously retrieves an OfflineUser instance for the specified UUID. - * The offline user might be a locked one with no data, use isLockedData() method - * to check if it's an empty locked data - * - * @param uuid The UUID of the player. - * @param lock Whether to lock the data during retrieval. - * @return A CompletableFuture that resolves to an Optional containing the OfflineUser instance if found, or empty if not found or locked. - */ - CompletableFuture> getOfflineUser(UUID uuid, boolean lock); - - /** - * If the offlineUser is locked with no data in it - * An user's data would be locked if he is playing on another server that connected - * to database. Modifying this data would actually do nothing. - * - * @param offlineUser offlineUser - * @return is locked or not - */ - boolean isLockedData(OfflineUser offlineUser); - - /** - * Asynchronously saves user data for an OfflineUser. - * - * @param offlineUser The OfflineUser whose data needs to be saved. - * @param unlock Whether to unlock the data after saving. - * @return A CompletableFuture that resolves to a boolean indicating the success of the data saving operation. - */ - CompletableFuture saveUserData(OfflineUser offlineUser, boolean unlock); - - /** - * Gets the data source used for data storage. - * - * @return The data source. - */ - DataStorageInterface getDataSource(); - - /** - * Checks if Redis is enabled. - * - * @return True if Redis is enabled; otherwise, false. - */ - boolean isRedisEnabled(); - - /** - * Converts PlayerData to bytes. - * - * @param data The PlayerData to be converted. - * @return The byte array representation of PlayerData. - */ - byte @NotNull [] toBytes(@NotNull PlayerData data); - - /** - * Converts PlayerData to JSON format. - * - * @param data The PlayerData to be converted. - * @return The JSON string representation of PlayerData. - */ - @NotNull String toJson(@NotNull PlayerData data); - - /** - * Converts JSON string to PlayerData. - * - * @param json The JSON string to be converted. - * @return The PlayerData object. - */ - @NotNull PlayerData fromJson(String json); - - /** - * Converts bytes to PlayerData. - * - * @param data The byte array to be converted. - * @return The PlayerData object. - */ - @NotNull PlayerData fromBytes(byte[] data); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/TotemManager.java b/api/src/main/java/net/momirealms/customfishing/api/manager/TotemManager.java deleted file mode 100644 index 02c3dd9b..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/TotemManager.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.manager; - -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import org.bukkit.Location; - -public interface TotemManager { - - /** - * Get the EffectCarrier associated with an activated totem located near the specified location. - * - * @param location The location to search for activated totems. - * @return The EffectCarrier associated with the nearest activated totem or null if none are found. - */ - EffectCarrier getTotemEffect(Location location); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/GlobalSettings.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/GlobalSettings.java deleted file mode 100644 index 0192b1d9..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/GlobalSettings.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import org.bukkit.configuration.ConfigurationSection; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents global settings for actions related to fishing, loot, rods, and bait. - */ -public class GlobalSettings { - - private static HashMap lootActions = new HashMap<>(); - private static HashMap rodActions = new HashMap<>(); - private static HashMap baitActions = new HashMap<>(); - private static HashMap hookActions = new HashMap<>(); - private static EffectModifier[] effectModifiers; - - /** - * Loads global settings from a configuration section. - * - * @param section The configuration section to load settings from. - */ - public static void loadEvents(ConfigurationSection section) { - if (section == null) return; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - HashMap map = CustomFishingPlugin.get().getActionManager().getActionMap(inner); - switch (entry.getKey()) { - case "loot" -> lootActions = map; - case "rod" -> rodActions = map; - case "bait" -> baitActions = map; - case "hook" -> hookActions = map; - } - } - } - } - - public static EffectModifier[] getEffectModifiers() { - return effectModifiers; - } - - public static void setEffects(EffectModifier[] modifiers) { - effectModifiers = modifiers; - } - - /** - * Unloads global settings, clearing all action maps. - */ - public static void unload() { - lootActions.clear(); - rodActions.clear(); - baitActions.clear(); - } - - /** - * Triggers loot-related actions for a specific trigger and condition. - * - * @param trigger The trigger to activate actions for. - * @param condition The condition that triggered the actions. - */ - public static void triggerLootActions(ActionTrigger trigger, Condition condition) { - Action[] actions = lootActions.get(trigger); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } - - /** - * Triggers rod-related actions for a specific trigger and condition. - * - * @param trigger The trigger to activate actions for. - * @param condition The condition that triggered the actions. - */ - public static void triggerRodActions(ActionTrigger trigger, Condition condition) { - Action[] actions = rodActions.get(trigger); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } - - /** - * Triggers bait-related actions for a specific trigger and condition. - * - * @param trigger The trigger to activate actions for. - * @param condition The condition that triggered the actions. - */ - public static void triggerBaitActions(ActionTrigger trigger, Condition condition) { - Action[] actions = baitActions.get(trigger); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } - - /** - * Triggers hook-related actions for a specific trigger and condition. - * - * @param trigger The trigger to activate actions for. - * @param condition The condition that triggered the actions. - */ - public static void triggerHookActions(ActionTrigger trigger, Condition condition) { - Action[] actions = hookActions.get(trigger); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/MechanicType.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/MechanicType.java new file mode 100644 index 00000000..ca0ac3c9 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/MechanicType.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic; + +import net.kyori.adventure.util.Index; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class MechanicType { + + private static final HashMap> types = new HashMap<>(); + + public static final MechanicType LOOT = of("loot"); + public static final MechanicType ROD = of("rod"); + public static final MechanicType UTIL = of("util"); + public static final MechanicType BAIT = of("bait"); + public static final MechanicType HOOK = of("hook"); + public static final MechanicType TOTEM = of("totem"); + public static final MechanicType ENTITY = of("entity"); + public static final MechanicType BLOCK = of("block"); + public static final MechanicType ENCHANT = of("enchant"); + + private final String type; + + public MechanicType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + private static MechanicType of(String type) { + return new MechanicType(type); + } + + public static MechanicType[] values() { + return new MechanicType[]{LOOT, ROD, UTIL, BAIT, HOOK, TOTEM, ENCHANT, ENTITY, BLOCK}; + } + + private static final Index INDEX = Index.create(MechanicType::getType, values()); + + public static Index index() { + return INDEX; + } + + @ApiStatus.Internal + public static void register(String id, MechanicType type) { + List previous = types.computeIfAbsent(id, k -> new ArrayList<>()); + previous.add(type); + } + + @Nullable + @ApiStatus.Internal + public static List getTypeByID(String id) { + return types.get(id); + } + + @ApiStatus.Internal + public static void reset() { + types.clear(); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + MechanicType mechanicType = (MechanicType) object; + return Objects.equals(type, mechanicType.type); + } + + @Override + public int hashCode() { + return Objects.hashCode(type); + } + + @Override + public String toString() { + return type; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/TempFishingState.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/TempFishingState.java deleted file mode 100644 index 503ef51e..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/TempFishingState.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic; - -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.loot.Loot; - -/** - * Represents a temporary state during fishing that includes an effect, preparation, and loot. - */ -public class TempFishingState { - - private final Effect effect; - private final FishingPreparation preparation; - private Loot loot; - - /** - * Creates a new instance of TempFishingState. - * - * @param effect The effect associated with this state. - * @param preparation The fishing preparation associated with this state. - * @param loot The loot associated with this state. - */ - public TempFishingState(Effect effect, FishingPreparation preparation, Loot loot) { - this.effect = effect; - this.preparation = preparation; - this.loot = loot; - } - - /** - * Gets the effect associated with this fishing state. - * - * @return The effect. - */ - public Effect getEffect() { - return effect; - } - - /** - * Gets the fishing preparation associated with this fishing state. - * - * @return The fishing preparation. - */ - public FishingPreparation getPreparation() { - return preparation; - } - - /** - * Gets the loot associated with this fishing state. - * - * @return The loot. - */ - public Loot getLoot() { - return loot; - } - - /** - * Set the loot associated with this fishing state. - * - */ - public void setLoot(Loot loot) { - this.loot = loot; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/Action.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/Action.java index af44d142..6276916d 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/Action.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/Action.java @@ -17,10 +17,14 @@ package net.momirealms.customfishing.api.mechanic.action; -import net.momirealms.customfishing.api.mechanic.condition.Condition; +import net.momirealms.customfishing.api.mechanic.context.Context; -public interface Action { - - void trigger(Condition condition); +public interface Action { + /** + * Triggers the action based on the provided condition. + * + * @param context the context + */ + void trigger(Context context); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionExpansion.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionExpansion.java index bd48cfd1..6fc0ff8c 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionExpansion.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionExpansion.java @@ -17,13 +17,39 @@ package net.momirealms.customfishing.api.mechanic.action; -public abstract class ActionExpansion { +/** + * Abstract class representing an expansion of an action in the custom fishing API. + * This class should be extended to provide specific implementations of actions. + * + * @param the type parameter for the action factory + */ +public abstract class ActionExpansion { + /** + * Retrieves the version of this action expansion. + * + * @return a String representing the version of the action expansion + */ public abstract String getVersion(); + /** + * Retrieves the author of this action expansion. + * + * @return a String representing the author of the action expansion + */ public abstract String getAuthor(); + /** + * Retrieves the type of this action. + * + * @return a String representing the type of action + */ public abstract String getActionType(); - public abstract ActionFactory getActionFactory(); + /** + * Retrieves the action factory associated with this action expansion. + * + * @return an ActionFactory of type T that creates instances of the action + */ + public abstract ActionFactory getActionFactory(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionFactory.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionFactory.java index fdaae29d..a3d526e0 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionFactory.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionFactory.java @@ -17,7 +17,18 @@ package net.momirealms.customfishing.api.mechanic.action; -public interface ActionFactory { +/** + * Interface representing a factory for creating actions. + * + * @param the type of object that the action will operate on + */ +public interface ActionFactory { - Action build(Object args, double chance); + /** + * Constructs an action based on the provided arguments. + * + * @param args the args containing the arguments needed to build the action + * @return the constructed action + */ + Action process(Object args, double chance); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionManager.java new file mode 100644 index 00000000..9e724f29 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionManager.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.action; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * The ActionManager interface manages custom action types and provides methods for handling actions. + * + * @param the type of the context in which the actions are triggered. + */ +public interface ActionManager extends Reloadable { + + /** + * Registers a custom action type with its corresponding factory. + * + * @param type The type identifier of the action. + * @param actionFactory The factory responsible for creating instances of the action. + * @return True if registration was successful, false if the type is already registered. + */ + boolean registerAction(String type, ActionFactory actionFactory); + + /** + * Unregisters a custom action type. + * + * @param type The type identifier of the action to unregister. + * @return True if unregistration was successful, false if the type is not registered. + */ + boolean unregisterAction(String type); + + /** + * Checks if an action type is registered. + * + * @param type The type identifier of the action. + * @return True if the action type is registered, otherwise false. + */ + boolean hasAction(@NotNull String type); + + /** + * Retrieves the action factory for the specified action type. + * + * @param type The type identifier of the action. + * @return The action factory for the specified type, or null if no factory is found. + */ + @Nullable + ActionFactory getActionFactory(@NotNull String type); + + /** + * Parses an action from a configuration section. + * + * @param section The configuration section containing the action definition. + * @return The parsed action. + */ + Action parseAction(Section section); + + /** + * Parses an array of actions from a configuration section. + * + * @param section The configuration section containing the action definitions. + * @return An array of parsed actions. + */ + @NotNull + Action[] parseActions(Section section); + + /** + * Parses an action from the given type and arguments. + * + * @param type The type identifier of the action. + * @param args The arguments for the action. + * @return The parsed action. + */ + Action parseAction(@NotNull String type, @NotNull Object args); + + /** + * Generates a map of actions triggered by specific events from a configuration section. + * + * @param section The configuration section containing event-action mappings. + * @return A map where the keys are action triggers and the values are arrays of actions associated with those triggers. + */ + default Map[]> parseEventActions(Section section) { + HashMap[]> actionMap = new HashMap<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + try { + actionMap.put( + ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)), + parseActions(innerSection) + ); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } + } + } + return actionMap; + } + + /** + * Parses a configuration section to generate a map of timed actions. + * + * @param section The configuration section containing time-action mappings. + * @return A TreeMap where the keys are time values (in integer form) and the values are arrays of actions associated with those times. + */ + default TreeMap[]> parseTimesActions(Section section) { + TreeMap[]> actionMap = new TreeMap<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + actionMap.put(Integer.parseInt(entry.getKey()), parseActions(innerSection)); + } + } + return actionMap; + } + + /** + * Triggers a list of actions with the given context. + * If the list of actions is not null, each action in the list is triggered. + * + * @param context The context associated with the actions. + * @param actions The list of actions to trigger. + */ + static void trigger(@NotNull Context context, @Nullable List> actions) { + if (actions != null) + for (Action action : actions) + action.trigger(context); + } + + /** + * Triggers an array of actions with the given context. + * If the array of actions is not null, each action in the array is triggered. + * + * @param context The context associated with the actions. + * @param actions The array of actions to trigger. + */ + static void trigger(@NotNull Context context, @Nullable Action[] actions) { + if (actions != null) + for (Action action : actions) + action.trigger(context); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java index 2f41923a..3b981bb1 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java @@ -18,7 +18,6 @@ package net.momirealms.customfishing.api.mechanic.action; public enum ActionTrigger { - SUCCESS, FAILURE, HOOK, @@ -26,8 +25,11 @@ public enum ActionTrigger { CAST, BITE, LAND, + LURE, + ESCAPE, ACTIVATE, TIMER, INTERACT, + REEL, NEW_SIZE_RECORD } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/ParseUtils.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/EmptyAction.java similarity index 61% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/ParseUtils.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/action/EmptyAction.java index 3791dab0..cb819b05 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/ParseUtils.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/EmptyAction.java @@ -15,19 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.papi; +package net.momirealms.customfishing.api.mechanic.action; -import me.clip.placeholderapi.PlaceholderAPI; -import org.bukkit.OfflinePlayer; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.entity.Player; -public class ParseUtils { +/** + * An implementation of the Action interface that represents an empty action with no behavior. + * This class serves as a default action to prevent NPE. + */ +public class EmptyAction implements Action { - public static String setPlaceholders(Player player, String text) { - return PlaceholderAPI.setPlaceholders(player, text); - } + public static final EmptyAction INSTANCE = new EmptyAction(); - public static String setPlaceholders(OfflinePlayer player, String text) { - return PlaceholderAPI.setPlaceholders(player, text); + @Override + public void trigger(Context context) { } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/VersionManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/BagManager.java similarity index 56% rename from api/src/main/java/net/momirealms/customfishing/api/manager/VersionManager.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/BagManager.java index d75bdfd1..5f01cf69 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/VersionManager.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/BagManager.java @@ -15,27 +15,26 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.manager; +package net.momirealms.customfishing.api.mechanic.bag; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.Player; + +import java.util.UUID; import java.util.concurrent.CompletableFuture; -public interface VersionManager { +public interface BagManager extends Reloadable { - boolean isVersionNewerThan1_19(); + static int getBagInventoryRows(Player player) { + int size = 1; + for (int i = 6; i > 1; i--) { + if (player.hasPermission("fishingbag.rows." + i)) { + size = i; + break; + } + } + return size; + } - boolean isVersionNewerThan1_19_R3(); - - boolean isVersionNewerThan1_19_R2(); - - CompletableFuture checkUpdate(); - - boolean isVersionNewerThan1_20(); - - boolean isSpigot(); - - public boolean hasRegionScheduler(); - - String getPluginVersion(); - - boolean isMojmap(); + CompletableFuture openBag(Player viewer, UUID owner); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/FishingBagHolder.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/FishingBagHolder.java index 17d0644a..010fb62d 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/FishingBagHolder.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/bag/FishingBagHolder.java @@ -17,6 +17,8 @@ package net.momirealms.customfishing.api.mechanic.bag; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -35,6 +37,20 @@ public class FishingBagHolder implements InventoryHolder { @Override public @NotNull Inventory getInventory() { + Player player = Bukkit.getPlayer(owner); + if (player != null) { + int rows = BagManager.getBagInventoryRows(player); + if (rows * 9 != inventory.getSize()) { + Inventory newBag = Bukkit.createInventory(this, rows * 9); + ItemStack[] newContents = new ItemStack[rows * 9]; + ItemStack[] oldContents = inventory.getContents(); + for (int i = 0; i < rows * 9 && i < oldContents.length; i++) { + newContents[i] = oldContents[i]; + } + newBag.setContents(newContents); + this.setInventory(newBag); + } + } return inventory; } @@ -49,4 +65,12 @@ public class FishingBagHolder implements InventoryHolder { public void setInventory(Inventory inventory) { this.inventory = inventory; } + + public static FishingBagHolder create(UUID owner, ItemStack[] itemStacks, int size) { + FishingBagHolder holder = new FishingBagHolder(owner); + Inventory inventory = Bukkit.createInventory(holder, size); + holder.setInventory(inventory); + holder.setItems(itemStacks); + return holder; + } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfig.java index d7da2a56..8f601de7 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfig.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfig.java @@ -19,85 +19,80 @@ package net.momirealms.customfishing.api.mechanic.block; import java.util.List; -public class BlockConfig implements BlockSettings { +/** + * Interface representing the configuration for a custom block in the CustomFishing plugin. + * Provides methods to access various block properties and modifiers. + */ +public interface BlockConfig { - private String blockID; - private List dataModifierList; - private List stateModifierList; - private boolean persist; - private double horizontalVector; - private double verticalVector; + String id(); - @Override - public String getBlockID() { - return blockID; + /** + * Gets the unique identifier for the block. + * + * @return The block's unique identifier. + */ + String blockID(); + + /** + * Gets the list of data modifiers applied to the block. + * + * @return A list of {@link BlockDataModifier} objects. + */ + List dataModifier(); + + /** + * Gets the list of state modifiers applied to the block. + * + * @return A list of {@link BlockStateModifier} objects. + */ + List stateModifiers(); + + /** + * Creates a new builder instance for constructing a {@link BlockConfig}. + * + * @return A new {@link Builder} instance. + */ + static Builder builder() { + return new BlockConfigImpl.BuilderImpl(); } - @Override - public List getDataModifier() { - return dataModifierList; + /** + * Builder interface for constructing a {@link BlockConfig} instance. + */ + interface Builder { + + Builder id(String id); + + /** + * Sets the block ID for the configuration. + * + * @param blockID The block's unique identifier. + * @return The current {@link Builder} instance. + */ + Builder blockID(String blockID); + + /** + * Sets the list of data modifiers for the configuration. + * + * @param dataModifierList A list of {@link BlockDataModifier} objects. + * @return The current {@link Builder} instance. + */ + Builder dataModifierList(List dataModifierList); + + /** + * Sets the list of state modifiers for the configuration. + * + * @param stateModifierList A list of {@link BlockStateModifier} objects. + * @return The current {@link Builder} instance. + */ + Builder stateModifierList(List stateModifierList); + + /** + * Builds and returns the configured {@link BlockConfig} instance. + * + * @return The constructed {@link BlockConfig} instance. + */ + BlockConfig build(); } - - @Override - public List getStateModifierList() { - return stateModifierList; - } - - @Override - public boolean isPersist() { - return persist; - } - - @Override - public double getHorizontalVector() { - return horizontalVector; - } - - @Override - public double getVerticalVector() { - return verticalVector; - } - - public static class Builder { - - private final BlockConfig config; - - public Builder() { - this.config = new BlockConfig(); - } - - public Builder persist(boolean value) { - config.persist = value; - return this; - } - - public Builder horizontalVector(double value) { - config.horizontalVector = value; - return this; - } - - public Builder verticalVector(double value) { - config.verticalVector = value; - return this; - } - - public Builder blockID(String value) { - config.blockID = value; - return this; - } - - public Builder dataModifiers(List value) { - config.dataModifierList = value; - return this; - } - - public Builder stateModifiers(List value) { - config.stateModifierList = value; - return this; - } - - public BlockConfig build() { - return config; - } - } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfigImpl.java new file mode 100644 index 00000000..ffcd0879 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockConfigImpl.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.block; + +import java.util.ArrayList; +import java.util.List; + +public class BlockConfigImpl implements BlockConfig { + + private final String blockID; + private final List dataModifierList; + private final List stateModifierList; + private final String id; + + public BlockConfigImpl(String id, String blockID, List dataModifierList, List stateModifierList) { + this.blockID = blockID; + this.dataModifierList = dataModifierList; + this.stateModifierList = stateModifierList; + this.id = id; + } + + @Override + public String id() { + return id; + } + + @Override + public String blockID() { + return blockID; + } + + @Override + public List dataModifier() { + return dataModifierList; + } + + @Override + public List stateModifiers() { + return stateModifierList; + } + + public static class BuilderImpl implements Builder { + private String blockID; + private final List dataModifierList = new ArrayList<>(); + private final List stateModifierList = new ArrayList<>(); + private String id; + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public Builder blockID(String blockID) { + this.blockID = blockID; + return this; + } + @Override + public Builder dataModifierList(List dataModifierList) { + this.dataModifierList.addAll(dataModifierList); + return this; + } + @Override + public Builder stateModifierList(List stateModifierList) { + this.stateModifierList.addAll(stateModifierList); + return this; + } + @Override + public BlockConfig build() { + return new BlockConfigImpl(id, blockID, dataModifierList, stateModifierList); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifier.java index 6ba31318..6830a4e5 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifier.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifier.java @@ -17,9 +17,12 @@ package net.momirealms.customfishing.api.mechanic.block; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; +@FunctionalInterface public interface BlockDataModifier { - void apply(Player player, BlockData blockData); + + void apply(Context context, BlockData blockData); } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierBuilder.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierFactory.java similarity index 87% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierBuilder.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierFactory.java index 3503cd3f..a92bdbfc 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierBuilder.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierFactory.java @@ -17,7 +17,8 @@ package net.momirealms.customfishing.api.mechanic.block; -public interface BlockStateModifierBuilder { +@FunctionalInterface +public interface BlockDataModifierFactory { - BlockStateModifier build(Object args); + BlockDataModifier process(Object args); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockLibrary.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockManager.java similarity index 63% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockLibrary.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockManager.java index e530a6b9..d6faca80 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockLibrary.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockManager.java @@ -17,19 +17,20 @@ package net.momirealms.customfishing.api.mechanic.block; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; +import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; -import java.util.List; +public interface BlockManager extends Reloadable { -public interface BlockLibrary { + boolean registerBlock(@NotNull BlockConfig block); - String identification(); + @NotNull + FallingBlock summonBlockLoot(@NotNull Context context); - BlockData getBlockData(Player player, String id, List modifiers); - - @Nullable - String getBlockID(Block block); + @NotNull + String getBlockID(@NotNull Block block); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifier.java index 60af5f23..d4007c31 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifier.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifier.java @@ -17,9 +17,12 @@ package net.momirealms.customfishing.api.mechanic.block; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; +@FunctionalInterface public interface BlockStateModifier { - void apply(Player player, BlockState blockState); + + void apply(Context context, BlockState blockState); } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierBuilder.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierFactory.java similarity index 87% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierBuilder.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierFactory.java index da0e2e0b..9e95d858 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockDataModifierBuilder.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockStateModifierFactory.java @@ -17,7 +17,8 @@ package net.momirealms.customfishing.api.mechanic.block; -public interface BlockDataModifierBuilder { +@FunctionalInterface +public interface BlockStateModifierFactory { - BlockDataModifier build(Object args); + BlockStateModifier process(Object args); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockDataModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockDataModifier.java new file mode 100644 index 00000000..b67bd45e --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockDataModifier.java @@ -0,0 +1,14 @@ +package net.momirealms.customfishing.api.mechanic.block; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; + +public class EmptyBlockDataModifier implements BlockDataModifier { + + public static final BlockDataModifier INSTANCE = new EmptyBlockDataModifier(); + + @Override + public void apply(Context context, BlockData blockData) { + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockStateModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockStateModifier.java new file mode 100644 index 00000000..68558c7b --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/EmptyBlockStateModifier.java @@ -0,0 +1,14 @@ +package net.momirealms.customfishing.api.mechanic.block; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; + +public class EmptyBlockStateModifier implements BlockStateModifier { + + public static final EmptyBlockStateModifier INSTANCE = new EmptyBlockStateModifier(); + + @Override + public void apply(Context context, BlockState blockState) { + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/ActionBarConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/ActionBarConfig.java deleted file mode 100644 index 06a39372..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/ActionBarConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.competition; - -public class ActionBarConfig extends AbstractCompetitionInfo { - - public static class Builder { - - private final ActionBarConfig config; - - public Builder() { - this.config = new ActionBarConfig(); - } - - public Builder showToAll(boolean showToAll) { - this.config.showToAll = showToAll; - return this; - } - - public Builder refreshRate(int rate) { - this.config.refreshRate = rate; - return this; - } - - public Builder switchInterval(int interval) { - this.config.switchInterval = interval; - return this; - } - - public Builder text(String[] texts) { - this.config.texts = texts; - return this; - } - - public ActionBarConfig build() { - return this.config; - } - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/BossBarConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/BossBarConfig.java deleted file mode 100644 index 32458903..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/BossBarConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.competition; - -import org.bukkit.boss.BarColor; - -public class BossBarConfig extends AbstractCompetitionInfo { - - private BarColor color; - private Overlay overlay; - - public BarColor getColor() { - return color; - } - - public Overlay getOverlay() { - return overlay; - } - - public static class Builder { - - private final BossBarConfig config; - - public Builder() { - this.config = new BossBarConfig(); - } - - public Builder showToAll(boolean showToAll) { - this.config.showToAll = showToAll; - return this; - } - - public Builder refreshRate(int rate) { - this.config.refreshRate = rate; - return this; - } - - public Builder switchInterval(int interval) { - this.config.switchInterval = interval; - return this; - } - - public Builder text(String[] texts) { - this.config.texts = texts; - return this; - } - - public Builder color(BarColor color) { - this.config.color = color; - return this; - } - - public Builder overlay(Overlay overlay) { - this.config.overlay = overlay; - return this; - } - - public BossBarConfig build() { - return this.config; - } - } - - public enum Overlay { - NOTCHED_6, - NOTCHED_10, - NOTCHED_12, - NOTCHED_20, - PROGRESS - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfig.java index adc698a0..4b714582 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfig.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfig.java @@ -18,178 +18,227 @@ package net.momirealms.customfishing.api.mechanic.competition; import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig; +import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig; import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.bukkit.entity.Player; import java.util.HashMap; -public class CompetitionConfig { +/** + * Interface representing the configuration for a fishing competition. + */ +public interface CompetitionConfig { - private final String key; - private int duration; - private int minPlayers; - private BossBarConfig bossBarConfig; - private ActionBarConfig actionBarConfig; - private Action[] skipActions; - private Action[] startActions; - private Action[] endActions; - private Action[] joinActions; - private Requirement[] requirements; - private CompetitionGoal goal; - private HashMap rewards; + CompetitionGoal DEFAULT_GOAL = CompetitionGoal.CATCH_AMOUNT; + int DEFAULT_DURATION = 300; + int DEFAULT_MIN_PLAYERS = 0; + Requirement[] DEFAULT_REQUIREMENTS = null; + Action[] DEFAULT_SKIP_ACTIONS = null; + Action[] DEFAULT_START_ACTIONS = null; + Action[] DEFAULT_END_ACTIONS = null; + Action[] DEFAULT_JOIN_ACTIONS = null; + HashMap[]> DEFAULT_REWARDS = new HashMap<>(); - public CompetitionConfig(String key) { - this.key = key; - } + /** + * Gets the unique key for the competition. + * + * @return the key for the competition. + */ + String key(); - public String getKey() { - return key; - } + /** + * Gets the duration of the competition in seconds. + * + * @return the duration in seconds. + */ + int durationInSeconds(); - public int getDurationInSeconds() { - return duration; - } + /** + * Gets the minimum number of players required to start the competition. + * + * @return the minimum number of players. + */ + int minPlayersToStart(); - public int getMinPlayersToStart() { - return minPlayers; - } + /** + * Gets the actions to be performed when the competition starts. + * + * @return an array of start actions. + */ + Action[] startActions(); - @Nullable - public Action[] getStartActions() { - return startActions; - } + /** + * Gets the actions to be performed when the competition ends. + * + * @return an array of end actions. + */ + Action[] endActions(); - @Nullable - public Action[] getEndActions() { - return endActions; + /** + * Gets the actions to be performed when a player joins the competition. + * + * @return an array of join actions. + */ + Action[] joinActions(); + + /** + * Gets the actions to be performed when a player skips the competition. + * + * @return an array of skip actions. + */ + Action[] skipActions(); + + /** + * Gets the requirements that players must meet to join the competition. + * + * @return an array of join requirements. + */ + Requirement[] joinRequirements(); + + /** + * Gets the goal of the competition. + * + * @return the competition goal. + */ + CompetitionGoal goal(); + + /** + * Gets the rewards for the competition. + * + * @return a hashmap where the key is a string identifier and the value is an array of actions. + */ + HashMap[]> rewards(); + + /** + * Gets the configuration for the boss bar during the competition. + * + * @return the boss bar configuration. + */ + BossBarConfig bossBarConfig(); + + /** + * Gets the configuration for the action bar during the competition. + * + * @return the action bar configuration. + */ + ActionBarConfig actionBarConfig(); + + /** + * Creates a new builder for the competition configuration. + * + * @return a new builder instance. + */ + static Builder builder() { + return new CompetitionConfigImpl.BuilderImpl(); } /** - * Get the actions to perform if player joined the competition - * - * @return actions + * Builder interface for constructing a CompetitionConfig instance. */ - @Nullable - public Action[] getJoinActions() { - return joinActions; - } + interface Builder { - /** - * Get the actions to perform if the amount of players doesn't meet the requirement - * - * @return actions - */ - @Nullable - public Action[] getSkipActions() { - return skipActions; - } + /** + * Sets the unique key for the competition. + * + * @param key the key for the competition. + * @return the builder instance. + */ + Builder key(String key); - /** - * Get the requirements for participating the competition - * - * @return requirements - */ - @Nullable - public Requirement[] getRequirements() { - return requirements; - } + /** + * Sets the goal of the competition. + * + * @param goal the competition goal. + * @return the builder instance. + */ + Builder goal(CompetitionGoal goal); - @NotNull - public CompetitionGoal getGoal() { - return goal; - } + /** + * Sets the duration of the competition. + * + * @param duration the duration in seconds. + * @return the builder instance. + */ + Builder duration(int duration); - /** - * Get the reward map - * - * @return reward map - */ - public HashMap getRewards() { - return rewards; - } + /** + * Sets the minimum number of players required to start the competition. + * + * @param minPlayers the minimum number of players. + * @return the builder instance. + */ + Builder minPlayers(int minPlayers); - @Nullable - public BossBarConfig getBossBarConfig() { - return bossBarConfig; - } + /** + * Sets the requirements that players must meet to join the competition. + * + * @param joinRequirements an array of join requirements. + * @return the builder instance. + */ + Builder joinRequirements(Requirement[] joinRequirements); - @Nullable - public ActionBarConfig getActionBarConfig() { - return actionBarConfig; - } + /** + * Sets the actions to be performed when a player skips the competition. + * + * @param skipActions an array of skip actions. + * @return the builder instance. + */ + Builder skipActions(Action[] skipActions); - public static Builder builder(String key) { - return new Builder(key); - } + /** + * Sets the actions to be performed when the competition starts. + * + * @param startActions an array of start actions. + * @return the builder instance. + */ + Builder startActions(Action[] startActions); - public static class Builder { + /** + * Sets the actions to be performed when the competition ends. + * + * @param endActions an array of end actions. + * @return the builder instance. + */ + Builder endActions(Action[] endActions); - private final CompetitionConfig config; + /** + * Sets the actions to be performed when a player joins the competition. + * + * @param joinActions an array of join actions. + * @return the builder instance. + */ + Builder joinActions(Action[] joinActions); - public Builder(String key) { - this.config = new CompetitionConfig(key); - } + /** + * Sets the rewards for the competition. + * + * @param rewards a hashmap where the key is a string identifier and the value is an array of actions. + * @return the builder instance. + */ + Builder rewards(HashMap[]> rewards); - public Builder duration(int duration) { - config.duration = duration; - return this; - } + /** + * Sets the configuration for the boss bar during the competition. + * + * @param bossBarConfig the boss bar configuration. + * @return the builder instance. + */ + Builder bossBarConfig(BossBarConfig bossBarConfig); - public Builder minPlayers(int min) { - config.minPlayers = min; - return this; - } + /** + * Sets the configuration for the action bar during the competition. + * + * @param actionBarConfig the action bar configuration. + * @return the builder instance. + */ + Builder actionBarConfig(ActionBarConfig actionBarConfig); - public Builder startActions(Action[] startActions) { - config.startActions = startActions; - return this; - } - - public Builder endActions(Action[] endActions) { - config.endActions = endActions; - return this; - } - - public Builder skipActions(Action[] skipActions) { - config.skipActions = skipActions; - return this; - } - - public Builder joinActions(Action[] joinActions) { - config.joinActions = joinActions; - return this; - } - - @SuppressWarnings("UnusedReturnValue") - public Builder actionbar(ActionBarConfig actionBarConfig) { - config.actionBarConfig = actionBarConfig; - return this; - } - - @SuppressWarnings("UnusedReturnValue") - public Builder bossbar(BossBarConfig bossBarConfig) { - config.bossBarConfig = bossBarConfig; - return this; - } - - public Builder requirements(Requirement[] requirements) { - config.requirements = requirements; - return this; - } - - public Builder goal(CompetitionGoal goal) { - config.goal = goal; - return this; - } - - public Builder rewards(HashMap rewards) { - config.rewards = rewards; - return this; - } - - public CompetitionConfig build() { - return config; - } + /** + * Builds and returns the CompetitionConfig instance. + * + * @return the constructed CompetitionConfig instance. + */ + CompetitionConfig build(); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfigImpl.java new file mode 100644 index 00000000..14bad1e3 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionConfigImpl.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition; + +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig; +import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.HashMap; + +public class CompetitionConfigImpl implements CompetitionConfig { + + private final String key; + private final CompetitionGoal goal; + private final int duration; + private final int minPlayers; + private final Requirement[] joinRequirements; + private final Action[] skipActions; + private final Action[] startActions; + private final Action[] endActions; + private final Action[] joinActions; + private final HashMap[]> rewards; + private final BossBarConfig bossBarConfig; + private final ActionBarConfig actionBarConfig; + + public CompetitionConfigImpl(String key, CompetitionGoal goal, int duration, int minPlayers, Requirement[] joinRequirements, Action[] skipActions, Action[] startActions, Action[] endActions, Action[] joinActions, HashMap[]> rewards, BossBarConfig bossBarConfig, ActionBarConfig actionBarConfig) { + this.key = key; + this.goal = goal; + this.duration = duration; + this.minPlayers = minPlayers; + this.joinRequirements = joinRequirements; + this.skipActions = skipActions; + this.startActions = startActions; + this.endActions = endActions; + this.joinActions = joinActions; + this.rewards = rewards; + this.bossBarConfig = bossBarConfig; + this.actionBarConfig = actionBarConfig; + } + + @Override + public String key() { + return key; + } + + @Override + public int durationInSeconds() { + return duration; + } + + @Override + public int minPlayersToStart() { + return minPlayers; + } + + @Override + public Action[] startActions() { + return startActions; + } + + @Override + public Action[] endActions() { + return endActions; + } + + @Override + public Action[] joinActions() { + return joinActions; + } + + @Override + public Action[] skipActions() { + return skipActions; + } + + @Override + public Requirement[] joinRequirements() { + return joinRequirements; + } + + @Override + public CompetitionGoal goal() { + return goal; + } + + @Override + public HashMap[]> rewards() { + return rewards; + } + + @Override + public BossBarConfig bossBarConfig() { + return bossBarConfig; + } + + @Override + public ActionBarConfig actionBarConfig() { + return actionBarConfig; + } + + public static class BuilderImpl implements Builder { + private String key; + private CompetitionGoal goal = DEFAULT_GOAL; + private int duration = DEFAULT_DURATION; + private int minPlayers = DEFAULT_MIN_PLAYERS; + private Requirement[] joinRequirements = DEFAULT_REQUIREMENTS; + private Action[] skipActions = DEFAULT_SKIP_ACTIONS; + private Action[] startActions = DEFAULT_START_ACTIONS; + private Action[] endActions = DEFAULT_END_ACTIONS; + private Action[] joinActions = DEFAULT_JOIN_ACTIONS; + private HashMap[]> rewards = DEFAULT_REWARDS; + private BossBarConfig bossBarConfig; + private ActionBarConfig actionBarConfig; + @Override + public Builder key(String key) { + this.key = key; + return this; + } + @Override + public Builder goal(CompetitionGoal goal) { + this.goal = goal; + return this; + } + @Override + public Builder duration(int duration) { + this.duration = duration; + return this; + } + @Override + public Builder minPlayers(int minPlayers) { + this.minPlayers = minPlayers; + return this; + } + @Override + public Builder joinRequirements(Requirement[] joinRequirements) { + this.joinRequirements = joinRequirements; + return this; + } + @Override + public Builder skipActions(Action[] skipActions) { + this.skipActions = skipActions; + return this; + } + @Override + public Builder startActions(Action[] startActions) { + this.startActions = startActions; + return this; + } + @Override + public Builder endActions(Action[] endActions) { + this.endActions = endActions; + return this; + } + @Override + public Builder joinActions(Action[] joinActions) { + this.joinActions = joinActions; + return this; + } + @Override + public Builder rewards(HashMap[]> rewards) { + this.rewards = rewards; + return this; + } + @Override + public Builder bossBarConfig(BossBarConfig bossBarConfig) { + this.bossBarConfig = bossBarConfig; + return this; + } + @Override + public Builder actionBarConfig(ActionBarConfig actionBarConfig) { + this.actionBarConfig = actionBarConfig; + return this; + } + @Override + public CompetitionConfig build() { + return new CompetitionConfigImpl(key, goal, duration, minPlayers, joinRequirements, skipActions, startActions, endActions, joinActions, rewards, bossBarConfig, actionBarConfig); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionGoal.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionGoal.java index 306471a9..7560c7c4 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionGoal.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionGoal.java @@ -17,17 +17,115 @@ package net.momirealms.customfishing.api.mechanic.competition; -import java.util.concurrent.ThreadLocalRandom; +import net.kyori.adventure.util.Index; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.util.RandomUtils; +import org.apache.logging.log4j.util.Supplier; +import org.apache.logging.log4j.util.TriConsumer; +import org.bukkit.entity.Player; -public enum CompetitionGoal { +import java.util.Optional; - CATCH_AMOUNT, - TOTAL_SCORE, - MAX_SIZE, - TOTAL_SIZE, - RANDOM; +public final class CompetitionGoal { + public static final CompetitionGoal CATCH_AMOUNT = new CompetitionGoal( + "catch_amount", + ((rankingProvider, player, score) -> rankingProvider.refreshData(player, 1)), + () -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.GOAL_CATCH_AMOUNT.build().key())).orElse("catch_amount") + ); + public static final CompetitionGoal TOTAL_SCORE = new CompetitionGoal( + "total_score", + (RankingProvider::refreshData), + () -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.GOAL_TOTAL_SCORE.build().key())).orElse("total_score") + ); + public static final CompetitionGoal MAX_SIZE = new CompetitionGoal( + "max_size", + ((rankingProvider, player, score) -> { + if (rankingProvider.getPlayerScore(player) < score) { + rankingProvider.setData(player, score); + } + }), + () -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.GOAL_MAX_SIZE.build().key())).orElse("max_size") + ); + public static final CompetitionGoal MIN_SIZE = new CompetitionGoal( + "min_size", + ((rankingProvider, player, score) -> { + if (rankingProvider.getPlayerScore(player) > score) { + rankingProvider.setData(player, score); + } + }), + () -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.GOAL_MIN_SIZE.build().key())).orElse("min_size") + ); + public static final CompetitionGoal TOTAL_SIZE = new CompetitionGoal( + "total_size", + (RankingProvider::refreshData), + () -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.GOAL_TOTAL_SIZE.build().key())).orElse("total_size") + ); + public static final CompetitionGoal RANDOM = new CompetitionGoal( + "random", + (rankingProvider, player, score) -> {}, + () -> "random" + ); + + private static final CompetitionGoal[] values = new CompetitionGoal[] { + CATCH_AMOUNT, TOTAL_SCORE, MAX_SIZE, TOTAL_SIZE, RANDOM + }; + + private static final Index index = Index.create(CompetitionGoal::key, values()); + + /** + * Gets an array containing all defined competition goals. + * + * @return An array of all competition goals. + */ + public static CompetitionGoal[] values() { + return values; + } + + /** + * Gets the index of competition goals by their keys. + * + * @return An index mapping keys to competition goals. + */ + public static Index index() { + return index; + } + + /** + * Gets a randomly selected competition goal. + * + * @return A randomly selected competition goal. + */ public static CompetitionGoal getRandom() { - return CompetitionGoal.values()[ThreadLocalRandom.current().nextInt(CompetitionGoal.values().length - 1)]; + return CompetitionGoal.values()[RandomUtils.generateRandomInt(0, values.length - 1)]; + } + + private final String key; + private final TriConsumer scoreConsumer; + private final Supplier nameSupplier; + + private CompetitionGoal(String key, TriConsumer scoreConsumer, Supplier nameSupplier) { + this.key = key; + this.scoreConsumer = scoreConsumer; + this.nameSupplier = nameSupplier; + } + + /** + * Gets the key representing this competition goal. + * + * @return The key of the competition goal. + */ + public String key() { + return key; + } + + public void refreshScore(RankingProvider ranking, Player player, Double score) { + scoreConsumer.accept(ranking, player.getName(), score); + } + + @Override + public String toString() { + return nameSupplier.get(); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionManager.java new file mode 100644 index 00000000..91efc706 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionManager.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public interface CompetitionManager extends Reloadable { + + boolean startCompetition(String competition, boolean force, @Nullable String serverGroup); + + boolean startCompetition(CompetitionConfig config, boolean force, @Nullable String serverGroup); + + @Nullable + FishingCompetition getOnGoingCompetition(); + + int getNextCompetitionInSeconds(); + + @Nullable + CompetitionConfig getCompetition(String key); + + Collection getCompetitionIDs(); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionPlayer.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionPlayer.java index 5a18ef9e..7ae2597c 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionPlayer.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/CompetitionPlayer.java @@ -19,52 +19,98 @@ package net.momirealms.customfishing.api.mechanic.competition; import org.jetbrains.annotations.NotNull; -public class CompetitionPlayer implements Comparable{ +/** + * Represents a player participating in a fishing competition. + */ +public class CompetitionPlayer implements Comparable { - public static CompetitionPlayer empty = new CompetitionPlayer("", 0); - private long time; private final String player; + private long time; private double score; + /** + * Constructs a new CompetitionPlayer with the specified player name and initial score. + * + * @param player the name of the player. + * @param score the initial score of the player. + */ public CompetitionPlayer(String player, double score) { this.player = player; this.score = score; this.time = System.currentTimeMillis(); } - public void addScore(double score){ + /** + * Adds the specified score to the player's current score. + * If the added score is positive, updates the player's time to the current time. + * + * @param score the score to add. + */ + public void addScore(double score) { this.score += score; + if (score <= 0) return; this.time = System.currentTimeMillis(); } - public void setScore(double score){ + /** + * Sets the player's score to the specified value and updates the player's time to the current time. + * + * @param score the new score for the player. + */ + public void setScore(double score) { this.score = score; this.time = System.currentTimeMillis(); } + /** + * Gets the time when the player's score was last updated. + * + * @return the last update time in milliseconds. + */ public long getTime() { - return time; + return this.time; } + /** + * Gets the player's current score. + * + * @return the current score. + */ public double getScore() { return this.score; } - public String getPlayer(){ + /** + * Gets the name of the player. + * + * @return the player's name. + */ + public String getPlayer() { return this.player; } + /** + * Compares this player to another CompetitionPlayer for ordering. + * Players are compared first by score, then by time if scores are equal. + * + * @param another the other player to compare to. + */ @Override - public int compareTo(@NotNull CompetitionPlayer competitionPlayer) { - if (competitionPlayer.getScore() != this.score) { - return (competitionPlayer.getScore() > this.score) ? 1 : -1; - } else if (competitionPlayer.getTime() != this.time) { - return (competitionPlayer.getTime() > this.time) ? 1 : -1; + public int compareTo(@NotNull CompetitionPlayer another) { + if (another.getScore() != this.score) { + return (another.getScore() > this.score) ? 1 : -1; + } else if (another.getTime() != this.time) { + return (another.getTime() > this.time) ? 1 : -1; } else { return 0; } } + /** + * Returns a string representation of the CompetitionPlayer. + * + * @return a string containing the player's name, score, and last update time. + */ @Override public String toString() { return "CompetitionPlayer[" + diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java index 1e5d7b1d..f19b46bd 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/FishingCompetition.java @@ -17,19 +17,17 @@ package net.momirealms.customfishing.api.mechanic.competition; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; public interface FishingCompetition { /** * Start the fishing competition */ - void start(); + void start(boolean triggerEvent); /** * Stop the fishing competition @@ -91,34 +89,29 @@ public interface FishingCompetition { * * @return The configuration of the fishing competition. */ - @NotNull CompetitionConfig getConfig(); + @NotNull + CompetitionConfig getConfig(); /** * Gets the goal of the fishing competition. * * @return The goal of the fishing competition. */ - @NotNull CompetitionGoal getGoal(); + @NotNull + CompetitionGoal getGoal(); /** * Gets the ranking data for the fishing competition. * * @return The ranking data for the fishing competition. */ - @NotNull Ranking getRanking(); + @NotNull + RankingProvider getRanking(); /** - * Gets the cached placeholders for the fishing competition. + * Get the public context * - * @return A ConcurrentHashMap containing cached placeholders. + * @return public context */ - @NotNull Map getCachedPlaceholders(); - - /** - * Gets a specific cached placeholder value by its key. - * - * @param papi The key of the cached placeholder. - * @return The cached placeholder value as a string, or null if not found. - */ - @Nullable String getCachedPlaceholder(String papi); + Context getPublicContext(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/Ranking.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/RankingProvider.java similarity index 94% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/Ranking.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/RankingProvider.java index 728ac725..7a65432e 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/Ranking.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/RankingProvider.java @@ -17,12 +17,12 @@ package net.momirealms.customfishing.api.mechanic.competition; -import net.momirealms.customfishing.api.common.Pair; +import net.momirealms.customfishing.common.util.Pair; import org.jetbrains.annotations.Nullable; import java.util.Iterator; -public interface Ranking { +public interface RankingProvider { /** * Clears the list of competition players. @@ -62,9 +62,9 @@ public interface Ranking { void removePlayer(String player); /** - * Returns an iterator for iterating over pairs of player names and scores. + * Returns an iterator for iterating over items of player names and scores. * - * @return An iterator for pairs of player names and scores. + * @return An iterator for items of player names and scores. */ Iterator> getIterator(); diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/AbstractCompetitionInfo.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/AbstractCompetitionInfo.java similarity index 71% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/AbstractCompetitionInfo.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/AbstractCompetitionInfo.java index 434395d7..d241e1ac 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/AbstractCompetitionInfo.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/AbstractCompetitionInfo.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.competition; +package net.momirealms.customfishing.api.mechanic.competition.info; /** * Abstract base class for competition information. @@ -27,13 +27,22 @@ public abstract class AbstractCompetitionInfo { protected int switchInterval; protected boolean showToAll; protected String[] texts; + protected boolean enabled; + + protected AbstractCompetitionInfo(boolean enabled, int refreshRate, int switchInterval, boolean showToAll, String[] texts) { + this.refreshRate = refreshRate; + this.switchInterval = switchInterval; + this.showToAll = showToAll; + this.texts = texts; + this.enabled = enabled; + } /** * Get the refresh rate for updating competition information. * * @return The refresh rate in ticks. */ - public int getRefreshRate() { + public int refreshRate() { return refreshRate; } @@ -42,7 +51,7 @@ public abstract class AbstractCompetitionInfo { * * @return The switch interval in ticks. */ - public int getSwitchInterval() { + public int switchInterval() { return switchInterval; } @@ -51,7 +60,7 @@ public abstract class AbstractCompetitionInfo { * * @return True if information is shown to all players, otherwise only to participants. */ - public boolean isShowToAll() { + public boolean showToAll() { return showToAll; } @@ -60,7 +69,16 @@ public abstract class AbstractCompetitionInfo { * * @return An array of competition information texts. */ - public String[] getTexts() { + public String[] texts() { return texts; } + + /** + * If the feature is enabled. + * + * @return enabled or not. + */ + public boolean enabled() { + return enabled; + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfig.java new file mode 100644 index 00000000..08cf314a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfig.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition.info; + +public interface ActionBarConfig { + + int DEFAULT_REFRESH_RATE = 20; + int DEFAULT_SWITCH_INTERVAL = 200; + boolean DEFAULT_VISIBILITY = true; + String[] DEFAULT_TEXTS = new String[]{""}; + + /** + * Get the refresh rate for updating the competition information on the action bar. + * + * @return The refresh rate in ticks. + */ + int refreshRate(); + + /** + * Get the switch interval for displaying different competition texts. + * + * @return The switch interval in ticks. + */ + int switchInterval(); + + /** + * Check if competition information should be shown to all players. + * + * @return True if information is shown to all players, otherwise only to participants. + */ + boolean showToAll(); + + /** + * Get an array of competition information texts. + * + * @return An array of competition information texts. + */ + String[] texts(); + + /** + * Is action bar enabled + * + * @return enabled or not + */ + boolean enabled(); + + /** + * Creates a new builder instance for constructing {@code ActionBarConfig} objects. + * + * @return A new {@code Builder} instance. + */ + static Builder builder() { + return new ActionBarConfigImpl.BuilderImpl(); + } + + /** + * Builder interface for constructing {@code ActionBarConfig} objects. + */ + interface Builder { + + /** + * Sets whether the competition information should be shown to all players. + * + * @param showToAll True to show information to all players, false to show only to participants. + * @return The current {@code Builder} instance. + */ + Builder showToAll(boolean showToAll); + + /** + * Sets the refresh rate for updating the competition information. + * + * @param rate The refresh rate in ticks. + * @return The current {@code Builder} instance. + */ + Builder refreshRate(int rate); + + /** + * Sets the interval for switching between different competition texts. + * + * @param interval The switch interval in ticks. + * @return The current {@code Builder} instance. + */ + Builder switchInterval(int interval); + + /** + * Sets the texts to be displayed on the action bar during the competition. + * + * @param texts An array of competition information texts. + * @return The current {@code Builder} instance. + */ + Builder text(String[] texts); + + Builder enable(boolean enable); + + /** + * Builds the {@code ActionBarConfig} object with the configured settings. + * + * @return The constructed {@code ActionBarConfig} object. + */ + ActionBarConfig build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfigImpl.java new file mode 100644 index 00000000..b0a359b4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/ActionBarConfigImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition.info; + +public class ActionBarConfigImpl extends AbstractCompetitionInfo implements ActionBarConfig { + + public ActionBarConfigImpl(boolean enable, int refreshRate, int switchInterval, boolean showToAll, String[] texts) { + super(enable, refreshRate, switchInterval, showToAll, texts); + } + + public static class BuilderImpl implements Builder { + private int refreshRate = DEFAULT_REFRESH_RATE; + private int switchInterval = DEFAULT_SWITCH_INTERVAL; + private boolean showToAll = DEFAULT_VISIBILITY; + private String[] texts = DEFAULT_TEXTS; + private boolean enable = true; + @Override + public Builder showToAll(boolean showToAll) { + this.showToAll = showToAll; + return this; + } + @Override + public Builder refreshRate(int rate) { + this.refreshRate = rate; + return this; + } + @Override + public Builder switchInterval(int interval) { + this.switchInterval = interval; + return this; + } + @Override + public Builder text(String[] texts) { + this.texts = texts; + return this; + } + @Override + public Builder enable(boolean enable) { + this.enable = enable; + return this; + } + @Override + public ActionBarConfig build() { + return new ActionBarConfigImpl(enable, refreshRate, switchInterval, showToAll, texts); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfig.java new file mode 100644 index 00000000..f1144d27 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfig.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition.info; + +import net.kyori.adventure.bossbar.BossBar; + +public interface BossBarConfig { + + int DEFAULT_REFRESH_RATE = 20; + int DEFAULT_SWITCH_INTERVAL = 200; + boolean DEFAULT_VISIBILITY = true; + String[] DEFAULT_TEXTS = new String[]{""}; + BossBar.Color DEFAULT_COLOR = BossBar.Color.BLUE; + BossBar.Overlay DEFAULT_OVERLAY = BossBar.Overlay.PROGRESS; + + /** + * Get the refresh rate for updating competition information. + * + * @return The refresh rate in ticks. + */ + int refreshRate(); + + /** + * Get the switch interval for displaying different competition texts. + * + * @return The switch interval in ticks. + */ + int switchInterval(); + + /** + * Check if competition information should be shown to all players. + * + * @return True if information is shown to all players, otherwise only to participants. + */ + boolean showToAll(); + + /** + * Get an array of competition information texts. + * + * @return An array of competition information texts. + */ + String[] texts(); + + /** + * Gets the color of the boss bar. + * + * @return The color of the boss bar. + */ + BossBar.Color color(); + + /** + * Gets the overlay style of the boss bar. + * + * @return The overlay style of the boss bar. + */ + BossBar.Overlay overlay(); + + /** + * Is boss bar enabled + * + * @return enabled or not + */ + boolean enabled(); + + /** + * Creates a new builder instance for constructing {@code BossBarConfig} objects. + * + * @return A new {@code Builder} instance. + */ + static Builder builder() { + return new BossBarConfigImpl.BuilderImpl(); + } + + /** + * Builder interface for constructing {@code BossBarConfig} objects. + */ + interface Builder { + + /** + * Sets whether the competition information should be shown to all players. + * + * @param showToAll True to show information to all players, false to show only to participants. + * @return The current {@code Builder} instance. + */ + Builder showToAll(boolean showToAll); + + /** + * Sets the refresh rate for updating the competition information. + * + * @param rate The refresh rate in ticks. + * @return The current {@code Builder} instance. + */ + Builder refreshRate(int rate); + + /** + * Sets the interval for switching between different competition texts. + * + * @param interval The switch interval in ticks. + * @return The current {@code Builder} instance. + */ + Builder switchInterval(int interval); + + /** + * Sets the texts to be displayed on the boss bar during the competition. + * + * @param texts An array of competition information texts. + * @return The current {@code Builder} instance. + */ + Builder text(String[] texts); + + /** + * Sets the color of the boss bar. + * + * @param color The color of the boss bar. + * @return The current {@code Builder} instance. + */ + Builder color(BossBar.Color color); + + /** + * Sets the overlay style of the boss bar. + * + * @param overlay The overlay style of the boss bar. + * @return The current {@code Builder} instance. + */ + Builder overlay(BossBar.Overlay overlay); + + Builder enable(boolean enable); + + /** + * Builds the {@code BossBarConfig} object with the configured settings. + * + * @return The constructed {@code BossBarConfig} object. + */ + BossBarConfig build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfigImpl.java new file mode 100644 index 00000000..f2a50971 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/competition/info/BossBarConfigImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.competition.info; + +import net.kyori.adventure.bossbar.BossBar; + +public class BossBarConfigImpl extends AbstractCompetitionInfo implements BossBarConfig { + + private final BossBar.Color color; + private final BossBar.Overlay overlay; + + public BossBarConfigImpl(boolean enable, int refreshRate, int switchInterval, boolean showToAll, String[] texts, BossBar.Color color, BossBar.Overlay overlay) { + super(enable, refreshRate, switchInterval, showToAll, texts); + this.color = color; + this.overlay = overlay; + } + + @Override + public BossBar.Color color() { + return color; + } + + @Override + public BossBar.Overlay overlay() { + return overlay; + } + + public static class BuilderImpl implements Builder { + private int refreshRate = DEFAULT_REFRESH_RATE; + private int switchInterval = DEFAULT_SWITCH_INTERVAL; + private boolean showToAll = DEFAULT_VISIBILITY; + private String[] texts = DEFAULT_TEXTS; + private BossBar.Overlay overlay = DEFAULT_OVERLAY; + private BossBar.Color color = DEFAULT_COLOR; + private boolean enable = true; + @Override + public Builder showToAll(boolean showToAll) { + this.showToAll = showToAll; + return this; + } + @Override + public Builder refreshRate(int rate) { + this.refreshRate = rate; + return this; + } + @Override + public Builder switchInterval(int interval) { + this.switchInterval = interval; + return this; + } + @Override + public Builder text(String[] texts) { + this.texts = texts; + return this; + } + @Override + public Builder color(BossBar.Color color) { + this.color = color; + return this; + } + @Override + public Builder overlay(BossBar.Overlay overlay) { + this.overlay = overlay; + return this; + } + @Override + public Builder enable(boolean enable) { + this.enable = enable; + return this; + } + @Override + public BossBarConfig build() { + return new BossBarConfigImpl(enable, refreshRate, switchInterval, showToAll, texts, color, overlay); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/Condition.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/Condition.java deleted file mode 100644 index 8d41e070..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/Condition.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.condition; - -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents a condition with associated data - */ -public class Condition { - - protected Location location; - protected final Player player; - protected final @NotNull Map args; - - /** - * Creates a new Condition object based on a player's location. - * - * @param player The player associated with this condition. - */ - public Condition(@NotNull Player player) { - this(player.getLocation(), player, new HashMap<>()); - } - - /** - * Creates a new Condition object with specified arguments. - * - * @param player The player associated with this condition. - * @param args A map of arguments associated with this condition. - */ - public Condition(@NotNull Player player, @NotNull Map args) { - this(player.getLocation(), player, args); - } - - /** - * Creates a new Condition object with a specific location, player, and arguments. - * - * @param location The location associated with this condition. - * @param player The player associated with this condition. - * @param args A map of arguments associated with this condition. - */ - public Condition(Location location, Player player, @NotNull Map args) { - this.location = location; - this.player = player; - this.args = args; - if (player != null) - this.args.put("{player}", player.getName()); - if (location != null) { - this.args.put("{x}", String.valueOf(location.getX())); - this.args.put("{y}", String.valueOf(location.getY())); - this.args.put("{z}", String.valueOf(location.getZ())); - this.args.put("{world}", location.getWorld().getName()); - } - } - - /** - * Sets the location associated with this condition. - * - * @param location The new location to set. - */ - public void setLocation(@NotNull Location location) { - this.location = location; - this.args.put("{x}", String.valueOf(location.getX())); - this.args.put("{y}", String.valueOf(location.getY())); - this.args.put("{z}", String.valueOf(location.getZ())); - this.args.put("{world}", location.getWorld().getName()); - } - - /** - * Gets the location associated with this condition. - * - * @return The location associated with this condition. - */ - public Location getLocation() { - return location; - } - - /** - * Gets the player associated with this condition. - * - * @return The player associated with this condition. - */ - public Player getPlayer() { - return player; - } - - /** - * Gets the map of arguments associated with this condition. - * - * @return A map of arguments associated with this condition. - */ - @NotNull - public Map getArgs() { - return args; - } - - /** - * Gets the value of a specific argument by its key. - * - * @param key The key of the argument to retrieve. - * @return The value of the argument or null if not found. - */ - @Nullable - public String getArg(String key) { - return args.get(key); - } - - /** - * Inserts or updates an argument with the specified key and value. - * - * @param key The key of the argument to insert or update. - * @param value The value to set for the argument. - */ - public void insertArg(String key, String value) { - args.put(key, value); - } - - /** - * Deletes an argument with the specified key. - * - * @param key The key of the argument to delete. - * @return The value of the deleted argument or null if not found. - */ - public String delArg(String key) { - return args.remove(key); - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/FishingPreparation.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/FishingPreparation.java deleted file mode 100644 index 2ce5b509..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/condition/FishingPreparation.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.condition; - -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class FishingPreparation extends Condition { - - public FishingPreparation(Player player) { - super(player); - } - - /** - * Retrieves the ItemStack representing the fishing rod. - * - * @return The ItemStack representing the fishing rod. - */ - @NotNull - public abstract ItemStack getRodItemStack(); - - /** - * Retrieves the ItemStack representing the bait (if available). - * - * @return The ItemStack representing the bait, or null if no bait is set. - */ - @Nullable - public abstract ItemStack getBaitItemStack(); - - /** - * Checks if player meet the requirements for fishing gears - * - * @return True if can fish, false otherwise. - */ - public abstract boolean canFish(); - - /** - * Merges a FishingEffect into this fishing rod, applying effect modifiers. - * - * @param effect The FishingEffect to merge into this rod. - */ - public abstract void mergeEffect(FishingEffect effect); - - /** - * Triggers actions associated with a specific action trigger. - * - * @param actionTrigger The action trigger that initiates the actions. - */ - public abstract void triggerActions(ActionTrigger actionTrigger); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BaitConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BaitConfigParser.java new file mode 100644 index 00000000..2014af75 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BaitConfigParser.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class BaitConfigParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> baseEffectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + + public BaitConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.getString("material"); + if (!section.contains("tag")) section.set("tag", true); + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + baseEffectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case ITEM -> { + ItemParserFunction propertyFunction = (ItemParserFunction) function; + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.BAIT); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id) + .type(MechanicType.BAIT); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : baseEffectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ITEM) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BlockConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BlockConfigParser.java new file mode 100644 index 00000000..261f00a5 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/BlockConfigParser.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.block.BlockConfig; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class BlockConfigParser { + + private final String id; + private final List> blockBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + + public BlockConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case BLOCK -> { + BlockParserFunction blockParserFunction = (BlockParserFunction) function; + Consumer consumer = blockParserFunction.accept(entry.getValue()); + blockBuilderConsumers.add(consumer); + } + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public BlockConfig getBlock() { + BlockConfig.Builder builder = BlockConfig.builder() + .id(id); + for (Consumer consumer : blockBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.BLOCK) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.BLOCK); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigManager.java new file mode 100644 index 00000000..bbdfdba2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigManager.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; +import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; +import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; +import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; +import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.entity.EntityConfig; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.hook.HookConfig; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; +import net.momirealms.customfishing.common.config.ConfigLoader; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.customfishing.common.util.TriConsumer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class ConfigManager implements ConfigLoader, Reloadable { + + private static ConfigManager instance; + protected final BukkitCustomFishingPlugin plugin; + protected final HashMap> formatFunctions = new HashMap<>(); + protected int placeholderLimit; + protected boolean redisRanking; + protected String serverGroup; + protected String[] itemDetectOrder = new String[0]; + protected String[] blockDetectOrder = new String[0]; + protected int dataSaveInterval; + protected boolean logDataSaving; + protected boolean lockData; + protected boolean metrics; + protected boolean checkUpdate; + protected boolean debug; + protected boolean overrideVanillaWaitTime; + protected int waterMinTime; + protected int waterMaxTime; + protected boolean enableLavaFishing; + protected int lavaMinTime; + protected int lavaMaxTime; + protected boolean enableVoidFishing; + protected int voidMinTime; + protected int voidMaxTime; + protected int multipleLootSpawnDelay; + protected boolean restrictedSizeRange; + protected List durabilityLore; + protected boolean allowMultipleTotemType; + protected boolean allowSameTotemType; + protected EventPriority eventPriority; + protected Requirement[] mechanicRequirements; + protected Requirement[] skipGameRequirements; + protected Requirement[] autoFishingRequirements; + protected boolean enableBag; + protected boolean baitAnimation; + protected List, Integer>> globalEffects; + + protected ConfigManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + instance = this; + } + + public static boolean debug() { + return instance.debug; + } + + public static int placeholderLimit() { + return instance.placeholderLimit; + } + + public static boolean redisRanking() { + return instance.redisRanking; + } + + public static String serverGroup() { + return instance.serverGroup; + } + + public static String[] itemDetectOrder() { + return instance.itemDetectOrder; + } + + public static String[] blockDetectOrder() { + return instance.blockDetectOrder; + } + + public static int dataSaveInterval() { + return instance.dataSaveInterval; + } + + public static boolean logDataSaving() { + return instance.logDataSaving; + } + + public static boolean lockData() { + return instance.lockData; + } + + public static boolean metrics() { + return instance.metrics; + } + + public static boolean checkUpdate() { + return instance.checkUpdate; + } + + public static boolean overrideVanillaWaitTime() { + return instance.overrideVanillaWaitTime; + } + + public static int waterMinTime() { + return instance.waterMinTime; + } + + public static int waterMaxTime() { + return instance.waterMaxTime; + } + + public static boolean enableLavaFishing() { + return instance.enableLavaFishing; + } + + public static int lavaMinTime() { + return instance.lavaMinTime; + } + + public static int lavaMaxTime() { + return instance.lavaMaxTime; + } + + public static boolean enableVoidFishing() { + return instance.enableVoidFishing; + } + + public static int voidMinTime() { + return instance.voidMinTime; + } + + public static int voidMaxTime() { + return instance.voidMaxTime; + } + + public static int multipleLootSpawnDelay() { + return instance.multipleLootSpawnDelay; + } + + public static boolean restrictedSizeRange() { + return instance.restrictedSizeRange; + } + + public static boolean allowMultipleTotemType() { + return instance.allowMultipleTotemType; + } + + public static boolean allowSameTotemType() { + return instance.allowSameTotemType; + } + + public static boolean enableBag() { + return instance.enableBag; + } + + public static boolean baitAnimation() { + return instance.baitAnimation; + } + + public static List durabilityLore() { + return instance.durabilityLore; + } + + public static EventPriority eventPriority() { + return instance.eventPriority; + } + + public static Requirement[] mechanicRequirements() { + return instance.mechanicRequirements; + } + + public static Requirement[] autoFishingRequirements() { + return instance.autoFishingRequirements; + } + + public static Requirement[] skipGameRequirements() { + return instance.skipGameRequirements; + } + + public static List, Integer>> globalEffects() { + return instance.globalEffects; + } + + public void registerHookParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new HookParserFunction(function)); + } + + public void registerTotemParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new TotemParserFunction(function)); + } + + public void registerLootParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new LootParserFunction(function)); + } + + public void registerItemParser(Function, Context>> function, int priority, String... nodes) { + registerNodeFunction(nodes, new ItemParserFunction(priority, function)); + } + + public void registerEffectModifierParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new EffectModifierParserFunction(function)); + } + + public void registerEntityParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new EntityParserFunction(function)); + } + + public void registerEventParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new EventParserFunction(function)); + } + + public void registerBaseEffectParser(Function> function, String... nodes) { + registerNodeFunction(nodes, new BaseEffectParserFunction(function)); + } + + public void unregisterNodeFunction(String... nodes) { + Map> functionMap = formatFunctions; + for (int i = 0; i < nodes.length; i++) { + if (functionMap.containsKey(nodes[i])) { + Node functionNode = functionMap.get(nodes[i]); + if (i != nodes.length - 1) { + if (functionNode.nodeValue() != null) { + return; + } else { + functionMap = functionNode.getChildTree(); + } + } else { + if (functionNode.nodeValue() != null) { + functionMap.remove(nodes[i]); + } + } + } + } + } + + public void registerNodeFunction(String[] nodes, ConfigParserFunction configParserFunction) { + Map> functionMap = formatFunctions; + for (int i = 0; i < nodes.length; i++) { + if (functionMap.containsKey(nodes[i])) { + Node functionNode = functionMap.get(nodes[i]); + if (functionNode.nodeValue() != null) { + throw new IllegalArgumentException("Format function '" + nodes[i] + "' already exists"); + } + functionMap = functionNode.getChildTree(); + } else { + if (i != nodes.length - 1) { + Node newNode = new Node<>(); + functionMap.put(nodes[i], newNode); + functionMap = newNode.getChildTree(); + } else { + functionMap.put(nodes[i], new Node<>(configParserFunction)); + } + } + } + } + + protected Path resolveConfig(String filePath) { + if (filePath == null || filePath.isEmpty()) { + throw new IllegalArgumentException("ResourcePath cannot be null or empty"); + } + filePath = filePath.replace('\\', '/'); + Path configFile = plugin.getConfigDirectory().resolve(filePath); + // if the config doesn't exist, create it based on the template in the resources dir + if (!Files.exists(configFile)) { + try { + Files.createDirectories(configFile.getParent()); + } catch (IOException e) { + // ignore + } + try (InputStream is = plugin.getResourceStream(filePath)) { + if (is == null) { + throw new IllegalArgumentException("The embedded resource '" + filePath + "' cannot be found"); + } + Files.copy(is, configFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return configFile; + } + + @Override + public YamlDocument loadConfig(String filePath) { + return loadConfig(filePath, '.'); + } + + @Override + public YamlDocument loadConfig(String filePath, char routeSeparator) { + try (InputStream inputStream = new FileInputStream(resolveConfig(filePath).toFile())) { + return YamlDocument.create( + inputStream, + plugin.getResourceStream(filePath), + GeneralSettings.builder().setRouteSeparator(routeSeparator).build(), + LoaderSettings + .builder() + .setAutoUpdate(true) + .build(), + DumperSettings.DEFAULT, + UpdaterSettings + .builder() + .setVersioning(new BasicVersioning("config-version")) + .build() + ); + } catch (IOException e) { + plugin.getPluginLogger().severe("Failed to load config " + filePath, e); + throw new RuntimeException(e); + } + } + + @Override + public YamlDocument loadData(File file) { + try (InputStream inputStream = new FileInputStream(file)) { + return YamlDocument.create(inputStream); + } catch (IOException e) { + plugin.getPluginLogger().severe("Failed to load config " + file, e); + throw new RuntimeException(e); + } + } + + @Override + public YamlDocument loadData(File file, char routeSeparator) { + try (InputStream inputStream = new FileInputStream(file)) { + return YamlDocument.create(inputStream, GeneralSettings.builder().setRouteSeparator(routeSeparator).build()); + } catch (IOException e) { + plugin.getPluginLogger().severe("Failed to load config " + file, e); + throw new RuntimeException(e); + } + } + + public Map> getFormatFunctions() { + return formatFunctions; + } + + public abstract List, Double, Double>>> parseWeightOperation(List ops); + + public abstract List, Double, Double>>> parseGroupWeightOperation(List gops); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigType.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigType.java new file mode 100644 index 00000000..87d6cab4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ConfigType.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.ConfigParserFunction; +import net.momirealms.customfishing.common.config.node.Node; +import org.apache.logging.log4j.util.TriConsumer; + +import java.util.Map; + +public class ConfigType { + + public static final ConfigType ITEM = of( + "item", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.LOOT); + ItemConfigParser config = new ItemConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getItemManager().registerItem(config.getItem()); + BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType ENTITY = of( + "entity", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.ENTITY); + EntityConfigParser config = new EntityConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getEntityManager().registerEntity(config.getEntity()); + BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType BLOCK = of( + "block", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.BLOCK); + BlockConfigParser config = new BlockConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getBlockManager().registerBlock(config.getBlock()); + BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType ROD = of( + "rod", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.ROD); + RodConfigParser config = new RodConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getItemManager().registerItem(config.getItem()); + //BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.ROD); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType BAIT = of( + "bait", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.BAIT); + BaitConfigParser config = new BaitConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getItemManager().registerItem(config.getItem()); + //BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.BAIT); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType HOOK = of( + "hook", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.HOOK); + HookConfigParser config = new HookConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getItemManager().registerItem(config.getItem()); + //BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.HOOK); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + BukkitCustomFishingPlugin.getInstance().getHookManager().registerHook(config.getHook()); + } + ); + + public static final ConfigType UTIL = of( + "util", + (id, section, functions) -> { + MechanicType.register(id, MechanicType.UTIL); + UtilConfigParser config = new UtilConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getItemManager().registerItem(config.getItem()); + //BukkitCustomFishingPlugin.getInstance().getLootManager().registerLoot(config.getLoot()); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.UTIL); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType TOTEM = of( + "totem", + (id, section, functions) -> { + TotemConfigParser config = new TotemConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.TOTEM); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + BukkitCustomFishingPlugin.getInstance().getTotemManager().registerTotem(config.getTotemConfig()); + } + ); + + public static final ConfigType ENCHANT = of( + "enchant", + (id, section, functions) -> { + EnchantConfigParser config = new EnchantConfigParser(id, section, functions); + BukkitCustomFishingPlugin.getInstance().getEffectManager().registerEffectModifier(config.getEffectModifier(), MechanicType.ENCHANT); + BukkitCustomFishingPlugin.getInstance().getEventManager().registerEventCarrier(config.getEventCarrier()); + } + ); + + public static final ConfigType MINI_GAME = of( + "minigame", + (id, section, functions) -> { + MiniGameConfigParser config = new MiniGameConfigParser(id, section); + BukkitCustomFishingPlugin.getInstance().getGameManager().registerGame(config.getGame()); + } + ); + + private static final ConfigType[] values = new ConfigType[] {ITEM, ENTITY, BLOCK, HOOK, ROD, BAIT, UTIL, TOTEM, ENCHANT, MINI_GAME}; + + public static ConfigType[] values() { + return values; + } + + private final String path; + private TriConsumer>> argumentConsumer; + + public ConfigType(String path, TriConsumer>> argumentConsumer) { + this.path = path; + this.argumentConsumer = argumentConsumer; + } + + public void argumentConsumer(TriConsumer>> argumentConsumer) { + this.argumentConsumer = argumentConsumer; + } + + public static ConfigType of(String path, TriConsumer>> argumentConsumer) { + return new ConfigType(path, argumentConsumer); + } + + public void parse(String id, Section section, Map> functions) { + argumentConsumer.accept(id, section, functions); + } + + public String path() { + return path; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EnchantConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EnchantConfigParser.java new file mode 100644 index 00000000..56ce60e0 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EnchantConfigParser.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.ConfigParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.EffectModifierParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.EventParserFunction; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.common.config.node.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class EnchantConfigParser { + + private final String id; + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + + public EnchantConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.ENCHANT); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id) + .type(MechanicType.ENCHANT); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EntityConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EntityConfigParser.java new file mode 100644 index 00000000..ec6e1522 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/EntityConfigParser.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.entity.EntityConfig; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class EntityConfigParser { + + private final String id; + private final List> entityBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + + public EntityConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case ENTITY -> { + EntityParserFunction entityParserFunction = (EntityParserFunction) function; + Consumer consumer = entityParserFunction.accept(entry.getValue()); + entityBuilderConsumers.add(consumer); + } + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public EntityConfig getEntity() { + EntityConfig.Builder builder = EntityConfig.builder() + .id(id); + for (Consumer consumer : entityBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ENTITY) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.ENTITY); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/GUIItemParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/GUIItemParser.java new file mode 100644 index 00000000..4c981e10 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/GUIItemParser.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.config.function.ConfigParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.ItemParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.PriorityFunction; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +public class GUIItemParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + + public GUIItemParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.getString("material"); + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + if (function instanceof ItemParserFunction propertyFunction) { + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/HookConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/HookConfigParser.java new file mode 100644 index 00000000..a6426132 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/HookConfigParser.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.hook.HookConfig; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class HookConfigParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> hookBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> baseEffectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + + public HookConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.getString("material"); + if (!section.contains("tag")) section.set("tag", true); + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + baseEffectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case ITEM -> { + ItemParserFunction propertyFunction = (ItemParserFunction) function; + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + case HOOK -> { + HookParserFunction hookParserFunction = (HookParserFunction) function; + Consumer consumer = hookParserFunction.accept(entry.getValue()); + hookBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.HOOK); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id) + .type(MechanicType.HOOK); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public HookConfig getHook() { + HookConfig.Builder builder = HookConfig.builder() + .id(id); + for (Consumer consumer : hookBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : baseEffectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ITEM) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ItemConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ItemConfigParser.java new file mode 100644 index 00000000..9347b54e --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/ItemConfigParser.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class ItemConfigParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + + public ItemConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.getString("material"); + if (!section.contains("tag")) section.set("tag", true); + if (!section.contains("nick")) { + if (section.contains("display.name")) { + section.set("nick", section.getString("display.name")); + } + } + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case ITEM -> { + ItemParserFunction propertyFunction = (ItemParserFunction) function; + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ITEM) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.LOOT); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/MiniGameConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/MiniGameConfigParser.java new file mode 100644 index 00000000..73cb6507 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/MiniGameConfigParser.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.game.Game; +import net.momirealms.customfishing.api.mechanic.game.GameFactory; + +public class MiniGameConfigParser { + + private final String id; + private Game game; + + public MiniGameConfigParser(String id, Section section) { + this.id = id; + analyze(section); + } + + private void analyze(Section section) { + String type = section.getString("game-type"); + GameFactory factory = BukkitCustomFishingPlugin.getInstance().getGameManager().getGameFactory(type); + if (factory == null) { + throw new RuntimeException("Unknown game-type: " + type); + } + this.game = factory.create(id, section); + } + + public Game getGame() { + return game; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/RodConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/RodConfigParser.java new file mode 100644 index 00000000..1b1bc19a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/RodConfigParser.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class RodConfigParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> baseEffectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + + public RodConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.contains("material") ? section.getString("material") : Material.FISHING_ROD.name(); + if (!section.contains("tag")) section.set("tag", true); + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + baseEffectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case ITEM -> { + ItemParserFunction propertyFunction = (ItemParserFunction) function; + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.ROD); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id) + .type(MechanicType.ROD); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : baseEffectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ITEM) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/TotemConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/TotemConfigParser.java new file mode 100644 index 00000000..555bb78b --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/TotemConfigParser.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.ConfigParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.EffectModifierParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.EventParserFunction; +import net.momirealms.customfishing.api.mechanic.config.function.TotemParserFunction; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; +import net.momirealms.customfishing.common.config.node.Node; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class TotemConfigParser { + + private final String id; + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> totemBuilderConsumers = new ArrayList<>(); + + public TotemConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + case TOTEM -> { + TotemParserFunction totemParserFunction = (TotemParserFunction) function; + Consumer consumer = totemParserFunction.accept(entry.getValue()); + totemBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.TOTEM); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id) + .type(MechanicType.TOTEM); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public TotemConfig getTotemConfig() { + TotemConfig.Builder builder = TotemConfig.builder() + .id(id); + for (Consumer consumer : totemBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/UtilConfigParser.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/UtilConfigParser.java new file mode 100644 index 00000000..5434d643 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/UtilConfigParser.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.function.*; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.common.config.node.Node; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public class UtilConfigParser { + + private final String id; + private final String material; + private final List, Context>>> tagConsumers = new ArrayList<>(); + private final List> eventBuilderConsumers = new ArrayList<>(); + private final List> effectBuilderConsumers = new ArrayList<>(); + private final List> baseEffectBuilderConsumers = new ArrayList<>(); + private final List> lootBuilderConsumers = new ArrayList<>(); + + public UtilConfigParser(String id, Section section, Map> functionMap) { + this.id = id; + this.material = section.getString("material"); + if (!section.contains("tag")) section.set("tag", true); + analyze(section, functionMap); + } + + private void analyze(Section section, Map> functionMap) { + Map dataMap = section.getStringRouteMappedValues(false); + for (Map.Entry entry : dataMap.entrySet()) { + String key = entry.getKey(); + Node node = functionMap.get(key); + if (node == null) continue; + ConfigParserFunction function = node.nodeValue(); + if (function != null) { + switch (function.type()) { + case BASE_EFFECT -> { + BaseEffectParserFunction baseEffectParserFunction = (BaseEffectParserFunction) function; + Consumer consumer = baseEffectParserFunction.accept(entry.getValue()); + baseEffectBuilderConsumers.add(consumer); + } + case LOOT -> { + LootParserFunction lootParserFunction = (LootParserFunction) function; + Consumer consumer = lootParserFunction.accept(entry.getValue()); + lootBuilderConsumers.add(consumer); + } + case ITEM -> { + ItemParserFunction propertyFunction = (ItemParserFunction) function; + BiConsumer, Context> result = propertyFunction.accept(entry.getValue()); + tagConsumers.add(new PriorityFunction<>(propertyFunction.getPriority(), result)); + } + case EVENT -> { + EventParserFunction eventParserFunction = (EventParserFunction) function; + Consumer consumer = eventParserFunction.accept(entry.getValue()); + eventBuilderConsumers.add(consumer); + } + case EFFECT_MODIFIER -> { + EffectModifierParserFunction effectModifierParserFunction = (EffectModifierParserFunction) function; + Consumer consumer = effectModifierParserFunction.accept(entry.getValue()); + effectBuilderConsumers.add(consumer); + } + } + continue; + } + if (entry.getValue() instanceof Section innerSection) { + analyze(innerSection, node.getChildTree()); + } + } + } + + public CustomFishingItem getItem() { + return CustomFishingItem.builder() + .material(material) + .id(id) + .tagConsumers(tagConsumers) + .build(); + } + + public EventCarrier getEventCarrier() { + EventCarrier.Builder builder = EventCarrier.builder() + .id(id) + .type(MechanicType.UTIL); + for (Consumer consumer : eventBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public EffectModifier getEffectModifier() { + EffectModifier.Builder builder = EffectModifier.builder() + .id(id); + for (Consumer consumer : effectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + private LootBaseEffect getBaseEffect() { + LootBaseEffect.Builder builder = LootBaseEffect.builder(); + for (Consumer consumer : baseEffectBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } + + public Loot getLoot() { + Loot.Builder builder = Loot.builder() + .id(id) + .type(LootType.ITEM) + .lootBaseEffect(getBaseEffect()); + for (Consumer consumer : lootBuilderConsumers) { + consumer.accept(builder); + } + return builder.build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BaseEffectParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BaseEffectParserFunction.java new file mode 100644 index 00000000..d76358d1 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BaseEffectParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class BaseEffectParserFunction implements ConfigParserFunction { + + private final Function> function; + + public BaseEffectParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.BASE_EFFECT; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BlockParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BlockParserFunction.java new file mode 100644 index 00000000..22e27322 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/BlockParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.block.BlockConfig; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class BlockParserFunction implements ConfigParserFunction { + + private final Function> function; + + public BlockParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.BLOCK; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ConfigParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ConfigParserFunction.java new file mode 100644 index 00000000..42c64dd9 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ConfigParserFunction.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +public interface ConfigParserFunction { + + ParserType type(); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EffectModifierParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EffectModifierParserFunction.java new file mode 100644 index 00000000..7f16e816 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EffectModifierParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class EffectModifierParserFunction implements ConfigParserFunction { + + private final Function> function; + + public EffectModifierParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.EFFECT_MODIFIER; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EntityParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EntityParserFunction.java new file mode 100644 index 00000000..203220d6 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EntityParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.entity.EntityConfig; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class EntityParserFunction implements ConfigParserFunction { + + private final Function> function; + + public EntityParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.ENTITY; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EventParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EventParserFunction.java new file mode 100644 index 00000000..1e048515 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/EventParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class EventParserFunction implements ConfigParserFunction { + + private final Function> function; + + public EventParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.EVENT; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/HookParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/HookParserFunction.java new file mode 100644 index 00000000..6ecf66e7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/HookParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.hook.HookConfig; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class HookParserFunction implements ConfigParserFunction { + + private final Function> function; + + public HookParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.HOOK; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ItemParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ItemParserFunction.java new file mode 100644 index 00000000..196526b7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ItemParserFunction.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class ItemParserFunction implements ConfigParserFunction { + + private final Function, Context>> function; + private final int priority; + + public ItemParserFunction(int priority, Function, Context>> function) { + this.function = function; + this.priority = priority; + } + + public BiConsumer, Context> accept(Object object) { + return function.apply(object); + } + + public int getPriority() { + return priority; + } + + @Override + public ParserType type() { + return ParserType.ITEM; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/LootParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/LootParserFunction.java new file mode 100644 index 00000000..83451837 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/LootParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.loot.Loot; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class LootParserFunction implements ConfigParserFunction { + + private final Function> function; + + public LootParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.LOOT; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/Value.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ParserType.java similarity index 77% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/Value.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ParserType.java index 1dbea0c4..338cff10 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/Value.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/ParserType.java @@ -15,13 +15,16 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.misc; +package net.momirealms.customfishing.api.mechanic.config.function; -import org.bukkit.entity.Player; - -import java.util.Map; - -public interface Value { - - double get(Player player, Map values); +public enum ParserType { + ITEM, + EFFECT_MODIFIER, + BASE_EFFECT, + LOOT, + EVENT, + ENTITY, + HOOK, + BLOCK, + TOTEM } diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/ExpressionValue.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/PriorityFunction.java similarity index 57% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/ExpressionValue.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/PriorityFunction.java index f0daf218..09a13dcd 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/ExpressionValue.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/PriorityFunction.java @@ -15,24 +15,26 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.misc.value; +package net.momirealms.customfishing.api.mechanic.config.function; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -import java.util.Map; +public class PriorityFunction implements Comparable> { -public class ExpressionValue implements Value { + private final int priority; + private final T function; - private final String expression; + public PriorityFunction(int priority, T function) { + this.priority = priority; + this.function = function; + } - public ExpressionValue(String expression) { - this.expression = expression; + public T get() { + return function; } @Override - public double get(Player player, Map values) { - return ConfigUtils.getExpressionValue(player, expression, values); + public int compareTo(@NotNull PriorityFunction o) { + return Integer.compare(this.priority, o.priority); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/TotemParserFunction.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/TotemParserFunction.java new file mode 100644 index 00000000..c7d47a1a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/config/function/TotemParserFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.config.function; + +import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class TotemParserFunction implements ConfigParserFunction { + + private final Function> function; + + public TotemParserFunction(Function> function) { + this.function = function; + } + + public Consumer accept(Object object) { + return function.apply(object); + } + + @Override + public ParserType type() { + return ParserType.TOTEM; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/Context.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/Context.java new file mode 100644 index 00000000..460900e4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/Context.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.context; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * The Context interface represents a generic context for custom fishing mechanics. + * It allows for storing and retrieving arguments, as well as getting the holder + * of the context. This can be used to maintain state or pass parameters within + * the custom fishing mechanics. + * + * @param the type of the holder object for this context + */ +public interface Context { + + /** + * Retrieves the map of arguments associated with this context. + * + * @return a map where the keys are argument names and the values are argument values. + */ + Map, Object> args(); + + /** + * Converts the context to a map of placeholders + * + * @return a map of placeholders + */ + Map placeholderMap(); + + /** + * Adds or updates an argument in the context. + * This method allows adding a new argument or updating the value of an existing argument. + * + * @param the type of the value being added to the context. + * @param key the ContextKeys key representing the argument to be added or updated. + * @param value the value to be associated with the specified key. + * @return the current context instance, allowing for method chaining. + */ + Context arg(ContextKeys key, C value); + + /** + * Combines one context with another + * + * @param other other + * @return this context + */ + Context combine(Context other); + + /** + * Retrieves the value of a specific argument from the context. + * This method fetches the value associated with the specified ContextKeys key. + * + * @param the type of the value being retrieved. + * @param key the ContextKeys key representing the argument to be retrieved. + * @return the value associated with the specified key, or null if the key does not exist. + */ + @Nullable + C arg(ContextKeys key); + + @Nullable + C remove(ContextKeys key); + + /** + * Gets the holder of this context. + * + * @return the holder object of type T. + */ + T getHolder(); + + /** + * Creates a player-specific context. + * + * @param player the player to be used as the holder of the context. + * @return a new Context instance with the specified player as the holder. + */ + static Context player(@Nullable Player player) { + return new PlayerContextImpl(player, false); + } + + static Context player(@Nullable Player player, boolean threadSafe) { + return new PlayerContextImpl(player, threadSafe); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/ContextKeys.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/ContextKeys.java new file mode 100644 index 00000000..eefc8aa4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/ContextKeys.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.context; + +import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import org.bukkit.Location; + +import java.util.Objects; + +public class ContextKeys { + + public static final ContextKeys LOCATION = of("location", Location.class); + public static final ContextKeys X = of("x", Integer.class); + public static final ContextKeys Y = of("y", Integer.class); + public static final ContextKeys Z = of("z", Integer.class); + public static final ContextKeys WORLD = of("world", String.class); + public static final ContextKeys ID = of("id", String.class); + public static final ContextKeys LOOT = of("loot", LootType.class); + public static final ContextKeys NICK = of("nick", String.class); + public static final ContextKeys OPEN_WATER = of("open_water", Boolean.class); + public static final ContextKeys SIZE = of("size", Float.class); + public static final ContextKeys SIZE_FORMATTED = of("size_formatted", String.class); + public static final ContextKeys PRICE = of("price", Double.class); + public static final ContextKeys PRICE_FORMATTED = of("price_formatted", String.class); + public static final ContextKeys SURROUNDING = of("surrounding", String.class); + public static final ContextKeys TEMP_NEAR_PLAYER = of("near", String.class); + public static final ContextKeys ROD = of("rod", String.class); + public static final ContextKeys BAIT = of("bait", String.class); + public static final ContextKeys HOOK = of("hook", String.class); + public static final ContextKeys IN_BAG = of("in_bag", Boolean.class); + public static final ContextKeys GOAL = of("goal", CompetitionGoal.class); + public static final ContextKeys HOUR = of("hour", String.class); + public static final ContextKeys MINUTE = of("minute", String.class); + public static final ContextKeys SECOND = of("second", String.class); + public static final ContextKeys SECONDS = of("seconds", Integer.class); + public static final ContextKeys PLAYER = of("player", String.class); + public static final ContextKeys SCORE_FORMATTED = of("score_formatted", String.class); + public static final ContextKeys SCORE = of("score", Double.class); + public static final ContextKeys CUSTOM_SCORE = of("custom_score", Double.class); + public static final ContextKeys RANK = of("rank", String.class); + public static final ContextKeys OTHER_LOCATION = of("other_location", Location.class); + public static final ContextKeys OTHER_X = of("other_x", Integer.class); + public static final ContextKeys OTHER_Y = of("other_y", Integer.class); + public static final ContextKeys OTHER_Z = of("other_z", Integer.class); + public static final ContextKeys MONEY = of("money", String.class); + public static final ContextKeys MONEY_FORMATTED = of("money_formatted", String.class); + public static final ContextKeys REST = of("rest", String.class); + public static final ContextKeys REST_FORMATTED = of("rest_formatted", String.class); + public static final ContextKeys SOLD_ITEM_AMOUNT = of("sold_item_amount", Integer.class); + public static final ContextKeys AMOUNT = of("amount", Integer.class); + public static final ContextKeys TOTAL_AMOUNT = of("total_amount", Integer.class); + public static final ContextKeys WEIGHT = of("0", Double.class); + public static final ContextKeys TIME_LEFT = of("time_left", String.class); + public static final ContextKeys PROGRESS = of("progress", String.class); + public static final ContextKeys RECORD = of("record", Float.class); + public static final ContextKeys RECORD_FORMATTED = of("record_formatted", String.class); + public static final ContextKeys CLICKS_LEFT = of("left_clicks", Integer.class); + public static final ContextKeys REQUIRED_TIMES = of("clicks", Integer.class); + + private final String key; + private final Class type; + + protected ContextKeys(String key, Class type) { + this.key = key; + this.type = type; + } + + public String key() { + return key; + } + + public Class type() { + return type; + } + + public static ContextKeys of(String key, Class type) { + return new ContextKeys(key, type); + } + + @Override + public final boolean equals(final Object other) { + if (this == other) { + return true; + } else if (other != null && this.getClass() == other.getClass()) { + ContextKeys that = (ContextKeys) other; + return Objects.equals(this.key, that.key); + } else { + return false; + } + } + + @Override + public final int hashCode() { + return Objects.hashCode(this.key); + } + + @Override + public String toString() { + return "ContextKeys{" + + "key='" + key + '\'' + + '}'; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/PlayerContextImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/PlayerContextImpl.java new file mode 100644 index 00000000..30963aeb --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/context/PlayerContextImpl.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.context; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The PlayerContextImpl class implements the Context interface specifically + * for the Player type. It allows for storing and retrieving arguments related + * to a player in the custom fishing mechanics. + */ +public final class PlayerContextImpl implements Context { + + private final Player player; + private final Map, Object> args; + private final Map placeholderMap; + + /** + * Constructs a new PlayerContextImpl with the specified player. + * + * @param player the player to be associated with this context. + */ + public PlayerContextImpl(@Nullable Player player, boolean sync) { + this.player = player; + this.args = sync ? new ConcurrentHashMap<>() : new HashMap<>(); + this.placeholderMap = sync ? new ConcurrentHashMap<>() : new HashMap<>(); + if (player == null) return; + final Location location = player.getLocation(); + arg(ContextKeys.PLAYER, player.getName()) + .arg(ContextKeys.LOCATION, location) + .arg(ContextKeys.X, location.getBlockX()) + .arg(ContextKeys.Y, location.getBlockY()) + .arg(ContextKeys.Z, location.getBlockZ()) + .arg(ContextKeys.WORLD, location.getWorld().getName()); + } + + @Override + public Map, Object> args() { + return args; + } + + @Override + public Map placeholderMap() { + return placeholderMap; + } + + @Override + public PlayerContextImpl arg(ContextKeys key, C value) { + this.args.put(key, value); + this.placeholderMap.put("{" + key.key() + "}", value.toString()); + return this; + } + + @Override + public Context combine(Context other) { + final PlayerContextImpl otherContext = (PlayerContextImpl) other; + this.args.putAll(otherContext.args); + this.placeholderMap.putAll(otherContext.placeholderMap); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public C arg(ContextKeys key) { + return (C) args.get(key); + } + + @Nullable + @SuppressWarnings("unchecked") + @Override + public C remove(ContextKeys key) { + placeholderMap.remove("{" + key.key() + "}"); + return (C) args.remove(key); + } + + @Override + public Player getHolder() { + return player; + } + + @Override + public String toString() { + return "PlayerContext{" + + "args=" + args + + ", player=" + player + + '}'; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/BaseEffect.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/BaseEffect.java deleted file mode 100644 index 53c716a8..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/BaseEffect.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.momirealms.customfishing.api.mechanic.effect; - -import net.momirealms.customfishing.api.mechanic.misc.Value; -import org.bukkit.entity.Player; - -import java.util.Map; - -public class BaseEffect { - - private final Value waitTime; - private final Value waitTimeMultiplier; - private final Value difficulty; - private final Value difficultyMultiplier; - private final Value gameTime; - private final Value gameTimeMultiplier; - - public BaseEffect(Value waitTime, Value waitTimeMultiplier, Value difficulty, Value difficultyMultiplier, Value gameTime, Value gameTimeMultiplier) { - this.waitTime = waitTime; - this.waitTimeMultiplier = waitTimeMultiplier; - this.difficulty = difficulty; - this.difficultyMultiplier = difficultyMultiplier; - this.gameTime = gameTime; - this.gameTimeMultiplier = gameTimeMultiplier; - } - - public Effect build(Player player, Map values) { - return new FishingEffect( - waitTime.get(player, values), - waitTimeMultiplier.get(player, values), - difficulty.get(player, values), - difficultyMultiplier.get(player, values), - gameTime.get(player, values), - gameTimeMultiplier.get(player, values) - ); - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/Effect.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/Effect.java index a63f7e90..43e38e2a 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/Effect.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/Effect.java @@ -17,40 +17,257 @@ package net.momirealms.customfishing.api.mechanic.effect; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.entity.Player; import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +/** + * Represents an effect applied in the custom fishing mechanic. + */ public interface Effect { - boolean canLavaFishing(); + /** + * Retrieves the properties of this effect. + * + * @return a map of effect properties and their values + */ + Map, Object> properties(); - double getMultipleLootChance(); + Effect properties(Map, Object> properties); - double getSize(); + /** + * Sets the specified property to the given value. + * + * @param key the property key + * @param value the property value + * @param the type of the property value + * @return the effect instance with the updated property + */ + EffectImpl arg(EffectProperties key, C value); - double getSizeMultiplier(); + /** + * Retrieves the value of the specified property. + * + * @param key the property key + * @param the type of the property value + * @return the value of the specified property + */ + C arg(EffectProperties key); - double getScore(); + /** + * Gets the chance of multiple loots. + * + * @return the multiple loot chance + */ + double multipleLootChance(); - double getScoreMultiplier(); + /** + * Sets the chance of multiple loots. + * + * @param multipleLootChance the new multiple loot chance + * @return the effect instance with the updated multiple loot chance + */ + Effect multipleLootChance(double multipleLootChance); - double getWaitTime(); + /** + * Gets the size adder. + * + * @return the size adder + */ + double sizeAdder(); - double getWaitTimeMultiplier(); + /** + * Sets the size adder. + * + * @param sizeAdder the new size adder + * @return the effect instance with the updated size adder + */ + Effect sizeAdder(double sizeAdder); - double getGameTime(); + /** + * Gets the size multiplier. + * + * @return the size multiplier + */ + double sizeMultiplier(); - double getGameTimeMultiplier(); + /** + * Sets the size multiplier. + * + * @param sizeMultiplier the new size multiplier + * @return the effect instance with the updated size multiplier + */ + Effect sizeMultiplier(double sizeMultiplier); - double getDifficulty(); + /** + * Gets the score adder. + * + * @return the score adder + */ + double scoreAdder(); - double getDifficultyMultiplier(); + /** + * Sets the score adder. + * + * @param scoreAdder the new score adder + * @return the effect instance with the updated score adder + */ + Effect scoreAdder(double scoreAdder); - List> getWeightModifier(); + /** + * Gets the score multiplier. + * + * @return the score multiplier + */ + double scoreMultiplier(); - List> getWeightModifierIgnored(); + /** + * Sets the score multiplier. + * + * @param scoreMultiplier the new score multiplier + * @return the effect instance with the updated score multiplier + */ + Effect scoreMultiplier(double scoreMultiplier); - void merge(Effect effect); + /** + * Gets the wait time adder. + * + * @return the wait time adder + */ + double waitTimeAdder(); + + /** + * Sets the wait time adder. + * + * @param waitTimeAdder the new wait time adder + * @return the effect instance with the updated wait time adder + */ + Effect waitTimeAdder(double waitTimeAdder); + + /** + * Gets the wait time multiplier. + * + * @return the wait time multiplier + */ + double waitTimeMultiplier(); + + /** + * Sets the wait time multiplier. + * + * @param waitTimeMultiplier the new wait time multiplier + * @return the effect instance with the updated wait time multiplier + */ + Effect waitTimeMultiplier(double waitTimeMultiplier); + + /** + * Gets the game time adder. + * + * @return the game time adder + */ + double gameTimeAdder(); + + /** + * Sets the game time adder. + * + * @param gameTimeAdder the new game time adder + * @return the effect instance with the updated game time adder + */ + Effect gameTimeAdder(double gameTimeAdder); + + /** + * Gets the game time multiplier. + * + * @return the game time multiplier + */ + double gameTimeMultiplier(); + + /** + * Sets the game time multiplier. + * + * @param gameTimeMultiplier the new game time multiplier + * @return the effect instance with the updated game time multiplier + */ + Effect gameTimeMultiplier(double gameTimeMultiplier); + + /** + * Gets the difficulty adder. + * + * @return the difficulty adder + */ + double difficultyAdder(); + + /** + * Sets the difficulty adder. + * + * @param difficultyAdder the new difficulty adder + * @return the effect instance with the updated difficulty adder + */ + Effect difficultyAdder(double difficultyAdder); + + /** + * Gets the difficulty multiplier. + * + * @return the difficulty multiplier + */ + double difficultyMultiplier(); + + /** + * Sets the difficulty multiplier. + * + * @param difficultyMultiplier the new difficulty multiplier + * @return the effect instance with the updated difficulty multiplier + */ + Effect difficultyMultiplier(double difficultyMultiplier); + + /** + * Gets the list of weight operations. + * + * @return the list of weight operations + */ + List, Double, Double>>> weightOperations(); + + /** + * Adds the list of weight operations. + * + * @param weightOperations the list of weight operations to add + * @return the effect instance with the updated weight operations + */ + Effect weightOperations(List, Double, Double>>> weightOperations); + + /** + * Gets the list of weight operations that are conditions ignored. + * + * @return the list of weight operations that are conditions ignored + */ + List, Double, Double>>> weightOperationsIgnored(); + + /** + * Adds the list of weight operations that are conditions ignored. + * + * @param weightOperations the list of weight operations that are conditions ignored + * @return the effect instance with the updated ignored weight operations + */ + Effect weightOperationsIgnored(List, Double, Double>>> weightOperations); + + /** + * Combines this effect with another effect. + * + * @param effect the effect to combine with + */ + void combine(Effect effect); + + Effect copy(); + + /** + * Creates a new instance of {@link Effect}. + * + * @return a new {@link Effect} instance + */ + static Effect newInstance() { + return new EffectImpl(); + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectCarrier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectCarrier.java deleted file mode 100644 index 103edce2..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectCarrier.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.effect; - -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class EffectCarrier { - - private Key key; - private Requirement[] requirements; - private EffectModifier[] effect; - private Map actionMap; - private boolean persist; - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private final EffectCarrier item; - - public Builder() { - this.item = new EffectCarrier(); - } - - public Builder persist(boolean persist) { - item.persist = persist; - return this; - } - - public Builder key(Key key) { - item.key = key; - return this; - } - - public Builder requirements(Requirement[] requirements) { - item.requirements = requirements; - return this; - } - - public Builder effect(EffectModifier[] effect) { - item.effect = effect; - return this; - } - - public Builder actionMap(Map actionMap) { - item.actionMap = actionMap; - return this; - } - - public EffectCarrier build() { - return item; - } - } - - public Key getKey() { - return key; - } - - public Requirement[] getRequirements() { - return requirements; - } - - public EffectModifier[] getEffectModifiers() { - return effect; - } - - public Map getActionMap() { - return actionMap; - } - - @Nullable - public Action[] getActions(ActionTrigger trigger) { - return actionMap.get(trigger); - } - - public boolean isPersist() { - return persist; - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean isConditionMet(Condition condition) { - if (requirements == null) return true; - for (Requirement requirement : requirements) { - if (!requirement.isConditionMet(condition)) { - return false; - } - } - return true; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectImpl.java new file mode 100644 index 00000000..be5ea5c8 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectImpl.java @@ -0,0 +1,234 @@ +package net.momirealms.customfishing.api.mechanic.effect; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +public class EffectImpl implements Effect { + + private final HashMap, Object> properties = new HashMap<>(); + private double multipleLootChance = 0; + private double sizeAdder = 0; + private double sizeMultiplier = 1; + private double scoreAdder = 0; + private double scoreMultiplier = 1; + private double gameTimeAdder = 0; + private double gameTimeMultiplier = 1; + private double waitTimeAdder = 0; + private double waitTimeMultiplier = 1; + private double difficultyAdder = 0; + private double difficultyMultiplier = 1; + private final List, Double, Double>>> weightOperations = new ArrayList<>(); + private final List, Double, Double>>> weightOperationsIgnored = new ArrayList<>(); + + @Override + public Map, Object> properties() { + return properties; + } + + @Override + public Effect properties(Map, Object> properties) { + this.properties.putAll(properties); + return this; + } + + @Override + public EffectImpl arg(EffectProperties key, C value) { + properties.put(key, value); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public C arg(EffectProperties key) { + return (C) properties.get(key); + } + + @Override + public double multipleLootChance() { + return multipleLootChance; + } + + @Override + public Effect multipleLootChance(double multipleLootChance) { + this.multipleLootChance = multipleLootChance; + return this; + } + + @Override + public double sizeAdder() { + return sizeAdder; + } + + @Override + public Effect sizeAdder(double sizeAdder) { + this.sizeAdder = sizeAdder; + return this; + } + + @Override + public double sizeMultiplier() { + return sizeMultiplier; + } + + @Override + public Effect sizeMultiplier(double sizeMultiplier) { + this.sizeMultiplier = sizeMultiplier; + return this; + } + + @Override + public double scoreAdder() { + return scoreAdder; + } + + @Override + public Effect scoreAdder(double scoreAdder) { + this.scoreAdder = scoreAdder; + return this; + } + + @Override + public double scoreMultiplier() { + return scoreMultiplier; + } + + @Override + public Effect scoreMultiplier(double scoreMultiplier) { + this.scoreMultiplier = scoreMultiplier; + return this; + } + + @Override + public double waitTimeAdder() { + return waitTimeAdder; + } + + @Override + public Effect waitTimeAdder(double waitTimeAdder) { + this.waitTimeAdder = waitTimeAdder; + return this; + } + + @Override + public double waitTimeMultiplier() { + return waitTimeMultiplier; + } + + @Override + public Effect waitTimeMultiplier(double waitTimeMultiplier) { + this.waitTimeMultiplier = waitTimeMultiplier; + return this; + } + + @Override + public double gameTimeAdder() { + return gameTimeAdder; + } + + @Override + public Effect gameTimeAdder(double gameTimeAdder) { + this.gameTimeAdder = gameTimeAdder; + return this; + } + + @Override + public double gameTimeMultiplier() { + return gameTimeMultiplier; + } + + @Override + public Effect gameTimeMultiplier(double gameTimeMultiplier) { + this.gameTimeMultiplier = gameTimeMultiplier; + return this; + } + + @Override + public double difficultyAdder() { + return difficultyAdder; + } + + @Override + public Effect difficultyAdder(double difficultyAdder) { + this.difficultyAdder = difficultyAdder; + return this; + } + + @Override + public double difficultyMultiplier() { + return difficultyMultiplier; + } + + @Override + public Effect difficultyMultiplier(double difficultyMultiplier) { + this.difficultyMultiplier = difficultyMultiplier; + return this; + } + + @Override + public List, Double, Double>>> weightOperations() { + return weightOperations; + } + + @Override + public Effect weightOperations(List, Double, Double>>> weightOperations) { + this.weightOperations.addAll(weightOperations); + return this; + } + + @Override + public List, Double, Double>>> weightOperationsIgnored() { + return weightOperationsIgnored; + } + + @Override + public Effect weightOperationsIgnored(List, Double, Double>>> weightOperations) { + this.weightOperationsIgnored.addAll(weightOperations); + return this; + } + + @Override + public void combine(Effect another) { + if (another == null) return; + this.scoreMultiplier += (another.scoreMultiplier() -1); + this.scoreAdder += another.scoreAdder(); + this.sizeMultiplier += (another.sizeMultiplier() -1); + this.sizeAdder += another.sizeAdder(); + this.difficultyMultiplier += (another.difficultyMultiplier() -1); + this.difficultyAdder += another.difficultyAdder(); + this.gameTimeMultiplier += (another.gameTimeMultiplier() - 1); + this.gameTimeAdder += another.gameTimeAdder(); + this.waitTimeMultiplier += (another.waitTimeMultiplier() -1); + this.waitTimeAdder += (another.waitTimeAdder()); + this.multipleLootChance += another.multipleLootChance(); + this.weightOperations.addAll(another.weightOperations()); + this.weightOperationsIgnored.addAll(another.weightOperationsIgnored()); + this.properties.putAll(another.properties()); + } + + @Override + public Effect copy() { + return Effect.newInstance() + .scoreMultiplier(this.scoreMultiplier) + .scoreAdder(this.scoreAdder) + .sizeMultiplier(this.sizeMultiplier) + .sizeAdder(this.sizeAdder) + .difficultyMultiplier(this.difficultyMultiplier) + .difficultyAdder(this.difficultyAdder) + .gameTimeMultiplier(this.gameTimeMultiplier) + .gameTimeAdder(this.gameTimeAdder) + .waitTimeMultiplier(this.waitTimeMultiplier) + .waitTimeAdder(this.waitTimeAdder) + .multipleLootChance(this.multipleLootChance) + .weightOperations(this.weightOperations) + .weightOperationsIgnored(this.weightOperationsIgnored) + .properties(this.properties); + + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectManager.java new file mode 100644 index 00000000..0e2edbfb --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectManager.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.effect; + +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; + +import java.util.Optional; + +public interface EffectManager extends Reloadable { + + boolean registerEffectModifier(EffectModifier effect, MechanicType type); + + Optional getEffectModifier(String id, MechanicType type); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifier.java index 6df659cf..df66b481 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifier.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifier.java @@ -1,25 +1,82 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - package net.momirealms.customfishing.api.mechanic.effect; -import net.momirealms.customfishing.api.mechanic.condition.Condition; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.common.util.TriConsumer; +import org.bukkit.entity.Player; +import java.util.List; + +/** + * EffectModifier interface for modifying effects in the CustomFishing plugin. + * This interface allows defining conditions and modifications for effects applied to players. + */ public interface EffectModifier { - void modify(FishingEffect effect, Condition condition); + String id(); + + /** + * Returns an array of requirements that must be met by a Player for the effect to be applied. + * + * @return an array of requirements + */ + Requirement[] requirements(); + + /** + * Returns a list of modifiers that apply changes to an effect within a given context. + * + * @return a list of effect modifiers + */ + List, Integer>> modifiers(); + + /** + * Creates and returns a new Builder instance for constructing EffectModifier instances. + * + * @return a new Builder instance + */ + static Builder builder() { + return new EffectModifierImpl.BuilderImpl(); + } + + MechanicType type(); + + /** + * Builder interface for constructing EffectModifier instances. + */ + interface Builder { + + Builder id(String id); + + /** + * Sets the requirements for the EffectModifier being built. + * + * @param requirements a list of requirements + * @return the current Builder instance + */ + Builder requirements(List> requirements); + + /** + * Sets the modifiers for the EffectModifier being built. + * + * @param modifiers a list of effect modifiers + * @return the current Builder instance + */ + Builder modifiers(List, Integer>> modifiers); + + /** + * Set the type of the item + * + * @param type type + * @return the Builder instance. + */ + Builder type(MechanicType type); + + /** + * Builds and returns the EffectModifier instance. + * + * @return the built EffectModifier instance + */ + EffectModifier build(); + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifierImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifierImpl.java new file mode 100644 index 00000000..6f0f8ef5 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectModifierImpl.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.effect; + +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.common.util.TriConsumer; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class EffectModifierImpl implements EffectModifier { + + private final Requirement[] requirements; + private final List, Integer>> modifiers; + private final String id; + private final MechanicType type; + + public EffectModifierImpl( + String id, + MechanicType type, + Requirement[] requirements, + List, Integer>> modifiers + ) { + this.requirements = requirements; + this.modifiers = modifiers; + this.id = id; + this.type = type; + } + + @Override + public String id() { + return id; + } + + @Override + public Requirement[] requirements() { + return requirements; + } + + @Override + public List, Integer>> modifiers() { + return modifiers; + } + + @Override + public MechanicType type() { + return type; + } + + public static class BuilderImpl implements Builder { + private final List> requirements = new ArrayList<>(); + private final List, Integer>> modifiers = new ArrayList<>(); + private String id; + private MechanicType type; + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public Builder requirements(List> requirements) { + this.requirements.addAll(requirements); + return this; + } + @Override + public Builder modifiers(List, Integer>> modifiers) { + this.modifiers.addAll(modifiers); + return this; + } + @Override + public Builder type(MechanicType type) { + this.type = type; + return this; + } + @Override + @SuppressWarnings("unchecked") + public EffectModifier build() { + return new EffectModifierImpl(id, type, this.requirements.toArray(new Requirement[0]), this.modifiers); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectProperties.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectProperties.java new file mode 100644 index 00000000..2e8d542d --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/EffectProperties.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.effect; + +import java.util.Objects; + +public class EffectProperties { + + public static final EffectProperties LAVA_FISHING = of("lava", Boolean.class); + public static final EffectProperties VOID_FISHING = of("void", Boolean.class); + // It's not actually used because it's a vanilla mechanic + public static final EffectProperties WATER_FISHING = of("water", Boolean.class); + + private final String key; + private final Class type; + + private EffectProperties(String key, Class type) { + this.key = key; + this.type = type; + } + + public String key() { + return key; + } + + public Class type() { + return type; + } + + public static EffectProperties of(String key, Class type) { + return new EffectProperties(key, type); + } + + @Override + public final boolean equals(final Object other) { + if (this == other) { + return true; + } else if (other != null && this.getClass() == other.getClass()) { + EffectProperties that = (EffectProperties) other; + return Objects.equals(this.key, that.key); + } else { + return false; + } + } + + @Override + public final int hashCode() { + return Objects.hashCode(this.key); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/FishingEffect.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/FishingEffect.java deleted file mode 100644 index f8408f48..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/FishingEffect.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.effect; - -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; - -import java.util.ArrayList; -import java.util.List; - -public class FishingEffect implements Effect { - - private boolean lavaFishing = false; - private double multipleLootChance = 0; - private double waitTime = 0; - private double waitTimeMultiplier = 1; - private double size = 0; - private double sizeMultiplier = 1; - private double score = 0; - private double scoreMultiplier = 1; - private double difficulty = 0; - private double difficultyMultiplier = 1; - private double gameTime = 0; - private double gameTimeMultiplier = 1; - - private final List> weightModifier = new ArrayList<>(); - private final List> weightModifierIgnored = new ArrayList<>(); - - public FishingEffect(double waitTime, double waitTimeMultiplier, double difficulty, double difficultyMultiplier, double gameTime, double gameTimeMultiplier) { - this.waitTime = waitTime; - this.waitTimeMultiplier = waitTimeMultiplier; - this.difficulty = difficulty; - this.difficultyMultiplier = difficultyMultiplier; - this.gameTime = gameTime; - this.gameTimeMultiplier = gameTimeMultiplier; - } - - public FishingEffect() { - } - - /** - * Sets whether lava fishing is enabled. - * - * @param lavaFishing True if lava fishing is enabled, false otherwise. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setLavaFishing(boolean lavaFishing) { - this.lavaFishing = lavaFishing; - return this; - } - - /** - * Sets the multiple loot chance. - * - * @param multipleLootChance The multiple loot chance value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setMultipleLootChance(double multipleLootChance) { - this.multipleLootChance = multipleLootChance; - return this; - } - - /** - * Sets the size multiplier. - * - * @param sizeMultiplier The size multiplier value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setSizeMultiplier(double sizeMultiplier) { - this.sizeMultiplier = sizeMultiplier; - return this; - } - - /** - * Sets the size. - * - * @param size The size value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setSize(double size) { - this.size = size; - return this; - } - - /** - * Sets the score multiplier. - * - * @param scoreMultiplier The score multiplier value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setScoreMultiplier(double scoreMultiplier) { - this.scoreMultiplier = scoreMultiplier; - return this; - } - - /** - * Sets the score - * - * @param score The score value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setScore(double score) { - this.score = score; - return this; - } - - /** - * Sets the wait time multiplier. - * - * @param timeMultiplier The wait time multiplier value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setWaitTimeMultiplier(double timeMultiplier) { - this.waitTimeMultiplier = timeMultiplier; - return this; - } - - /** - * Sets the wait time. - * - * @param waitTime The wait time value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setWaitTime(double waitTime) { - this.waitTime = waitTime; - return this; - } - - /** - * Sets the difficulty. - * - * @param difficulty The difficulty value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setDifficulty(double difficulty) { - this.difficulty = difficulty; - return this; - } - - /** - * Sets the difficulty multiplier. - * - * @param difficultyMultiplier The difficulty multiplier value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setDifficultyMultiplier(double difficultyMultiplier) { - this.difficultyMultiplier = difficultyMultiplier; - return this; - } - - /** - * Sets the game time. - * - * @param gameTime The game time value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setGameTime(double gameTime) { - this.gameTime = gameTime; - return this; - } - - /** - * Sets the game time multiplier. - * - * @param gameTimeMultiplier The game time multiplier value to set. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect setGameTimeMultiplier(double gameTimeMultiplier) { - this.gameTimeMultiplier = gameTimeMultiplier; - return this; - } - - - /** - * Adds weight modifiers to the FishingEffect. - * - * @param weightModifier A list of pairs representing weight modifiers to add. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect addWeightModifier(List> weightModifier) { - this.weightModifier.addAll(weightModifier); - return this; - } - - /** - * Adds ignored weight modifiers to the FishingEffect. - * - * @param weightModifierIgnored A list of pairs representing ignored weight modifiers to add. - * @return The FishingEffect instance for method chaining. - */ - public FishingEffect addWeightModifierIgnored(List> weightModifierIgnored) { - this.weightModifierIgnored.addAll(weightModifierIgnored); - return this; - } - - /** - * Checks if lava fishing is enabled. - * - * @return True if lava fishing is enabled, false otherwise. - */ - @Override - public boolean canLavaFishing() { - return lavaFishing; - } - - /** - * Retrieves the multiple loot chance. - * - * @return The multiple loot chance value. - */ - @Override - public double getMultipleLootChance() { - return multipleLootChance; - } - - /** - * Retrieves the size multiplier. - * - * @return The size multiplier value. - */ - @Override - public double getSizeMultiplier() { - return sizeMultiplier; - } - - /** - * Retrieves the size. - * - * @return The size value. - */ - @Override - public double getSize() { - return size; - } - - /** - * Retrieves the score multiplier. - * - * @return The score multiplier value. - */ - @Override - public double getScoreMultiplier() { - return scoreMultiplier; - } - - /** - * Retrieves the wait time multiplier. - * - * @return The wait time multiplier value. - */ - @Override - public double getWaitTimeMultiplier() { - return waitTimeMultiplier; - } - - /** - * Retrieves the wait time. - * - * @return The wait time . - */ - @Override - public double getWaitTime() { - return waitTime; - } - - /** - * Retrieves the game time. - * - * @return The game time value. - */ - @Override - public double getGameTime() { - return gameTime; - } - - /** - * Retrieves the game time multiplier. - * - * @return The game time value multiplier. - */ - @Override - public double getGameTimeMultiplier() { - return gameTimeMultiplier; - } - - /** - * Retrieves score modifier. - * - * @return The score value. - */ - @Override - public double getScore() { - return score; - } - - /** - * Retrieves the difficulty. - * - * @return The difficulty value. - */ - @Override - public double getDifficulty() { - return difficulty; - } - - /** - * Retrieves the difficulty multiplier. - * - * @return The difficulty multiplier value. - */ - @Override - public double getDifficultyMultiplier() { - return difficultyMultiplier; - } - - /** - * Retrieves the list of weight modifiers. - * - * @return The list of weight modifiers. - */ - @Override - public List> getWeightModifier() { - return weightModifier; - } - - /** - * Retrieves the list of weight modifiers ignoring conditions. - * - * @return The list of weight modifiers ignoring conditions. - */ - @Override - public List> getWeightModifierIgnored() { - return weightModifierIgnored; - } - - /** - * Merges another Effect into this FishingEffect, combining their properties. - * - * @param another The Effect to merge into this FishingEffect. - */ - @Override - public void merge(Effect another) { - if (another == null) return; - if (another.canLavaFishing()) this.lavaFishing = true; - this.scoreMultiplier += (another.getScoreMultiplier() -1); - this.score += another.getScore(); - this.sizeMultiplier += (another.getSizeMultiplier() -1); - this.size += another.getSize(); - this.difficultyMultiplier += (another.getDifficultyMultiplier() -1); - this.difficulty += another.getDifficulty(); - this.gameTimeMultiplier += (another.getGameTimeMultiplier() - 1); - this.gameTime += another.getGameTime(); - this.waitTimeMultiplier += (another.getWaitTimeMultiplier() -1); - this.multipleLootChance += another.getMultipleLootChance(); - this.weightModifierIgnored.addAll(another.getWeightModifierIgnored()); - this.weightModifier.addAll(another.getWeightModifier()); - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffect.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffect.java new file mode 100644 index 00000000..60bb20c7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffect.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.effect; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import org.bukkit.entity.Player; + +/** + * Represents the base effect applied to loot in the custom fishing mechanic. + */ +public interface LootBaseEffect { + + MathValue DEFAULT_WAIT_TIME_ADDER = MathValue.plain(0); + MathValue DEFAULT_WAIT_TIME_MULTIPLIER = MathValue.plain(1); + MathValue DEFAULT_DIFFICULTY_ADDER = MathValue.plain(0); + MathValue DEFAULT_DIFFICULTY_MULTIPLIER = MathValue.plain(1); + MathValue DEFAULT_GAME_TIME_ADDER = MathValue.plain(0); + MathValue DEFAULT_GAME_TIME_MULTIPLIER = MathValue.plain(1); + + /** + * Gets the adder value for wait time. + * + * @return the wait time adder value + */ + MathValue waitTimeAdder(); + + /** + * Gets the multiplier value for wait time. + * + * @return the wait time multiplier value + */ + MathValue waitTimeMultiplier(); + + /** + * Gets the adder value for difficulty. + * + * @return the difficulty adder value + */ + MathValue difficultyAdder(); + + /** + * Gets the multiplier value for difficulty. + * + * @return the difficulty multiplier value + */ + MathValue difficultyMultiplier(); + + /** + * Gets the adder value for game time. + * + * @return the game time adder value + */ + MathValue gameTimeAdder(); + + /** + * Gets the multiplier value for game time. + * + * @return the game time multiplier value + */ + MathValue gameTimeMultiplier(); + + /** + * Creates a new {@link Builder} instance for constructing {@link LootBaseEffect} objects. + * + * @return a new {@link Builder} instance + */ + static Builder builder() { + return new LootBaseEffectImpl.BuilderImpl(); + } + + Effect toEffect(Context context); + + /** + * Builder interface for constructing {@link LootBaseEffect} instances. + */ + interface Builder { + + /** + * Sets the adder value for wait time. + * + * @param waitTimeAdder the wait time adder value + * @return the builder instance + */ + Builder waitTimeAdder(MathValue waitTimeAdder); + + /** + * Sets the multiplier value for wait time. + * + * @param waitTimeMultiplier the wait time multiplier value + * @return the builder instance + */ + Builder waitTimeMultiplier(MathValue waitTimeMultiplier); + + /** + * Sets the adder value for difficulty. + * + * @param difficultyAdder the difficulty adder value + * @return the builder instance + */ + Builder difficultyAdder(MathValue difficultyAdder); + + /** + * Sets the multiplier value for difficulty. + * + * @param difficultyMultiplier the difficulty multiplier value + * @return the builder instance + */ + Builder difficultyMultiplier(MathValue difficultyMultiplier); + + /** + * Sets the adder value for game time. + * + * @param gameTimeAdder the game time adder value + * @return the builder instance + */ + Builder gameTimeAdder(MathValue gameTimeAdder); + + /** + * Sets the multiplier value for game time. + * + * @param gameTimeMultiplier the game time multiplier value + * @return the builder instance + */ + Builder gameTimeMultiplier(MathValue gameTimeMultiplier); + + /** + * Builds and returns the {@link LootBaseEffect} instance. + * + * @return the built {@link LootBaseEffect} instance + */ + LootBaseEffect build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffectImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffectImpl.java new file mode 100644 index 00000000..bbc47ce0 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/effect/LootBaseEffectImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.effect; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import org.bukkit.entity.Player; + +public class LootBaseEffectImpl implements LootBaseEffect { + + private final MathValue waitTimeAdder; + private final MathValue waitTimeMultiplier; + private final MathValue difficultyAdder; + private final MathValue difficultyMultiplier; + private final MathValue gameTimeAdder; + private final MathValue gameTimeMultiplier; + + public LootBaseEffectImpl( + MathValue waitTimeAdder, + MathValue waitTimeMultiplier, + MathValue difficultyAdder, + MathValue difficultyMultiplier, + MathValue gameTimeAdder, + MathValue gameTimeMultiplier + ) { + this.waitTimeAdder = waitTimeAdder; + this.waitTimeMultiplier = waitTimeMultiplier; + this.difficultyAdder = difficultyAdder; + this.difficultyMultiplier = difficultyMultiplier; + this.gameTimeAdder = gameTimeAdder; + this.gameTimeMultiplier = gameTimeMultiplier; + } + + @Override + public MathValue waitTimeAdder() { + return waitTimeAdder; + } + + @Override + public MathValue waitTimeMultiplier() { + return waitTimeMultiplier; + } + + @Override + public MathValue difficultyAdder() { + return difficultyAdder; + } + + @Override + public MathValue difficultyMultiplier() { + return difficultyMultiplier; + } + + @Override + public MathValue gameTimeAdder() { + return gameTimeAdder; + } + + @Override + public MathValue gameTimeMultiplier() { + return gameTimeMultiplier; + } + + @Override + public Effect toEffect(Context context) { + Effect effect = Effect.newInstance(); + effect.waitTimeAdder(waitTimeAdder.evaluate(context)); + effect.waitTimeMultiplier(waitTimeMultiplier.evaluate(context)); + effect.difficultyAdder(difficultyAdder.evaluate(context)); + effect.difficultyMultiplier(difficultyMultiplier.evaluate(context)); + effect.gameTimeAdder(gameTimeAdder.evaluate(context)); + effect.gameTimeMultiplier(gameTimeMultiplier.evaluate(context)); + return effect; + } + + public static class BuilderImpl implements Builder { + private MathValue waitTimeAdder = DEFAULT_WAIT_TIME_ADDER; + private MathValue waitTimeMultiplier = DEFAULT_WAIT_TIME_MULTIPLIER; + private MathValue difficultyAdder = DEFAULT_DIFFICULTY_ADDER; + private MathValue difficultyMultiplier = DEFAULT_DIFFICULTY_MULTIPLIER; + private MathValue gameTimeAdder = DEFAULT_GAME_TIME_ADDER; + private MathValue gameTimeMultiplier = DEFAULT_GAME_TIME_MULTIPLIER; + @Override + public Builder waitTimeAdder(MathValue waitTimeAdder) { + this.waitTimeAdder = waitTimeAdder; + return this; + } + @Override + public Builder waitTimeMultiplier(MathValue waitTimeMultiplier) { + this.waitTimeMultiplier = waitTimeMultiplier; + return this; + } + @Override + public Builder difficultyAdder(MathValue difficultyAdder) { + this.difficultyAdder = difficultyAdder; + return this; + } + @Override + public Builder difficultyMultiplier(MathValue difficultyMultiplier) { + this.difficultyMultiplier = difficultyMultiplier; + return this; + } + @Override + public Builder gameTimeAdder(MathValue gameTimeAdder) { + this.gameTimeAdder = gameTimeAdder; + return this; + } + @Override + public Builder gameTimeMultiplier(MathValue gameTimeMultiplier) { + this.gameTimeMultiplier = gameTimeMultiplier; + return this; + } + @Override + public LootBaseEffect build() { + return new LootBaseEffectImpl(waitTimeAdder, waitTimeMultiplier, difficultyAdder, difficultyMultiplier, gameTimeAdder, gameTimeMultiplier); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfig.java index 8590a413..0afc2328 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfig.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfig.java @@ -17,76 +17,109 @@ package net.momirealms.customfishing.api.mechanic.entity; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + import java.util.Map; -public class EntityConfig implements EntitySettings { +/** + * The EntityConfig interface defines the configuration for an entity used in custom fishing mechanics. + * It includes methods to retrieve various properties of the entity such as vectors and an ID, as well + * as a nested Builder interface for constructing instances of EntityConfig. + */ +public interface EntityConfig { - private String entity; - private double horizontalVector; - private double verticalVector; - private Map propertyMap; - private boolean persist; + MathValue DEFAULT_HORIZONTAL_VECTOR = MathValue.plain(1.1); + MathValue DEFAULT_VERTICAL_VECTOR = MathValue.plain(1.2); + String DEFAULT_ENTITY_ID = "COD"; + Map DEFAULT_PROPERTY_MAP = Map.of(); - @Override - public boolean isPersist() { - return persist; + String id(); + + /** + * Retrieves the horizontal vector value for the entity. + * + * @return the horizontal vector value as a double + */ + MathValue horizontalVector(); + + /** + * Retrieves the vertical vector value for the entity. + * + * @return the vertical vector value as a double + */ + MathValue verticalVector(); + + /** + * Retrieves the unique identifier for the entity. + * + * @return the entity ID as a non-null String + */ + @NotNull + String entityID(); + + /** + * Retrieves a map of properties associated with the entity. + * + * @return a non-null map where keys are property names and values are property values + */ + @NotNull + Map propertyMap(); + + /** + * Creates a new Builder instance for constructing an EntityConfig. + * + * @return a new Builder instance + */ + static Builder builder() { + return new EntityConfigImpl.BuilderImpl(); } - @Override - public double getHorizontalVector() { - return horizontalVector; - } + /** + * Builder interface for constructing instances of EntityConfig. + */ + interface Builder { - @Override - public double getVerticalVector() { - return verticalVector; - } + Builder id(String id); - @Override - public String getEntityID() { - return entity; - } + /** + * Sets the entity ID for the EntityConfig being built. + * + * @param value the entity ID as a String + * @return the current Builder instance + */ + Builder entityID(String value); - @Override - public Map getPropertyMap() { - return propertyMap; - } + /** + * Sets the vertical vector value for the EntityConfig being built. + * + * @param value the vertical vector value as a double + * @return the current Builder instance + */ + Builder verticalVector(MathValue value); - public static class Builder { + /** + * Sets the horizontal vector value for the EntityConfig being built. + * + * @param value the horizontal vector value as a double + * @return the current Builder instance + */ + Builder horizontalVector(MathValue value); - private final EntityConfig config; + /** + * Sets the property map for the EntityConfig being built. + * + * @param value a map of properties where keys are property names and values are property values + * @return the current Builder instance + */ + Builder propertyMap(Map value); - public Builder() { - this.config = new EntityConfig(); - } - - public Builder entityID(String value) { - this.config.entity = value; - return this; - } - - public Builder persist(boolean value) { - this.config.persist = value; - return this; - } - - public Builder verticalVector(double value) { - this.config.verticalVector = value; - return this; - } - - public Builder horizontalVector(double value) { - this.config.horizontalVector = value; - return this; - } - - public Builder propertyMap(Map value) { - this.config.propertyMap = value; - return this; - } - - public EntityConfig build() { - return config; - } + /** + * Builds and returns the EntityConfig instance based on the current state of the Builder. + * + * @return a new EntityConfig instance + */ + EntityConfig build(); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfigImpl.java new file mode 100644 index 00000000..d327c601 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityConfigImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.entity; + +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class EntityConfigImpl implements EntityConfig { + + private final String id; + private final String entityID; + private final MathValue horizontalVector; + private final MathValue verticalVector; + private final Map propertyMap; + + public EntityConfigImpl(String id, String entityID, MathValue horizontalVector, MathValue verticalVector, Map propertyMap) { + this.id = id; + this.entityID = entityID; + this.horizontalVector = horizontalVector; + this.verticalVector = verticalVector; + this.propertyMap = propertyMap; + } + + @Override + public String id() { + return id; + } + + @Override + public MathValue horizontalVector() { + return horizontalVector; + } + + @Override + public MathValue verticalVector() { + return verticalVector; + } + + @NotNull + @Override + public String entityID() { + return entityID; + } + + @NotNull + @Override + public Map propertyMap() { + return propertyMap; + } + + public static class BuilderImpl implements Builder { + private String entity = DEFAULT_ENTITY_ID; + private MathValue horizontalVector = DEFAULT_HORIZONTAL_VECTOR; + private MathValue verticalVector = DEFAULT_VERTICAL_VECTOR; + private Map propertyMap = DEFAULT_PROPERTY_MAP; + private String id; + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public BuilderImpl entityID(String value) { + this.entity = value; + return this; + } + @Override + public BuilderImpl verticalVector(MathValue value) { + this.verticalVector = value; + return this; + } + @Override + public BuilderImpl horizontalVector(MathValue value) { + this.horizontalVector = value; + return this; + } + @Override + public BuilderImpl propertyMap(Map value) { + this.propertyMap = value; + return this; + } + @Override + public EntityConfigImpl build() { + return new EntityConfigImpl(id, entity, horizontalVector, verticalVector, propertyMap); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityManager.java new file mode 100644 index 00000000..d56ded96 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityManager.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.entity; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * EntityManager interface for managing custom entities in the fishing plugin. + */ +public interface EntityManager extends Reloadable { + + /** + * Retrieves the configuration for a custom entity by its identifier. + * + * @param id The unique identifier of the entity configuration. + * @return An Optional containing the EntityConfig if found, or an empty Optional if not found. + */ + Optional getEntity(String id); + + boolean registerEntity(EntityConfig entity); + + @NotNull + Entity summonEntityLoot(Context context); +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrier.java new file mode 100644 index 00000000..ce7fb560 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrier.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.event; + +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.TreeMap; + +/** + * The EventCarrier interface represents an object that carries events in the custom fishing system. + * It defines methods to trigger actions based on specific triggers and contexts. + */ +public interface EventCarrier { + + /** + * Get the type of item + * + * @return type + */ + MechanicType type(); + + String id(); + + /** + * Whether to disable global actions + * + * @return disable global actions or not + */ + boolean disableGlobalActions(); + + /** + * Triggers actions based on the given context and trigger. + * + * @param context the context of the event, typically containing information about the player involved. + * @param trigger the trigger that activates the actions. + */ + void trigger(Context context, ActionTrigger trigger); + + /** + * Triggers actions based on the given context, trigger, and action occurrence times. + * + * @param context the context of the event, typically containing information about the player involved. + * @param trigger the trigger that activates the actions. + * @param previousTimes the number of times the action has been triggered before. + * @param afterTimes the number of times the action will be triggered after. + */ + void trigger(Context context, ActionTrigger trigger, int previousTimes, int afterTimes); + + /** + * Creates a new Builder instance for constructing EventCarrier objects. + * + * @return a new Builder instance. + */ + static Builder builder() { + return new EventCarrierImpl.BuilderImpl(); + } + + /** + * The Builder interface provides a fluent API for constructing EventCarrier instances. + */ + interface Builder { + + Builder id(String id); + + /** + * Sets the map of actions associated with their triggers. + * + * @param actionMap the map of actions associated with their triggers. + * @return the Builder instance. + */ + Builder actionMap(HashMap[]> actionMap); + + Builder action(ActionTrigger trigger, Action[] actions); + + /** + * Sets the map of actions associated with their triggers and occurrence times. + * + * @param actionTimesMap the map of actions associated with their triggers and occurrence times. + * @return the Builder instance. + */ + Builder actionTimesMap(HashMap[]>> actionTimesMap); + + Builder actionTimes(ActionTrigger trigger, TreeMap[]> actions); + + /** + * Set the type of the item + * + * @param type type + * @return the Builder instance. + */ + Builder type(MechanicType type); + + /** + * Set whether to disable global events + * + * @param value disable or not + * @return the Builder instance. + */ + Builder disableGlobalActions(boolean value); + + /** + * Builds and returns the EventCarrier instance. + * + * @return the constructed EventCarrier instance. + */ + EventCarrier build(); + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrierImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrierImpl.java new file mode 100644 index 00000000..3ab0a189 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventCarrierImpl.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.event; + +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +import static java.util.Objects.requireNonNull; + +public class EventCarrierImpl implements EventCarrier { + + private final HashMap[]> actionMap; + private final HashMap[]>> actionTimesMap; + private final MechanicType type; + private final boolean disableGlobalActions; + private final String id; + + public EventCarrierImpl(String id, MechanicType type, boolean disableGlobalActions, HashMap[]> actionMap, HashMap[]>> actionTimesMap) { + this.actionMap = actionMap; + this.actionTimesMap = actionTimesMap; + this.type = type; + this.disableGlobalActions = disableGlobalActions; + this.id = id; + } + + @Override + public MechanicType type() { + return type; + } + + @Override + public String id() { + return id; + } + + @Override + public boolean disableGlobalActions() { + return disableGlobalActions; + } + + @Override + public void trigger(Context context, ActionTrigger trigger) { + Optional.ofNullable(actionMap.get(trigger)).ifPresent(actions -> { + ActionManager.trigger(context, actions); + }); + } + + @Override + public void trigger(Context context, ActionTrigger trigger, int previousTimes, int afterTimes) { + Optional.ofNullable(actionTimesMap.get(trigger)).ifPresent(integerTreeMap -> { + for (Map.Entry[]> entry : integerTreeMap.entrySet()) { + if (entry.getKey() <= previousTimes) + continue; + if (entry.getKey() > afterTimes) + return; + ActionManager.trigger(context, entry.getValue()); + } + }); + } + + public static class BuilderImpl implements Builder { + private final HashMap[]> actionMap = new HashMap<>(); + private final HashMap[]>> actionTimesMap = new HashMap<>(); + private MechanicType type = null; + private boolean disableGlobalActions = false; + private String id; + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public Builder actionMap(HashMap[]> actionMap) { + this.actionMap.putAll(actionMap); + return this; + } + @Override + public Builder action(ActionTrigger trigger, Action[] actions) { + this.actionMap.put(trigger, actions); + return this; + } + @Override + public Builder actionTimesMap(HashMap[]>> actionTimesMap) { + this.actionTimesMap.putAll(actionTimesMap); + return this; + } + @Override + public Builder actionTimes(ActionTrigger trigger, TreeMap[]> actions) { + this.actionTimesMap.put(trigger, actions); + return this; + } + @Override + public Builder type(MechanicType type) { + this.type = type; + return this; + } + @Override + public Builder disableGlobalActions(boolean value) { + this.disableGlobalActions = value; + return this; + } + @Override + public EventCarrier build() { + return new EventCarrierImpl(requireNonNull(id), requireNonNull(type), disableGlobalActions, actionMap, actionTimesMap); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventManager.java new file mode 100644 index 00000000..1cb5a711 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/event/EventManager.java @@ -0,0 +1,138 @@ +package net.momirealms.customfishing.api.mechanic.event; + +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +/** + * EventManager interface for managing events and their associated actions within the custom fishing plugin. + * It provides methods to register, retrieve, and trigger events based on different conditions. + */ +public interface EventManager extends Reloadable { + + /** + * A map storing global actions for different item types and triggers. + */ + Map[]>> GLOBAL_ACTIONS = new HashMap<>(); + + /** + * A map storing global timed actions for different item types and triggers. + */ + Map[]>>> GLOBAL_TIMES_ACTION = new HashMap<>(); + + /** + * Retrieves an EventCarrier by its identifier. + * + * @param id The unique identifier of the event carrier. + * @param type + * @return An Optional containing the EventCarrier if found, or an empty Optional if not found. + */ + Optional getEventCarrier(String id, MechanicType type); + + /** + * Registers a new EventCarrier with a specified identifier. + * + * @param carrier The EventCarrier to be registered. + * @return True if the registration was successful, false otherwise. + */ + boolean registerEventCarrier(EventCarrier carrier); + + /** + * Triggers an event for a given context, identifier, and trigger. + * + * @param context The context in which the event is triggered. + * @param id The unique identifier of the event carrier. + * @param trigger The trigger that initiates the event. + */ + default void trigger(Context context, String id, MechanicType type, ActionTrigger trigger) { + getEventCarrier(id, type).ifPresent(carrier -> trigger(context, carrier, trigger)); + } + + /** + * Triggers an event for a given context, identifier, trigger, and a range of times. + * + * @param context The context in which the event is triggered. + * @param id The unique identifier of the event carrier. + * @param trigger The trigger that initiates the event. + * @param previousTimes The previous times count for the event. + * @param afterTimes The after times count for the event. + */ + default void trigger(Context context, String id, MechanicType type, ActionTrigger trigger, int previousTimes, int afterTimes) { + getEventCarrier(id, type).ifPresent(carrier -> trigger(context, carrier, trigger, previousTimes, afterTimes)); + } + + /** + * Triggers the event actions for a given context and trigger on a specified carrier. + * + * @param context The context in which the event is triggered. + * @param carrier The event carrier. + * @param trigger The trigger that initiates the event. + */ + static void trigger(Context context, EventCarrier carrier, ActionTrigger trigger) { + if (!carrier.disableGlobalActions()) { + triggerGlobalActions(context, carrier.type(), trigger); + } + carrier.trigger(context, trigger); + } + + /** + * Triggers the event actions for a given context, trigger, and times range on a specified carrier. + * + * @param context The context in which the event is triggered. + * @param carrier The event carrier. + * @param trigger The trigger that initiates the event. + * @param previousTimes The previous times count for the event. + * @param afterTimes The after times count for the event. + */ + static void trigger(Context context, EventCarrier carrier, ActionTrigger trigger, int previousTimes, int afterTimes) { + if (!carrier.disableGlobalActions()) { + triggerGlobalActions(context, carrier.type(), trigger, previousTimes, afterTimes); + } + carrier.trigger(context, trigger, previousTimes, afterTimes); + } + + /** + * Triggers global actions for a given context, item type, and trigger. + * + * @param context The context in which the event is triggered. + * @param type The type of item that triggered the event. + * @param trigger The trigger that initiates the event. + */ + static void triggerGlobalActions(Context context, MechanicType type, ActionTrigger trigger) { + Optional.ofNullable(GLOBAL_ACTIONS.get(type)) + .flatMap(actionTriggerMap -> Optional.ofNullable(actionTriggerMap.get(trigger))) + .ifPresent(action -> ActionManager.trigger(context, action)); + } + + /** + * Triggers global timed actions for a given context, item type, trigger, and times range. + * + * @param context The context in which the event is triggered. + * @param type The type of item that triggered the event. + * @param trigger The trigger that initiates the event. + * @param previousTimes The previous times count for the event. + * @param afterTimes The after times count for the event. + */ + static void triggerGlobalActions(Context context, MechanicType type, ActionTrigger trigger, int previousTimes, int afterTimes) { + Optional.ofNullable(GLOBAL_TIMES_ACTION.get(type)) + .flatMap(actionTriggerMap -> Optional.ofNullable(actionTriggerMap.get(trigger))) + .ifPresent(integerTreeMap -> { + for (Map.Entry[]> entry : integerTreeMap.entrySet()) { + if (entry.getKey() <= previousTimes) + continue; + if (entry.getKey() > afterTimes) + return; + ActionManager.trigger(context, entry.getValue()); + } + }); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/BaitAnimationTask.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/BaitAnimationTask.java new file mode 100644 index 00000000..40e1c29a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/BaitAnimationTask.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.TimeUnit; + +public class BaitAnimationTask implements Runnable { + + private final SchedulerTask task; + private final int entityID; + private final Player player; + private final FishHook fishHook; + + public BaitAnimationTask(BukkitCustomFishingPlugin plugin, Player player, FishHook fishHook, ItemStack baitItem) { + this.player = player; + this.fishHook = fishHook; + this.task = plugin.getScheduler().asyncRepeating(this, 50, 50, TimeUnit.MILLISECONDS); + ItemStack itemStack = baitItem.clone(); + itemStack.setAmount(1); + this.entityID = SparrowHeart.getInstance().dropFakeItem(player, itemStack, fishHook.getLocation().clone().subtract(0,0.6,0)); + } + + @Override + public void run() { + SparrowHeart.getInstance().sendClientSideEntityMotion(player, fishHook.getVelocity(), entityID); + SparrowHeart.getInstance().sendClientSideTeleportEntity(player, fishHook.getLocation().clone().subtract(0,0.6,0), false, entityID); + } + + public void cancel() { + task.cancel(); + SparrowHeart.getInstance().removeClientSideEntity(player, entityID); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/CustomFishingHook.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/CustomFishingHook.java new file mode 100644 index 00000000..ea68be0c --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/CustomFishingHook.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingLootSpawnEvent; +import net.momirealms.customfishing.api.event.FishingResultEvent; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.fishing.hook.HookMechanic; +import net.momirealms.customfishing.api.mechanic.fishing.hook.LavaFishingMechanic; +import net.momirealms.customfishing.api.mechanic.fishing.hook.VanillaMechanic; +import net.momirealms.customfishing.api.mechanic.fishing.hook.VoidFishingMechanic; +import net.momirealms.customfishing.api.mechanic.game.Game; +import net.momirealms.customfishing.api.mechanic.game.GamingPlayer; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.TriConsumer; +import net.momirealms.customfishing.common.util.TriFunction; +import net.momirealms.sparrow.heart.SparrowHeart; +import net.momirealms.sparrow.heart.feature.inventory.HandSlot; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Statistic; +import org.bukkit.entity.*; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class CustomFishingHook { + + private final BukkitCustomFishingPlugin plugin; + private final FishHook hook; + private final SchedulerTask task; + private final FishingGears gears; + private final Context context; + private Effect tempFinalEffect; + private HookMechanic hookMechanic; + private Loot nextLoot; + private GamingPlayer gamingPlayer; + private BaitAnimationTask baitAnimationTask; + + private static TriFunction, Effect, List> mechanicProviders = defaultMechanicProviders(); + + public static TriFunction, Effect, List> defaultMechanicProviders() { + return (h, c, e) -> { + ArrayList mechanics = new ArrayList<>(); + mechanics.add(new VanillaMechanic(h, c)); + if (ConfigManager.enableLavaFishing()) mechanics.add(new LavaFishingMechanic(h, e, c)); + if (ConfigManager.enableVoidFishing()) mechanics.add(new VoidFishingMechanic(h, e, c)); + return mechanics; + }; + } + + public static void mechanicProviders(TriFunction, Effect, List> mechanicProviders) { + CustomFishingHook.mechanicProviders = mechanicProviders; + } + + public CustomFishingHook(BukkitCustomFishingPlugin plugin, FishHook hook, FishingGears gears, Context context) { + this.gears = gears; + this.gears.trigger(ActionTrigger.CAST, context); + this.plugin = plugin; + this.hook = hook; + // once it becomes a custom hook, the wait time is controlled by plugin + this.context = context; + Effect effect = Effect.newInstance(); + // The effects impact mechanism at this stage + for (EffectModifier modifier : gears.effectModifiers()) { + for (TriConsumer, Integer> consumer : modifier.modifiers()) { + consumer.accept(effect, context, 0); + } + } + // enable bait animation + if (ConfigManager.baitAnimation() && !gears.getItem(FishingGears.GearType.BAIT).isEmpty()) { + this.baitAnimationTask = new BaitAnimationTask(plugin, context.getHolder(), hook, gears.getItem(FishingGears.GearType.BAIT).stream().findAny().get().right()); + } + + List enabledMechanics = mechanicProviders.apply(hook, context, effect); + this.task = plugin.getScheduler().sync().runRepeating(() -> { + // destroy if hook is invalid + if (!hook.isValid()) { + plugin.getFishingManager().destroy(hook.getOwnerUniqueId()); + return; + } + if (isPlayingGame()) { + return; + } + if (this.hookMechanic != null) { + if (this.hookMechanic.shouldStop()) { + this.hookMechanic.destroy(); + this.hookMechanic = null; + } + } + for (HookMechanic mechanic : enabledMechanics) { + // find the first available mechanic + if (mechanic.canStart()) { + if (this.hookMechanic != mechanic) { + if (this.hookMechanic != null) this.hookMechanic.destroy(); + this.hookMechanic = mechanic; + + // remove bait animation if there exists + if (this.baitAnimationTask != null) { + this.baitAnimationTask.cancel(); + this.baitAnimationTask = null; + } + + // to update some properties + mechanic.preStart(); + Effect tempEffect = effect.copy(); + for (EffectModifier modifier : gears.effectModifiers()) { + for (TriConsumer, Integer> consumer : modifier.modifiers()) { + consumer.accept(tempEffect, context, 1); + } + } + + context.arg(ContextKeys.OTHER_LOCATION, hook.getLocation()); + context.arg(ContextKeys.OTHER_X, hook.getLocation().getBlockX()); + context.arg(ContextKeys.OTHER_Y, hook.getLocation().getBlockY()); + context.arg(ContextKeys.OTHER_Z, hook.getLocation().getBlockZ()); + + // get the next loot + Loot loot = plugin.getLootManager().getNextLoot(effect, context); + if (loot != null) { + this.nextLoot = loot; + + context.arg(ContextKeys.ID, loot.id()); + context.arg(ContextKeys.NICK, loot.nick()); + context.arg(ContextKeys.LOOT, loot.type()); + + plugin.debug("Next loot: " + loot.id()); + // get its basic properties + Effect baseEffect = loot.baseEffect().toEffect(context); + tempEffect.combine(baseEffect); + // apply the gears' effects + for (EffectModifier modifier : gears.effectModifiers()) { + for (TriConsumer, Integer> consumer : modifier.modifiers()) { + consumer.accept(tempEffect, context, 2); + } + } + // start the mechanic + mechanic.start(tempEffect); + + this.tempFinalEffect = tempEffect; + } else { + mechanic.start(tempEffect); + this.tempFinalEffect = tempEffect; + // to prevent players from getting any loot + mechanic.freeze(); + } + } + } + } + }, 1, 1, hook.getLocation()); + } + + public void destroy() { + if (task != null) task.cancel(); + if (hook.isValid()) hook.remove(); + if (hookMechanic != null) hookMechanic.destroy(); + if (gamingPlayer != null) gamingPlayer.destroy(); + if (this.baitAnimationTask != null) { + this.baitAnimationTask.cancel(); + this.baitAnimationTask = null; + } + } + + public Context getContext() { + return context; + } + + @NotNull + public FishHook getHookEntity() { + return hook; + } + + @Nullable + public HookMechanic getCurrentHookMechanic() { + return hookMechanic; + } + + @Nullable + public Loot getNextLoot() { + return nextLoot; + } + + public void onReelIn() { + if (isPlayingGame()) return; + if (hookMechanic != null) { + if (!hookMechanic.isHooked()) { + gears.trigger(ActionTrigger.REEL, context); + end(); + } else { + if (nextLoot.disableGame() || RequirementManager.isSatisfied(context, ConfigManager.skipGameRequirements())) { + handleSuccessfulFishing(); + end(); + } else { + gameStart(); + } + } + } else { + gears.trigger(ActionTrigger.REEL, context); + end(); + } + } + + public void end() { + plugin.getFishingManager().destroy(context.getHolder().getUniqueId()); + } + + public void onBite() { + if (isPlayingGame()) return; + plugin.getEventManager().trigger(context, nextLoot.id(), MechanicType.LOOT, ActionTrigger.BITE); + gears.trigger(ActionTrigger.BITE, context); + if (RequirementManager.isSatisfied(context, ConfigManager.autoFishingRequirements())) { + handleSuccessfulFishing(); + SparrowHeart.getInstance().swingHand(context.getHolder(), gears.getRodSlot()); + end(); + scheduleNextFishing(); + return; + } + if (nextLoot.instantGame()) { + gameStart(); + } + } + + public boolean isPlayingGame() { + return gamingPlayer != null && gamingPlayer.isValid(); + } + + public void cancelCurrentGame() { + if (gamingPlayer == null || !gamingPlayer.isValid()) { + throw new RuntimeException("You can't call this method if the player is not playing the game"); + } + gamingPlayer.cancel(); + gamingPlayer = null; + if (hookMechanic != null) { + hookMechanic.unfreeze(tempFinalEffect); + } + } + + public void gameStart() { + if (isPlayingGame()) + return; + Game nextGame = plugin.getGameManager().getNextGame(tempFinalEffect, context); + if (nextGame != null) { + plugin.debug("Next game: " + nextGame.id()); + gamingPlayer = nextGame.start(this, tempFinalEffect); + if (this.hookMechanic != null) { + this.hookMechanic.freeze(); + } + } else { + plugin.debug("Next game: " + "`null`"); + handleSuccessfulFishing(); + end(); + } + } + + public Optional getGamingPlayer() { + return Optional.ofNullable(gamingPlayer); + } + + private void scheduleNextFishing() { + final Player player = context.getHolder(); + plugin.getScheduler().sync().runLater(() -> { + if (player.isOnline()) { + ItemStack item = player.getInventory().getItem(gears.getRodSlot() == HandSlot.MAIN ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND); + if (item.getType() == Material.FISHING_ROD) { + SparrowHeart.getInstance().useItem(player, gears.getRodSlot(), item); + SparrowHeart.getInstance().swingHand(context.getHolder(), gears.getRodSlot()); + } + } + }, 20, player.getLocation()); + } + + public void onLand() { + gears.trigger(ActionTrigger.LAND, context); + } + + public void onEscape() { + plugin.getEventManager().trigger(context, nextLoot.id(), MechanicType.LOOT, ActionTrigger.ESCAPE); + gears.trigger(ActionTrigger.ESCAPE, context); + } + + public void onLure() { + plugin.getEventManager().trigger(context, nextLoot.id(), MechanicType.LOOT, ActionTrigger.LURE); + gears.trigger(ActionTrigger.LURE, context); + } + + public void handleFailedFishing() { + + // update the hook location + context.arg(ContextKeys.OTHER_LOCATION, hook.getLocation()); + context.arg(ContextKeys.OTHER_X, hook.getLocation().getBlockX()); + context.arg(ContextKeys.OTHER_Y, hook.getLocation().getBlockY()); + context.arg(ContextKeys.OTHER_Z, hook.getLocation().getBlockZ()); + + gears.trigger(ActionTrigger.FAILURE, context); + plugin.getEventManager().trigger(context, nextLoot.id(), MechanicType.LOOT, ActionTrigger.FAILURE); + } + + public void handleSuccessfulFishing() { + + // update the hook location + context.arg(ContextKeys.OTHER_LOCATION, hook.getLocation()); + context.arg(ContextKeys.OTHER_X, hook.getLocation().getBlockX()); + context.arg(ContextKeys.OTHER_Y, hook.getLocation().getBlockY()); + context.arg(ContextKeys.OTHER_Z, hook.getLocation().getBlockZ()); + + LootType lootType = context.arg(ContextKeys.LOOT); + Objects.requireNonNull(lootType, "Missing loot type"); + Objects.requireNonNull(tempFinalEffect, "Missing final effects"); + + int amount; + if (lootType == LootType.ITEM) { + amount = (int) tempFinalEffect.multipleLootChance(); + amount += Math.random() < (tempFinalEffect.multipleLootChance() - amount) ? 2 : 1; + } else { + amount = 1; + } + // set the amount of loot + context.arg(ContextKeys.AMOUNT, amount); + + FishingResultEvent event = new FishingResultEvent(context, FishingResultEvent.Result.SUCCESS, hook, nextLoot); + if (EventUtils.fireAndCheckCancel(event)) { + return; + } + + gears.trigger(ActionTrigger.SUCCESS, context); + + switch (lootType) { + case ITEM -> { + for (int i = 0; i < amount; i++) { + plugin.getScheduler().sync().runLater(() -> { + Item item = plugin.getItemManager().dropItemLoot(context, gears.getItem(FishingGears.GearType.ROD).stream().findAny().orElseThrow().right(), hook); + if (item != null && Objects.equals(context.arg(ContextKeys.NICK), "UNDEFINED")) { + ItemStack stack = item.getItemStack(); + Optional displayName = plugin.getItemManager().wrap(stack).displayName(); + if (displayName.isPresent()) { + context.arg(ContextKeys.NICK, AdventureHelper.jsonToMiniMessage(displayName.get())); + } else { + context.arg(ContextKeys.NICK, ""); + } + } + + FishingLootSpawnEvent spawnEvent = new FishingLootSpawnEvent(context, hook.getLocation(), nextLoot, item); + Bukkit.getPluginManager().callEvent(spawnEvent); + if (item != null && !spawnEvent.summonEntity()) + item.remove(); + if (spawnEvent.skipActions()) + return; + if (item != null && item.isValid() && nextLoot.preventGrabbing()) { + item.getPersistentDataContainer().set(Objects.requireNonNull(NamespacedKey.fromString("owner", plugin.getBoostrap())), PersistentDataType.STRING, context.getHolder().getName()); + } + doSuccessActions(); + }, (long) ConfigManager.multipleLootSpawnDelay() * i, hook.getLocation()); + } + } + case BLOCK -> { + FallingBlock fallingBlock = plugin.getBlockManager().summonBlockLoot(context); + FishingLootSpawnEvent spawnEvent = new FishingLootSpawnEvent(context, hook.getLocation(), nextLoot, fallingBlock); + Bukkit.getPluginManager().callEvent(spawnEvent); + if (!spawnEvent.summonEntity()) + fallingBlock.remove(); + if (spawnEvent.skipActions()) + return; + doSuccessActions(); + } + case ENTITY -> { + Entity entity = plugin.getEntityManager().summonEntityLoot(context); + FishingLootSpawnEvent spawnEvent = new FishingLootSpawnEvent(context, hook.getLocation(), nextLoot, entity); + Bukkit.getPluginManager().callEvent(spawnEvent); + if (!spawnEvent.summonEntity()) + entity.remove(); + if (spawnEvent.skipActions()) + return; + doSuccessActions(); + } + } + } + + private void doSuccessActions() { + FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); + if (competition != null && RequirementManager.isSatisfied(context, competition.getConfig().joinRequirements())) { + Double customScore = context.arg(ContextKeys.CUSTOM_SCORE); + if (customScore != null) { + competition.refreshData(context.getHolder(), customScore); + context.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", customScore)); + context.arg(ContextKeys.SCORE, customScore); + } else { + double score = 0; + if (competition.getGoal() == CompetitionGoal.CATCH_AMOUNT) { + score = 1; + competition.refreshData(context.getHolder(), score); + } else if (competition.getGoal() == CompetitionGoal.MAX_SIZE || competition.getGoal() == CompetitionGoal.MIN_SIZE) { + Float size = context.arg(ContextKeys.SIZE); + if (size != null) { + competition.refreshData(context.getHolder(), size); + } + } else if (competition.getGoal() == CompetitionGoal.TOTAL_SCORE) { + score = nextLoot.score().evaluate(context); + if (score != 0) { + competition.refreshData(context.getHolder(), score); + } + } + context.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", score)); + context.arg(ContextKeys.SCORE, score); + } + } else { + context.arg(ContextKeys.SCORE_FORMATTED, "0.0"); + context.arg(ContextKeys.SCORE, 0d); + } + + String id = context.arg(ContextKeys.ID); + Player player = context.getHolder(); + + if (!nextLoot.disableStats()) { + plugin.getStorageManager().getOnlineUser(player.getUniqueId()).ifPresent( + userData -> { + userData.statistics().addAmount(nextLoot.statisticKey().amountKey(), 1); + context.arg(ContextKeys.TOTAL_AMOUNT, userData.statistics().getAmount(nextLoot.statisticKey().amountKey())); + Optional.ofNullable(context.arg(ContextKeys.SIZE)).ifPresent(size -> { + float max = Math.max(0, userData.statistics().getMaxSize(nextLoot.statisticKey().sizeKey())); + context.arg(ContextKeys.RECORD, max); + context.arg(ContextKeys.RECORD_FORMATTED, String.format("%.2f", max)); + if (userData.statistics().updateSize(nextLoot.statisticKey().sizeKey(), size)) { + plugin.getEventManager().trigger(context, id, MechanicType.LOOT, ActionTrigger.NEW_SIZE_RECORD); + } + }); + } + ); + } + + plugin.getEventManager().trigger(context, id, MechanicType.LOOT, ActionTrigger.SUCCESS); + player.setStatistic(Statistic.FISH_CAUGHT, player.getStatistic(Statistic.FISH_CAUGHT) + 1); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingGears.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingGears.java new file mode 100644 index 00000000..5c23fd0d --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingGears.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ScoreComponent; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.hook.HookConfig; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.customfishing.common.util.TriConsumer; +import net.momirealms.sparrow.heart.feature.inventory.HandSlot; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.BiConsumer; + +public class FishingGears { + + private static final Map, ItemStack>> triggers = new HashMap<>(); + + static { + triggers.put(ActionTrigger.CAST, ((type, context, itemStack) -> type.castFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.REEL, ((type, context, itemStack) -> type.reelFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.LAND, ((type, context, itemStack) -> type.landFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.ESCAPE, ((type, context, itemStack) -> type.escapeFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.LURE, ((type, context, itemStack) -> type.lureFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.SUCCESS, ((type, context, itemStack) -> type.successFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.FAILURE, ((type, context, itemStack) -> type.failureFunction.accept(context, itemStack))); + triggers.put(ActionTrigger.BITE, ((type, context, itemStack) -> type.biteFunction.accept(context, itemStack))); + } + + private static BiConsumer, FishingGears> fishingGearsConsumers = defaultFishingGearsConsumers(); + private final HashMap>> gears = new HashMap<>(); + private final ArrayList modifiers = new ArrayList<>(); + private boolean canFish = true; + private HandSlot rodSlot; + + public static void fishingGearsConsumers(BiConsumer, FishingGears> fishingGearsConsumers) { + FishingGears.fishingGearsConsumers = fishingGearsConsumers; + } + + public FishingGears(Context context) { + fishingGearsConsumers.accept(context, this); + } + + public boolean canFish() { + return canFish; + } + + public void trigger(ActionTrigger trigger, Context context) { + for (Map.Entry>> entry : gears.entrySet()) { + for (Pair itemPair : entry.getValue()) { + BukkitCustomFishingPlugin.getInstance().debug(entry.getKey() + " | " + itemPair.left() + " | " + trigger); + triggers.get(trigger).accept(entry.getKey(), context, itemPair.right()); + BukkitCustomFishingPlugin.getInstance().getEventManager().trigger(context, itemPair.left(), entry.getKey().getType(), trigger); + } + } + } + + @NotNull + public List effectModifiers() { + return modifiers; + } + + public HandSlot getRodSlot() { + return rodSlot; + } + + @NotNull + public Collection> getItem(GearType type) { + return gears.getOrDefault(type, List.of()); + } + + public static BiConsumer, FishingGears> defaultFishingGearsConsumers() { + return (context, fishingGears) -> { + Player player = context.getHolder(); + PlayerInventory playerInventory = player.getInventory(); + ItemStack mainHandItem = playerInventory.getItemInMainHand(); + ItemStack offHandItem = playerInventory.getItemInOffHand(); + // set rod + boolean rodOnMainHand = mainHandItem.getType() == Material.FISHING_ROD; + ItemStack rodItem = rodOnMainHand ? mainHandItem : offHandItem; + String rodID = BukkitCustomFishingPlugin.getInstance().getItemManager().getItemID(rodItem); + fishingGears.gears.put(GearType.ROD, List.of(Pair.of(rodID, rodItem))); + context.arg(ContextKeys.ROD, rodID); + fishingGears.rodSlot = rodOnMainHand ? HandSlot.MAIN : HandSlot.OFF; + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(rodID, MechanicType.ROD).ifPresent(fishingGears.modifiers::add); + + // set enchantments + List> enchants = BukkitCustomFishingPlugin.getInstance().getIntegrationManager().getEnchantments(rodItem); + for (Pair enchantment : enchants) { + String effectID = enchantment.left() + ":" + enchantment.right(); + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(effectID, MechanicType.ENCHANT).ifPresent(fishingGears.modifiers::add); + } + + // set hook + BukkitCustomFishingPlugin.getInstance().getHookManager().getHookID(rodItem).ifPresent(hookID -> { + fishingGears.gears.put(GearType.HOOK, List.of(Pair.of(hookID, rodItem))); + context.arg(ContextKeys.HOOK, hookID); + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(hookID, MechanicType.HOOK).ifPresent(fishingGears.modifiers::add); + }); + + // set bait if it is + boolean hasBait = false; + String anotherItemID = BukkitCustomFishingPlugin.getInstance().getItemManager().getItemID(rodOnMainHand ? offHandItem : mainHandItem); + List type = MechanicType.getTypeByID(anotherItemID); + if (type != null && type.contains(MechanicType.BAIT)) { + fishingGears.gears.put(GearType.BAIT, List.of(Pair.of(anotherItemID, rodOnMainHand ? offHandItem : mainHandItem))); + context.arg(ContextKeys.BAIT, anotherItemID); + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(anotherItemID, MechanicType.BAIT).ifPresent(fishingGears.modifiers::add); + hasBait = true; + } + + // search the bag + if (ConfigManager.enableBag()) { + Optional dataOptional = BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()); + if (dataOptional.isPresent()) { + UserData data = dataOptional.get(); + Inventory bag = data.holder().getInventory(); + HashMap uniqueUtils = new HashMap<>(); + for (int i = 0; i < bag.getSize(); i++) { + ItemStack itemInBag = bag.getItem(i); + if (itemInBag == null) continue; + String bagItemID = BukkitCustomFishingPlugin.getInstance().getItemManager().getItemID(itemInBag); + List bagItemType = MechanicType.getTypeByID(bagItemID); + if (bagItemType != null) { + if (!hasBait && bagItemType.contains(MechanicType.BAIT)) { + fishingGears.gears.put(GearType.BAIT, List.of(Pair.of(bagItemID, itemInBag))); + context.arg(ContextKeys.BAIT, bagItemID); + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(bagItemID, MechanicType.BAIT).ifPresent(fishingGears.modifiers::add); + hasBait = true; + } + if (bagItemType.contains(MechanicType.UTIL)) { + uniqueUtils.put(bagItemID, itemInBag); + } + } + } + if (!uniqueUtils.isEmpty()) { + ArrayList> utils = new ArrayList<>(); + for (Map.Entry entry : uniqueUtils.entrySet()) { + utils.add(Pair.of(entry.getKey(), entry.getValue())); + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(entry.getKey(), MechanicType.UTIL).ifPresent(fishingGears.modifiers::add); + } + fishingGears.gears.put(GearType.UTIL, utils); + } + } + } + + // check requirements before checking totems + for (EffectModifier modifier : fishingGears.modifiers) { + if (!RequirementManager.isSatisfied(context, modifier.requirements())) { + fishingGears.canFish = false; + } + } + + // set totems + Collection totemIDs = BukkitCustomFishingPlugin.getInstance().getTotemManager().getActivatedTotems(player.getLocation()); + for (String id : totemIDs) { + BukkitCustomFishingPlugin.getInstance().getEffectManager().getEffectModifier(id, MechanicType.TOTEM).ifPresent(fishingGears.modifiers::add); + } + + // add global effects + fishingGears.modifiers.add( + EffectModifier.builder() + .id("__GLOBAL__") + .modifiers(ConfigManager.globalEffects()) + .build() + ); + }; + } + + public static class GearType { + + public static final GearType ROD = new GearType(MechanicType.ROD, + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> { + if (context.getHolder().getGameMode() != GameMode.CREATIVE) + BukkitCustomFishingPlugin.getInstance().getItemManager().decreaseDurability(context.getHolder(), itemStack, 1, false); + }), + ((context, itemStack) -> { + if (context.getHolder().getGameMode() != GameMode.CREATIVE) + BukkitCustomFishingPlugin.getInstance().getItemManager().decreaseDurability(context.getHolder(), itemStack, 1, false); + }), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}) + ); + + public static final GearType BAIT = new GearType(MechanicType.BAIT, + ((context, itemStack) -> { + if (context.getHolder().getGameMode() != GameMode.CREATIVE) + itemStack.setAmount(itemStack.getAmount() - 1); + }), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}) + ); + + public static final GearType HOOK = new GearType(MechanicType.HOOK, + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> { + if (context.getHolder().getGameMode() != GameMode.CREATIVE) { + Item wrapped = BukkitCustomFishingPlugin.getInstance().getItemManager().wrap(itemStack.clone()); + String hookID = (String) wrapped.getTag("CustomFishing", "hook_id").orElseThrow(() -> new RuntimeException("This error should never occur")); + wrapped.getTag("CustomFishing", "hook_max_damage").ifPresent(max -> { + int maxDamage = (int) max; + int hookDamage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0) + 1; + if (hookDamage >= maxDamage) { + wrapped.removeTag("CustomFishing", "hook_damage"); + wrapped.removeTag("CustomFishing", "hook_id"); + wrapped.removeTag("CustomFishing", "hook_stack"); + wrapped.removeTag("CustomFishing", "hook_max_damage"); + BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(context.getHolder()).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1)); + } else { + wrapped.setTag(hookDamage, "CustomFishing", "hook_damage"); + HookConfig hookConfig = BukkitCustomFishingPlugin.getInstance().getHookManager().getHook(hookID).orElseThrow(); + List previousLore = wrapped.lore().orElse(new ArrayList<>()); + List newLore = new ArrayList<>(); + List durabilityLore = new ArrayList<>(); + for (String previous : previousLore) { + Component component = AdventureHelper.jsonToComponent(previous); + if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) { + if (scoreComponent.objective().equals("hook")) { + continue; + } else if (scoreComponent.objective().equals("durability")) { + durabilityLore.add(previous); + continue; + } + } + newLore.add(previous); + } + for (String lore : hookConfig.lore()) { + ScoreComponent.Builder builder = Component.score().name("cf").objective("hook"); + builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(maxDamage - hookDamage)).replace("{max}", String.valueOf(maxDamage)))); + newLore.add(AdventureHelper.componentToJson(builder.build())); + } + newLore.addAll(durabilityLore); + wrapped.lore(newLore); + } + itemStack.setItemMeta(wrapped.load().getItemMeta()); + }); + } + }), + ((context, itemStack) -> { + if (context.getHolder().getGameMode() != GameMode.CREATIVE) { + Item wrapped = BukkitCustomFishingPlugin.getInstance().getItemManager().wrap(itemStack.clone()); + String hookID = (String) wrapped.getTag("CustomFishing", "hook_id").orElseThrow(() -> new RuntimeException("This error should never occur")); + wrapped.getTag("CustomFishing", "hook_max_damage").ifPresent(max -> { + int maxDamage = (int) max; + int hookDamage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0) + 1; + if (hookDamage >= maxDamage) { + wrapped.removeTag("CustomFishing", "hook_damage"); + wrapped.removeTag("CustomFishing", "hook_id"); + wrapped.removeTag("CustomFishing", "hook_stack"); + wrapped.removeTag("CustomFishing", "hook_max_damage"); + BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(context.getHolder()).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1)); + } else { + wrapped.setTag(hookDamage, "CustomFishing", "hook_damage"); + HookConfig hookConfig = BukkitCustomFishingPlugin.getInstance().getHookManager().getHook(hookID).orElseThrow(); + List previousLore = wrapped.lore().orElse(new ArrayList<>()); + List newLore = new ArrayList<>(); + List durabilityLore = new ArrayList<>(); + for (String previous : previousLore) { + Component component = AdventureHelper.jsonToComponent(previous); + if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) { + if (scoreComponent.objective().equals("hook")) { + continue; + } else if (scoreComponent.objective().equals("durability")) { + durabilityLore.add(previous); + continue; + } + } + newLore.add(previous); + } + for (String lore : hookConfig.lore()) { + ScoreComponent.Builder builder = Component.score().name("cf").objective("hook"); + builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(maxDamage - hookDamage)).replace("{max}", String.valueOf(maxDamage)))); + newLore.add(AdventureHelper.componentToJson(builder.build())); + } + newLore.addAll(durabilityLore); + wrapped.lore(newLore); + } + itemStack.setItemMeta(wrapped.load().getItemMeta()); + }); + } + }), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}) + ); + + public static final GearType UTIL = new GearType(MechanicType.UTIL, + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}), + ((context, itemStack) -> {}) + ); + + private final MechanicType type; + private BiConsumer, ItemStack> castFunction; + private BiConsumer, ItemStack> reelFunction; + private BiConsumer, ItemStack> biteFunction; + private BiConsumer, ItemStack> successFunction; + private BiConsumer, ItemStack> failureFunction; + private BiConsumer, ItemStack> lureFunction; + private BiConsumer, ItemStack> escapeFunction; + private BiConsumer, ItemStack> landFunction; + + public GearType(MechanicType type, + BiConsumer, ItemStack> castFunction, BiConsumer, ItemStack> reelFunction, + BiConsumer, ItemStack> biteFunction, BiConsumer, ItemStack> successFunction, + BiConsumer, ItemStack> failureFunction, BiConsumer, ItemStack> lureFunction, + BiConsumer, ItemStack> escapeFunction, BiConsumer, ItemStack> landFunction + ) { + this.type = type; + this.castFunction = castFunction; + this.reelFunction = reelFunction; + this.biteFunction = biteFunction; + this.successFunction = successFunction; + this.failureFunction = failureFunction; + this.landFunction = landFunction; + this.lureFunction = lureFunction; + this.escapeFunction = escapeFunction; + } + + public void castFunction(BiConsumer, ItemStack> castFunction) { + this.castFunction = castFunction; + } + + public void reelFunction(BiConsumer, ItemStack> reelFunction) { + this.reelFunction = reelFunction; + } + + public void biteFunction(BiConsumer, ItemStack> biteFunction) { + this.biteFunction = biteFunction; + } + + public void successFunction(BiConsumer, ItemStack> successFunction) { + this.successFunction = successFunction; + } + + public void failureFunction(BiConsumer, ItemStack> failureFunction) { + this.failureFunction = failureFunction; + } + + public void escapeFunction(BiConsumer, ItemStack> escapeFunction) { + this.escapeFunction = escapeFunction; + } + + public void lureFunction(BiConsumer, ItemStack> lureFunction) { + this.lureFunction = lureFunction; + } + + public void landFunction(BiConsumer, ItemStack> landFunction) { + this.landFunction = landFunction; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + GearType gearType = (GearType) object; + return Objects.equals(type, gearType.type); + } + + @Override + public int hashCode() { + return Objects.hashCode(type); + } + + @Override + public String toString() { + return type.toString(); + } + + public MechanicType getType() { + return type; + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/LegacyDataStorageInterface.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingManager.java similarity index 62% rename from api/src/main/java/net/momirealms/customfishing/api/data/LegacyDataStorageInterface.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingManager.java index 190206bc..803a3860 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/LegacyDataStorageInterface.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingManager.java @@ -15,19 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data; +package net.momirealms.customfishing.api.mechanic.fishing; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; -public interface LegacyDataStorageInterface extends DataStorageInterface { +public interface FishingManager extends Reloadable { - /** - * Retrieve legacy player data from the SQL database. - * - * @param uuid The UUID of the player. - * @return A CompletableFuture containing the optional legacy player data. - */ - CompletableFuture> getLegacyPlayerData(UUID uuid); + Optional getFishHook(Player player); + + Optional getFishHook(UUID player); + + Optional getOwner(FishHook hook); + + void destroy(UUID player); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/Icon.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingPreparation.java similarity index 88% rename from plugin/src/main/java/net/momirealms/customfishing/gui/Icon.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingPreparation.java index 7124f185..1b9825d3 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/Icon.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/FishingPreparation.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.gui; +package net.momirealms.customfishing.api.mechanic.fishing; -public interface Icon { +public class FishingPreparation { } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityLibrary.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/HookMechanic.java similarity index 65% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityLibrary.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/HookMechanic.java index 74f4358e..3b0c367b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntityLibrary.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/HookMechanic.java @@ -15,16 +15,25 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.entity; +package net.momirealms.customfishing.api.mechanic.fishing.hook; -import org.bukkit.Location; -import org.bukkit.entity.Entity; +import net.momirealms.customfishing.api.mechanic.effect.Effect; -import java.util.Map; +public interface HookMechanic +{ + boolean canStart(); -public interface EntityLibrary { + boolean shouldStop(); - String identification(); + void preStart(); - Entity spawn(Location location, String id, Map propertyMap); + void start(Effect finalEffect); + + boolean isHooked(); + + void destroy(); + + void freeze(); + + void unfreeze(Effect finalEffect); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/LavaFishingMechanic.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/LavaFishingMechanic.java new file mode 100644 index 00000000..543dd7c9 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/LavaFishingMechanic.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing.hook; + +import io.papermc.paper.block.fluid.FluidData; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingHookStateEvent; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectProperties; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.RandomUtils; +import org.bukkit.*; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.Vector; + +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; + +public class LavaFishingMechanic implements HookMechanic { + + private final FishHook hook; + private final Effect gearsEffect; + private final Context context; + private ArmorStand tempEntity; + private SchedulerTask task; + private int timeUntilLured; + private int timeUntilHooked; + private int nibble; + private boolean hooked; + private float fishAngle; + private int currentState; + private int jumpTimer; + private boolean firstTime = true; + private boolean freeze = false; + + public LavaFishingMechanic(FishHook hook, Effect gearsEffect, Context context) { + this.hook = hook; + this.gearsEffect = gearsEffect; + this.context = context; + } + + @Override + public boolean canStart() { + if (!(boolean) gearsEffect.properties().getOrDefault(EffectProperties.LAVA_FISHING, false)) { + return false; + } + if (hook.isInLava()) { + return true; + } + float lavaHeight = 0F; + FluidData fluidData = this.hook.getWorld().getFluidData(this.hook.getLocation()); + if (fluidData.getFluidType() == Fluid.LAVA || fluidData.getFluidType() == Fluid.FLOWING_LAVA) { + lavaHeight = (float) (fluidData.getLevel() * 0.125); + } + return lavaHeight > 0 && this.hook.getY() % 1 <= lavaHeight; + } + + @Override + public boolean shouldStop() { + if (hook.isInLava()) { + return false; + } + return hook.isOnGround() || (hook.getLocation().getBlock().getType() != Material.LAVA && hook.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() != Material.LAVA); + } + + @Override + public void preStart() { + this.context.arg(ContextKeys.SURROUNDING, EffectProperties.LAVA_FISHING.key()); + } + + @Override + public void start(Effect finalEffect) { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.LAND)); + this.setWaitTime(finalEffect); + this.task = BukkitCustomFishingPlugin.getInstance().getScheduler().sync().runRepeating(() -> { + float lavaHeight = 0F; + FluidData fluidData = this.hook.getWorld().getFluidData(this.hook.getLocation()); + if (fluidData.getFluidType() == Fluid.LAVA || fluidData.getFluidType() == Fluid.FLOWING_LAVA) { + lavaHeight = (float) (fluidData.getLevel() * 0.125); + } + if (this.nibble > 0) { + --this.nibble; + if (this.hook.getY() % 1 <= lavaHeight) { + this.jumpTimer++; + if (this.jumpTimer >= 4) { + this.jumpTimer = 0; + this.hook.setVelocity(new Vector(0,0.24,0)); + } + } + if (this.nibble <= 0) { + this.timeUntilLured = 0; + this.timeUntilHooked = 0; + this.hooked = false; + this.jumpTimer = 0; + this.currentState = 0; + } + } else { + if (this.hook.getY() % 1 <= lavaHeight || this.hook.isInLava()) { + Vector previousVector = this.hook.getVelocity(); + this.hook.setVelocity(new Vector(previousVector.getX() * 0.6, Math.min(0.1, Math.max(-0.1, previousVector.getY() + 0.1)), previousVector.getZ() * 0.6)); + this.currentState = 1; + } else { + if (currentState == 1) { + this.currentState = 0; + // set temp entity + this.tempEntity = this.hook.getWorld().spawn(this.hook.getLocation().clone().subtract(0,1,0), ArmorStand.class); + this.setTempEntityProperties(this.tempEntity); + this.hook.setHookedEntity(this.tempEntity); + if (!firstTime) { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.ESCAPE)); + } + firstTime = false; + } + } + float f; + float f1; + float f2; + double d0; + double d1; + double d2; + if (this.timeUntilHooked > 0) { + this.timeUntilHooked -= 1; + if (this.timeUntilHooked > 0) { + this.fishAngle += (float) RandomUtils.triangle(0.0D, 9.188D); + f = this.fishAngle * 0.017453292F; + f1 = (float) Math.sin(f); + f2 = (float) Math.cos(f); + d0 = hook.getX() + (double) (f1 * (float) this.timeUntilHooked * 0.1F); + d1 = hook.getY(); + d2 = hook.getZ() + (double) (f2 * (float) this.timeUntilHooked * 0.1F); + if (RandomUtils.generateRandomFloat(0,1) < 0.15F) { + hook.getWorld().spawnParticle(Particle.FLAME, d0, d1 - 0.10000000149011612D, d2, 1, f1, 0.1D, f2, 0.0D); + } + float f3 = f1 * 0.04F; + float f4 = f2 * 0.04F; + hook.getWorld().spawnParticle(Particle.FLAME, d0, d1, d2, 0, f4, 0.01D, -f3, 1.0D); + } else { + double d3 = hook.getY() + 0.5D; + hook.getWorld().spawnParticle(Particle.FLAME, hook.getX(), d3, hook.getZ(), (int) (1.0F + 0.3 * 20.0F), 0.3, 0.0D, 0.3, 0.20000000298023224D); + this.nibble = RandomUtils.generateRandomInt(20, 40); + this.hooked = true; + hook.getWorld().playSound(hook.getLocation(), Sound.ENTITY_GENERIC_EXTINGUISH_FIRE, 0.25F, 1.0F + (RandomUtils.generateRandomFloat(0,1)-RandomUtils.generateRandomFloat(0,1)) * 0.4F); + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.BITE)); + if (this.tempEntity != null && this.tempEntity.isValid()) { + this.tempEntity.remove(); + } + } + } else if (timeUntilLured > 0) { + if (!freeze) { + timeUntilLured--; + } + if (this.timeUntilLured <= 0) { + this.fishAngle = RandomUtils.generateRandomFloat(0F, 360F); + this.timeUntilHooked = RandomUtils.generateRandomInt(20, 80); + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.LURE)); + } + } else { + setWaitTime(finalEffect); + } + } + }, 1, 1, hook.getLocation()); + } + + @Override + public boolean isHooked() { + return hooked; + } + + @Override + public void destroy() { + if (this.tempEntity != null && this.tempEntity.isValid()) { + this.tempEntity.remove(); + } + if (this.task != null) { + this.task.cancel(); + } + freeze = false; + } + + @Override + public void freeze() { + freeze = true; + } + + @Override + public void unfreeze(Effect effect) { + freeze = false; + } + + private void setWaitTime(Effect effect) { + int before = ThreadLocalRandom.current().nextInt(ConfigManager.lavaMaxTime() - ConfigManager.lavaMinTime() + 1) + ConfigManager.lavaMinTime(); + int after = Math.max(ConfigManager.lavaMinTime(), (int) (before * effect.waitTimeMultiplier() + effect.waitTimeAdder())); + BukkitCustomFishingPlugin.getInstance().debug("Wait time: " + before + " -> " + after + " ticks"); + this.timeUntilLured = after; + } + + private void setTempEntityProperties(ArmorStand entity) { + entity.setInvisible(true); + entity.setCollidable(false); + entity.setInvulnerable(true); + entity.setVisible(false); + entity.setCustomNameVisible(false); + entity.setSmall(true); + entity.setGravity(false); + entity.getPersistentDataContainer().set( + Objects.requireNonNull(NamespacedKey.fromString("temp-entity", BukkitCustomFishingPlugin.getInstance().getBoostrap())), + PersistentDataType.STRING, + "lava" + ); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VanillaMechanic.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VanillaMechanic.java new file mode 100644 index 00000000..e3ff638a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VanillaMechanic.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing.hook; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingHookStateEvent; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectProperties; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; + +import java.util.concurrent.ThreadLocalRandom; + +public class VanillaMechanic implements HookMechanic { + + private final FishHook hook; + private final Context context; + private SchedulerTask task; + private boolean isHooked = false; + private int tempWaitTime; + + public VanillaMechanic(FishHook hook, Context context) { + this.hook = hook; + this.context = context; + } + + @Override + public boolean canStart() { + return hook.isInWater(); + } + + @Override + public boolean shouldStop() { + return hook.getState() != FishHook.HookState.BOBBING; + } + + @Override + public void preStart() { + this.context.arg(ContextKeys.SURROUNDING, EffectProperties.WATER_FISHING.key()); + } + + @Override + public void start(Effect finalEffect) { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.LAND)); + setWaitTime(hook, finalEffect); + this.task = BukkitCustomFishingPlugin.getInstance().getScheduler().sync().runRepeating(() -> { + if (isHooked) { + if (!isHooked()) { + isHooked = false; + setWaitTime(hook, finalEffect); + } + } else { + if (isHooked()) { + isHooked = true; + } + } + }, 1, 1, hook.getLocation()); + } + + private void setWaitTime(FishHook hook, Effect effect) { + BukkitCustomFishingPlugin.getInstance().getScheduler().sync().runLater(() -> { + if (!ConfigManager.overrideVanillaWaitTime()) { + int before = Math.max(hook.getWaitTime(), 0); + int after = (int) Math.max(100, before * effect.waitTimeMultiplier() + effect.waitTimeAdder()); + BukkitCustomFishingPlugin.getInstance().debug("Wait time: " + before + " -> " + after + " ticks"); + hook.setWaitTime(after); + } else { + int before = ThreadLocalRandom.current().nextInt(ConfigManager.waterMaxTime() - ConfigManager.waterMinTime() + 1) + ConfigManager.waterMinTime(); + int after = Math.max(ConfigManager.waterMinTime(), (int) (before * effect.waitTimeMultiplier() + effect.waitTimeAdder())); + hook.setWaitTime(after); + BukkitCustomFishingPlugin.getInstance().debug("Wait time: " + before + " -> " + after + " ticks"); + } + }, 1, hook.getLocation()); + } + + @Override + public boolean isHooked() { + return SparrowHeart.getInstance().isFishingHookBit(hook); + } + + @Override + public void destroy() { + if (this.task != null) this.task.cancel(); + this.tempWaitTime = 0; + } + + @Override + public void freeze() { + if (hook.getWaitTime() > 0) { + this.tempWaitTime = hook.getWaitTime(); + } + hook.setWaitTime(Integer.MAX_VALUE); + } + + @Override + public void unfreeze(Effect effect) { + if (this.tempWaitTime != 0) { + hook.setWaitTime(this.tempWaitTime); + this.tempWaitTime = 0; + } else { + setWaitTime(hook, effect); + } + } + + public void onBite() { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.BITE)); + } + + public void onFailedAttempt() { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.ESCAPE)); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VoidFishingMechanic.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VoidFishingMechanic.java new file mode 100644 index 00000000..fb5b6e8b --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/fishing/hook/VoidFishingMechanic.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.fishing.hook; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingHookStateEvent; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectProperties; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.RandomUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; + +public class VoidFishingMechanic implements HookMechanic { + + private final FishHook hook; + private final Effect gearsEffect; + private final Context context; + private ArmorStand tempEntity; + private SchedulerTask task; + private int timeUntilLured; + private int timeUntilHooked; + private int nibble; + private boolean hooked; + private float fishAngle; + private int timer; + private boolean freeze; + + public VoidFishingMechanic(FishHook hook, Effect gearsEffect, Context context) { + this.hook = hook; + this.gearsEffect = gearsEffect; + this.context = context; + } + + @Override + public boolean canStart() { + if (!(boolean) gearsEffect.properties().getOrDefault(EffectProperties.VOID_FISHING, false)) { + return false; + } + return hook.getLocation().getY() <= hook.getWorld().getMinHeight(); + } + + @Override + public boolean shouldStop() { + return hook.getLocation().getY() > hook.getWorld().getMinHeight(); + } + + @Override + public void preStart() { + this.context.arg(ContextKeys.SURROUNDING, EffectProperties.VOID_FISHING.key()); + } + + @Override + public void start(Effect finalEffect) { + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.LAND)); + this.setWaitTime(finalEffect); + this.tempEntity = hook.getWorld().spawn(hook.getLocation().clone().subtract(0,1,0), ArmorStand.class); + this.setTempEntityProperties(this.tempEntity); + this.hook.setHookedEntity(this.tempEntity); + this.task = BukkitCustomFishingPlugin.getInstance().getScheduler().sync().runRepeating(() -> { + timer++; + if (timer % 2 == 0) { + if (timer >= 16) timer = 0; + hook.getWorld().spawnParticle(Particle.END_ROD, hook.getX() + 0.5 * Math.cos(timer * 22.5D * 0.017453292F), hook.getY() - 0.15, hook.getZ() + 0.5 * Math.sin(timer * 22.5D * 0.017453292F), 0,0,0,0); + } + if (this.nibble > 0) { + --this.nibble; + if (this.nibble % 4 == 0) { + if (RandomUtils.generateRandomDouble(0, 1) < 0.5) { + hook.getWorld().spawnParticle(Particle.END_ROD, hook.getX(), hook.getY(), hook.getZ(), (int) (1.0F + 0.3 * 20.0F), 0.3, 0.0D, 0.3, 0.10000000298023224D); + } else { + hook.getWorld().spawnParticle(Particle.DRAGON_BREATH, hook.getX(), hook.getY(), hook.getZ(), (int) (1.0F + 0.3 * 20.0F), 0.3, 0.0D, 0.3, 0.10000000298023224D); + } + } + if (this.nibble <= 0) { + this.timeUntilLured = 0; + this.timeUntilHooked = 0; + this.hooked = false; + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.ESCAPE)); + } + } else { + float f; + float f1; + float f2; + double d0; + double d1; + double d2; + if (this.timeUntilHooked > 0) { + this.timeUntilHooked -= 1; + if (this.timeUntilHooked > 0) { + this.fishAngle += (float) RandomUtils.triangle(0.0D, 9.188D); + f = this.fishAngle * 0.017453292F; + f1 = (float) Math.sin(f); + f2 = (float) Math.cos(f); + d0 = hook.getX() + (double) (f1 * (float) this.timeUntilHooked * 0.1F); + d1 = hook.getY(); + d2 = hook.getZ() + (double) (f2 * (float) this.timeUntilHooked * 0.1F); + if (RandomUtils.generateRandomFloat(0,1) < 0.15F) { + hook.getWorld().spawnParticle(Particle.END_ROD, d0, d1 - 0.10000000149011612D, d2, 1, f1, 0.1D, f2, 0.0D); + } + float f3 = f1 * 0.04F; + float f4 = f2 * 0.04F; + hook.getWorld().spawnParticle(Particle.END_ROD, d0, d1, d2, 0, f4, 0.01D, -f3, 1.0D); + } else { + double d3 = hook.getY() + 0.5D; + hook.getWorld().spawnParticle(Particle.END_ROD, hook.getX(), d3, hook.getZ(), (int) (1.0F + 0.3 * 20.0F), 0.3, 0.0D, 0.3, 0.20000000298023224D); + this.nibble = RandomUtils.generateRandomInt(20, 40); + this.hooked = true; + hook.getWorld().playSound(hook.getLocation(), Sound.ITEM_TRIDENT_THUNDER, 0.25F, 1.0F + (RandomUtils.generateRandomFloat(0,1)-RandomUtils.generateRandomFloat(0,1)) * 0.4F); + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.BITE)); + } + } else if (timeUntilLured > 0) { + if (!freeze) { + timeUntilLured--; + } + f = 0.1F; + if (this.timeUntilLured < 20) { + f += (float) (20 - this.timeUntilLured) * 0.05F; + } else if (this.timeUntilLured < 40) { + f += (float) (40 - this.timeUntilLured) * 0.02F; + } else if (this.timeUntilLured < 60) { + f += (float) (60 - this.timeUntilLured) * 0.01F; + } + if (RandomUtils.generateRandomFloat(0, 1) < f) { + f1 = RandomUtils.generateRandomFloat(0.0F, 360.0F) * 0.017453292F; + f2 = RandomUtils.generateRandomFloat(25.0F, 60.0F); + d0 = hook.getX() + Math.sin(f1) * f2 * 0.1D; + d1 = hook.getY(); + d2 = hook.getZ() + Math.cos(f1) * f2 * 0.1D; + hook.getWorld().spawnParticle(Particle.DRAGON_BREATH, d0, d1, d2, 2 + RandomUtils.generateRandomInt(0,1), 0.10000000149011612D, 0.0D, 0.10000000149011612D, 0.0D); + } + if (this.timeUntilLured <= 0) { + this.fishAngle = RandomUtils.generateRandomFloat(0F, 360F); + this.timeUntilHooked = RandomUtils.generateRandomInt(20, 80); + EventUtils.fireAndForget(new FishingHookStateEvent(context.getHolder(), hook, FishingHookStateEvent.State.LURE)); + } + } else { + setWaitTime(finalEffect); + } + } + }, 1, 1, hook.getLocation()); + } + + @Override + public boolean isHooked() { + return hooked; + } + + @Override + public void destroy() { + if (this.tempEntity != null && this.tempEntity.isValid()) { + this.tempEntity.remove(); + } + if (this.task != null) { + this.task.cancel(); + } + freeze = false; + } + + @Override + public void freeze() { + freeze = true; + } + + @Override + public void unfreeze(Effect finalEffect) { + freeze = false; + } + + private void setWaitTime(Effect effect) { + int before = ThreadLocalRandom.current().nextInt(ConfigManager.voidMaxTime() - ConfigManager.voidMinTime() + 1) + ConfigManager.voidMinTime(); + int after = Math.max(ConfigManager.voidMinTime(), (int) (before * effect.waitTimeMultiplier() + effect.waitTimeAdder())); + BukkitCustomFishingPlugin.getInstance().debug("Wait time: " + before + " -> " + after + " ticks"); + this.timeUntilLured = after; + } + + private void setTempEntityProperties(ArmorStand entity) { + entity.setInvisible(true); + entity.setCollidable(false); + entity.setInvulnerable(true); + entity.setVisible(false); + entity.setCustomNameVisible(false); + entity.setSmall(true); + entity.setGravity(false); + entity.getPersistentDataContainer().set( + Objects.requireNonNull(NamespacedKey.fromString("temp-entity", BukkitCustomFishingPlugin.getInstance().getBoostrap())), + PersistentDataType.STRING, + "void" + ); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGame.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGame.java new file mode 100644 index 00000000..9a3e38e7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGame.java @@ -0,0 +1,29 @@ +package net.momirealms.customfishing.api.mechanic.game; + +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook; + +import java.util.function.BiFunction; + +public abstract class AbstractGame implements Game { + + private final GameBasics basics; + private final String id; + + public AbstractGame(String id, GameBasics basics) { + this.basics = basics; + this.id = id; + } + + @Override + public String id() { + return id; + } + + @Override + public GamingPlayer start(CustomFishingHook hook, Effect effect) { + return gamingPlayerProvider().apply(hook, basics.toGameSetting(effect)); + } + + public abstract BiFunction gamingPlayerProvider(); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGamingPlayer.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGamingPlayer.java index 3a163850..de4b258d 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGamingPlayer.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/AbstractGamingPlayer.java @@ -17,43 +17,46 @@ package net.momirealms.customfishing.api.mechanic.game; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.FishingManager; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Material; -import org.bukkit.entity.FishHook; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; import org.bukkit.entity.Player; -import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.ApiStatus; + +import java.util.concurrent.TimeUnit; public abstract class AbstractGamingPlayer implements GamingPlayer, Runnable { - private final FishingManager manager; protected long deadline; protected boolean success; - protected CancellableTask task; - protected Player player; - protected GameSettings settings; - protected FishHook fishHook; + protected SchedulerTask task; + protected GameSetting settings; + protected CustomFishingHook hook; protected boolean isTimeOut; + private boolean valid = true; + private boolean firstFlag = true; - public AbstractGamingPlayer(Player player, FishHook hook, GameSettings settings) { - this.player = player; - this.fishHook = hook; + public AbstractGamingPlayer(CustomFishingHook hook, GameSetting settings) { + this.hook = hook; this.settings = settings; - this.manager = CustomFishingPlugin.get().getFishingManager(); - this.deadline = (long) (System.currentTimeMillis() + settings.getTime() * 1000L); + this.deadline = (long) (System.currentTimeMillis() + settings.time() * 1000L); this.arrangeTask(); } public void arrangeTask() { - this.task = CustomFishingPlugin.get().getScheduler().runTaskSyncTimer(this, fishHook.getLocation(), 1, 1); + this.task = BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(this, 50, 50, TimeUnit.MILLISECONDS); + } + + @Override + public void destroy() { + if (task != null) task.cancel(); + valid = false; } @Override public void cancel() { - if (task != null && !task.isCancelled()) - task.cancel(); + destroy(); } @Override @@ -61,45 +64,53 @@ public abstract class AbstractGamingPlayer implements GamingPlayer, Runnable { return success; } + @ApiStatus.Internal + public void internalRightClick() { + firstFlag = true; + handleRightClick(); + } + @Override - public boolean onRightClick() { + public void handleRightClick() { endGame(); - return true; + } + + @ApiStatus.Internal + public boolean internalLeftClick() { + if (firstFlag) { + firstFlag = false; + return false; + } + return handleLeftClick(); } @Override - public boolean onLeftClick() { + public boolean handleLeftClick() { return false; } @Override - public boolean onChat(String message) { + public boolean handleChat(String message) { return false; } @Override - public boolean onSwapHand() { + public void handleSwapHand() { + } + + @Override + public boolean handleJump() { return false; } @Override - public boolean onJump() { - return false; - } - - @Override - public boolean onSneak() { + public boolean handleSneak() { return false; } @Override public Player getPlayer() { - return player; - } - - @Override - public Effect getEffectReward() { - return null; + return hook.getContext().getHolder(); } @Override @@ -107,16 +118,27 @@ public abstract class AbstractGamingPlayer implements GamingPlayer, Runnable { if (timeOutCheck()) { return; } - switchItemCheck(); - onTick(); + tick(); } - public void onTick() { - + @Override + public boolean isValid() { + return valid; } + protected abstract void tick(); + protected void endGame() { - this.manager.processGameResult(this); + destroy(); + boolean success = isSuccessful(); + BukkitCustomFishingPlugin.getInstance().getScheduler().sync().run(() -> { + if (success) { + hook.handleSuccessfulFishing(); + } else { + hook.handleFailedFishing(); + } + hook.end(); + }, hook.getHookEntity().getLocation()); } protected void setGameResult(boolean success) { @@ -124,22 +146,13 @@ public abstract class AbstractGamingPlayer implements GamingPlayer, Runnable { } protected boolean timeOutCheck() { - if (System.currentTimeMillis() > deadline) { + long delta = deadline - System.currentTimeMillis(); + if (delta <= 0) { isTimeOut = true; - cancel(); endGame(); return true; } + hook.getContext().arg(ContextKeys.TIME_LEFT, String.format("%.1f", (double) delta / 1000)); return false; } - - protected void switchItemCheck() { - PlayerInventory playerInventory = player.getInventory(); - if (playerInventory.getItemInMainHand().getType() != Material.FISHING_ROD - && playerInventory.getItemInOffHand().getType() != Material.FISHING_ROD - ) { - cancel(); - endGame(); - } - } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/BasicGameConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/BasicGameConfig.java deleted file mode 100644 index 5983cc79..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/BasicGameConfig.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.game; - -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.ThreadLocalRandom; - -public class BasicGameConfig { - - private int minTime; - private int maxTime; - private int minDifficulty; - private int maxDifficulty; - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private final BasicGameConfig basicGameConfig; - - public Builder() { - basicGameConfig = new BasicGameConfig(); - } - - @SuppressWarnings("UnusedReturnValue") - public Builder difficulty(int value) { - basicGameConfig.minDifficulty = (basicGameConfig.maxDifficulty = value); - return this; - } - - @SuppressWarnings("UnusedReturnValue") - public Builder difficulty(int min, int max) { - basicGameConfig.minDifficulty = min; - basicGameConfig.maxDifficulty = max; - return this; - } - - public Builder time(int value) { - basicGameConfig.minTime = (basicGameConfig.maxTime = value); - return this; - } - - public Builder time(int min, int max) { - basicGameConfig.minTime = min; - basicGameConfig.maxTime = max; - return this; - } - - public BasicGameConfig build() { - return basicGameConfig; - } - } - - /** - * Generates random game settings based on specified time and difficulty ranges, adjusted by an effect's difficulty modifier. - * - * @param effect The effect to adjust the difficulty. - * @return A {@link GameSettings} object representing the generated game settings. - */ - @Nullable - public GameSettings getGameSetting(Effect effect) { - return new GameSettings( - ThreadLocalRandom.current().nextInt(minTime, maxTime + 1) * effect.getGameTimeMultiplier() + effect.getGameTime(), - (int) Math.min(100, Math.max(1, ThreadLocalRandom.current().nextInt(minDifficulty, maxDifficulty + 1) * effect.getDifficultyMultiplier() + effect.getDifficulty())) - ); - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameInstance.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/Game.java similarity index 75% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameInstance.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/game/Game.java index cca32618..4e17e272 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameInstance.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/Game.java @@ -17,10 +17,12 @@ package net.momirealms.customfishing.api.mechanic.game; -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook; -public interface GameInstance { +public interface Game { + + String id(); - GamingPlayer start(Player player, FishHook hook, GameSettings settings); + GamingPlayer start(CustomFishingHook hook, Effect effect); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasics.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasics.java new file mode 100644 index 00000000..d3f4cc09 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasics.java @@ -0,0 +1,35 @@ +package net.momirealms.customfishing.api.mechanic.game; + +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import org.jetbrains.annotations.NotNull; + +public interface GameBasics { + + int minTime(); + + int maxTime(); + + int minDifficulty(); + + int maxDifficulty(); + + static GameBasics.Builder builder() { + return new GameBasicsImpl.BuilderImpl(); + } + + @NotNull + GameSetting toGameSetting(Effect effect); + + interface Builder { + + Builder difficulty(int value); + + Builder difficulty(int min, int max); + + Builder time(int value); + + Builder time(int min, int max); + + GameBasics build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasicsImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasicsImpl.java new file mode 100644 index 00000000..20f51bc0 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameBasicsImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.game; + +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.common.util.RandomUtils; +import org.jetbrains.annotations.NotNull; + +public record GameBasicsImpl(int minTime, int maxTime, int minDifficulty, int maxDifficulty) implements GameBasics { + + public static class BuilderImpl implements Builder { + private int minTime; + private int maxTime; + private int minDifficulty; + private int maxDifficulty; + @Override + public Builder difficulty(int value) { + minDifficulty = (maxDifficulty = value); + return this; + } + @Override + public Builder difficulty(int min, int max) { + minDifficulty = min; + maxDifficulty = max; + return this; + } + @Override + public Builder time(int value) { + minTime = (maxTime = value); + return this; + } + @Override + public Builder time(int min, int max) { + minTime = min; + maxTime = max; + return this; + } + @Override + public GameBasics build() { + return new GameBasicsImpl(minTime, maxTime, minDifficulty, maxDifficulty); + } + } + + @Override + @NotNull + public GameSetting toGameSetting(Effect effect) { + return new GameSetting( + RandomUtils.generateRandomInt(minTime, maxTime) * effect.gameTimeMultiplier() + effect.gameTimeAdder(), + (int) Math.min(100, Math.max(1, RandomUtils.generateRandomInt(minDifficulty, maxDifficulty) * effect.difficultyMultiplier() + effect.difficultyAdder())) + ); + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameFactory.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameFactory.java index aad604a8..6e1c6892 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameFactory.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameFactory.java @@ -17,10 +17,9 @@ package net.momirealms.customfishing.api.mechanic.game; -import org.bukkit.configuration.ConfigurationSection; +import dev.dejvokep.boostedyaml.block.implementation.Section; public interface GameFactory { - GameInstance setArgs(ConfigurationSection section); - + Game create(String id, Section section); } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/GameManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameManager.java similarity index 57% rename from api/src/main/java/net/momirealms/customfishing/api/manager/GameManager.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameManager.java index 305a35af..38893364 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/GameManager.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameManager.java @@ -15,18 +15,17 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.manager; +package net.momirealms.customfishing.api.mechanic.game; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.game.BasicGameConfig; -import net.momirealms.customfishing.api.mechanic.game.GameFactory; -import net.momirealms.customfishing.api.mechanic.game.GameInstance; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; +import java.util.Optional; -public interface GameManager { +public interface GameManager extends Reloadable { /** * Registers a new game type with the specified type identifier. @@ -51,22 +50,13 @@ public interface GameManager { * @param type The type identifier of the game. * @return The {@code GameFactory} for the specified game type, or {@code null} if not found. */ - @Nullable GameFactory getGameFactory(String type); + @Nullable + GameFactory getGameFactory(String type); - /** - * Retrieves a game instance and its basic configuration associated with the specified key. - * - * @param key The key identifying the game instance. - * @return An {@code Optional} containing a {@code Pair} of the basic game configuration and the game instance - * if found, or an empty {@code Optional} if not found. - */ - @Nullable Pair getGameInstance(String key); + Optional getGame(String id); - /** - * Retrieves a map of game names and their associated weights based on the specified conditions. - * - * @param condition The condition to evaluate game weights. - * @return A {@code HashMap} containing game names as keys and their associated weights as values. - */ - HashMap getGameWithWeight(Condition condition); + boolean registerGame(Game game); + + @Nullable + Game getNextGame(Effect effect, Context context); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/YamlPage.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSetting.java similarity index 85% rename from plugin/src/main/java/net/momirealms/customfishing/gui/YamlPage.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSetting.java index 53e7deba..f9b5468a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/YamlPage.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSetting.java @@ -15,9 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.gui; +package net.momirealms.customfishing.api.mechanic.game; -public interface YamlPage extends ParentPage { - - void save(); +public record GameSetting(double time, int difficulty) { } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GamingPlayer.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GamingPlayer.java index 4ca24a3c..8971b8fa 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GamingPlayer.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GamingPlayer.java @@ -17,53 +17,29 @@ package net.momirealms.customfishing.api.mechanic.game; -import net.momirealms.customfishing.api.mechanic.effect.Effect; import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; public interface GamingPlayer { - /** - * Cancel the game - */ + boolean isValid(); + + void destroy(); + void cancel(); boolean isSuccessful(); - /** - * @return whether to cancel the event - */ - boolean onRightClick(); + void handleRightClick(); - /** - * @return whether to cancel the event - */ - boolean onSwapHand(); + void handleSwapHand(); - /** - * @return whether to cancel the event - */ - boolean onLeftClick(); + boolean handleLeftClick(); - /** - * @return whether to cancel the event - */ - boolean onChat(String message); + boolean handleChat(String message); - /** - * @return whether to cancel the event - */ - boolean onJump(); + boolean handleJump(); - /** - * @return whether to cancel the event - */ - boolean onSneak(); + boolean handleSneak(); Player getPlayer(); - - /** - * @return effect reward based on game results - */ - @Nullable Effect getEffectReward(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfig.java new file mode 100644 index 00000000..3259c2f4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfig.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.hook; + +import java.util.List; + +public interface HookConfig { + + String id(); + + List lore(); + + static Builder builder() { + return new HookConfigImpl.BuilderImpl(); + } + + interface Builder { + + Builder id(String id); + + Builder lore(List lore); + + HookConfig build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookSetting.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfigImpl.java similarity index 52% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookSetting.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfigImpl.java index 9c4222f0..284b469d 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookSetting.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookConfigImpl.java @@ -17,51 +17,47 @@ package net.momirealms.customfishing.api.mechanic.hook; -import java.util.ArrayList; import java.util.List; -public class HookSetting { +public class HookConfigImpl implements HookConfig { - private final String key; - private int maxDurability; - private List lore; + private final String id; + private final int maxDurability; + private final List lore; - public HookSetting(String key) { - this.key = key; + public HookConfigImpl(String id, int maxDurability, List lore) { + this.id = id; + this.maxDurability = maxDurability; + this.lore = lore; } - public String getKey() { - return key; + @Override + public String id() { + return id; } - public int getMaxDurability() { - return maxDurability; + @Override + public List lore() { + return lore; } - public List getLore() { - return lore == null ? new ArrayList<>() : lore; - } - - public static class Builder { - - private final HookSetting setting; - - public Builder(String key) { - this.setting = new HookSetting(key); - } - - public Builder durability(int maxDurability) { - setting.maxDurability = maxDurability; + public static class BuilderImpl implements Builder { + private String id; + private int maxDurability; + private List lore; + @Override + public Builder id(String id) { + this.id = id; return this; } - + @Override public Builder lore(List lore) { - setting.lore = lore; + this.lore = lore; return this; } - - public HookSetting build() { - return setting; + @Override + public HookConfig build() { + return new HookConfigImpl(id, maxDurability, lore); } } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookManager.java new file mode 100644 index 00000000..a58ca2f6 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/hook/HookManager.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.hook; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public interface HookManager extends Reloadable { + + boolean registerHook(HookConfig hook); + + @NotNull + Optional getHook(String id); + + Optional getHookID(ItemStack rod); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItem.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItem.java new file mode 100644 index 00000000..d9a3c997 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItem.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.function.PriorityFunction; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.function.BiConsumer; + +public interface CustomFishingItem { + + String DEFAULT_MATERIAL = "PAPER"; + + /** + * Returns the material type of the custom fishing item. + * + * @return the material type as a String. + */ + String material(); + + String id(); + + /** + * Returns a list of tag consumers which are functions that take an item and context as parameters + * and perform some operation on them. + * + * @return a list of BiConsumer instances. + */ + List, Context>> tagConsumers(); + + default ItemStack build(Context context) { + return BukkitCustomFishingPlugin.getInstance().getItemManager().build(context, this); + } + + /** + * Creates a new Builder instance to construct a CustomFishingItem. + * + * @return a new Builder instance. + */ + static Builder builder() { + return new CustomFishingItemImpl.BuilderImpl(); + } + + /** + * Builder interface for constructing instances of CustomFishingItem. + */ + interface Builder { + + Builder id(String id); + + /** + * Sets the material type for the CustomFishingItem being built. + * + * @param material the material type as a String. + * @return the Builder instance for method chaining. + */ + Builder material(String material); + + /** + * Sets the list of tag consumers for the CustomFishingItem being built. + * + * @param tagConsumers a list of BiConsumer instances. + * @return the Builder instance for method chaining. + */ + Builder tagConsumers(List, Context>>> tagConsumers); + + /** + * Builds and returns a new CustomFishingItem instance. + * + * @return a new CustomFishingItem instance. + */ + CustomFishingItem build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItemImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItemImpl.java new file mode 100644 index 00000000..362a2af2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/CustomFishingItemImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item; + +import net.momirealms.customfishing.api.mechanic.config.function.PriorityFunction; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; +import java.util.TreeSet; +import java.util.function.BiConsumer; + +import static java.util.Objects.requireNonNull; + +public class CustomFishingItemImpl implements CustomFishingItem { + + private final String material; + private final String id; + + private final List, Context>> tagConsumers; + + public CustomFishingItemImpl(String id, String material, List, Context>> tagConsumers) { + this.material = material; + this.id = id; + this.tagConsumers = tagConsumers; + } + + @Override + public String material() { + return Optional.ofNullable(material).orElse("AIR"); + } + + @Override + public String id() { + return id; + } + + @Override + public List, Context>> tagConsumers() { + return tagConsumers; + } + + public static class BuilderImpl implements Builder { + + private String material = DEFAULT_MATERIAL; + private String id; + private final TreeSet, Context>>> tagConsumers = new TreeSet<>(); + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder material(String material) { + this.material = material; + return this; + } + + @Override + public Builder tagConsumers(List, Context>>> tagConsumers) { + this.tagConsumers.addAll(tagConsumers); + return this; + } + + @Override + public CustomFishingItem build() { + return new CustomFishingItemImpl(requireNonNull(id), material, tagConsumers.stream().map(PriorityFunction::get).toList()); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemBuilder.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemBuilder.java deleted file mode 100644 index 97d3e4fb..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemBuilder.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.item; - -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.common.Tuple; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public interface ItemBuilder extends BuildableItem { - - ItemBuilder customModelData(int value); - - ItemBuilder name(String name); - - ItemBuilder amount(int amount); - - ItemBuilder amount(int min_amount, int max_amount); - - ItemBuilder tag(boolean tag, String type, String id); - - ItemBuilder unbreakable(boolean unbreakable); - - ItemBuilder placeable(boolean placeable); - - ItemBuilder lore(List lore); - - ItemBuilder nbt(Map nbt); - - ItemBuilder itemFlag(List itemFlags); - - ItemBuilder nbt(ConfigurationSection section); - - ItemBuilder enchantment(List> enchantments, boolean store); - - ItemBuilder randomEnchantments(List> enchantments, boolean store); - - ItemBuilder enchantmentPool(List> amountPairs, List, Value>> enchantments, boolean store); - - ItemBuilder maxDurability(int max); - - ItemBuilder price(float base, float bonus); - - ItemBuilder size(Pair size); - - ItemBuilder stackable(boolean stackable); - - ItemBuilder preventGrabbing(boolean prevent); - - ItemBuilder head(String base64); - - ItemBuilder randomDamage(boolean damage); - - @NotNull - String getId(); - - @NotNull - String getLibrary(); - - int getAmount(); - - Collection getEditors(); - - ItemBuilder removeEditor(String type); - - ItemBuilder registerCustomEditor(String type, ItemPropertyEditor editor); - - interface ItemPropertyEditor { - - void edit(Player player, NBTItem nbtItem, Map placeholders); - - default void edit(Player player, NBTItem nbtItem) { - edit(player, nbtItem, null); - } - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemLibrary.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemEditor.java similarity index 79% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemLibrary.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemEditor.java index a4419a9b..902de141 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemLibrary.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemEditor.java @@ -17,14 +17,12 @@ package net.momirealms.customfishing.api.mechanic.item; +import com.saicone.rtag.RtagItem; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -public interface ItemLibrary { +@FunctionalInterface +public interface ItemEditor { - String identification(); - - ItemStack buildItem(Player player, String id); - - String getItemID(ItemStack itemStack); + void apply(RtagItem item, Context context); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemManager.java new file mode 100644 index 00000000..cd1bac7c --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/ItemManager.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item; + +import com.saicone.rtag.RtagItem; +import net.momirealms.customfishing.api.integration.ItemProvider; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.item.ItemFactory; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public interface ItemManager extends Reloadable { + + boolean registerItem(@NotNull CustomFishingItem item); + + @Nullable + ItemStack buildInternal(@NotNull Context context, @NotNull String id) throws NullPointerException; + + ItemStack build(@NotNull Context context, @NotNull CustomFishingItem item); + + @Nullable + ItemStack buildAny(@NotNull Context context, @NotNull String id); + + @NotNull + String getItemID(@NotNull ItemStack itemStack); + + @Nullable + String getCustomFishingItemID(@NotNull ItemStack itemStack); + + @Nullable + Item dropItemLoot(@NotNull Context context, ItemStack rod, FishHook hook); + + boolean hasCustomDurability(ItemStack itemStack); + + void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean incorrectUsage); + + void setDurability(Player player, ItemStack itemStack, int damage); + + ItemFactory getFactory(); + + ItemProvider[] getItemProviders(); + + Collection getItemIDs(); + + net.momirealms.customfishing.common.item.Item wrap(ItemStack itemStack); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMap.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMap.java new file mode 100644 index 00000000..e0537e08 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMap.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item.tag; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.entity.Player; + +import java.util.Map; + +public interface TagMap { + + Map apply(Context context); + + static TagMap of(Map inputMap) { + return new TagMapImpl(inputMap); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMapImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMapImpl.java new file mode 100644 index 00000000..5a608ece --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagMapImpl.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item.tag; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.*; + +import static net.momirealms.customfishing.api.util.TagUtils.toTypeAndData; +import static net.momirealms.customfishing.common.util.ArrayUtils.splitValue; + +public class TagMapImpl implements TagMap { + + private final Map convertedMap = new HashMap<>(); + + public TagMapImpl(Map inputMap) { + this.analyze(inputMap, convertedMap); + } + + private void analyze(Map inputMap, Map outPutMap) { + for (Map.Entry entry : inputMap.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Map inner) { + Map inputInnerMap = (Map) inner; + HashMap outputInnerMap = new HashMap<>(); + outPutMap.put(key, outputInnerMap); + analyze(inputInnerMap, outputInnerMap); + } else if (value instanceof List list) { + Object first = list.get(0); + ArrayList outputList = new ArrayList<>(); + if (first instanceof Map) { + for (Object o : list) { + Map inputListMap = (Map) o; + outputList.add(TagMap.of(inputListMap)); + } + } else if (first instanceof String) { + for (Object o : list) { + String str = (String) o; + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case STRING -> { + TextValue textValue = TextValue.auto(pair.right()); + outputList.add((ValueProvider) textValue::render); + } + case BYTE -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (byte) mathValue.evaluate(context)); + } + case SHORT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (short) mathValue.evaluate(context)); + } + case INT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (int) mathValue.evaluate(context)); + } + case LONG -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (long) mathValue.evaluate(context)); + } + case FLOAT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (float) mathValue.evaluate(context)); + } + case DOUBLE -> { + MathValue mathValue = MathValue.auto(pair.right()); + outputList.add((ValueProvider) context -> (double) mathValue.evaluate(context)); + } + } + } + } else { + outputList.addAll(list); + } + outPutMap.put(key, outputList); + } else if (value instanceof String str) { + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case INTARRAY -> { + String[] split = splitValue(str); + int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); + outPutMap.put(pair.right(), array); + } + case BYTEARRAY -> { + String[] split = splitValue(str); + byte[] bytes = new byte[split.length]; + for (int i = 0; i < split.length; i++){ + bytes[i] = Byte.parseByte(split[i]); + } + outPutMap.put(pair.right(), bytes); + } + case STRING -> { + TextValue textValue = TextValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) textValue::render); + } + case BYTE -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (byte) mathValue.evaluate(context)); + } + case SHORT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (short) mathValue.evaluate(context)); + } + case INT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (int) mathValue.evaluate(context)); + } + case LONG -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (long) mathValue.evaluate(context)); + } + case FLOAT -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (float) mathValue.evaluate(context)); + } + case DOUBLE -> { + MathValue mathValue = MathValue.auto(pair.right()); + outPutMap.put(key, (ValueProvider) context -> (double) mathValue.evaluate(context)); + } + } + } else { + outPutMap.put(key, value); + } + } + } + + @Override + public Map apply(Context context) { + HashMap output = new HashMap<>(); + setMapValue(convertedMap, output, context); + return output; + } + + private void setMapValue(Map inputMap, Map outPutMap, Context context) { + for (Map.Entry entry : inputMap.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Map inner) { + Map inputInnerMap = (Map) inner; + HashMap outputInnerMap = new HashMap<>(); + outPutMap.put(key, outputInnerMap); + setMapValue(inputInnerMap, outputInnerMap, context); + } else if (value instanceof List list) { + ArrayList convertedList = new ArrayList<>(); + Object first = list.get(0); + if (first instanceof TagMap) { + for (Object o : list) { + TagMap map = (TagMap) o; + convertedList.add(map.apply(context)); + } + } else if (first instanceof ValueProvider) { + for (Object o : list) { + ValueProvider pd = (ValueProvider) o; + convertedList.add(pd.apply(context)); + } + } else { + convertedList.addAll(list); + } + outPutMap.put(key, convertedList); + } else if (value instanceof ValueProvider provider) { + outPutMap.put(key, provider.apply(context)); + } else { + outPutMap.put(key, value); + } + } + } + + @FunctionalInterface + public interface ValueProvider { + Object apply(Context context); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagValueType.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagValueType.java new file mode 100644 index 00000000..bacfa32d --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/tag/TagValueType.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.item.tag; + +public enum TagValueType { + BYTE, + INT, + DOUBLE, + LONG, + FLOAT, + SHORT, + STRING, + BYTEARRAY, + INTARRAY +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/CFLoot.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/CFLoot.java deleted file mode 100644 index 0b7419a3..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/CFLoot.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.loot; - -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.BaseEffect; -import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKey; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; - -public class CFLoot implements Loot { - - private final String id; - private final LootType type; - private final HashMap actionMap; - private final HashMap successTimesActionMap; - private String nick; - private boolean showInFinder; - private boolean disableGame; - private boolean disableGlobalAction; - private boolean disableStats; - private boolean instanceGame; - private double score; - private String[] lootGroup; - private StatisticsKey statisticsKey; - private BaseEffect effect; - - public CFLoot(String id, LootType type) { - this.id = id; - this.type = type; - this.actionMap = new HashMap<>(); - this.successTimesActionMap = new HashMap<>(); - } - - public static Builder builder(String id, LootType type) { - return new Builder(id, type); - } - - /** - * Builder class for CFLoot. - */ - public static class Builder { - - private final CFLoot loot; - - public Builder(String id, LootType type) { - this.loot = new CFLoot(id, type); - } - - /** - * Set the file path for this loot. - * - * @param path file path - * @return The builder. - */ - public Builder filePath(String path) { - return this; - } - - /** - * Set the nickname for this loot. - * - * @param nick The nickname. - * @return The builder. - */ - public Builder nick(String nick) { - this.loot.nick = nick; - return this; - } - - /** - * Set whether this loot should be shown in the finder. - * - * @param show True if it should be shown, false otherwise. - * @return The builder. - */ - public Builder showInFinder(boolean show) { - this.loot.showInFinder = show; - return this; - } - - /** - * Set whether this loot should have an instance game. - * - * @param instant True if it should be an instance game, false otherwise. - * @return The builder. - */ - public Builder instantGame(boolean instant) { - this.loot.instanceGame = instant; - return this; - } - - /** - * Set whether games are disabled for this loot. - * - * @param disable True if games are disabled, false otherwise. - * @return The builder. - */ - public Builder disableGames(boolean disable) { - this.loot.disableGame = disable; - return this; - } - - /** - * Set whether statistics are disabled for this loot. - * - * @param disable True if statistics are disabled, false otherwise. - * @return The builder. - */ - public Builder disableStats(boolean disable) { - this.loot.disableStats = disable; - return this; - } - - /** - * Set whether global actions are disabled for this loot. - * - * @param disable True if statistics are disabled, false otherwise. - * @return The builder. - */ - public Builder disableGlobalActions(boolean disable) { - this.loot.disableGlobalAction = disable; - return this; - } - - /** - * Set the score for this loot. - * - * @param score The score. - * @return The builder. - */ - public Builder score(double score) { - this.loot.score = score; - return this; - } - - /** - * Set the loot group for this loot. - * - * @param groups The loot group. - * @return The builder. - */ - public Builder lootGroup(String[] groups) { - this.loot.lootGroup = groups; - return this; - } - - /** - * Set the statistics key for this loot - * - * @param statisticsKey statistics key - * @return The builder. - */ - public Builder statsKey(StatisticsKey statisticsKey) { - this.loot.statisticsKey = statisticsKey; - return this; - } - - /** - * Set the effects for the loot - * - * @param effect effect - * @return The builder. - */ - public Builder baseEffect(BaseEffect effect) { - this.loot.effect = effect; - return this; - } - - /** - * Add actions triggered by a specific trigger. - * - * @param trigger The trigger for the actions. - * @param actions The actions to add. - * @return The builder. - */ - public Builder addActions(ActionTrigger trigger, Action[] actions) { - this.loot.actionMap.put(trigger, actions); - return this; - } - - /** - * Add actions triggered by multiple triggers. - * - * @param actionMap A map of triggers to actions. - * @return The builder. - */ - public Builder addActions(HashMap actionMap) { - this.loot.actionMap.putAll(actionMap); - return this; - } - - /** - * Add actions triggered by the number of successes. - * - * @param times The number of successes for triggering the actions. - * @param actions The actions to add. - * @return The builder. - */ - public Builder addTimesActions(int times, Action[] actions) { - this.loot.successTimesActionMap.put(times, actions); - return this; - } - - /** - * Add actions triggered by multiple numbers of successes. - * - * @param actionMap A map of numbers of successes to actions. - * @return The builder. - */ - public Builder addTimesActions(HashMap actionMap) { - this.loot.successTimesActionMap.putAll(actionMap); - return this; - } - - /** - * Build the CFLoot object. - * - * @return The built CFLoot object. - */ - public CFLoot build() { - return loot; - } - } - - @Override - public boolean instanceGame() { - return this.instanceGame; - } - - @Override - public String getID() { - return this.id; - } - - @Override - public LootType getType() { - return this.type; - } - - @Override - public @NotNull String getNick() { - return this.nick; - } - - @Override - public StatisticsKey getStatisticKey() { - return this.statisticsKey; - } - - @Override - public boolean showInFinder() { - return this.showInFinder; - } - - @Override - public double getScore() { - return this.score; - } - - @Override - public boolean disableGame() { - return this.disableGame; - } - - @Override - public boolean disableStats() { - return this.disableStats; - } - - @Override - public boolean disableGlobalAction() { - return this.disableGlobalAction; - } - - @Override - public String[] getLootGroup() { - return lootGroup; - } - - @Override - public Action[] getActions(ActionTrigger actionTrigger) { - return actionMap.get(actionTrigger); - } - - @Override - public void triggerActions(ActionTrigger actionTrigger, Condition condition) { - Action[] actions = getActions(actionTrigger); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } - - @Override - public BaseEffect getBaseEffect() { - return effect; - } - - @Override - public Action[] getSuccessTimesActions(int times) { - return successTimesActionMap.get(times); - } - - @Override - public HashMap getSuccessTimesActionMap() { - return successTimesActionMap; - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/Loot.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/Loot.java index 020c4991..c8dc965b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/Loot.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/Loot.java @@ -17,67 +17,30 @@ package net.momirealms.customfishing.api.mechanic.loot; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.BaseEffect; -import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKey; +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKeys; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; public interface Loot { + class DefaultProperties { + public static boolean DEFAULT_INSTANT_GAME = false; + public static boolean DEFAULT_DISABLE_GAME = false; + public static boolean DEFAULT_DISABLE_STATS = false; + public static boolean DEFAULT_SHOW_IN_FINDER = true; + } + + LootType DEFAULT_TYPE = LootType.ITEM; + MathValue DEFAULT_SCORE = MathValue.plain(0); + /** - * Check if this loot has an instance game. + * Check if this loot triggers an instant game. * - * @return True if it's an instance game, false otherwise. + * @return True if it triggers an instant game, false otherwise. */ - boolean instanceGame(); - - /** - * Check if the loot disables global actions - */ - boolean disableGlobalAction(); - - /** - * Get the unique ID of this loot. - * - * @return The unique ID. - */ - String getID(); - - /** - * Get the type of this loot. - * - * @return The loot type. - */ - LootType getType(); - - /** - * Get the nickname of this loot. - * - * @return The nickname. - */ - @NotNull - String getNick(); - - StatisticsKey getStatisticKey(); - - /** - * Check if this loot should be shown in the finder. - * - * @return True if it should be shown, false otherwise. - */ - boolean showInFinder(); - - /** - * Get the score of this loot. - * - * @return The score. - */ - double getScore(); + boolean instantGame(); /** * Check if games are disabled for this loot. @@ -87,55 +50,180 @@ public interface Loot { boolean disableGame(); /** - * Check if statistics are disabled for this loot. + * Check if statistics recording is disabled for this loot. * * @return True if statistics are disabled, false otherwise. */ boolean disableStats(); /** - * Get the loot group of this loot. + * Check if this loot should be displayed in the finder tool. * - * @return The loot group. + * @return True if it should be shown in the finder, false otherwise. */ - String[] getLootGroup(); + boolean showInFinder(); + + boolean preventGrabbing(); /** - * Get the actions triggered by a specific action trigger. + * Get the unique identifier for this loot. * - * @param actionTrigger The action trigger. - * @return The actions triggered by the given trigger. + * @return The unique ID of the loot. */ - @Nullable - Action[] getActions(ActionTrigger actionTrigger); + String id(); /** - * Trigger actions associated with a specific action trigger. + * Get the type of this loot. * - * @param actionTrigger The action trigger. - * @param condition The condition under which the actions are triggered. + * @return The type of the loot. */ - void triggerActions(ActionTrigger actionTrigger, Condition condition); + LootType type(); /** - * Get effects that bond to this loot + * Get the display nickname for this loot. * - * @return effects + * @return The nickname of the loot. */ - BaseEffect getBaseEffect(); + @NotNull + String nick(); /** - * Get the actions triggered by a specific number of successes. + * Get the statistics key associated with this loot. * - * @param times The number of successes. - * @return The actions triggered by the specified number of successes. + * @return The statistics key for this loot. */ - Action[] getSuccessTimesActions(int times); + StatisticsKeys statisticKey(); /** - * Get a map of actions triggered by different numbers of successes. + * Get the score value for this loot. * - * @return A map of actions triggered by success times. + * @return The score associated with the loot. */ - HashMap getSuccessTimesActionMap(); + MathValue score(); + + /** + * Get the groups this loot belongs to. + * + * @return An array of group names. + */ + String[] lootGroup(); + + /** + * Get the base effect associated with this loot. + * + * @return The base effect for the loot. + */ + LootBaseEffect baseEffect(); + + /** + * Create a new builder for constructing a Loot instance. + * + * @return A new Loot builder. + */ + static Builder builder() { + return new LootImpl.BuilderImpl(); + } + + /** + * Builder interface for constructing instances of Loot. + */ + interface Builder { + + /** + * Set the type of the loot. + * + * @param type The type of the loot. + * @return The builder instance. + */ + Builder type(LootType type); + + /** + * Specify whether the loot triggers an instant game. + * + * @param instantGame True if it should trigger an instant game. + * @return The builder instance. + */ + Builder instantGame(boolean instantGame); + + /** + * Specify whether games are disabled for this loot. + * + * @param disableGame True if games should be disabled. + * @return The builder instance. + */ + Builder disableGame(boolean disableGame); + + Builder preventGrabbing(boolean preventGrabbing); + + /** + * Specify whether statistics recording is disabled for this loot. + * + * @param disableStatistics True if statistics should be disabled. + * @return The builder instance. + */ + Builder disableStatistics(boolean disableStatistics); + + /** + * Specify whether the loot should be shown in the finder tool. + * + * @param showInFinder True if it should be shown in the finder. + * @return The builder instance. + */ + Builder showInFinder(boolean showInFinder); + + /** + * Set the unique ID for the loot. + * + * @param id The unique identifier. + * @return The builder instance. + */ + Builder id(String id); + + /** + * Set the nickname for the loot. + * + * @param nick The nickname. + * @return The builder instance. + */ + Builder nick(String nick); + + /** + * Set the statistics key for the loot. + * + * @param statisticsKeys The statistics key. + * @return The builder instance. + */ + Builder statisticsKeys(StatisticsKeys statisticsKeys); + + /** + * Set the score for the loot. + * + * @param score The score value. + * @return The builder instance. + */ + Builder score(MathValue score); + + /** + * Set the groups that the loot belongs to. + * + * @param groups An array of group names. + * @return The builder instance. + */ + Builder groups(String[] groups); + + /** + * Set the base effect for the loot. + * + * @param lootBaseEffect The base effect. + * @return The builder instance. + */ + Builder lootBaseEffect(LootBaseEffect lootBaseEffect); + + /** + * Build and return the Loot instance. + * + * @return The constructed Loot instance. + */ + Loot build(); + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootImpl.java new file mode 100644 index 00000000..093d75f2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootImpl.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.loot; + +import net.momirealms.customfishing.api.mechanic.effect.LootBaseEffect; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKeys; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class LootImpl implements Loot { + + private final LootType type; + private final boolean instantGame; + private final boolean disableGame; + private final boolean disableStatistics; + private final boolean showInFinder; + private final boolean preventGrabbing; + private final String id; + private final String nick; + private final StatisticsKeys statisticsKeys; + private final MathValue score; + private final String[] groups; + private final LootBaseEffect lootBaseEffect; + + public LootImpl(LootType type, boolean instantGame, boolean disableGame, boolean disableStatistics, boolean showInFinder, boolean preventGrabbing, String id, String nick, StatisticsKeys statisticsKeys, MathValue score, String[] groups, LootBaseEffect lootBaseEffect) { + this.type = type; + this.instantGame = instantGame; + this.disableGame = disableGame; + this.disableStatistics = disableStatistics; + this.showInFinder = showInFinder; + this.id = id; + this.nick = nick; + this.statisticsKeys = statisticsKeys; + this.score = score; + this.groups = groups; + this.lootBaseEffect = lootBaseEffect; + this.preventGrabbing = preventGrabbing; + } + + @Override + public boolean instantGame() { + return instantGame; + } + + @Override + public String id() { + return id; + } + + @Override + public LootType type() { + return type; + } + + @NotNull + @Override + public String nick() { + return nick; + } + + @Override + public StatisticsKeys statisticKey() { + return statisticsKeys; + } + + @Override + public boolean showInFinder() { + return showInFinder; + } + + @Override + public boolean preventGrabbing() { + return preventGrabbing; + } + + @Override + public MathValue score() { + return score; + } + + @Override + public boolean disableGame() { + return disableGame; + } + + @Override + public boolean disableStats() { + return disableStatistics; + } + + @Override + public String[] lootGroup() { + return groups; + } + + @Override + public LootBaseEffect baseEffect() { + return lootBaseEffect; + } + + public static class BuilderImpl implements Builder { + + private LootType type = DEFAULT_TYPE; + private boolean instantGame = Loot.DefaultProperties.DEFAULT_INSTANT_GAME; + private boolean disableGame = Loot.DefaultProperties.DEFAULT_DISABLE_GAME; + private boolean disableStatistics = Loot.DefaultProperties.DEFAULT_DISABLE_STATS; + private boolean showInFinder = Loot.DefaultProperties.DEFAULT_SHOW_IN_FINDER; + private boolean preventGrabbing = false; + private String id = null; + private String nick = "UNDEFINED"; + private StatisticsKeys statisticsKeys = null; + private MathValue score = DEFAULT_SCORE; + private String[] groups = new String[0]; + private LootBaseEffect lootBaseEffect = null; + + @Override + public Builder type(LootType type) { + this.type = type; + return this; + } + @Override + public Builder instantGame(boolean instantGame) { + this.instantGame = instantGame; + return this; + } + @Override + public Builder disableGame(boolean disableGame) { + this.disableGame = disableGame; + return this; + } + @Override + public Builder preventGrabbing(boolean preventGrabbing) { + this.preventGrabbing = preventGrabbing; + return this; + } + @Override + public Builder disableStatistics(boolean disableStatistics) { + this.disableStatistics = disableStatistics; + return this; + } + @Override + public Builder showInFinder(boolean showInFinder) { + this.showInFinder = showInFinder; + return this; + } + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public Builder nick(String nick) { + this.nick = nick; + return this; + } + @Override + public Builder statisticsKeys(StatisticsKeys statisticsKeys) { + this.statisticsKeys = statisticsKeys; + return this; + } + @Override + public Builder score(MathValue score) { + this.score = score; + return this; + } + @Override + public Builder groups(String[] groups) { + this.groups = groups; + return this; + } + @Override + public Builder lootBaseEffect(LootBaseEffect lootBaseEffect) { + this.lootBaseEffect = lootBaseEffect; + return this; + } + @Override + public Loot build() { + return new LootImpl( + type, + instantGame, + disableGame, + disableStatistics, + showInFinder, + preventGrabbing, + requireNonNull(id), + Optional.ofNullable(nick).orElse(id), + Optional.ofNullable(statisticsKeys).orElse(new StatisticsKeys(id, id)), + score, + groups, + requireNonNull(lootBaseEffect) + ); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackGroundItem.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootManager.java similarity index 50% rename from plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackGroundItem.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootManager.java index 057c68a5..02316584 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackGroundItem.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootManager.java @@ -15,27 +15,31 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.gui.icon; +package net.momirealms.customfishing.api.mechanic.loot; -import net.momirealms.customfishing.gui.Icon; -import org.bukkit.Material; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; +import org.jetbrains.annotations.Nullable; -public class BackGroundItem extends AbstractItem implements Icon { +import java.util.List; +import java.util.Map; +import java.util.Optional; - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(""); - } +public interface LootManager extends Reloadable { - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { + boolean registerLoot(@NotNull Loot loot); - } -} \ No newline at end of file + @NotNull + List getGroupMembers(String key); + + @NotNull + Optional getLoot(String key); + + Map getWeightedLoots(Effect effect, Context context); + + @Nullable + Loot getNextLoot(Effect effect, Context context); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootType.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootType.java index 784e6c49..9d0a967b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootType.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/loot/LootType.java @@ -21,6 +21,10 @@ public enum LootType { ITEM, ENTITY, - BLOCK, - GLOBAL + BLOCK; + + @Override + public String toString() { + return name(); + } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/BuildableItem.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/market/MarketManager.java similarity index 60% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/item/BuildableItem.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/market/MarketManager.java index 9ac17591..2e37b69e 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/item/BuildableItem.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/market/MarketManager.java @@ -15,28 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.item; +package net.momirealms.customfishing.api.mechanic.market; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import java.util.HashMap; -import java.util.Map; +public interface MarketManager extends Reloadable { -public interface BuildableItem { + boolean openMarketGUI(Player player); - default ItemStack build() { - return build(null, new HashMap<>()); - } + double getItemPrice(Context context, ItemStack itemStack); - default ItemStack build(Player player) { - return build(player, new HashMap<>()); - } + String getFormula(); - ItemStack build(Player player, Map placeholders); - - /** - * Whether the item would be removed from cache when reloading - */ - boolean persist(); + double earningLimit(Context context); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/WeightModifier.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/WeightModifier.java deleted file mode 100644 index d481639a..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/WeightModifier.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.misc; - -import org.bukkit.entity.Player; - -public interface WeightModifier { - - double modify(Player player, double weight); -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/CoolDownManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/cooldown/CoolDownManager.java similarity index 80% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/CoolDownManager.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/cooldown/CoolDownManager.java index acfffad8..024ab4e7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/CoolDownManager.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/cooldown/CoolDownManager.java @@ -15,16 +15,19 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.misc; +package net.momirealms.customfishing.api.mechanic.misc.cooldown; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -32,12 +35,12 @@ import java.util.concurrent.ConcurrentHashMap; * Manages cooldowns for various actions or events. * Keeps track of cooldown times for different keys associated with player UUIDs. */ -public class CoolDownManager implements Listener { +public class CoolDownManager implements Listener, Reloadable { private final ConcurrentHashMap dataMap; - private final CustomFishingPlugin plugin; + private final BukkitCustomFishingPlugin plugin; - public CoolDownManager(CustomFishingPlugin plugin) { + public CoolDownManager(BukkitCustomFishingPlugin plugin) { this.dataMap = new ConcurrentHashMap<>(); this.plugin = plugin; } @@ -55,14 +58,17 @@ public class CoolDownManager implements Listener { return data.isCoolDown(key, time); } + @Override public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); } + @Override public void unload() { HandlerList.unregisterAll(this); } + @Override public void disable() { unload(); this.dataMap.clear(); @@ -80,10 +86,10 @@ public class CoolDownManager implements Listener { public static class Data { - private final HashMap coolDownMap; + private final Map coolDownMap; public Data() { - this.coolDownMap = new HashMap<>(); + this.coolDownMap = Collections.synchronizedMap(new HashMap<>()); } /** @@ -93,7 +99,7 @@ public class CoolDownManager implements Listener { * @param delay The cooldown delay in milliseconds. * @return True if the player is in cooldown, false otherwise. */ - public synchronized boolean isCoolDown(String key, long delay) { + public boolean isCoolDown(String key, long delay) { long time = System.currentTimeMillis(); long last = coolDownMap.getOrDefault(key, time - delay); if (last + delay > time) { diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/BukkitPlaceholderManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/BukkitPlaceholderManager.java new file mode 100644 index 00000000..66043318 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/BukkitPlaceholderManager.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.placeholder; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +public class BukkitPlaceholderManager implements PlaceholderManager { + + private final BukkitCustomFishingPlugin plugin; + private boolean hasPapi; + private final HashMap customPlaceholderMap; + private static BukkitPlaceholderManager instance; + + public BukkitPlaceholderManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.customPlaceholderMap = new HashMap<>(); + instance = this; + } + + @Override + public void reload() { + this.hasPapi = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + } + + public static BukkitPlaceholderManager getInstance() { + return instance; + } + + @Override + public boolean registerCustomPlaceholder(String placeholder, String original) { + if (this.customPlaceholderMap.containsKey(placeholder)) return false; + this.customPlaceholderMap.put(placeholder, original); + return true; + } + + @Override + public List resolvePlaceholders(String text) { + List placeholders = new ArrayList<>(); + Matcher matcher = PATTERN.matcher(text); + while (matcher.find()) placeholders.add(matcher.group()); + return placeholders; + } + + private String setPlaceholders(OfflinePlayer player, String text) { + return hasPapi ? PlaceholderAPIUtils.parse(player, text) : text; + } + + @Override + public String parseSingle(@Nullable OfflinePlayer player, String placeholder, Map replacements) { + String result = null; + if (replacements != null) + result = replacements.get(placeholder); + if (result != null) + return result; + String custom = customPlaceholderMap.get(placeholder); + if (custom == null) + return placeholder; + return setPlaceholders(player, custom); + } + + @Override + public String parse(@Nullable OfflinePlayer player, String text, Map replacements) { + var list = resolvePlaceholders(text); + for (String papi : list) { + String replacer = null; + if (replacements != null) { + replacer = replacements.get(papi); + } + if (replacer == null) { + String custom = customPlaceholderMap.get(papi); + if (custom != null) + replacer = setPlaceholders(player, parse(player, custom, replacements)); + } + if (replacer != null) + text = text.replace(papi, replacer); + } + return text; + } + + @Override + public List parse(@Nullable OfflinePlayer player, List list, Map replacements) { + return list.stream() + .map(s -> parse(player, s, replacements)) + .collect(Collectors.toList()); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderAPIUtils.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderAPIUtils.java new file mode 100644 index 00000000..b23f1a3f --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderAPIUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.placeholder; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +/** + * Utility class for interacting with the PlaceholderAPI. + * Provides methods to parse placeholders in strings for both online and offline players. + */ +public class PlaceholderAPIUtils { + + /** + * Parses placeholders in the provided text for an online player. + * + * @param player The online player for whom the placeholders should be parsed. + * @param text The text containing placeholders to be parsed. + * @return The text with parsed placeholders. + */ + public static String parse(Player player, String text) { + return PlaceholderAPI.setPlaceholders(player, text); + } + + /** + * Parses placeholders in the provided text for an offline player. + * + * @param player The offline player for whom the placeholders should be parsed. + * @param text The text containing placeholders to be parsed. + * @return The text with parsed placeholders. + */ + public static String parse(OfflinePlayer player, String text) { + return PlaceholderAPI.setPlaceholders(player, text); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderManager.java new file mode 100644 index 00000000..94cda0bd --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/placeholder/PlaceholderManager.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.placeholder; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface PlaceholderManager extends Reloadable { + + Pattern PATTERN = Pattern.compile("\\{[^{}]+}"); + + /** + * Registers a custom placeholder with its corresponding original string. + * + * @param placeholder the placeholder to register. + * @param original the original string corresponding to the placeholder. + * @return true if the placeholder was successfully registered, false if it already exists. + */ + boolean registerCustomPlaceholder(String placeholder, String original); + + /** + * Resolves all placeholders within a given text. + * + * @param text the text to resolve placeholders in. + * @return a list of found placeholders. + */ + List resolvePlaceholders(String text); + + /** + * Parses a single placeholder for a specified player, optionally using a map of replacements. + * + * @param player the player for whom the placeholder should be parsed. + * @param placeholder the placeholder to parse. + * @param replacements a map of replacement strings for placeholders. + * @return the parsed placeholder string. + */ + String parseSingle(@Nullable OfflinePlayer player, String placeholder, Map replacements); + + /** + * Parses all placeholders in the given text for a specified player, optionally using a map of replacements. + * + * @param player the player for whom the placeholders should be parsed. + * @param text the text containing placeholders. + * @param replacements a map of replacement strings for placeholders. + * @return the text with parsed placeholders. + */ + String parse(@Nullable OfflinePlayer player, String text, Map replacements); + + /** + * Parses all placeholders in a list of strings for a specified player, optionally using a map of replacements. + * + * @param player the player for whom the placeholders should be parsed. + * @param list the list of strings containing placeholders. + * @param replacements a map of replacement strings for placeholders. + * @return the list of strings with parsed placeholders. + */ + List parse(@Nullable OfflinePlayer player, List list, Map replacements); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/season/Season.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/season/Season.java new file mode 100644 index 00000000..02c897ba --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/season/Season.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.season; + +public enum Season { + SPRING, + SUMMER, + AUTUMN, + WINTER, + DISABLE +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/DynamicText.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/DynamicText.java similarity index 75% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/DynamicText.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/DynamicText.java index 4e72eac2..356225eb 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/DynamicText.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/DynamicText.java @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.misc; +package net.momirealms.customfishing.api.mechanic.misc.value; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager; import org.bukkit.entity.Player; import java.util.ArrayList; @@ -39,7 +39,7 @@ public class DynamicText { private void analyze(String value) { // Analyze the provided text to find and replace placeholders with '%s'. // Store the original value, placeholders, and the initial latest value. - List placeholdersOwner = new ArrayList<>(PlaceholderManagerImpl.getInstance().detectPlaceholders(value)); + List placeholdersOwner = new ArrayList<>(BukkitPlaceholderManager.getInstance().resolvePlaceholders(value)); String origin = value; for (String placeholder : placeholdersOwner) { origin = origin.replace(placeholder, "%s"); @@ -54,22 +54,20 @@ public class DynamicText { } public boolean update(Map placeholders) { - // Update the dynamic text by replacing placeholders with actual values. String string = originalValue; if (this.placeholders.length != 0) { - PlaceholderManagerImpl placeholderManagerImpl = PlaceholderManagerImpl.getInstance(); + BukkitPlaceholderManager bukkitPlaceholderManager = BukkitPlaceholderManager.getInstance(); if ("%s".equals(originalValue)) { - string = placeholderManagerImpl.getSingleValue(owner, this.placeholders[0], placeholders); + string = bukkitPlaceholderManager.parseSingle(owner, this.placeholders[0], placeholders); } else { Object[] values = new String[this.placeholders.length]; for (int i = 0; i < this.placeholders.length; i++) { - values[i] = placeholderManagerImpl.getSingleValue(owner, this.placeholders[i], placeholders); + values[i] = bukkitPlaceholderManager.parseSingle(owner, this.placeholders[i], placeholders); } string = String.format(originalValue, values); } } if (!latestValue.equals(string)) { - // If the updated value is different from the latest value, update it. latestValue = string; return true; } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/ExpressionMathValueImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/ExpressionMathValueImpl.java new file mode 100644 index 00000000..99d77e08 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/ExpressionMathValueImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.value; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager; +import net.momirealms.customfishing.common.helper.ExpressionHelper; +import org.bukkit.OfflinePlayer; + +import java.util.Map; + +public class ExpressionMathValueImpl implements MathValue { + + private final String raw; + + public ExpressionMathValueImpl(String raw) { + this.raw = raw; + } + + @Override + public double evaluate(Context context) { + Map replacements = context.placeholderMap(); + String expression; + if (context.getHolder() instanceof OfflinePlayer player) expression = BukkitPlaceholderManager.getInstance().parse(player, raw, replacements); + else expression = BukkitPlaceholderManager.getInstance().parse(null, raw, replacements); + return ExpressionHelper.evaluate(expression); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/MathValue.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/MathValue.java new file mode 100644 index 00000000..4e81c549 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/MathValue.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.value; + +import net.momirealms.customfishing.api.mechanic.context.Context; + +/** + * The MathValue interface represents a mathematical value that can be evaluated + * within a specific context. This interface allows for the evaluation of mathematical + * expressions or plain numerical values in the context of custom fishing mechanics. + * + * @param the type of the holder object for the context + */ +public interface MathValue { + + /** + * Evaluates the mathematical value within the given context. + * + * @param context the context in which the value is evaluated + * @return the evaluated value as a double + */ + double evaluate(Context context); + + /** + * Creates a MathValue based on a mathematical expression. + * + * @param expression the mathematical expression to evaluate + * @param the type of the holder object for the context + * @return a MathValue instance representing the given expression + */ + static MathValue expression(String expression) { + return new ExpressionMathValueImpl<>(expression); + } + + /** + * Creates a MathValue based on a plain numerical value. + * + * @param value the numerical value to represent + * @param the type of the holder object for the context + * @return a MathValue instance representing the given plain value + */ + static MathValue plain(double value) { + return new PlainMathValueImpl<>(value); + } + + static MathValue ranged(String value) { + return new RangedMathValueImpl<>(value); + } + + /** + * Automatically creates a MathValue based on the given object. + * If the object is a String, it is treated as a mathematical expression. + * If the object is a numerical type (Double, Integer, Long, Float), it is treated as a plain value. + * + * @param o the object to evaluate and create a MathValue from + * @param the type of the holder object for the context + * @return a MathValue instance representing the given object, either as an expression or a plain value + * @throws IllegalArgumentException if the object type is not supported + */ + static MathValue auto(Object o) { + if (o instanceof String s) { + if (s.contains("~")) { + return ranged(s); + } + try { + return plain(Double.parseDouble(s)); + } catch (NumberFormatException e) { + return expression(s); + } + } else if (o instanceof Number n) { + return plain(n.doubleValue()); + } + throw new IllegalArgumentException("Unsupported type: " + o.getClass()); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlaceholderTextValueImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlaceholderTextValueImpl.java new file mode 100644 index 00000000..6d3dea9a --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlaceholderTextValueImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.value; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager; +import org.bukkit.OfflinePlayer; + +import java.util.Map; + +public class PlaceholderTextValueImpl implements TextValue { + + private final String raw; + + public PlaceholderTextValueImpl(String raw) { + this.raw = raw; + } + + @Override + public String render(Context context) { + Map replacements = context.placeholderMap(); + String text; + if (context.getHolder() instanceof OfflinePlayer player) text = BukkitPlaceholderManager.getInstance().parse(player, raw, replacements); + else text = BukkitPlaceholderManager.getInstance().parse(null, raw, replacements); + return text; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/PlainValue.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainMathValueImpl.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/PlainValue.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainMathValueImpl.java index 8c22a4c5..9fd0f4de 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/value/PlainValue.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainMathValueImpl.java @@ -15,23 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.misc.value; +package net.momirealms.customfishing.api.mechanic.misc.value; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import org.bukkit.entity.Player; +import net.momirealms.customfishing.api.mechanic.context.Context; -import java.util.Map; - -public class PlainValue implements Value { +public class PlainMathValueImpl implements MathValue { private final double value; - public PlainValue(double value) { + public PlainMathValueImpl(double value) { this.value = value; } @Override - public double get(Player player, Map values) { + public double evaluate(Context context) { return value; } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSettings.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainTextValueImpl.java similarity index 64% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSettings.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainTextValueImpl.java index 98bea519..6a3907e9 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/game/GameSettings.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/PlainTextValueImpl.java @@ -15,23 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.game; +package net.momirealms.customfishing.api.mechanic.misc.value; -public class GameSettings { +import net.momirealms.customfishing.api.mechanic.context.Context; - private final double time; - private final int difficulty; +public class PlainTextValueImpl implements TextValue { - public GameSettings(double time, int difficulty) { - this.time = time; - this.difficulty = difficulty; + private final String raw; + + public PlainTextValueImpl(String raw) { + this.raw = raw; } - public double getTime() { - return time; - } - - public int getDifficulty() { - return difficulty; + @Override + public String render(Context context) { + return raw; } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/RangedMathValueImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/RangedMathValueImpl.java new file mode 100644 index 00000000..dde3dd34 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/RangedMathValueImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.value; + +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.util.RandomUtils; + +public class RangedMathValueImpl implements MathValue { + + private final double min; + private final double max; + + public RangedMathValueImpl(String value) { + String[] split = value.split("~"); + if (split.length != 2) { + throw new IllegalArgumentException("Correct ranged format `a~b`"); + } + double min = Double.parseDouble(split[0]); + double max = Double.parseDouble(split[1]); + if (min > max) { + double temp = max; + max = min; + min = temp; + } + this.min = min; + this.max = max; + } + + @Override + public double evaluate(Context context) { + return RandomUtils.generateRandomDouble(min, max); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/TextValue.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/TextValue.java new file mode 100644 index 00000000..1640ccd3 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/misc/value/TextValue.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.misc.value; + +import net.momirealms.customfishing.api.mechanic.context.Context; + +import java.util.regex.Pattern; + +/** + * The TextValue interface represents a text value that can be rendered + * within a specific context. This interface allows for the rendering of + * placeholder-based or plain text values in the context of custom fishing mechanics. + * + * @param the type of the holder object for the context + */ +public interface TextValue { + + Pattern pattern = Pattern.compile("\\{[^{}]+}"); + + /** + * Renders the text value within the given context. + * + * @param context the context in which the text value is rendered + * @return the rendered text as a String + */ + String render(Context context); + + /** + * Creates a TextValue based on a placeholder text. + * Placeholders can be dynamically replaced with context-specific values. + * + * @param text the placeholder text to render + * @param the type of the holder object for the context + * @return a TextValue instance representing the given placeholder text + */ + static TextValue placeholder(String text) { + return new PlaceholderTextValueImpl<>(text); + } + + /** + * Creates a TextValue based on plain text. + * + * @param text the plain text to render + * @param the type of the holder object for the context + * @return a TextValue instance representing the given plain text + */ + static TextValue plain(String text) { + return new PlainTextValueImpl<>(text); + } + + /** + * Automatically creates a TextValue based on the given argument. + * If the argument contains placeholders (detected by a regex pattern), + * a PlaceholderTextValueImpl instance is created. Otherwise, a PlainTextValueImpl + * instance is created. + * + * @param arg the text to evaluate and create a TextValue from + * @param the type of the holder object for the context + * @return a TextValue instance representing the given text, either as a placeholder or plain text + */ + static TextValue auto(String arg) { + if (pattern.matcher(arg).find()) + return placeholder(arg); + else + return plain(arg); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/ConditionalElement.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/ConditionalElement.java new file mode 100644 index 00000000..eeb6e829 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/ConditionalElement.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.requirement; + +import java.util.Map; + +public class ConditionalElement { + + private final E element; + private final Map> subElements; + private final Requirement[] requirements; + + public ConditionalElement(E element, Map> subElements, Requirement[] requirements) { + this.element = element; + this.subElements = subElements; + this.requirements = requirements; + } + + public E getElement() { + return element; + } + + public Requirement[] getRequirements() { + return requirements; + } + + public Map> getSubElements() { + return subElements; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/EmptyRequirement.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/EmptyRequirement.java similarity index 68% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/EmptyRequirement.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/EmptyRequirement.java index f01645be..e82217fc 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/EmptyRequirement.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/EmptyRequirement.java @@ -15,20 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.requirement; +package net.momirealms.customfishing.api.mechanic.requirement; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.entity.Player; /** * Represents an empty requirement that always returns true when checking conditions. */ -public class EmptyRequirement implements Requirement { +public class EmptyRequirement implements Requirement { - public static EmptyRequirement instance = new EmptyRequirement(); + public static final EmptyRequirement INSTANCE = new EmptyRequirement(); @Override - public boolean isConditionMet(Condition condition) { + public boolean isSatisfied(Context context) { return true; } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/Requirement.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/Requirement.java index 3fac8c48..40d635fb 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/Requirement.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/Requirement.java @@ -17,15 +17,21 @@ package net.momirealms.customfishing.api.mechanic.requirement; -import net.momirealms.customfishing.api.mechanic.condition.Condition; +import net.momirealms.customfishing.api.mechanic.context.Context; -public interface Requirement { +/** + * Interface representing a requirement that must be met in the custom fishing API. + * This can be used to define conditions that need to be satisfied within a given context. + * + * @param the type parameter for the context + */ +public interface Requirement { /** - * Is condition met the requirement + * Evaluates whether the requirement is met within the given context. * - * @param condition condition - * @return meet or not + * @param context the context in which the requirement is evaluated + * @return true if the requirement is met, false otherwise */ - boolean isConditionMet(Condition condition); -} + boolean isSatisfied(Context context); +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementExpansion.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementExpansion.java index 11d04713..0e274c47 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementExpansion.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementExpansion.java @@ -21,7 +21,7 @@ package net.momirealms.customfishing.api.mechanic.requirement; * An abstract class representing a requirement expansion * Requirement expansions are used to define custom requirements for various functionalities. */ -public abstract class RequirementExpansion { +public abstract class RequirementExpansion { /** * Get the version of this requirement expansion. @@ -49,5 +49,5 @@ public abstract class RequirementExpansion { * * @return The requirement factory. */ - public abstract RequirementFactory getRequirementFactory(); + public abstract RequirementFactory getRequirementFactory(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementFactory.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementFactory.java index adbb1030..a3657b15 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementFactory.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementFactory.java @@ -22,19 +22,21 @@ import net.momirealms.customfishing.api.mechanic.action.Action; import java.util.List; /** - * An interface for a requirement factory that builds requirements. + * Interface representing a factory for creating requirements. + * + * @param the type of object that the requirement will operate on */ -public interface RequirementFactory { +public interface RequirementFactory { /** - * Build a requirement with the given arguments, not met actions, and check action flag. + * Build a requirement with the given arguments, not satisfied actions, and check run actions flag. * - * @param args The arguments used to build the requirement. - * @param notMetActions Actions to be triggered when the requirement is not met (can be null). - * @param advanced Flag indicating whether to check the action when building the requirement. + * @param args The arguments used to build the requirement. + * @param notSatisfiedActions Actions to be triggered when the requirement is not met (can be null). + * @param runActions Flag indicating whether to run the action if the requirement is not met. * @return The built requirement. */ - Requirement build(Object args, List notMetActions, boolean advanced); + Requirement process(Object args, List> notSatisfiedActions, boolean runActions); /** * Build a requirement with the given arguments. @@ -42,7 +44,7 @@ public interface RequirementFactory { * @param args The arguments used to build the requirement. * @return The built requirement. */ - default Requirement build(Object args) { - return build(args, null, false); + default Requirement process(Object args) { + return process(args, List.of(), false); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementManager.java new file mode 100644 index 00000000..a6d6eedc --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/requirement/RequirementManager.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.requirement; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * The RequirementManager interface manages custom requirement types and provides methods for handling requirements. + * + * @param the type of the context in which the requirements are evaluated. + */ +public interface RequirementManager extends Reloadable { + + /** + * Registers a custom requirement type with its corresponding factory. + * + * @param type The type identifier of the requirement. + * @param requirementFactory The factory responsible for creating instances of the requirement. + * @return True if registration was successful, false if the type is already registered. + */ + boolean registerRequirement(@NotNull String type, @NotNull RequirementFactory requirementFactory); + + /** + * Unregisters a custom requirement type. + * + * @param type The type identifier of the requirement to unregister. + * @return True if unregistration was successful, false if the type is not registered. + */ + boolean unregisterRequirement(@NotNull String type); + + /** + * Checks if a requirement type is registered. + * + * @param type The type identifier of the requirement. + * @return True if the requirement type is registered, otherwise false. + */ + boolean hasRequirement(@NotNull String type); + + /** + * Retrieves a RequirementFactory based on the specified requirement type. + * + * @param type The requirement type for which to retrieve a factory. + * @return A RequirementFactory for the specified type, or null if no factory is found. + */ + @Nullable + RequirementFactory getRequirementFactory(@NotNull String type); + + /** + * Retrieves an array of requirements based on a configuration section. + * + * @param section The configuration section containing requirement definitions. + * @param runActions A flag indicating whether to use advanced requirements. + * @return An array of Requirement objects based on the configuration section. + */ + @NotNull + Requirement[] parseRequirements(Section section, boolean runActions); + + /** + * Retrieves a Requirement object based on a configuration section and advanced flag. + * + * @param section The configuration section containing requirement definitions. + * @param runActions A flag indicating whether to use advanced requirements. + * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid. + */ + @NotNull + Requirement parseRequirement(@NotNull Section section, boolean runActions); + + /** + * Gets a requirement based on the provided type and value. + * If a valid RequirementFactory is found for the type, it is used to create the requirement. + * + * @param type The type representing the requirement type. + * @param value The value associated with the requirement. + * @return A Requirement instance based on the type and value, or an EmptyRequirement if the type is invalid. + */ + @NotNull + Requirement parseRequirement(@NotNull String type, @NotNull Object value); + + /** + * Checks if all requirements in the provided array are satisfied within the given context. + * + * @param context The context in which the requirements are evaluated. + * @param requirements An array of requirements to check. + * @return True if all requirements are satisfied, otherwise false. + */ + static boolean isSatisfied(Context context, @Nullable Requirement[] requirements) { + if (requirements == null) return true; + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied(context)) { + return false; + } + } + return true; + } + + static boolean isSatisfied(Context context, @Nullable List> requirements) { + if (requirements == null) return true; + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied(context)) { + return false; + } + } + return true; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatistics.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatistics.java new file mode 100644 index 00000000..c21bf6cd --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatistics.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.statistic; + +import net.momirealms.customfishing.common.util.Pair; + +import java.util.Map; + +/** + * The FishingStatistics interface represents statistics related to fishing activities. + * It provides methods to retrieve and manipulate statistics such as the amount of fish caught and the maximum size of fish. + */ +public interface FishingStatistics { + + /** + * Retrieves the total amount of fish caught. + * + * @return the total amount of fish caught. + */ + int amountOfFishCaught(); + + /** + * Sets the total amount of fish caught. + * + * @param amountOfFishCaught the new total amount of fish caught. + */ + void amountOfFishCaught(int amountOfFishCaught); + + /** + * Retrieves the amount of fish caught with the specified ID. + * + * @param id the ID of the fish. + * @return the amount of fish caught with the specified ID. -1 if not exist. + */ + int getAmount(String id); + + /** + * Adds the specified amount to the fish caught with the specified ID and returns the updated amount. + * + * @param id the ID of the fish. + * @param amount the amount to add. + * @return a Pair containing the previous amount and the updated amount. + */ + Pair addAmount(String id, int amount); + + /** + * Sets the amount of fish caught with the specified ID. + * + * @param id the ID of the fish. + * @param amount the new amount to set. + */ + void setAmount(String id, int amount); + + /** + * Retrieves the maximum size of the fish with the specified ID. + * + * @param id the ID of the fish. + * @return the maximum size of the fish with the specified ID. -1f if not exist. + */ + float getMaxSize(String id); + + /** + * Sets the maximum size of the fish with the specified ID. + * + * @param id the ID of the fish. + * @param maxSize the new maximum size to set. + */ + void setMaxSize(String id, float maxSize); + + /** + * Updates the maximum size of the fish with the specified ID and returns true if successful, false otherwise. + * + * @param id the ID of the fish. + * @param newSize the new maximum size. + * @return true if the update is successful, false otherwise. + */ + boolean updateSize(String id, float newSize); + + /** + * Resets the fishing statistics, clearing all recorded data. + */ + void reset(); + + /** + * Retrieves the map containing the amounts of fish caught. + * + * @return the map containing the amounts of fish caught. + */ + Map amountMap(); + + /** + * Retrieves the map containing the maximum sizes of fish. + * + * @return the map containing the maximum sizes of fish. + */ + Map sizeMap(); + + /** + * Creates a new Builder instance for constructing FishingStatistics objects. + * + * @return a new Builder instance. + */ + static Builder builder() { + return new FishingStatisticsImpl.BuilderImpl(); + } + + /** + * The Builder interface provides a fluent API for constructing FishingStatistics instances. + */ + interface Builder { + + /** + * Sets the map containing the amounts of fish caught. + * + * @param amountMap the map containing the amounts of fish caught. + * @return the Builder instance. + */ + Builder amountMap(Map amountMap); + + /** + * Sets the map containing the maximum sizes of fish. + * + * @param sizeMap the map containing the maximum sizes of fish. + * @return the Builder instance. + */ + Builder sizeMap(Map sizeMap); + + /** + * Builds and returns the FishingStatistics instance. + * + * @return the constructed FishingStatistics instance. + */ + FishingStatistics build(); + } + + enum Type { + MAX_SIZE, + AMOUNT_OF_FISH_CAUGHT + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatisticsImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatisticsImpl.java new file mode 100644 index 00000000..5dcbc2ec --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/FishingStatisticsImpl.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.statistic; + +import net.momirealms.customfishing.common.util.Pair; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class FishingStatisticsImpl implements FishingStatistics { + + private int amountOfFishCaught; + private final Map amountMap; + private final Map sizeMap; + + public FishingStatisticsImpl(HashMap amountMap, HashMap sizeMap) { + this.amountMap = Collections.synchronizedMap(amountMap); + this.sizeMap = Collections.synchronizedMap(sizeMap); + this.amountOfFishCaught = amountMap.values().stream().mapToInt(Integer::intValue).sum(); + } + + @Override + public int amountOfFishCaught() { + return amountOfFishCaught; + } + + @Override + public void amountOfFishCaught(int amountOfFishCaught) { + this.amountOfFishCaught = amountOfFishCaught; + } + + @Override + public int getAmount(String id) { + return amountMap.getOrDefault(id, -1); + } + + @Override + public Pair addAmount(String id, int amount) { + if (amount <= 0) return Pair.of(-1, -1); + int previous = amountMap.getOrDefault(id, 0); + amountMap.put(id, previous + amount); + amountOfFishCaught += amount; + return Pair.of(previous, previous + amount); + } + + @Override + public void setAmount(String id, int amount) { + if (amount < 0) amount = 0; + int previous = amountMap.getOrDefault(id, 0); + int delta = amount - previous; + this.amountOfFishCaught += delta; + amountMap.put(id, amount); + } + + @Override + public float getMaxSize(String id) { + return sizeMap.getOrDefault(id, -1f); + } + + @Override + public void setMaxSize(String id, float maxSize) { + if (maxSize < 0) maxSize = 0; + sizeMap.put(id, maxSize); + } + + @Override + public boolean updateSize(String id, float newSize) { + if (newSize <= 0) return false; + float previous = sizeMap.getOrDefault(id, 0f); + if (previous >= newSize) return false; + sizeMap.put(id, newSize); + return true; + } + + @Override + public void reset() { + this.sizeMap.clear(); + this.amountMap.clear(); + this.amountOfFishCaught = 0; + } + + @Override + public Map amountMap() { + return amountMap; + } + + @Override + public Map sizeMap() { + return sizeMap; + } + + public static class BuilderImpl implements Builder { + private final HashMap amountMap = new HashMap<>(); + private final HashMap sizeMap = new HashMap<>(); + @Override + public Builder amountMap(Map amountMap) { + this.amountMap.putAll(amountMap); + return this; + } + @Override + public Builder sizeMap(Map sizeMap) { + this.sizeMap.putAll(sizeMap); + return this; + } + @Override + public FishingStatistics build() { + return new FishingStatisticsImpl(amountMap, sizeMap); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/Statistics.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/Statistics.java deleted file mode 100644 index 081fb28e..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/Statistics.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.mechanic.statistic; - -import net.momirealms.customfishing.api.data.StatisticData; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.loot.Loot; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Represents a statistics system for tracking loot and catch amounts. - */ -public class Statistics { - - private final ConcurrentHashMap statisticMap; - private final ConcurrentHashMap sizeMap; - private int total; - - /** - * Creates a new instance of Statistics based on provided statistic data. - * - * @param statisticData The initial statistic data. - */ - public Statistics(StatisticData statisticData) { - this.statisticMap = new ConcurrentHashMap<>(statisticData.amountMap); - this.sizeMap = new ConcurrentHashMap<>(statisticData.sizeMap); - this.total = statisticMap.values().stream().mapToInt(Integer::intValue).sum(); - } - - /** - * Adds an amount of loot to the statistics. - * - * @param loot The loot item. - * @param condition The condition associated with the loot. - * @param amount The amount of loot to add. - */ - public synchronized void addLootAmount(Loot loot, Condition condition, int amount) { - if (amount < 1) { - return; - } - if (amount == 1) { - addSingleLootAmount(loot, condition); - return; - } - Integer previous = statisticMap.get(loot.getStatisticKey().getAmountKey()); - if (previous == null) previous = 0; - int after = previous + amount; - statisticMap.put(loot.getStatisticKey().getAmountKey(), after); - total += amount; - doSuccessTimesAction(previous, after, condition, loot); - } - - /** - * Performs actions associated with the success times of acquiring loot. - * - * @param previous The previous success times. - * @param after The updated success times. - * @param condition The condition associated with the loot. - * @param loot The loot item. - */ - private void doSuccessTimesAction(Integer previous, int after, Condition condition, Loot loot) { - HashMap actionMap = loot.getSuccessTimesActionMap(); - if (actionMap != null) { - for (Map.Entry entry : actionMap.entrySet()) { - if (entry.getKey() > previous && entry.getKey() <= after) { - for (Action action : entry.getValue()) { - action.trigger(condition); - } - } - } - } - } - - public boolean setSizeIfHigher(String loot, float size) { - float previous = sizeMap.getOrDefault(loot, 0f); - if (previous >= size) return false; - sizeMap.put(loot, size); - return true; - } - - /** - * Adds a single loot amount to the statistics. - * - * @param loot The loot item. - * @param condition The condition associated with the loot. - */ - private void addSingleLootAmount(Loot loot, Condition condition) { - Integer previous = statisticMap.get(loot.getID()); - if (previous == null) previous = 0; - int after = previous + 1; - statisticMap.put(loot.getID(), after); - total += 1; - Action[] actions = loot.getSuccessTimesActionMap().get(after); - if (actions != null) - for (Action action : actions) { - action.trigger(condition); - } - } - - /** - * Gets the amount of a specific loot item in the statistics. - * - * @param key The key of the loot item. - * @return The amount of the specified loot item. - */ - public int getLootAmount(String key) { - return statisticMap.getOrDefault(key, 0); - } - - public float getSizeRecord(String key) { - return sizeMap.getOrDefault(key, 0f); - } - - /** - * Resets the statistics data. - */ - public void reset() { - statisticMap.clear(); - total = 0; - } - - /** - * Gets the statistic map containing loot item keys and their respective amounts. - * - * @return The statistic map. - */ - public Map getStatisticMap() { - return statisticMap; - } - - public ConcurrentHashMap getSizeMap() { - return sizeMap; - } - - /** - * Sets data for a specific key in the statistics. - * - * @param key The key to set data for. - * @param value The value to set. - */ - public void setData(String key, int value) { - if (value <= 0) { - statisticMap.remove(key); - return; - } - statisticMap.put(key, value); - } - - /** - * Gets the total catch amount across all loot items. - * - * @return The total catch amount. - */ - public int getTotalCatchAmount() { - return total; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKey.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKey.java deleted file mode 100644 index 6d3c1b16..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKey.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.momirealms.customfishing.api.mechanic.statistic; - -public class StatisticsKey { - - private final String amountKey; - private final String sizeKey; - - public StatisticsKey(String amountKey, String sizeKey) { - this.amountKey = amountKey; - this.sizeKey = sizeKey; - } - - public String getAmountKey() { - return amountKey; - } - - public String getSizeKey() { - return sizeKey; - } -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKeys.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKeys.java new file mode 100644 index 00000000..b79647b2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsKeys.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.statistic; + +public record StatisticsKeys(String amountKey, String sizeKey) { +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockSettings.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsManager.java similarity index 69% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockSettings.java rename to api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsManager.java index 657d6b45..3d044bcf 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/block/BlockSettings.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/statistic/StatisticsManager.java @@ -15,20 +15,15 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.block; +package net.momirealms.customfishing.api.mechanic.statistic; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.NotNull; import java.util.List; -public interface BlockSettings { - String getBlockID(); +public interface StatisticsManager extends Reloadable { - List getDataModifier(); - - List getStateModifierList(); - - boolean isPersist(); - - double getHorizontalVector(); - - double getVerticalVector(); + @NotNull + List getCategoryMembers(String key); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfig.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfig.java index a19ef39c..e6bed212 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfig.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfig.java @@ -17,181 +17,43 @@ package net.momirealms.customfishing.api.mechanic.totem; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock; import org.bukkit.Location; +import org.bukkit.entity.Player; -/** - * This class represents the configuration for a totem. - * It defines various settings and properties for the totem. - */ -public class TotemConfig { +public interface TotemConfig { - private String key; - private TotemModel[] totemModels; - private TotemParticle[] particleSettings; - private Requirement[] requirements; - private double radius; - private int duration; + TotemModel[] totemModels(); - /** - * Get the array of totem models that define the totem's pattern. - * - * @return An array of TotemModel objects. - */ - public TotemModel[] getTotemModels() { - return totemModels; + String id(); + + boolean isRightPattern(Location location); + + TotemParticle[] particleSettings(); + + MathValue radius(); + + MathValue duration(); + + TotemBlock[] totemCore(); + + static Builder builder() { + return new TotemConfigImpl.BuilderImpl(); } - /** - * Get the array of requirements for totem activation. - * - * @return An array of Requirement objects. - */ - public Requirement[] getRequirements() { - return requirements; - } + interface Builder { - /** - * Get the unique key associated with this totem configuration. - * - * @return The unique key as a string. - */ - public String getKey() { - return key; - } + Builder id(String id); - /** - * Check if the provided location matches any of the totem model patterns. - * - * @param location The location to check. - * @return True if the location matches a totem model pattern, false otherwise. - */ - public boolean isRightPattern(Location location) { - for (TotemModel totemModel : totemModels) { - if (totemModel.isPatternSatisfied(location)) { - return true; - } - } - return false; - } + Builder totemModels(TotemModel[] totemModels); - /** - * Get the array of particle settings for the totem's visual effects. - * - * @return An array of TotemParticle objects. - */ - public TotemParticle[] getParticleSettings() { - return particleSettings; - } + Builder particleSettings(TotemParticle[] particleSettings); - /** - * Get the activation radius of the totem. - * - * @return The activation radius as a double. - */ - public double getRadius() { - return radius; - } + Builder radius(MathValue radius); - /** - * Get the duration of the totem's effect when activated. - * - * @return The duration in seconds as an integer. - */ - public int getDuration() { - return duration; - } + Builder duration(MathValue duration); - /** - * Get the totem core associated with the first totem model. - * This is used for some internal functionality. - * - * @return An array of TotemBlock objects representing the totem core. - */ - public TotemBlock[] getTotemCore() { - return totemModels[0].getTotemCore(); - } - - public static Builder builder(String key) { - return new Builder(key); - } - - /** - * This class represents a builder for creating instances of TotemConfig. - * It allows for the convenient construction of TotemConfig objects with various settings. - */ - public static class Builder { - - private final TotemConfig config; - - public Builder(String key) { - this.config = new TotemConfig(); - this.config.key = key; - } - - /** - * Sets the totem models for the TotemConfig being built. - * - * @param totemModels An array of TotemModel objects representing different totem models. - * @return The builder instance to allow for method chaining. - */ - public Builder setTotemModels(TotemModel[] totemModels) { - config.totemModels = totemModels; - return this; - } - - /** - * Sets the particle settings for the TotemConfig being built. - * - * @param particleSettings An array of TotemParticle objects representing particle settings. - * @return The builder instance to allow for method chaining. - */ - public Builder setParticleSettings(TotemParticle[] particleSettings) { - config.particleSettings = particleSettings; - return this; - } - - /** - * Sets the requirements for the TotemConfig being built. - * - * @param requirements An array of Requirement objects representing activation requirements. - * @return The builder instance to allow for method chaining. - */ - public Builder setRequirements(Requirement[] requirements) { - config.requirements = requirements; - return this; - } - - /** - * Sets the radius for the TotemConfig being built. - * - * @param radius The activation radius for the totem. - * @return The builder instance to allow for method chaining. - */ - public Builder setRadius(double radius) { - config.radius = radius; - return this; - } - - /** - * Sets the duration for the TotemConfig being built. - * - * @param duration The duration of the totem's effect. - * @return The builder instance to allow for method chaining. - */ - public Builder setDuration(int duration) { - config.duration = duration; - return this; - } - - /** - * Builds and returns the finalized TotemConfig object. - * - * @return The constructed TotemConfig object. - */ - public TotemConfig build() { - return config; - } + TotemConfig build(); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfigImpl.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfigImpl.java new file mode 100644 index 00000000..a73000c1 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemConfigImpl.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.totem; + +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import static java.util.Objects.requireNonNull; + +public class TotemConfigImpl implements TotemConfig { + + private final String id; + private final TotemModel[] totemModels; + private final TotemParticle[] particleSettings; + private final MathValue radius; + private final MathValue duration; + + public TotemConfigImpl(String id, TotemModel[] totemModels, TotemParticle[] particleSettings, MathValue radius, MathValue duration) { + this.id = id; + this.totemModels = totemModels; + this.particleSettings = particleSettings; + this.radius = radius; + this.duration = duration; + } + + @Override + public TotemModel[] totemModels() { + return totemModels; + } + + @Override + public String id() { + return id; + } + + @Override + public boolean isRightPattern(Location location) { + for (TotemModel totemModel : totemModels) { + if (totemModel.isPatternSatisfied(location)) { + return true; + } + } + return false; + } + + @Override + public TotemParticle[] particleSettings() { + return particleSettings; + } + + @Override + public MathValue radius() { + return radius; + } + + @Override + public MathValue duration() { + return duration; + } + + @Override + public TotemBlock[] totemCore() { + return totemModels[0].getTotemCore(); + } + + public static class BuilderImpl implements Builder { + private String id; + private TotemModel[] totemModels; + private TotemParticle[] particleSettings; + private MathValue radius; + private MathValue duration; + @Override + public Builder id(String id) { + this.id = id; + return this; + } + @Override + public Builder totemModels(TotemModel[] totemModels) { + this.totemModels = totemModels; + return this; + } + @Override + public Builder particleSettings(TotemParticle[] particleSettings) { + this.particleSettings = particleSettings; + return this; + } + @Override + public Builder radius(MathValue radius) { + this.radius = radius; + return this; + } + @Override + public Builder duration(MathValue duration) { + this.duration = duration; + return this; + } + @Override + public TotemConfig build() { + return new TotemConfigImpl(requireNonNull(id), requireNonNull(totemModels), particleSettings, radius, duration); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemManager.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemManager.java new file mode 100644 index 00000000..3b11f6aa --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemManager.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.mechanic.totem; + +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Optional; + +public interface TotemManager extends Reloadable { + + Collection getActivatedTotems(Location location); + + boolean registerTotem(TotemConfig totem); + + @NotNull + Optional getTotem(String id); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemParticle.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemParticle.java index 0321992c..71ec13ef 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemParticle.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/TotemParticle.java @@ -1,6 +1,23 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package net.momirealms.customfishing.api.mechanic.totem; -import net.momirealms.customfishing.api.scheduler.CancellableTask; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; import org.bukkit.Location; public interface TotemParticle { @@ -12,5 +29,5 @@ public interface TotemParticle { * @param radius totem radius * @return cancellable task */ - CancellableTask start(Location location, double radius); -} \ No newline at end of file + SchedulerTask start(Location location, double radius); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/block/type/EqualType.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/block/type/EqualType.java index 8323aac5..dfed289b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/block/type/EqualType.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/totem/block/type/EqualType.java @@ -17,7 +17,7 @@ package net.momirealms.customfishing.api.mechanic.totem.block.type; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import org.bukkit.block.Block; import java.io.Serializable; @@ -41,7 +41,7 @@ public class EqualType implements TypeCondition, Serializable { */ @Override public boolean isMet(Block type) { - return this.type.equals(CustomFishingPlugin.get().getBlockManager().getAnyPluginBlockID(type)); + return this.type.equals(BukkitCustomFishingPlugin.getInstance().getBlockManager().getBlockID(type)); } /** diff --git a/api/src/main/java/net/momirealms/customfishing/api/scheduler/Scheduler.java b/api/src/main/java/net/momirealms/customfishing/api/scheduler/Scheduler.java deleted file mode 100644 index 515a4c51..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/scheduler/Scheduler.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.scheduler; - -import org.bukkit.Location; - -import java.util.concurrent.TimeUnit; - -public interface Scheduler { - - /** - * Runs a task synchronously on the main server thread or region thread. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - void runTaskSync(Runnable runnable, Location location); - - /** - * Runs a task synchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the first execution. - * @param periodTicks The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks); - - /** - * Runs a task asynchronously with a specified delay. - * - * @param runnable The task to run. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit); - - /** - * Runs a task asynchronously. - * - * @param runnable The task to run. - */ - void runTaskAsync(Runnable runnable); - - /** - * Runs a task synchronously with a specified delay. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit); - - /** - * Runs a task synchronously with a specified delay in ticks. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks); - - /** - * Runs a task asynchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param delay The delay before the first execution. - * @param period The period between subsequent executions. - * @param timeUnit The time unit for the delay and period. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit); -} diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/DataStorageProvider.java b/api/src/main/java/net/momirealms/customfishing/api/storage/DataStorageProvider.java new file mode 100644 index 00000000..339d8572 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/DataStorageProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface DataStorageProvider { + + void initialize(YamlDocument config); + + void disable(); + + StorageType getStorageType(); + + CompletableFuture> getPlayerData(UUID uuid, boolean lock); + + CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock); + + CompletableFuture updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock); + + void updateManyPlayersData(Collection users, boolean unlock); + + void lockOrUnlockPlayerData(UUID uuid, boolean lock); + + Set getUniqueUsers(); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/StorageManager.java b/api/src/main/java/net/momirealms/customfishing/api/storage/StorageManager.java new file mode 100644 index 00000000..bb4f72e6 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/StorageManager.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage; + +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface StorageManager extends Reloadable { + + @NotNull + String getServerID(); + + @NotNull + Optional getOnlineUser(UUID uuid); + + @NotNull + Collection getOnlineUsers(); + + CompletableFuture> getOfflineUserData(UUID uuid, boolean lock); + + CompletableFuture saveUserData(UserData userData, boolean unlock); + + @NotNull + DataStorageProvider getDataSource(); + + boolean isRedisEnabled(); + + /** + * Converts PlayerData to bytes. + * + * @param data The PlayerData to be converted. + * @return The byte array representation of PlayerData. + */ + byte[] toBytes(@NotNull PlayerData data); + + /** + * Converts PlayerData to JSON format. + * + * @param data The PlayerData to be converted. + * @return The JSON string representation of PlayerData. + */ + @NotNull String toJson(@NotNull PlayerData data); + + /** + * Converts JSON string to PlayerData. + * + * @param json The JSON string to be converted. + * @return The PlayerData object. + */ + @NotNull PlayerData fromJson(String json); + + /** + * Converts bytes to PlayerData. + * + * @param data The byte array to be converted. + * @return The PlayerData object. + */ + @NotNull PlayerData fromBytes(byte[] data); +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/StorageType.java b/api/src/main/java/net/momirealms/customfishing/api/storage/StorageType.java similarity index 94% rename from api/src/main/java/net/momirealms/customfishing/api/data/StorageType.java rename to api/src/main/java/net/momirealms/customfishing/api/storage/StorageType.java index da6a3fe4..8a641db4 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/StorageType.java +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/StorageType.java @@ -15,10 +15,9 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data; +package net.momirealms.customfishing.api.storage; public enum StorageType { - JSON, YAML, H2, diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/data/EarningData.java b/api/src/main/java/net/momirealms/customfishing/api/storage/data/EarningData.java new file mode 100644 index 00000000..60b1e8e3 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/data/EarningData.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage.data; + +import com.google.gson.annotations.SerializedName; + +import java.util.Calendar; + +/** + * The EarningData class holds data related to the earnings of a player from selling fish. + * It includes the total earnings and the date of the earnings record. + */ +public class EarningData { + + @SerializedName("earnings") + public double earnings; + @SerializedName("date") + public int date; + + /** + * Constructs a new EarningData instance with specified earnings and date. + * + * @param earnings the total earnings from fishing. + * @param date the date of the earnings record. + */ + public EarningData(double earnings, int date) { + this.earnings = earnings; + this.date = date; + this.refresh(); + } + + /** + * Creates an instance of EarningData with default values (zero earnings and date). + * + * @return a new instance of EarningData with default values. + */ + public static EarningData empty() { + return new EarningData(0d, 0); + } + + public EarningData copy() { + return new EarningData(earnings, date); + } + + public double earnings() { + return earnings; + } + + public int date() { + return date; + } + + public void refresh() { + Calendar calendar = Calendar.getInstance(); + int dat = (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE); + if (dat != date) { + date = dat; + earnings = 0; + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/InventoryData.java b/api/src/main/java/net/momirealms/customfishing/api/storage/data/InventoryData.java similarity index 62% rename from api/src/main/java/net/momirealms/customfishing/api/data/InventoryData.java rename to api/src/main/java/net/momirealms/customfishing/api/storage/data/InventoryData.java index ab8a3208..1d2b2118 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/InventoryData.java +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/data/InventoryData.java @@ -15,22 +15,36 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data; +package net.momirealms.customfishing.api.storage.data; import com.google.gson.annotations.SerializedName; +/** + * The InventoryData class holds data related to a player's fishing bag. + * It includes a serialized representation of the inventory and the size of the inventory. + */ public class InventoryData { @SerializedName("inventory") public String serialized; - @SerializedName("size") public int size; + /** + * Creates an instance of InventoryData with default values (empty inventory and size of 9). + * + * @return a new instance of InventoryData with default values. + */ public static InventoryData empty() { return new InventoryData("", 9); } + /** + * Constructs a new InventoryData instance with specified serialized inventory and size. + * + * @param serialized the serialized representation of the inventory. + * @param size the size of the inventory. + */ public InventoryData(String serialized, int size) { this.serialized = serialized; this.size = size; diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/data/PlayerData.java b/api/src/main/java/net/momirealms/customfishing/api/storage/data/PlayerData.java new file mode 100644 index 00000000..97dd6fac --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/data/PlayerData.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage.data; + +import com.google.gson.annotations.SerializedName; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +import static java.util.Objects.requireNonNull; + +/** + * The PlayerData class holds data related to a player. + * It includes the player's name, their fishing statistics, inventory data, and earnings data. + */ +public class PlayerData { + + public static final String DEFAULT_NAME = ""; + public static final StatisticData DEFAULT_STATISTICS = StatisticData.empty(); + public static final InventoryData DEFAULT_BAG = InventoryData.empty(); + public static final EarningData DEFAULT_EARNING = EarningData.empty(); + + @SerializedName("name") + protected String name; + @SerializedName("stats") + protected StatisticData statisticsData; + @SerializedName("bag") + protected InventoryData bagData; + @SerializedName("trade") + protected EarningData earningData; + transient private UUID uuid; + transient private boolean locked; + + public PlayerData(UUID uuid, String name, StatisticData statisticsData, InventoryData bagData, EarningData earningData, boolean isLocked) { + this.name = name; + this.statisticsData = statisticsData; + this.bagData = bagData; + this.earningData = earningData; + this.locked = isLocked; + this.uuid = uuid; + } + + public static Builder builder() { + return new Builder(); + } + + public static PlayerData empty() { + return new Builder() + .bag(InventoryData.empty()) + .earnings(EarningData.empty()) + .statistics(StatisticData.empty()) + .uuid(new UUID(0, 0)) + .locked(false) + .build(); + } + + /** + * The Builder class provides a fluent API for constructing PlayerData instances. + */ + public static class Builder { + + private String name = DEFAULT_NAME; + private StatisticData statisticsData = DEFAULT_STATISTICS; + private InventoryData bagData = DEFAULT_BAG; + private EarningData earningData = DEFAULT_EARNING; + private boolean isLocked = false; + private UUID uuid; + + @NotNull + public Builder name(@NotNull String name) { + this.name = name; + return this; + } + + @NotNull + public Builder uuid(@NotNull UUID uuid) { + this.uuid = uuid; + return this; + } + + @NotNull + public Builder locked(boolean locked) { + this.isLocked = locked; + return this; + } + + @NotNull + public Builder statistics(@Nullable StatisticData statisticsData) { + this.statisticsData = statisticsData; + return this; + } + + @NotNull + public Builder bag(@Nullable InventoryData inventoryData) { + this.bagData = inventoryData; + return this; + } + + @NotNull + public Builder earnings(@Nullable EarningData earningData) { + this.earningData = earningData; + return this; + } + + @NotNull + public PlayerData build() { + return new PlayerData(requireNonNull(uuid), name, statisticsData, bagData, earningData, isLocked); + } + } + + /** + * Gets the statistics data for the player. + * + * @return the fishing statistics data. + */ + public StatisticData statistics() { + return statisticsData; + } + + /** + * Gets the bag data for the player. + * + * @return the bag data. + */ + public InventoryData bagData() { + return bagData; + } + + /** + * Gets the earnings data for the player. + * + * @return the earnings data. + */ + public EarningData earningData() { + return earningData; + } + + /** + * Gets the name of the player. + * + * @return the player's name. + */ + public String name() { + return name; + } + + /** + * Gets if the data is locked + * + * @return locked or not + */ + public boolean locked() { + return locked; + } + + public void locked(boolean locked) { + this.locked = locked; + } + + /** + * Gets the uuid + * + * @return uuid + */ + public UUID uuid() { + return uuid; + } + + public void uuid(UUID uuid) { + this.uuid = uuid; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/StatisticData.java b/api/src/main/java/net/momirealms/customfishing/api/storage/data/StatisticData.java similarity index 64% rename from api/src/main/java/net/momirealms/customfishing/api/data/StatisticData.java rename to api/src/main/java/net/momirealms/customfishing/api/storage/data/StatisticData.java index 80b4cef4..24ef0a7a 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/StatisticData.java +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/data/StatisticData.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data; +package net.momirealms.customfishing.api.storage.data; import com.google.gson.annotations.SerializedName; import org.jetbrains.annotations.NotNull; @@ -23,6 +23,10 @@ import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; +/** + * The StatisticData class stores fishing statistics including amounts and sizes + * of fish caught, represented as maps. + */ public class StatisticData { @SerializedName(value="amount", alternate={"map"}) @@ -31,16 +35,30 @@ public class StatisticData { @SerializedName("size") public Map sizeMap; - public StatisticData() { + /** + * Default constructor that initializes the sizeMap and amountMap as empty HashMaps. + */ + private StatisticData() { this.sizeMap = new HashMap<>(); this.amountMap = new HashMap<>(); } + /** + * Parameterized constructor that initializes the sizeMap and amountMap with provided values. + * + * @param amount a map containing the amount of each type of fish caught. + * @param size a map containing the size of each type of fish caught. + */ public StatisticData(@NotNull Map amount, @NotNull Map size) { this.amountMap = amount; this.sizeMap = size; } + /** + * Creates an instance of StatisticData with empty maps. + * + * @return a new instance of StatisticData with empty maps. + */ public static StatisticData empty() { return new StatisticData(); } diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserData.java b/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserData.java new file mode 100644 index 00000000..00aaa036 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserData.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage.user; + +import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.api.storage.data.EarningData; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public interface UserData { + + /** + * Get the username + * + * @return user name + */ + @NotNull + String name(); + + /** + * Get the user's uuid + * + * @return uuid + */ + @NotNull + UUID uuid(); + + /** + * Get the player instance if that player is online + * + * @return player + */ + @Nullable + Player player(); + + /** + * Get the fishing bag holder + * + * @return fishing bag holder + */ + @NotNull + FishingBagHolder holder(); + + /** + * Get the player's earning data + * + * @return earning data + */ + @NotNull + EarningData earningData(); + + /** + * Get the player's statistics + * + * @return statistics + */ + @NotNull + FishingStatistics statistics(); + + /** + * If the user is online on current server + * + * @return online or not + */ + boolean isOnline(); + + /** + * If the data is locked + * + * @return locked or not + */ + boolean isLocked(); + + /** + * Get the data in another minimized format that can be saved + * + * @return player data + */ + @NotNull + PlayerData toPlayerData(); + + static Builder builder() { + return new UserDataImpl.BuilderImpl(); + } + + interface Builder { + + /** + * Set the username for the UserData being built. + * + * @param name the username to set. + * @return the current Builder instance. + */ + Builder name(String name); + + /** + * Set the UUID for the UserData being built. + * + * @param uuid the UUID to set. + * @return the current Builder instance. + */ + Builder uuid(UUID uuid); + + /** + * Set the FishingBagHolder for the UserData being built. + * + * @param holder the FishingBagHolder to set. + * @return the current Builder instance. + */ + Builder holder(FishingBagHolder holder); + + /** + * Set the EarningData for the UserData being built. + * + * @param earningData the EarningData to set. + * @return the current Builder instance. + */ + Builder earningData(EarningData earningData); + + /** + * Set the FishingStatistics for the UserData being built. + * + * @param statistics the FishingStatistics to set. + * @return the current Builder instance. + */ + Builder statistics(FishingStatistics statistics); + + Builder locked(boolean isLocked); + + Builder data(PlayerData playerData); + + /** + * Build and return the UserData instance based on the current state of the Builder. + * + * @return the constructed UserData instance. + */ + UserData build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserDataImpl.java b/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserDataImpl.java new file mode 100644 index 00000000..fafb6652 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/storage/user/UserDataImpl.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.api.storage.user; + +import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.api.storage.data.EarningData; +import net.momirealms.customfishing.api.storage.data.InventoryData; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.data.StatisticData; +import net.momirealms.customfishing.api.util.InventoryUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; + +public class UserDataImpl implements UserData { + + private final String name; + private final UUID uuid; + private final FishingBagHolder holder; + private final EarningData earningData; + private final FishingStatistics statistics; + private final boolean isLocked; + + public UserDataImpl(String name, UUID uuid, FishingBagHolder holder, EarningData earningData, FishingStatistics statistics, boolean isLocked) { + this.name = name; + this.uuid = uuid; + this.holder = holder; + this.earningData = earningData; + this.statistics = statistics; + this.isLocked = isLocked; + } + + public static class BuilderImpl implements Builder { + private String name; + private UUID uuid; + private FishingBagHolder holder; + private EarningData earningData; + private FishingStatistics statistics; + private boolean isLocked; + @Override + public Builder name(String name) { + this.name = name; + return this; + } + @Override + public Builder uuid(UUID uuid) { + this.uuid = uuid; + return this; + } + @Override + public Builder holder(FishingBagHolder holder) { + this.holder = holder; + return this; + } + @Override + public Builder earningData(EarningData earningData) { + this.earningData = earningData.copy(); + return this; + } + @Override + public Builder statistics(FishingStatistics statistics) { + this.statistics = statistics; + return this; + } + @Override + public Builder locked(boolean isLocked) { + this.isLocked = isLocked; + return this; + } + @Override + public Builder data(PlayerData playerData) { + this.isLocked = playerData.locked(); + this.uuid = playerData.uuid(); + this.name = playerData.name(); + this.earningData = playerData.earningData().copy(); + this.holder = FishingBagHolder.create(playerData.uuid(), InventoryUtils.getInventoryItems(playerData.bagData().serialized), playerData.bagData().size); + this.statistics = FishingStatistics.builder().amountMap(playerData.statistics().amountMap).sizeMap(playerData.statistics().sizeMap).build(); + return this; + } + @Override + public UserData build() { + return new UserDataImpl(name, uuid, holder, earningData, statistics, isLocked); + } + } + + @NotNull + @Override + public String name() { + return name; + } + + @NotNull + @Override + public UUID uuid() { + return uuid; + } + + @Nullable + @Override + public Player player() { + return Bukkit.getPlayer(uuid); + } + + @NotNull + @Override + public FishingBagHolder holder() { + return holder; + } + + @NotNull + @Override + public EarningData earningData() { + return earningData; + } + + @NotNull + @Override + public FishingStatistics statistics() { + return statistics; + } + + @Override + public boolean isOnline() { + return Optional.ofNullable(Bukkit.getPlayer(uuid)).map(OfflinePlayer::isOnline).orElse(false); + } + + @Override + public boolean isLocked() { + return isLocked; + } + + @NotNull + @Override + public PlayerData toPlayerData() { + return PlayerData.builder() + .uuid(uuid) + .bag(new InventoryData(InventoryUtils.stacksToBase64(holder.getInventory().getStorageContents()), holder.getInventory().getSize())) + .earnings(earningData) + .statistics(new StatisticData(statistics.amountMap(), statistics.sizeMap())) + .name(name) + .build(); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/FontUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/EventUtils.java similarity index 56% rename from api/src/main/java/net/momirealms/customfishing/api/util/FontUtils.java rename to api/src/main/java/net/momirealms/customfishing/api/util/EventUtils.java index cd311e70..33a4d4ea 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/util/FontUtils.java +++ b/api/src/main/java/net/momirealms/customfishing/api/util/EventUtils.java @@ -17,23 +17,20 @@ package net.momirealms.customfishing.api.util; -/** - * Utility class for working with fonts in text. - */ -public class FontUtils { +import org.bukkit.Bukkit; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; - private FontUtils() { - throw new UnsupportedOperationException("This class cannot be instantiated"); +public class EventUtils { + + public static void fireAndForget(Event event) { + Bukkit.getPluginManager().callEvent(event); } - /** - * Surrounds the given text with a specified font tag. - * - * @param text The text to be surrounded with the font tag. - * @param font The font to use in the font tag. - * @return The input text surrounded by the font tag. - */ - public static String surroundWithFont(String text, String font) { - return "" + text + ""; + public static boolean fireAndCheckCancel(Event event) { + if (!(event instanceof Cancellable cancellable)) + throw new IllegalArgumentException("Only cancellable events are allowed here"); + Bukkit.getPluginManager().callEvent(event); + return cancellable.isCancelled(); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/InventoryUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/InventoryUtils.java index bb1645b6..1b75dd2b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/util/InventoryUtils.java +++ b/api/src/main/java/net/momirealms/customfishing/api/util/InventoryUtils.java @@ -17,11 +17,6 @@ package net.momirealms.customfishing.api.util; -import net.kyori.adventure.text.Component; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.util.io.BukkitObjectInputStream; import org.bukkit.util.io.BukkitObjectOutputStream; @@ -32,8 +27,6 @@ import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; /** * Utility class for working with Bukkit Inventories and item stacks. @@ -41,65 +34,6 @@ import java.lang.reflect.Method; public class InventoryUtils { private InventoryUtils() { - throw new UnsupportedOperationException("This class cannot be instantiated"); - } - - /** - * Create a custom inventory with a specified size and title component. - * - * @param inventoryHolder The holder of the inventory. - * @param size The size of the inventory. - * @param component The title component of the inventory. - * @return The created Inventory instance. - */ - public static Inventory createInventory(InventoryHolder inventoryHolder, int size, Component component) { - try { - boolean isSpigot = CustomFishingPlugin.get().getVersionManager().isSpigot(); - Method createInvMethod = ReflectionUtils.bukkitClass.getMethod( - "createInventory", - InventoryHolder.class, - int.class, - isSpigot ? String.class : ReflectionUtils.componentClass - ); - return (Inventory) createInvMethod.invoke( - null, - inventoryHolder, - size, - isSpigot ? CustomFishingPlugin.get().getAdventure().componentToLegacy(component) : CustomFishingPlugin.get().getAdventure().shadedComponentToOriginalComponent(component) - ); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException exception) { - exception.printStackTrace(); - return null; - } - } - - /** - * Create a custom inventory with a specified type and title component. - * - * @param inventoryHolder The holder of the inventory. - * @param type The type of the inventory. - * @param component The title component of the inventory. - * @return The created Inventory instance. - */ - public static Inventory createInventory(InventoryHolder inventoryHolder, InventoryType type, Component component) { - try { - boolean isSpigot = CustomFishingPlugin.get().getVersionManager().isSpigot(); - Method createInvMethod = ReflectionUtils.bukkitClass.getMethod( - "createInventory", - InventoryHolder.class, - InventoryType.class, - isSpigot ? String.class : ReflectionUtils.componentClass - ); - return (Inventory) createInvMethod.invoke( - null, - inventoryHolder, - type, - isSpigot ? CustomFishingPlugin.get().getAdventure().componentToLegacy(component) : CustomFishingPlugin.get().getAdventure().shadedComponentToOriginalComponent(component) - ); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException exception) { - exception.printStackTrace(); - return null; - } } /** @@ -109,7 +43,7 @@ public class InventoryUtils { * @return The Base64-encoded string representing the serialized ItemStacks. */ public static @NotNull String stacksToBase64(ItemStack[] contents) { - if (contents.length == 0) { + if (contents == null || contents.length == 0) { return ""; } try { @@ -124,7 +58,7 @@ public class InventoryUtils { outputStream.close(); return Base64Coder.encodeLines(byteArr); } catch (IOException e) { - LogUtils.warn("Encoding error", e); + e.printStackTrace(); } return ""; } @@ -138,40 +72,29 @@ public class InventoryUtils { @Nullable public static ItemStack[] getInventoryItems(String base64) { ItemStack[] itemStacks = null; - try { - itemStacks = stacksFromBase64(base64); - } catch (IllegalArgumentException exception) { - exception.printStackTrace(); - } - return itemStacks; - } - - private static ItemStack[] stacksFromBase64(String data) { - if (data == null || data.equals("")) return new ItemStack[]{}; - + if (base64 == null || base64.isEmpty()) return new ItemStack[]{}; ByteArrayInputStream inputStream; try { - inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data)); - } catch (IllegalArgumentException e) { + inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64)); + } catch (IllegalArgumentException ignored) { return new ItemStack[]{}; } BukkitObjectInputStream dataInput = null; - ItemStack[] stacks = null; try { dataInput = new BukkitObjectInputStream(inputStream); - stacks = new ItemStack[dataInput.readInt()]; - } catch (IOException e) { - e.printStackTrace(); + itemStacks = new ItemStack[dataInput.readInt()]; + } catch (IOException ioException) { + ioException.printStackTrace(); } - if (stacks == null) return new ItemStack[]{}; - for (int i = 0; i < stacks.length; i++) { + if (itemStacks == null) return new ItemStack[]{}; + for (int i = 0; i < itemStacks.length; i++) { try { - stacks[i] = (ItemStack) dataInput.readObject(); + itemStacks[i] = (ItemStack) dataInput.readObject(); } catch (IOException | ClassNotFoundException | NullPointerException e) { try { dataInput.close(); - } catch (IOException exception) { - LogUtils.severe("Failed to read fishing bag data"); + } catch (IOException ioException) { + ioException.printStackTrace(); } return null; } @@ -180,6 +103,6 @@ public class InventoryUtils { dataInput.close(); } catch (IOException ignored) { } - return stacks; + return itemStacks; } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/LogUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/LogUtils.java deleted file mode 100644 index b99d4112..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/util/LogUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.util; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import org.jetbrains.annotations.NotNull; - -import java.util.logging.Level; - -/** - * Utility class for logging messages with various log levels. - */ -public final class LogUtils { - - /** - * Log an informational message. - * - * @param message The message to log. - */ - public static void info(@NotNull String message) { - CustomFishingPlugin.getInstance().getLogger().info(message); - } - - /** - * Log a warning message. - * - * @param message The message to log. - */ - public static void warn(@NotNull String message) { - CustomFishingPlugin.getInstance().getLogger().warning(message); - } - - /** - * Log a severe error message. - * - * @param message The message to log. - */ - public static void severe(@NotNull String message) { - CustomFishingPlugin.getInstance().getLogger().severe(message); - } - - /** - * Log a warning message with a throwable exception. - * - * @param message The message to log. - * @param throwable The throwable exception to log. - */ - public static void warn(@NotNull String message, Throwable throwable) { - CustomFishingPlugin.getInstance().getLogger().log(Level.WARNING, message, throwable); - } - - /** - * Log a severe error message with a throwable exception. - * - * @param message The message to log. - * @param throwable The throwable exception to log. - */ - public static void severe(@NotNull String message, Throwable throwable) { - CustomFishingPlugin.getInstance().getLogger().log(Level.SEVERE, message, throwable); - } - - private LogUtils() { - throw new UnsupportedOperationException("This class cannot be instantiated"); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/MoonPhase.java b/api/src/main/java/net/momirealms/customfishing/api/util/MoonPhase.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/util/MoonPhase.java rename to api/src/main/java/net/momirealms/customfishing/api/util/MoonPhase.java index 25803379..338e2f72 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/util/MoonPhase.java +++ b/api/src/main/java/net/momirealms/customfishing/api/util/MoonPhase.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.util; +package net.momirealms.customfishing.api.util; import org.jetbrains.annotations.NotNull; diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/OffsetUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/OffsetUtils.java index bfb0956e..55a1c4a8 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/util/OffsetUtils.java +++ b/api/src/main/java/net/momirealms/customfishing/api/util/OffsetUtils.java @@ -17,7 +17,9 @@ package net.momirealms.customfishing.api.util; -import org.bukkit.configuration.ConfigurationSection; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import org.jetbrains.annotations.ApiStatus; /** * Utility class for generating offset characters based on a font configuration. @@ -47,12 +49,8 @@ public class OffsetUtils { private static String positive_64; private static String positive_128; - /** - * Load font configuration from a given section. - * - * @param section The configuration section containing font settings. - */ - public static void loadConfig(ConfigurationSection section) { + @ApiStatus.Internal + public static void load(Section section) { if (section != null) { font = section.getString("font", "customfishing:offset_chars"); positive_1 = section.getString("1"); @@ -167,9 +165,9 @@ public class OffsetUtils { */ public static String getOffsetChars(int n) { if (n > 0) { - return "" + getShortestPosChars(n) + ""; + return AdventureHelper.surroundWithMiniMessageFont(getShortestPosChars(n), font); } else { - return "" + getShortestNegChars(-n) + ""; + return AdventureHelper.surroundWithMiniMessageFont(getShortestNegChars(-n), font); } } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/ReflectionUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/ReflectionUtils.java deleted file mode 100644 index 5283a8de..00000000 --- a/api/src/main/java/net/momirealms/customfishing/api/util/ReflectionUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.api.util; - -import com.comphenix.protocol.utility.MinecraftReflection; -import net.momirealms.customfishing.api.CustomFishingPlugin; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class ReflectionUtils { - - public static Object removeBossBarPacket; - public static Constructor progressConstructor; - public static Constructor updateConstructor; - public static Method iChatComponentMethod; - public static Method gsonDeserializeMethod; - public static Object gsonInstance; - public static Class componentClass; - public static Class bukkitClass; - - public static void load() { - if (CustomFishingPlugin.get().getVersionManager().isMojmap()) { - try { - Class bar = Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket"); - Field remove = bar.getDeclaredField("REMOVE_OPERATION"); - remove.setAccessible(true); - removeBossBarPacket = remove.get(null); - Class packetBossClassF = Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket$UpdateProgressOperation"); - progressConstructor = packetBossClassF.getDeclaredConstructor(float.class); - progressConstructor.setAccessible(true); - Class packetBossClassE = Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket$UpdateNameOperation"); - updateConstructor = packetBossClassE.getDeclaredConstructor(MinecraftReflection.getIChatBaseComponentClass()); - updateConstructor.setAccessible(true); - Class craftChatMessageClass = Class.forName("org.bukkit.craftbukkit.util.CraftChatMessage"); - iChatComponentMethod = craftChatMessageClass.getDeclaredMethod("fromJSON", String.class); - iChatComponentMethod.setAccessible(true); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException exception) { - LogUtils.severe("Error occurred when loading reflections", exception); - } - } else { - try { - Class bar = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss"); - Field remove = bar.getDeclaredField("f"); - remove.setAccessible(true); - removeBossBarPacket = remove.get(null); - Class packetBossClassF = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss$f"); - progressConstructor = packetBossClassF.getDeclaredConstructor(float.class); - progressConstructor.setAccessible(true); - Class packetBossClassE = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutBoss$e"); - updateConstructor = packetBossClassE.getDeclaredConstructor(MinecraftReflection.getIChatBaseComponentClass()); - updateConstructor.setAccessible(true); - iChatComponentMethod = MinecraftReflection.getChatSerializerClass().getMethod("a", String.class); - iChatComponentMethod.setAccessible(true); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException exception) { - LogUtils.severe("Error occurred when loading reflections", exception); - } - } - if (CustomFishingPlugin.get().getVersionManager().isSpigot()) return; - try { - componentClass = Class.forName("net;kyori;adventure;text;Component".replace(";", ".")); - bukkitClass = Class.forName("org;bukkit;Bukkit".replace(";", ".")); - Class gsonComponentSerializerClass = Class.forName("net;kyori;adventure;text;serializer;gson;GsonComponentSerializer".replace(";", ".")); - Class gsonComponentSerializerImplClass = Class.forName("net;kyori;adventure;text;serializer;gson;GsonComponentSerializerImpl".replace(";", ".")); - Method gsonMethod = gsonComponentSerializerClass.getMethod("gson"); - gsonInstance = gsonMethod.invoke(null); - gsonDeserializeMethod = gsonComponentSerializerImplClass.getMethod("deserialize", String.class); - gsonDeserializeMethod.setAccessible(true); - } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException exception) { - LogUtils.severe("Error occurred when loading reflections", exception); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/common/SimpleLocation.java b/api/src/main/java/net/momirealms/customfishing/api/util/SimpleLocation.java similarity index 80% rename from api/src/main/java/net/momirealms/customfishing/api/common/SimpleLocation.java rename to api/src/main/java/net/momirealms/customfishing/api/util/SimpleLocation.java index b14bec65..c83d3208 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/common/SimpleLocation.java +++ b/api/src/main/java/net/momirealms/customfishing/api/util/SimpleLocation.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.common; +package net.momirealms.customfishing.api.util; import org.bukkit.Location; @@ -51,13 +51,13 @@ public record SimpleLocation(String worldName, int x, int y, int z) { public int hashCode() { int hash = 3; hash = 19 * hash + (worldName != null ? worldName.hashCode() : 0); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.x)); + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.y)); + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.z)); return hash; } - public static SimpleLocation getByBukkitLocation(Location location) { + public static SimpleLocation of(Location location) { return new SimpleLocation(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/TagUtils.java b/api/src/main/java/net/momirealms/customfishing/api/util/TagUtils.java new file mode 100644 index 00000000..f83ef1f2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customfishing/api/util/TagUtils.java @@ -0,0 +1,22 @@ +package net.momirealms.customfishing.api.util; + +import net.momirealms.customfishing.api.mechanic.item.tag.TagValueType; +import net.momirealms.customfishing.common.util.Pair; + +import java.util.Locale; + +public class TagUtils { + + public static Pair toTypeAndData(String str) { + String[] parts = str.split("\\s+", 2); + if (parts.length == 1) { + return Pair.of(TagValueType.STRING, parts[0]); + } + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid value format: " + str); + } + TagValueType type = TagValueType.valueOf(parts[0].substring(1, parts[0].length() - 1).toUpperCase(Locale.ENGLISH)); + String data = parts[1]; + return Pair.of(type, data); + } +} diff --git a/build.gradle.kts b/build.gradle.kts index d06408b6..ea3eae0e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,80 +1,47 @@ +import java.io.ByteArrayOutputStream + plugins { id("java") - id("application") - id("maven-publish") - id("io.github.goooler.shadow") version "8.1.7" } -allprojects { +val commitID : String = versionBanner() +ext["commitID"] = commitID - version = "2.1.6.6" +subprojects { - apply() apply(plugin = "java") - apply(plugin = "application") - apply(plugin = "io.github.goooler.shadow") - apply(plugin = "org.gradle.maven-publish") - - application { - mainClass.set("") - } + apply(plugin = "java-library") repositories { mavenCentral() - maven("https://maven.aliyun.com/repository/public/") - maven("https://papermc.io/repo/repository/maven-public/") - maven("https://oss.sonatype.org/content/groups/public/") - maven("https://repo.dmulloy2.net/repository/public/") - maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") - maven("https://repo.codemc.org/repository/maven-public/") - maven("https://maven.enginehub.org/repo/") - maven("https://jitpack.io/") - maven("https://mvn.lumine.io/repository/maven-public/") - maven("https://repo.rapture.pw/repository/maven-releases/") - maven("https://nexus.phoenixdevt.fr/repository/maven-public/") - maven("https://r.irepo.space/maven/") - maven("https://repo.auxilor.io/repository/maven-public/") - maven("https://repo.william278.net/releases/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - maven("https://repo.minebench.de/") - maven("https://repo.xenondevs.xyz/releases/") - maven("https://repo.oraxen.com/releases") - maven("https://nexus.betonquest.org/repository/betonquest/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://jitpack.io/") // sparrow-heart, rtag + maven("https://papermc.io/repo/repository/maven-public/") // paper + maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // spigot + } + + tasks.processResources { + filteringCharset = "UTF-8" + + filesMatching(arrayListOf("library-version.properties")) { + expand(rootProject.properties) + } + + filesMatching(arrayListOf("plugin.yml", "*.yml", "*/*.yml")) { + expand( + Pair("git_version", commitID), + Pair("project_version", rootProject.properties["project_version"]), + Pair("config_version", rootProject.properties["config_version"]) + ) + } } } -subprojects { - tasks.processResources { - val props = mapOf("version" to version) - inputs.properties(props) - filteringCharset = "UTF-8" - filesMatching("*plugin.yml") { - expand(props) - } - } - - tasks.withType { - options.encoding = "UTF-8" - options.release.set(17) - } - - tasks.shadowJar { - destinationDirectory.set(file("$rootDir/target")) - archiveClassifier.set("") - archiveFileName.set("CustomFishing-" + project.name + "-" + project.version + ".jar") - } - - if ("api" == project.name) { - publishing { - publications { - create("mavenJava") { - groupId = "net.momirealms" - artifactId = "CustomFishing" - version = rootProject.version.toString() - artifact(tasks.shadowJar) - } - } - } +fun versionBanner(): String { + val os = ByteArrayOutputStream() + project.exec { + commandLine = "git rev-parse --short=8 HEAD".split(" ") + standardOutput = os } + return String(os.toByteArray()).trim() } \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 00000000..4bdcb717 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,38 @@ +repositories { + maven("https://jitpack.io/") // rtag +} + +dependencies { + compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") { + exclude(module = "adventure-bom") + exclude(module = "checker-qual") + exclude(module = "annotations") + } + compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") + compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}") + compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + compileOnly("org.jetbrains:annotations:${rootProject.properties["jetbrains_annotations_version"]}") + compileOnly("org.slf4j:slf4j-api:${rootProject.properties["slf4j_version"]}") + compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.properties["log4j_version"]}") + compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") + compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") + compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}") + compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}") + compileOnly("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}") + compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}") + compileOnly("com.google.guava:guava:${rootProject.properties["guava_version"]}") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandFeature.java b/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandFeature.java new file mode 100644 index 00000000..0597df25 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandFeature.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customfishing.common.sender.SenderFactory; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; + +public abstract class AbstractCommandFeature implements CommandFeature { + + protected final CustomFishingCommandManager commandManager; + protected CommandConfig commandConfig; + + public AbstractCommandFeature(CustomFishingCommandManager commandManager) { + this.commandManager = commandManager; + } + + protected abstract SenderFactory getSenderFactory(); + + public abstract Command.Builder assembleCommand(CommandManager manager, Command.Builder builder); + + @Override + @SuppressWarnings("unchecked") + public Command registerCommand(CommandManager manager, Command.Builder builder) { + Command command = (Command) assembleCommand(manager, builder).build(); + manager.command(command); + return command; + } + + @Override + public void registerRelatedFunctions() { + // empty + } + + @Override + public void unregisterRelatedFunctions() { + // empty + } + + @Override + @SuppressWarnings("unchecked") + public void handleFeedback(CommandContext context, TranslatableComponent.Builder key, Component... args) { + if (context.flags().hasFlag("silent")) { + return; + } + commandManager.handleCommandFeedback((C) context.sender(), key, args); + } + + @Override + public void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args) { + commandManager.handleCommandFeedback(sender, key, args); + } + + @Override + public CustomFishingCommandManager getCustomFishingCommandManager() { + return commandManager; + } + + @Override + public CommandConfig getCommandConfig() { + return commandConfig; + } + + public void setCommandConfig(CommandConfig commandConfig) { + this.commandConfig = commandConfig; + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandManager.java b/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandManager.java new file mode 100644 index 00000000..32f9d41a --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/AbstractCommandManager.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customfishing.common.locale.CustomFishingCaptionFormatter; +import net.momirealms.customfishing.common.locale.CustomFishingCaptionProvider; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.sender.Sender; +import net.momirealms.customfishing.common.util.ArrayUtils; +import net.momirealms.customfishing.common.util.TriConsumer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.caption.Caption; +import org.incendo.cloud.caption.StandardCaptionKeys; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.exception.*; +import org.incendo.cloud.exception.handling.ExceptionContext; +import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +public abstract class AbstractCommandManager implements CustomFishingCommandManager { + + protected final HashSet> registeredRootCommandComponents = new HashSet<>(); + protected final HashSet> registeredFeatures = new HashSet<>(); + protected final CommandManager commandManager; + protected final CustomFishingPlugin plugin; + private final CustomFishingCaptionFormatter captionFormatter = new CustomFishingCaptionFormatter(); + private final MinecraftExceptionHandler.Decorator decorator = (formatter, ctx, msg) -> msg; + + private TriConsumer feedbackConsumer; + + public AbstractCommandManager(CustomFishingPlugin plugin, CommandManager commandManager) { + this.commandManager = commandManager; + this.plugin = plugin; + this.inject(); + this.feedbackConsumer = defaultFeedbackConsumer(); + } + + @Override + public void setFeedbackConsumer(@NotNull TriConsumer feedbackConsumer) { + this.feedbackConsumer = feedbackConsumer; + } + + @Override + public TriConsumer defaultFeedbackConsumer() { + return ((sender, node, component) -> { + wrapSender(sender).sendMessage( + component, true + ); + }); + } + + protected abstract Sender wrapSender(C c); + + private void inject() { + getCommandManager().captionRegistry().registerProvider(new CustomFishingCaptionProvider<>()); + injectExceptionHandler(InvalidSyntaxException.class, MinecraftExceptionHandler.createDefaultInvalidSyntaxHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SYNTAX); + injectExceptionHandler(InvalidCommandSenderException.class, MinecraftExceptionHandler.createDefaultInvalidSenderHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SENDER); + injectExceptionHandler(NoPermissionException.class, MinecraftExceptionHandler.createDefaultNoPermissionHandler(), StandardCaptionKeys.EXCEPTION_NO_PERMISSION); + injectExceptionHandler(ArgumentParseException.class, MinecraftExceptionHandler.createDefaultArgumentParsingHandler(), StandardCaptionKeys.EXCEPTION_INVALID_ARGUMENT); + injectExceptionHandler(CommandExecutionException.class, MinecraftExceptionHandler.createDefaultCommandExecutionHandler(), StandardCaptionKeys.EXCEPTION_UNEXPECTED); + } + + private void injectExceptionHandler(Class type, MinecraftExceptionHandler.MessageFactory factory, Caption key) { + getCommandManager().exceptionController().registerHandler(type, ctx -> { + final @Nullable ComponentLike message = factory.message(captionFormatter, (ExceptionContext) ctx); + if (message != null) { + handleCommandFeedback(ctx.context().sender(), key.key(), decorator.decorate(captionFormatter, ctx, message.asComponent()).asComponent()); + } + }); + } + + @Override + public CommandConfig getCommandConfig(YamlDocument document, String featureID) { + Section section = document.getSection(featureID); + if (section == null) return null; + return new CommandConfig.Builder() + .permission(section.getString("permission")) + .usages(section.getStringList("usage")) + .enable(section.getBoolean("enable", false)) + .build(); + } + + @Override + public Collection> buildCommandBuilders(CommandConfig config) { + ArrayList> list = new ArrayList<>(); + for (String usage : config.getUsages()) { + if (!usage.startsWith("/")) continue; + String command = usage.substring(1).trim(); + String[] split = command.split(" "); + Command.Builder builder = new CommandBuilder.BasicCommandBuilder<>(getCommandManager(), split[0]) + .setCommandNode(ArrayUtils.subArray(split, 1)) + .setPermission(config.getPermission()) + .getBuiltCommandBuilder(); + list.add(builder); + } + return list; + } + + @Override + public void registerFeature(CommandFeature feature, CommandConfig config) { + if (!config.isEnable()) throw new RuntimeException("Registering a disabled command feature is not allowed"); + for (Command.Builder builder : buildCommandBuilders(config)) { + Command command = feature.registerCommand(commandManager, builder); + this.registeredRootCommandComponents.add(command.rootComponent()); + } + feature.registerRelatedFunctions(); + this.registeredFeatures.add(feature); + ((AbstractCommandFeature) feature).setCommandConfig(config); + } + + @Override + public void registerDefaultFeatures() { + YamlDocument document = plugin.getConfigManager().loadConfig(commandsFile); + this.getFeatures().values().forEach(feature -> { + CommandConfig config = getCommandConfig(document, feature.getFeatureID()); + if (config.isEnable()) { + registerFeature(feature, config); + } + }); + } + + @Override + public void unregisterFeatures() { + this.registeredRootCommandComponents.forEach(component -> this.commandManager.commandRegistrationHandler().unregisterRootCommand(component)); + this.registeredRootCommandComponents.clear(); + this.registeredFeatures.forEach(CommandFeature::unregisterRelatedFunctions); + this.registeredFeatures.clear(); + } + + @Override + public CommandManager getCommandManager() { + return commandManager; + } + + @Override + public void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args) { + TranslatableComponent component = key.arguments(args).build(); + this.feedbackConsumer.accept(sender, component.key(), TranslationManager.render(component)); + } + + @Override + public void handleCommandFeedback(C sender, String node, Component component) { + this.feedbackConsumer.accept(sender, node, component); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/CommandBuilder.java b/common/src/main/java/net/momirealms/customfishing/common/command/CommandBuilder.java new file mode 100644 index 00000000..2bf569c2 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/CommandBuilder.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public interface CommandBuilder { + + CommandBuilder setPermission(String permission); + + CommandBuilder setCommandNode(String... subNodes); + + Command.Builder getBuiltCommandBuilder(); + + class BasicCommandBuilder implements CommandBuilder { + + private Command.Builder commandBuilder; + + public BasicCommandBuilder(CommandManager commandManager, String rootNode) { + this.commandBuilder = commandManager.commandBuilder(rootNode); + } + + @Override + public CommandBuilder setPermission(String permission) { + this.commandBuilder = this.commandBuilder.permission(permission); + return this; + } + + @Override + public CommandBuilder setCommandNode(String... subNodes) { + for (String sub : subNodes) { + this.commandBuilder = this.commandBuilder.literal(sub); + } + return this; + } + + @Override + public Command.Builder getBuiltCommandBuilder() { + return commandBuilder; + } + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/CommandConfig.java b/common/src/main/java/net/momirealms/customfishing/common/command/CommandConfig.java new file mode 100644 index 00000000..d07e0307 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/CommandConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import java.util.ArrayList; +import java.util.List; + +public class CommandConfig { + + private boolean enable = false; + private List usages = new ArrayList<>(); + private String permission = null; + + private CommandConfig() { + } + + public CommandConfig(boolean enable, List usages, String permission) { + this.enable = enable; + this.usages = usages; + this.permission = permission; + } + + public boolean isEnable() { + return enable; + } + + public List getUsages() { + return usages; + } + + public String getPermission() { + return permission; + } + + public static class Builder { + + private final CommandConfig config; + + public Builder() { + this.config = new CommandConfig<>(); + } + + public Builder usages(List usages) { + config.usages = usages; + return this; + } + + public Builder permission(String permission) { + config.permission = permission; + return this; + } + + public Builder enable(boolean enable) { + config.enable = enable; + return this; + } + + public CommandConfig build() { + return config; + } + } +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/CommandFeature.java b/common/src/main/java/net/momirealms/customfishing/common/command/CommandFeature.java new file mode 100644 index 00000000..b3f88835 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/CommandFeature.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; + +public interface CommandFeature { + + Command registerCommand(CommandManager cloudCommandManager, Command.Builder builder); + + String getFeatureID(); + + void registerRelatedFunctions(); + + void unregisterRelatedFunctions(); + + void handleFeedback(CommandContext context, TranslatableComponent.Builder key, Component... args); + + void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args); + + CustomFishingCommandManager getCustomFishingCommandManager(); + + CommandConfig getCommandConfig(); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/command/CustomFishingCommandManager.java b/common/src/main/java/net/momirealms/customfishing/common/command/CustomFishingCommandManager.java new file mode 100644 index 00000000..09b69c55 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/command/CustomFishingCommandManager.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.command; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.util.Index; +import net.momirealms.customfishing.common.util.TriConsumer; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +public interface CustomFishingCommandManager { + + String commandsFile = "commands.yml"; + + void unregisterFeatures(); + + void registerFeature(CommandFeature feature, CommandConfig config); + + void registerDefaultFeatures(); + + Index> getFeatures(); + + void setFeedbackConsumer(@NotNull TriConsumer feedbackConsumer); + + TriConsumer defaultFeedbackConsumer(); + + CommandConfig getCommandConfig(YamlDocument document, String featureID); + + Collection> buildCommandBuilders(CommandConfig config); + + CommandManager getCommandManager(); + + void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args); + + void handleCommandFeedback(C sender, String node, Component component); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/config/ConfigLoader.java b/common/src/main/java/net/momirealms/customfishing/common/config/ConfigLoader.java new file mode 100644 index 00000000..1f07cb17 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/config/ConfigLoader.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.config; + +import dev.dejvokep.boostedyaml.YamlDocument; + +import java.io.File; + +public interface ConfigLoader { + + YamlDocument loadConfig(String filePath); + + YamlDocument loadConfig(String filePath, char routeSeparator); + + YamlDocument loadData(File file); + + YamlDocument loadData(File file, char routeSeparator); + + void saveResource(String filePath); +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/season/RealisticSeasonsImpl.java b/common/src/main/java/net/momirealms/customfishing/common/config/node/Node.java similarity index 51% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/season/RealisticSeasonsImpl.java rename to common/src/main/java/net/momirealms/customfishing/common/config/node/Node.java index f0999f1f..7357ee78 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/season/RealisticSeasonsImpl.java +++ b/common/src/main/java/net/momirealms/customfishing/common/config/node/Node.java @@ -15,25 +15,43 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.season; +package net.momirealms.customfishing.common.config.node; -import me.casperge.realisticseasons.api.SeasonsAPI; -import net.momirealms.customfishing.api.integration.SeasonInterface; -import org.bukkit.World; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class RealisticSeasonsImpl implements SeasonInterface { +import java.util.HashMap; + +public class Node { + + private final T value; + private final HashMap> childTree = new HashMap<>(); + + public Node(T value) { + this.value = value; + } + + public Node() { + this(null); + } + + @Nullable + public T nodeValue() { + return value; + } @NotNull - @Override - public String getSeason(World world) { - return switch (SeasonsAPI.getInstance().getSeason(world)) { - case WINTER -> "winter"; - case SPRING -> "spring"; - case SUMMER -> "summer"; - case FALL -> "autumn"; - case DISABLED -> "disabled"; - case RESTORE -> "restore"; - }; + public HashMap> getChildTree() { + return childTree; + } + + @Nullable + public Node getChild(String node) { + return childTree.get(node); + } + + @Nullable + public Node removeChild(String node) { + return childTree.remove(node); } } diff --git a/common/src/main/java/net/momirealms/customfishing/common/dependency/Dependency.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/Dependency.java new file mode 100644 index 00000000..0570aa42 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/Dependency.java @@ -0,0 +1,357 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.dependency; + +import net.momirealms.customfishing.common.dependency.relocation.Relocation; +import org.jetbrains.annotations.Nullable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * The dependencies used by CustomFishing. + */ +public enum Dependency { + + ASM( + "org.ow2.asm", + "asm", + "maven", + "asm" + ), + ASM_COMMONS( + "org.ow2.asm", + "asm-commons", + "maven", + "asm-commons" + ), + JAR_RELOCATOR( + "me.lucko", + "jar-relocator", + "maven", + "jar-relocator" + ), + H2_DRIVER( + "com.h2database", + "h2", + "maven", + "h2-driver" + ), + SQLITE_DRIVER( + "org.xerial", + "sqlite-jdbc", + "maven", + "sqlite-driver" + ), + CLOUD_CORE( + "org{}incendo", + "cloud-core", + "maven", + "cloud-core", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_BRIGADIER( + "org{}incendo", + "cloud-brigadier", + "maven", + "cloud-brigadier", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_SERVICES( + "org{}incendo", + "cloud-services", + "maven", + "cloud-services", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_BUKKIT( + "org{}incendo", + "cloud-bukkit", + "maven", + "cloud-bukkit", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_PAPER( + "org{}incendo", + "cloud-paper", + "maven", + "cloud-paper", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_MINECRAFT_EXTRAS( + "org{}incendo", + "cloud-minecraft-extras", + "maven", + "cloud-minecraft-extras", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("adventure", "net{}kyori{}adventure"), + Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + GEANTY_REF( + "io{}leangen{}geantyref", + "geantyref", + "maven", + "geantyref", + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + BOOSTED_YAML( + "dev{}dejvokep", + "boosted-yaml", + "maven", + "boosted-yaml", + Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") + ), + BYTEBUDDY( + "net{}bytebuddy", + "byte-buddy", + "maven", + "byte-buddy", + Relocation.of("bytebuddy", "net{}bytebuddy") + ), + MARIADB_DRIVER( + "org{}mariadb{}jdbc", + "mariadb-java-client", + "maven", + "mariadb-java-client", + Relocation.of("mariadb", "org{}mariadb") + ), + MYSQL_DRIVER( + "com{}mysql", + "mysql-connector-j", + "maven", + "mysql-connector-j", + Relocation.of("mysql", "com{}mysql") + ), + HIKARI_CP( + "com{}zaxxer", + "HikariCP", + "maven", + "hikari-cp", + Relocation.of("hikari", "com{}zaxxer{}hikari") + ), + MONGODB_DRIVER_CORE( + "org{}mongodb", + "mongodb-driver-core", + "maven", + "mongodb-driver-core", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ), + MONGODB_DRIVER_SYNC( + "org{}mongodb", + "mongodb-driver-sync", + "maven", + "mongodb-driver-sync", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ) { + @Override + public String getVersion() { + return Dependency.MONGODB_DRIVER_CORE.getVersion(); + } + }, + MONGODB_DRIVER_BSON( + "org{}mongodb", + "bson", + "maven", + "mongodb-bson", + Relocation.of("mongodb", "com{}mongodb"), + Relocation.of("bson", "org{}bson") + ) { + @Override + public String getVersion() { + return Dependency.MONGODB_DRIVER_CORE.getVersion(); + } + }, + COMMONS_POOL_2( + "org{}apache{}commons", + "commons-pool2", + "maven", + "commons-pool", + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + BSTATS_BASE( + "org{}bstats", + "bstats-base", + "maven", + "bstats-base", + Relocation.of("bstats", "org{}bstats") + ), + BSTATS_BUKKIT( + "org{}bstats", + "bstats-bukkit", + "maven", + "bstats-bukkit", + Relocation.of("bstats", "org{}bstats") + ) { + @Override + public String getVersion() { + return Dependency.BSTATS_BASE.getVersion(); + } + }, + GSON( + "com.google.code.gson", + "gson", + "maven", + "gson" + ), + CAFFEINE( + "com{}github{}ben-manes{}caffeine", + "caffeine", + "maven", + "caffeine", + Relocation.of("caffeine", "com{}github{}benmanes{}caffeine") + ), + JEDIS( + "redis{}clients", + "jedis", + "maven", + "jedis", + Relocation.of("jedis", "redis{}clients{}jedis"), + Relocation.of("commonspool2", "org{}apache{}commons{}pool2") + ), + EXP4J( + "net{}objecthunter", + "exp4j", + "maven", + "exp4j", + Relocation.of("exp4j", "net{}objecthunter{}exp4j") + ), + SLF4J_SIMPLE( + "org.slf4j", + "slf4j-simple", + "maven", + "slf4j_simple" + ) { + @Override + public String getVersion() { + return Dependency.SLF4J_API.getVersion(); + } + }, + SLF4J_API( + "org.slf4j", + "slf4j-api", + "maven", + "slf4j" + ), + LZ4( + "org{}lz4", + "lz4-java", + "maven", + "lz4-java", + Relocation.of("jpountz", "net{}jpountz") + ); + + private final List relocations; + private final String repo; + private final String groupId; + private String rawArtifactId; + private String customArtifactID; + + private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; + + Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID) { + this(groupId, rawArtifactId, repo, customArtifactID, new Relocation[0]); + } + + Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID, Relocation... relocations) { + this.rawArtifactId = rawArtifactId; + this.groupId = groupId; + this.relocations = new ArrayList<>(Arrays.stream(relocations).toList()); + this.repo = repo; + this.customArtifactID = customArtifactID; + } + + public Dependency setCustomArtifactID(String customArtifactID) { + this.customArtifactID = customArtifactID; + return this; + } + + public Dependency setRawArtifactID(String artifactId) { + this.rawArtifactId = artifactId; + return this; + } + + public String getVersion() { + return DependencyProperties.getDependencyVersion(customArtifactID); + } + + private static String rewriteEscaping(String s) { + return s.replace("{}", "."); + } + + public String getFileName(String classifier) { + String name = customArtifactID.toLowerCase(Locale.ROOT).replace('_', '-'); + String extra = classifier == null || classifier.isEmpty() + ? "" + : "-" + classifier; + return name + "-" + this.getVersion() + extra + ".jar"; + } + + String getMavenRepoPath() { + return String.format(MAVEN_FORMAT, + rewriteEscaping(groupId).replace(".", "/"), + rewriteEscaping(rawArtifactId), + getVersion(), + rewriteEscaping(rawArtifactId), + getVersion() + ); + } + + public List getRelocations() { + return this.relocations; + } + + /** + * Creates a {@link MessageDigest} suitable for computing the checksums + * of dependencies. + * + * @return the digest + */ + public static MessageDigest createDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Nullable + public String getRepo() { + return repo; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyDownloadException.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyDownloadException.java index f48aa426..6bbf30a7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyDownloadException.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyDownloadException.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies; +package net.momirealms.customfishing.common.dependency; /** * Exception thrown if a dependency cannot be downloaded. diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManager.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManager.java index 2f09a5a0..81f1b229 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManager.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManager.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies; +package net.momirealms.customfishing.common.dependency; import java.util.Collection; import java.util.Set; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManagerImpl.java similarity index 68% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManagerImpl.java index 878bb175..7364164a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyManagerImpl.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyManagerImpl.java @@ -23,19 +23,15 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies; +package net.momirealms.customfishing.common.dependency; -import com.google.common.collect.ImmutableSet; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.libraries.classpath.ClassPathAppender; -import net.momirealms.customfishing.libraries.dependencies.classloader.IsolatedClassLoader; -import net.momirealms.customfishing.libraries.dependencies.relocation.Relocation; -import net.momirealms.customfishing.libraries.dependencies.relocation.RelocationHandler; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import net.momirealms.customfishing.common.dependency.classloader.IsolatedClassLoader; +import net.momirealms.customfishing.common.dependency.relocation.Relocation; +import net.momirealms.customfishing.common.dependency.relocation.RelocationHandler; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customfishing.common.util.FileUtils; -import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -43,6 +39,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; /** * Loads and manages runtime dependencies for the plugin. @@ -58,20 +55,24 @@ public class DependencyManagerImpl implements DependencyManager { /** A map of dependencies which have already been loaded. */ private final EnumMap loaded = new EnumMap<>(Dependency.class); /** A map of isolated classloaders which have been created. */ - private final Map, IsolatedClassLoader> loaders = new HashMap<>(); + private final Map, IsolatedClassLoader> loaders = new HashMap<>(); /** Cached relocation handler instance. */ - private @MonotonicNonNull RelocationHandler relocationHandler = null; + private final RelocationHandler relocationHandler; + private final Executor loadingExecutor; + private final CustomFishingPlugin plugin; - public DependencyManagerImpl(CustomFishingPluginImpl plugin, ClassPathAppender classPathAppender) { + public DependencyManagerImpl(CustomFishingPlugin plugin) { + this.plugin = plugin; this.registry = new DependencyRegistry(); this.cacheDirectory = setupCacheDirectory(plugin); - this.classPathAppender = classPathAppender; + this.classPathAppender = plugin.getClassPathAppender(); + this.loadingExecutor = plugin.getScheduler().async(); this.relocationHandler = new RelocationHandler(this); } @Override public ClassLoader obtainClassLoaderWith(Set dependencies) { - ImmutableSet set = ImmutableSet.copyOf(dependencies); + Set set = new HashSet<>(dependencies); for (Dependency dependency : dependencies) { if (!this.loaded.containsKey(dependency)) { @@ -112,13 +113,15 @@ public class DependencyManagerImpl implements DependencyManager { continue; } - try { - loadDependency(dependency); - } catch (Throwable e) { - new RuntimeException("Unable to load dependency " + dependency.name(), e).printStackTrace(); - } finally { - latch.countDown(); - } + this.loadingExecutor.execute(() -> { + try { + loadDependency(dependency); + } catch (Throwable e) { + this.plugin.getPluginLogger().warn("Unable to load dependency " + dependency.name(), e); + } finally { + latch.countDown(); + } + }); } try { @@ -143,7 +146,8 @@ public class DependencyManagerImpl implements DependencyManager { } private Path downloadDependency(Dependency dependency) throws DependencyDownloadException { - Path file = this.cacheDirectory.resolve(dependency.getFileName(null)); + String fileName = dependency.getFileName(null); + Path file = this.cacheDirectory.resolve(fileName); // if the file already exists, don't attempt to re-download it. if (Files.exists(file)) { @@ -151,18 +155,20 @@ public class DependencyManagerImpl implements DependencyManager { } DependencyDownloadException lastError = null; - - String fileName = dependency.getFileName(null); - - // attempt to download the dependency from each repo in order. - for (DependencyRepository repo : DependencyRepository.values()) { - try { - LogUtils.info("Downloading dependency(" + fileName + ") from " + repo.getUrl() + dependency.getMavenRepoPath()); - repo.download(dependency, file); - LogUtils.info("Successfully downloaded " + fileName); - return file; - } catch (DependencyDownloadException e) { - lastError = e; + String forceRepo = dependency.getRepo(); + List repository = DependencyRepository.getByID(forceRepo); + if (!repository.isEmpty()) { + int i = 0; + while (i < repository.size()) { + try { + plugin.getPluginLogger().info("Downloading dependency(" + fileName + ") from " + repository.get(i).getUrl() + dependency.getMavenRepoPath()); + repository.get(i).download(dependency, file); + plugin.getPluginLogger().info("Successfully downloaded " + fileName); + return file; + } catch (DependencyDownloadException e) { + lastError = e; + i++; + } } } throw Objects.requireNonNull(lastError); @@ -181,16 +187,21 @@ public class DependencyManagerImpl implements DependencyManager { return remappedFile; } - LogUtils.info("Remapping " + dependency.getFileName(null)); + plugin.getPluginLogger().info("Remapping " + dependency.getFileName(null)); relocationHandler.remap(normalFile, remappedFile, rules); - LogUtils.info("Successfully remapped " + dependency.getFileName(null)); + plugin.getPluginLogger().info("Successfully remapped " + dependency.getFileName(null)); return remappedFile; } private static Path setupCacheDirectory(CustomFishingPlugin plugin) { - File folder = new File(plugin.getDataFolder(), "libs"); - folder.mkdirs(); - return folder.toPath(); + Path cacheDirectory = plugin.getDataDirectory().resolve("libs"); + try { + FileUtils.createDirectoriesIfNotExists(cacheDirectory); + } catch (IOException e) { + throw new RuntimeException("Unable to create libs directory", e); + } + + return cacheDirectory; } @Override @@ -210,8 +221,7 @@ public class DependencyManagerImpl implements DependencyManager { } if (firstEx != null) { - firstEx.printStackTrace(); + plugin.getPluginLogger().severe(firstEx.getMessage(), firstEx); } } - } diff --git a/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyProperties.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyProperties.java new file mode 100644 index 00000000..132781bd --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyProperties.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.dependency; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class DependencyProperties { + + private final HashMap versionMap; + + private DependencyProperties(HashMap versionMap) { + this.versionMap = versionMap; + } + + public static String getDependencyVersion(String dependencyID) { + if (!SingletonHolder.INSTANCE.versionMap.containsKey(dependencyID)) { + throw new RuntimeException("Unknown dependency: " + dependencyID); + } + return SingletonHolder.INSTANCE.versionMap.get(dependencyID); + } + + private static class SingletonHolder { + + private static final DependencyProperties INSTANCE = getInstance(); + + private static DependencyProperties getInstance() { + try (InputStream inputStream = DependencyProperties.class.getClassLoader().getResourceAsStream("library-version.properties")) { + HashMap versionMap = new HashMap<>(); + Properties properties = new Properties(); + properties.load(inputStream); + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey() instanceof String key && entry.getValue() instanceof String value) { + versionMap.put(key, value); + } + } + return new DependencyProperties(versionMap); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRegistry.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRegistry.java index 0218d0c0..cc58a661 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRegistry.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRegistry.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies; +package net.momirealms.customfishing.common.dependency; import com.google.gson.JsonElement; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRepository.java similarity index 80% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRepository.java index a8538048..ac51e65e 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/DependencyRepository.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/DependencyRepository.java @@ -23,9 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies; - -import com.google.common.io.ByteStreams; +package net.momirealms.customfishing.common.dependency; import java.io.IOException; import java.io.InputStream; @@ -33,38 +31,53 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.List; /** * Represents a repository which contains {@link Dependency}s. */ public enum DependencyRepository { - MAVEN_CENTRAL("https://repo1.maven.org/maven2/") { - @Override - protected URLConnection openConnection(Dependency dependency) throws IOException { - URLConnection connection = super.openConnection(dependency); - connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5)); - connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(5)); - return connection; - } - }, - /** * Maven Central */ - MAVEN_CENTRAL_MIRROR("https://maven.aliyun.com/repository/public/"); + MAVEN_CENTRAL("maven", "https://repo1.maven.org/maven2/") { + @Override + protected URLConnection openConnection(Dependency dependency) throws IOException { + URLConnection connection = super.openConnection(dependency); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + return connection; + } + }, + /** + * Maven Central Mirror + */ + MAVEN_CENTRAL_MIRROR("maven", "https://maven.aliyun.com/repository/public/"); private final String url; + private final String id; - DependencyRepository(String url) { + DependencyRepository(String id, String url) { this.url = url; + this.id = id; } public String getUrl() { return url; } + public static List getByID(String id) { + ArrayList repositories = new ArrayList<>(); + for (DependencyRepository repository : values()) { + if (id.equals(repository.id)) { + repositories.add(repository); + } + } + return repositories; + } + /** * Opens a connection to the given {@code dependency}. * @@ -88,7 +101,7 @@ public enum DependencyRepository { try { URLConnection connection = openConnection(dependency); try (InputStream in = connection.getInputStream()) { - byte[] bytes = ByteStreams.toByteArray(in); + byte[] bytes = in.readAllBytes(); if (bytes.length == 0) { throw new DependencyDownloadException("Empty stream"); } @@ -123,4 +136,8 @@ public enum DependencyRepository { throw new DependencyDownloadException(e); } } + + public String getId() { + return id; + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/classloader/IsolatedClassLoader.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/classloader/IsolatedClassLoader.java index 0dbe284c..24be0818 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/classloader/IsolatedClassLoader.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/classloader/IsolatedClassLoader.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies.classloader; +package net.momirealms.customfishing.common.dependency.classloader; import java.net.URL; import java.net.URLClassLoader; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/Relocation.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/Relocation.java index e4513f49..c7a6c8dc 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/Relocation.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/Relocation.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies.relocation; +package net.momirealms.customfishing.common.dependency.relocation; import java.util.Objects; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHandler.java similarity index 89% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHandler.java index 8479442c..90a257be 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHandler.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHandler.java @@ -23,12 +23,11 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies.relocation; +package net.momirealms.customfishing.common.dependency.relocation; - -import net.momirealms.customfishing.libraries.dependencies.Dependency; -import net.momirealms.customfishing.libraries.dependencies.DependencyManager; -import net.momirealms.customfishing.libraries.dependencies.classloader.IsolatedClassLoader; +import net.momirealms.customfishing.common.dependency.Dependency; +import net.momirealms.customfishing.common.dependency.DependencyManager; +import net.momirealms.customfishing.common.dependency.classloader.IsolatedClassLoader; import java.io.File; import java.io.IOException; @@ -67,8 +66,8 @@ public class RelocationHandler { this.jarRelocatorRunMethod.setAccessible(true); } catch (Exception e) { try { - if (classLoader instanceof IsolatedClassLoader) { - ((IsolatedClassLoader) classLoader).close(); + if (classLoader instanceof IsolatedClassLoader isolatedClassLoader) { + isolatedClassLoader.close(); } } catch (IOException ex) { e.addSuppressed(ex); diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHelper.java similarity index 95% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java rename to common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHelper.java index 76958563..84207a41 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/relocation/RelocationHelper.java +++ b/common/src/main/java/net/momirealms/customfishing/common/dependency/relocation/RelocationHelper.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.dependencies.relocation; +package net.momirealms.customfishing.common.dependency.relocation; public final class RelocationHelper { @@ -32,6 +32,5 @@ public final class RelocationHelper { public static final String OKHTTP3_STRING = String.valueOf(new char[]{'o', 'k', 'h', 't', 't', 'p', '3'}); private RelocationHelper() { - } } diff --git a/common/src/main/java/net/momirealms/customfishing/common/helper/AdventureHelper.java b/common/src/main/java/net/momirealms/customfishing/common/helper/AdventureHelper.java new file mode 100644 index 00000000..319d6e70 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/helper/AdventureHelper.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.helper; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.title.Title; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public class AdventureHelper { + + private final MiniMessage miniMessage; + private final MiniMessage miniMessageStrict; + private final GsonComponentSerializer gsonComponentSerializer; + private final Cache miniMessageToJsonCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); + public static boolean legacySupport = false; + + private AdventureHelper() { + this.miniMessage = MiniMessage.builder().build(); + this.miniMessageStrict = MiniMessage.builder().strict(true).build(); + this.gsonComponentSerializer = GsonComponentSerializer.builder().build(); + } + + private static class SingletonHolder { + private static final AdventureHelper INSTANCE = new AdventureHelper(); + } + + public static AdventureHelper getInstance() { + return SingletonHolder.INSTANCE; + } + + public static Component miniMessage(String text) { + if (legacySupport) { + return getMiniMessage().deserialize(legacyToMiniMessage(text)); + } else { + return getMiniMessage().deserialize(text); + } + } + + public static MiniMessage getMiniMessage() { + return getInstance().miniMessage; + } + + public static GsonComponentSerializer getGson() { + return getInstance().gsonComponentSerializer; + } + + public static String miniMessageToJson(String miniMessage) { + AdventureHelper instance = getInstance(); + return instance.miniMessageToJsonCache.get(miniMessage, (text) -> instance.gsonComponentSerializer.serialize(miniMessage(text))); + } + + public static void sendTitle(Audience audience, Component title, Component subtitle, int fadeIn, int stay, int fadeOut) { + audience.showTitle(Title.title(title, subtitle, Title.Times.times(Duration.ofMillis(fadeIn * 50L), Duration.ofMillis(stay * 50L), Duration.ofMillis(fadeOut * 50L)))); + } + + public static void sendActionBar(Audience audience, Component actionBar) { + audience.sendActionBar(actionBar); + } + + public static void sendMessage(Audience audience, Component message) { + audience.sendMessage(message); + } + + public static void playSound(Audience audience, Sound sound) { + audience.playSound(sound); + } + + public static String surroundWithMiniMessageFont(String text, Key font) { + return "" + text + ""; + } + + public static String surroundWithMiniMessageFont(String text, String font) { + return "" + text + ""; + } + + public static String jsonToMiniMessage(String json) { + return getInstance().miniMessageStrict.serialize(getInstance().gsonComponentSerializer.deserialize(json)); + } + + public static Component jsonToComponent(String json) { + return getInstance().gsonComponentSerializer.deserialize(json); + } + + public static String legacyToMiniMessage(String legacy) { + StringBuilder stringBuilder = new StringBuilder(); + char[] chars = legacy.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (!isColorCode(chars[i])) { + stringBuilder.append(chars[i]); + continue; + } + if (i + 1 >= chars.length) { + stringBuilder.append(chars[i]); + continue; + } + switch (chars[i+1]) { + case '0' -> stringBuilder.append(""); + case '1' -> stringBuilder.append(""); + case '2' -> stringBuilder.append(""); + case '3' -> stringBuilder.append(""); + case '4' -> stringBuilder.append(""); + case '5' -> stringBuilder.append(""); + case '6' -> stringBuilder.append(""); + case '7' -> stringBuilder.append(""); + case '8' -> stringBuilder.append(""); + case '9' -> stringBuilder.append(""); + case 'a' -> stringBuilder.append(""); + case 'b' -> stringBuilder.append(""); + case 'c' -> stringBuilder.append(""); + case 'd' -> stringBuilder.append(""); + case 'e' -> stringBuilder.append(""); + case 'f' -> stringBuilder.append(""); + case 'r' -> stringBuilder.append(""); + case 'l' -> stringBuilder.append(""); + case 'm' -> stringBuilder.append(""); + case 'o' -> stringBuilder.append(""); + case 'n' -> stringBuilder.append(""); + case 'k' -> stringBuilder.append(""); + case 'x' -> { + if (i + 13 >= chars.length + || !isColorCode(chars[i+2]) + || !isColorCode(chars[i+4]) + || !isColorCode(chars[i+6]) + || !isColorCode(chars[i+8]) + || !isColorCode(chars[i+10]) + || !isColorCode(chars[i+12])) { + stringBuilder.append(chars[i]); + continue; + } + stringBuilder + .append("<#") + .append(chars[i+3]) + .append(chars[i+5]) + .append(chars[i+7]) + .append(chars[i+9]) + .append(chars[i+11]) + .append(chars[i+13]) + .append(">"); + i += 12; + } + default -> { + stringBuilder.append(chars[i]); + continue; + } + } + i++; + } + return stringBuilder.toString(); + } + + public static String componentToJson(Component component) { + return getGson().serialize(component); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isColorCode(char c) { + return c == '§' || c == '&'; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/ParentPage.java b/common/src/main/java/net/momirealms/customfishing/common/helper/ExpressionHelper.java similarity index 72% rename from plugin/src/main/java/net/momirealms/customfishing/gui/ParentPage.java rename to common/src/main/java/net/momirealms/customfishing/common/helper/ExpressionHelper.java index 55ac592d..f418c795 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/ParentPage.java +++ b/common/src/main/java/net/momirealms/customfishing/common/helper/ExpressionHelper.java @@ -15,16 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.gui; +package net.momirealms.customfishing.common.helper; -import net.momirealms.customfishing.gui.icon.BackToPageItem; -import xyz.xenondevs.invui.item.Item; +import net.objecthunter.exp4j.ExpressionBuilder; -public interface ParentPage { +public class ExpressionHelper { - void reOpen(); - - default Item getBackItem() { - return new BackToPageItem(this); + public static double evaluate(String expression) { + return new ExpressionBuilder(expression).build().evaluate(); } } diff --git a/common/src/main/java/net/momirealms/customfishing/common/helper/GsonHelper.java b/common/src/main/java/net/momirealms/customfishing/common/helper/GsonHelper.java new file mode 100644 index 00000000..fb9ba96a --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/helper/GsonHelper.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.helper; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class GsonHelper { + + private final Gson gson; + + public GsonHelper() { + this.gson = new GsonBuilder() + .create(); + } + + public Gson getGson() { + return gson; + } + + public static Gson get() { + return SingletonHolder.INSTANCE.getGson(); + } + + private static class SingletonHolder { + private static final GsonHelper INSTANCE = new GsonHelper(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/version/VersionManagerImpl.java b/common/src/main/java/net/momirealms/customfishing/common/helper/VersionHelper.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customfishing/version/VersionManagerImpl.java rename to common/src/main/java/net/momirealms/customfishing/common/helper/VersionHelper.java index 99a687ff..54e37652 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/version/VersionManagerImpl.java +++ b/common/src/main/java/net/momirealms/customfishing/common/helper/VersionHelper.java @@ -15,12 +15,9 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.version; +package net.momirealms.customfishing.common.helper; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.manager.VersionManager; -import net.momirealms.customfishing.api.util.LogUtils; -import org.bukkit.Bukkit; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; import java.io.BufferedReader; import java.io.InputStream; @@ -28,93 +25,17 @@ import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; /** * This class implements the VersionManager interface and is responsible for managing version-related information. */ -public class VersionManagerImpl implements VersionManager { - - private final CustomFishingPluginImpl plugin; - private final float mcVersion; - private boolean isFolia; - private boolean isMojmap; - private boolean isSpigot; - private final String pluginVersion; - - @SuppressWarnings("deprecation") - public VersionManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - // Get the server version - - String[] split = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); - this.mcVersion = Float.parseFloat(split[1] + "." + (split.length >= 3 ? split[2] : "0")); - - // Get the plugin version - this.pluginVersion = plugin.getDescription().getVersion(); - - this.isSpigot = Bukkit.getServer().getName().equals("CraftBukkit"); - - // Check if the server is Folia - try { - Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); - this.isFolia = true; - } catch (ClassNotFoundException ignored) { - this.isFolia = false; - } - - // Check if the server is Mojmap - try { - Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket"); - this.isMojmap = true; - } catch (ClassNotFoundException ignored) { - } - } - - @Override - public boolean isVersionNewerThan1_19() { - return mcVersion >= 19; - } - - @Override - public boolean isVersionNewerThan1_19_R3() { - return mcVersion >= 19.4; - } - - @Override - public boolean isVersionNewerThan1_19_R2() { - return mcVersion >= 19.3; - } - - @Override - public boolean isVersionNewerThan1_20() { - return mcVersion >= 20; - } - - @Override - public boolean isSpigot() { - return false; - } - - @Override - public String getPluginVersion() { - return pluginVersion; - } - - @Override - public boolean hasRegionScheduler() { - return isFolia; - } - - @Override - public boolean isMojmap() { - return isMojmap; - } +public class VersionHelper { // Method to asynchronously check for plugin updates - @Override - public CompletableFuture checkUpdate() { + public static final Function> UPDATE_CHECKER = (plugin) -> { CompletableFuture updateFuture = new CompletableFuture<>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try { URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2723&key=version"); URLConnection conn = url.openConnection(); @@ -122,7 +43,7 @@ public class VersionManagerImpl implements VersionManager { conn.setReadTimeout(60000); InputStream inputStream = conn.getInputStream(); String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine(); - String current = plugin.getVersionManager().getPluginVersion(); + String current = plugin.getPluginVersion(); inputStream.close(); if (!compareVer(newest, current)) { updateFuture.complete(false); @@ -130,15 +51,75 @@ public class VersionManagerImpl implements VersionManager { } updateFuture.complete(true); } catch (Exception exception) { - LogUtils.warn("Error occurred when checking update.", exception); + plugin.getPluginLogger().warn("Error occurred when checking update.", exception); updateFuture.complete(false); } }); return updateFuture; + }; + + private static float version; + private static boolean mojmap; + private static boolean folia; + + public static void init(String serverVersion) { + String[] split = serverVersion.split("\\."); + version = Float.parseFloat(split[1] + "." + (split.length == 3 ? split[2] : "0")); + checkMojMap(); + checkFolia(); + } + + private static void checkMojMap() { + // Check if the server is Mojmap + try { + Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket"); + mojmap = true; + } catch (ClassNotFoundException ignored) { + } + } + + private static void checkFolia() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + } catch (ClassNotFoundException ignored) { + } + } + + public static boolean isVersionNewerThan1_19() { + return version >= 19; + } + + public static boolean isVersionNewerThan1_19_4() { + return version >= 19.4; + } + + public static boolean isVersionNewerThan1_19_3() { + return version >= 19.3; + } + + public static boolean isVersionNewerThan1_20() { + return version >= 20.0; + } + + public static boolean isVersionNewerThan1_20_5() { + return version >= 20.5; + } + + public boolean isNewerThan1_20_5() { + return version >= 20.5; + } + + public static boolean isFolia() { + return folia; + } + + public static boolean isMojmap() { + return mojmap; } // Method to compare two version strings - private boolean compareVer(String newV, String currentV) { + private static boolean compareVer(String newV, String currentV) { if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) { return false; } diff --git a/common/src/main/java/net/momirealms/customfishing/common/item/AbstractItem.java b/common/src/main/java/net/momirealms/customfishing/common/item/AbstractItem.java new file mode 100644 index 00000000..0db7e297 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/item/AbstractItem.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.item; + +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Key; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class AbstractItem implements Item { + + private final CustomFishingPlugin plugin; + private final ItemFactory factory; + private final R item; + + AbstractItem(CustomFishingPlugin plugin, ItemFactory factory, R item) { + this.plugin = plugin; + this.factory = factory; + this.item = item; + } + + @Override + public Item damage(Integer data) { + factory.damage(item, data); + return this; + } + + @Override + public Optional damage() { + return factory.damage(item); + } + + @Override + public Item maxDamage(Integer data) { + factory.maxDamage(item, data); + return this; + } + + @Override + public Optional maxDamage() { + return factory.maxDamage(item); + } + + @Override + public Item customModelData(Integer data) { + factory.customModelData(item, data); + return this; + } + + @Override + public Optional customModelData() { + return factory.customModelData(item); + } + + @Override + public Optional displayName() { + return factory.displayName(item); + } + + @Override + public Item lore(List lore) { + factory.lore(item, lore); + return this; + } + + @Override + public Optional> lore() { + return factory.lore(item); + } + + @Override + public Item unbreakable(boolean unbreakable) { + factory.unbreakable(item, unbreakable); + return this; + } + + @Override + public boolean unbreakable() { + return factory.unbreakable(item); + } + + @Override + public Item displayName(String displayName) { + factory.displayName(item, displayName); + return this; + } + + @Override + public Item skull(String data) { + factory.skull(item, data); + return this; + } + + @Override + public Item enchantments(Map enchantments) { + factory.enchantments(item, enchantments); + return this; + } + + @Override + public Item addEnchantment(Key enchantment, int level) { + factory.addEnchantment(item, enchantment, level); + return this; + } + + @Override + public Item storedEnchantments(Map enchantments) { + factory.storedEnchantments(item, enchantments); + return this; + } + + @Override + public Item addStoredEnchantment(Key enchantment, int level) { + factory.addStoredEnchantment(item, enchantment, level); + return this; + } + + @Override + public Item itemFlags(List flags) { + factory.itemFlags(item, flags); + return this; + } + + @Override + public Optional getTag(Object... path) { + return factory.getTag(item, path); + } + + @Override + public Item setTag(Object value, Object... path) { + factory.setTag(item, value, path); + return this; + } + + @Override + public boolean hasTag(Object... path) { + return factory.hasTag(item, path); + } + + @Override + public boolean removeTag(Object... path) { + return factory.removeTag(item, path); + } + + @Override + public I getItem() { + return factory.getItem(item); + } + + @Override + public I load() { + return factory.load(item); + } + + @Override + public I loadCopy() { + return factory.loadCopy(item); + } + + @Override + public void update() { + factory.update(item); + } + + public R getRTagItem() { + return item; + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/item/ComponentKeys.java b/common/src/main/java/net/momirealms/customfishing/common/item/ComponentKeys.java new file mode 100644 index 00000000..c5aa2a71 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/item/ComponentKeys.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.item; + +import net.kyori.adventure.key.Key; + +public class ComponentKeys { + + public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString(); + public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString(); + public static final String LORE = Key.key("minecraft", "lore").asString(); + public static final String DAMAGE = Key.key("minecraft", "damage").asString(); + public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString(); + public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString(); + public static final String ENCHANTMENTS = Key.key("minecraft", "enchantments").asString(); + public static final String STORED_ENCHANTMENTS = Key.key("minecraft", "stored_enchantments").asString(); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/item/Item.java b/common/src/main/java/net/momirealms/customfishing/common/item/Item.java new file mode 100644 index 00000000..99a6d1a5 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/item/Item.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.item; + +import net.momirealms.customfishing.common.util.Key; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface Item { + + Item customModelData(Integer data); + + Optional customModelData(); + + Item damage(Integer data); + + Optional damage(); + + Item maxDamage(Integer data); + + Optional maxDamage(); + + Item displayName(String displayName); + + Optional displayName(); + + Item lore(List lore); + + Optional> lore(); + + Item unbreakable(boolean unbreakable); + + boolean unbreakable(); + + Item skull(String data); + + Item enchantments(Map enchantments); + + Item addEnchantment(Key enchantment, int level); + + Item storedEnchantments(Map enchantments); + + Item addStoredEnchantment(Key enchantment, int level); + + Item itemFlags(List flags); + + Optional getTag(Object... path); + + Item setTag(Object value, Object... path); + + boolean hasTag(Object... path); + + boolean removeTag(Object... path); + + I getItem(); + + I load(); + + I loadCopy(); + + void update(); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/item/ItemFactory.java b/common/src/main/java/net/momirealms/customfishing/common/item/ItemFactory.java new file mode 100644 index 00000000..9f2e3cd9 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/item/ItemFactory.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.item; + +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Key; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public abstract class ItemFactory

{ + + protected final P plugin; + + protected ItemFactory(P plugin) { + this.plugin = plugin; + } + + public Item wrap(R item) { + Objects.requireNonNull(item, "item"); + return new AbstractItem<>(this.plugin, this, item); + } + + protected abstract Optional getTag(R item, Object... path); + + protected abstract void setTag(R item, Object value, Object... path); + + protected abstract boolean hasTag(R item, Object... path); + + protected abstract boolean removeTag(R item, Object... path); + + protected abstract void update(R item); + + protected abstract I load(R item); + + protected abstract I getItem(R item); + + protected abstract I loadCopy(R item); + + protected abstract void customModelData(R item, Integer data); + + protected abstract Optional customModelData(R item); + + protected abstract void displayName(R item, String json); + + protected abstract Optional displayName(R item); + + protected abstract void skull(R item, String skullData); + + protected abstract Optional> lore(R item); + + protected abstract void lore(R item, List lore); + + protected abstract boolean unbreakable(R item); + + protected abstract void unbreakable(R item, boolean unbreakable); + + protected abstract Optional glint(R item); + + protected abstract void glint(R item, Boolean glint); + + protected abstract Optional damage(R item); + + protected abstract void damage(R item, Integer damage); + + protected abstract Optional maxDamage(R item); + + protected abstract void maxDamage(R item, Integer damage); + + protected abstract void enchantments(R item, Map enchantments); + + protected abstract void storedEnchantments(R item, Map enchantments); + + protected abstract void addEnchantment(R item, Key enchantment, int level); + + protected abstract void addStoredEnchantment(R item, Key enchantment, int level); + + protected abstract void itemFlags(R item, List flags); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionFormatter.java b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionFormatter.java new file mode 100644 index 00000000..b63e2e48 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionFormatter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.caption.Caption; +import org.incendo.cloud.caption.CaptionVariable; +import org.incendo.cloud.minecraft.extras.caption.ComponentCaptionFormatter; + +import java.util.List; + +public class CustomFishingCaptionFormatter implements ComponentCaptionFormatter { + + @Override + public @NonNull Component formatCaption(@NonNull Caption captionKey, @NonNull C recipient, @NonNull String caption, @NonNull List<@NonNull CaptionVariable> variables) { + Component component = ComponentCaptionFormatter.translatable().formatCaption(captionKey, recipient, caption, variables); + return TranslationManager.render(component); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionKeys.java b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionKeys.java new file mode 100644 index 00000000..47dcf374 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionKeys.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import org.incendo.cloud.caption.Caption; + +public final class CustomFishingCaptionKeys { + + public static final Caption ARGUMENT_PARSE_FAILURE_TIME = Caption.of("argument.parse.failure.time"); + public static final Caption ARGUMENT_PARSE_FAILURE_URL = Caption.of("argument.parse.failure.url"); + public static final Caption ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR = Caption.of("argument.parse.failure.namedtextcolor"); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionProvider.java b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionProvider.java new file mode 100644 index 00000000..226be8bf --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/CustomFishingCaptionProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.caption.CaptionProvider; +import org.incendo.cloud.caption.DelegatingCaptionProvider; + +public final class CustomFishingCaptionProvider extends DelegatingCaptionProvider { + + private static final CaptionProvider PROVIDER = CaptionProvider.constantProvider() + .putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_URL, "") + .putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_TIME, "") + .putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR, "") + .build(); + + @SuppressWarnings("unchecked") + @Override + public @NonNull CaptionProvider delegate() { + return (CaptionProvider) PROVIDER; + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/MessageConstants.java b/common/src/main/java/net/momirealms/customfishing/common/locale/MessageConstants.java new file mode 100644 index 00000000..58a416b3 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/MessageConstants.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; + +public interface MessageConstants { + + TranslatableComponent.Builder COMPETITION_NO_PLAYER = Component.translatable().key("competition.no_player");; + TranslatableComponent.Builder COMPETITION_NO_SCORE = Component.translatable().key("competition.no_score"); + TranslatableComponent.Builder COMPETITION_NO_RANK = Component.translatable().key("competition.no_rank"); + + TranslatableComponent.Builder GOAL_TOTAL_SIZE = Component.translatable().key("competition.goal.total_size"); + TranslatableComponent.Builder GOAL_CATCH_AMOUNT = Component.translatable().key("competition.goal.catch_amount"); + TranslatableComponent.Builder GOAL_TOTAL_SCORE = Component.translatable().key("competition.goal.total_score"); + TranslatableComponent.Builder GOAL_MAX_SIZE = Component.translatable().key("competition.goal.max_size"); + TranslatableComponent.Builder GOAL_MIN_SIZE = Component.translatable().key("competition.goal.min_size"); + + TranslatableComponent.Builder FORMAT_SECOND = Component.translatable().key("format.second"); + TranslatableComponent.Builder FORMAT_MINUTE = Component.translatable().key("format.minute"); + TranslatableComponent.Builder FORMAT_HOUR = Component.translatable().key("format.hour"); + TranslatableComponent.Builder FORMAT_DAY = Component.translatable().key("format.day"); + + TranslatableComponent.Builder COMMAND_RELOAD_SUCCESS = Component.translatable().key("command.reload.success"); + TranslatableComponent.Builder COMMAND_ITEM_FAILURE_NOT_EXIST = Component.translatable().key("command.item.failure.not_exist"); + TranslatableComponent.Builder COMMAND_ITEM_GIVE_SUCCESS = Component.translatable().key("command.item.give.success"); + TranslatableComponent.Builder COMMAND_ITEM_GET_SUCCESS = Component.translatable().key("command.item.get.success"); + TranslatableComponent.Builder COMMAND_ITEM_IMPORT_FAILURE_NO_ITEM = Component.translatable().key("command.item.import.failure.no_item"); + TranslatableComponent.Builder COMMAND_ITEM_IMPORT_SUCCESS = Component.translatable().key("command.item.import.success"); + TranslatableComponent.Builder COMMAND_FISH_FINDER_POSSIBLE_LOOTS = Component.translatable().key("command.fish_finder.possible_loots"); + TranslatableComponent.Builder COMMAND_FISH_FINDER_NO_LOOT = Component.translatable().key("command.fish_finder.no_loot"); + TranslatableComponent.Builder COMMAND_FISH_FINDER_SPLIT_CHAR = Component.translatable().key("command.fish_finder.split_char"); + TranslatableComponent.Builder COMMAND_COMPETITION_FAILURE_NOT_EXIST = Component.translatable().key("command.competition.failure.not_exist"); + TranslatableComponent.Builder COMMAND_COMPETITION_FAILURE_NO_COMPETITION = Component.translatable().key("command.competition.failure.no_competition"); + TranslatableComponent.Builder COMMAND_COMPETITION_START_SUCCESS = Component.translatable().key("command.competition.start.success"); + TranslatableComponent.Builder COMMAND_COMPETITION_STOP_SUCCESS = Component.translatable().key("command.competition.stop.success"); + TranslatableComponent.Builder COMMAND_COMPETITION_END_SUCCESS = Component.translatable().key("command.competition.end.success"); + TranslatableComponent.Builder COMMAND_BAG_EDIT_FAILURE_UNSAFE = Component.translatable().key("command.bag.edit.failure.unsafe"); + TranslatableComponent.Builder COMMAND_BAG_EDIT_FAILURE_NEVER_PLAYED = Component.translatable().key("command.bag.edit.failure.never_played"); + TranslatableComponent.Builder COMMAND_BAG_OPEN_SUCCESS = Component.translatable().key("command.bag.open.success"); + TranslatableComponent.Builder COMMAND_BAG_OPEN_FAILURE_NOT_LOADED = Component.translatable().key("command.bag.open.failure.not_loaded"); + TranslatableComponent.Builder COMMAND_DATA_FAILURE_NOT_LOADED = Component.translatable().key("command.data.failure.not_loaded"); + TranslatableComponent.Builder COMMAND_MARKET_OPEN_SUCCESS = Component.translatable().key("command.market.open.success"); + TranslatableComponent.Builder COMMAND_MARKET_OPEN_FAILURE_NOT_LOADED = Component.translatable().key("command.market.open.failure.not_loaded"); + TranslatableComponent.Builder COMMAND_DATA_UNLOCK_SUCCESS = Component.translatable().key("command.data.unlock.success"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_NOT_EXISTS = Component.translatable().key("command.data.import.failure.not_exists"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_PLAYER_ONLINE = Component.translatable().key("command.data.import.failure.player_online"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_INVALID_FILE = Component.translatable().key("command.data.import.failure.invalid_file"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_START = Component.translatable().key("command.data.import.start"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_PROGRESS = Component.translatable().key("command.data.import.progress"); + TranslatableComponent.Builder COMMAND_DATA_IMPORT_SUCCESS = Component.translatable().key("command.data.import.success"); + TranslatableComponent.Builder COMMAND_DATA_EXPORT_FAILURE_PLAYER_ONLINE = Component.translatable().key("command.data.export.failure.player_online"); + TranslatableComponent.Builder COMMAND_DATA_EXPORT_START = Component.translatable().key("command.data.export.start"); + TranslatableComponent.Builder COMMAND_DATA_EXPORT_PROGRESS = Component.translatable().key("command.data.export.progress"); + TranslatableComponent.Builder COMMAND_DATA_EXPORT_SUCCESS = Component.translatable().key("command.data.export.success"); + TranslatableComponent.Builder COMMAND_STATISTICS_FAILURE_NOT_LOADED = Component.translatable().key("command.statistics.failure.not_loaded"); + TranslatableComponent.Builder COMMAND_STATISTICS_FAILURE_UNSUPPORTED = Component.translatable().key("command.statistics.failure.unsupported"); + TranslatableComponent.Builder COMMAND_STATISTICS_MODIFY_SUCCESS = Component.translatable().key("command.statistics.modify.success"); + TranslatableComponent.Builder COMMAND_STATISTICS_RESET_SUCCESS = Component.translatable().key("command.statistics.reset.success"); + TranslatableComponent.Builder COMMAND_STATISTICS_QUERY_AMOUNT = Component.translatable().key("command.statistics.query.amount"); + TranslatableComponent.Builder COMMAND_STATISTICS_QUERY_SIZE = Component.translatable().key("command.statistics.query.size"); + +// TranslatableComponent.Builder GUI_SELECT_FILE = Component.translatable().key("gui.select_file"); +// TranslatableComponent.Builder GUI_SELECT_ITEM = Component.translatable().key("gui.select_item"); +// TranslatableComponent.Builder GUI_INVALID_KEY = Component.translatable().key("gui.invalid_key"); +// TranslatableComponent.Builder GUI_NEW_VALUE = Component.translatable().key("gui.new_value"); +// TranslatableComponent.Builder GUI_TEMP_NEW_KEY = Component.translatable().key("gui.temp_new_key"); +// TranslatableComponent.Builder GUI_SET_NEW_KEY = Component.translatable().key("gui.set_new_key"); +// TranslatableComponent.Builder GUI_EDIT_KEY = Component.translatable().key("gui.edit_key"); +// TranslatableComponent.Builder GUI_DELETE_PROPERTY = Component.translatable().key("gui.delete_property"); +// TranslatableComponent.Builder GUI_CLICK_CONFIRM = Component.translatable().key("gui.click_confirm"); +// TranslatableComponent.Builder GUI_INVALID_NUMBER = Component.translatable().key("gui.invalid_number"); +// TranslatableComponent.Builder GUI_ILLEGAL_FORMAT = Component.translatable().key("gui.illegal_format"); +// TranslatableComponent.Builder GUI_SCROLL_UP = Component.translatable().key("gui.scroll_up"); +// TranslatableComponent.Builder GUI_SCROLL_DOWN = Component.translatable().key("gui.scroll_down"); +// TranslatableComponent.Builder GUI_CANNOT_SCROLL_UP = Component.translatable().key("gui.cannot_scroll_up"); +// TranslatableComponent.Builder GUI_CANNOT_SCROLL_DOWN = Component.translatable().key("gui.cannot_scroll_down"); +// TranslatableComponent.Builder GUI_NEXT_PAGE = Component.translatable().key("gui.next_page"); +// TranslatableComponent.Builder GUI_GOTO_NEXT_PAGE = Component.translatable().key("gui.goto_next_page"); +// TranslatableComponent.Builder GUI_CANNOT_GOTO_NEXT_PAGE = Component.translatable().key("gui.cannot_goto_next_page"); +// TranslatableComponent.Builder GUI_PREVIOUS_PAGE = Component.translatable().key("gui.previous_page"); +// TranslatableComponent.Builder GUI_GOTO_PREVIOUS_PAGE = Component.translatable().key("gui.goto_previous_page"); +// TranslatableComponent.Builder GUI_CANNOT_GOTO_PREVIOUS_PAGE = Component.translatable().key("gui.cannot_goto_previous_page"); +// TranslatableComponent.Builder GUI_BACK_TO_PARENT_PAGE = Component.translatable().key("gui.back_to_parent_page"); +// TranslatableComponent.Builder GUI_BACK_TO_PARENT_FOLDER = Component.translatable().key("gui.back_to_parent_folder"); +// TranslatableComponent.Builder GUI_CURRENT_VALUE = Component.translatable().key("gui.current_value"); +// TranslatableComponent.Builder GUI_CLICK_TO_TOGGLE = Component.translatable().key("gui.click_to_toggle"); +// TranslatableComponent.Builder GUI_LEFT_CLICK_EDIT = Component.translatable().key("gui.left_click_edit"); +// TranslatableComponent.Builder GUI_RIGHT_CLICK_RESET = Component.translatable().key("gui.right_click_reset"); +// TranslatableComponent.Builder GUI_RIGHT_CLICK_DELETE = Component.translatable().key("gui.right_click_delete"); +// TranslatableComponent.Builder GUI_RIGHT_CLICK_CANCEL = Component.translatable().key("gui.right_click_cancel"); +// TranslatableComponent.Builder GUI_LOOT_SHOW_IN_FINDER = Component.translatable().key("gui.loot_show_in_finder"); +// TranslatableComponent.Builder GUI_LOOT_SCORE = Component.translatable().key("gui.loot_score"); +// TranslatableComponent.Builder GUI_LOOT_NICK = Component.translatable().key("gui.loot_nick"); +// TranslatableComponent.Builder GUI_LOOT_INSTANT_GAME = Component.translatable().key("gui.loot_instant_game"); +// TranslatableComponent.Builder GUI_LOOT_DISABLE_STATISTICS = Component.translatable().key("gui.loot_disable_statistics"); +// TranslatableComponent.Builder GUI_LOOT_DISABLE_GAME = Component.translatable().key("gui.loot_disable_game"); +// TranslatableComponent.Builder GUI_ITEM_AMOUNT = Component.translatable().key("gui.item_amount"); +// TranslatableComponent.Builder GUI_ITEM_CUSTOM_MODEL_DATA = Component.translatable().key("gui.item_custom_model_data"); +// TranslatableComponent.Builder GUI_ITEM_DISPLAY_NAME = Component.translatable().key("gui.item_display_name"); +// TranslatableComponent.Builder GUI_ITEM_CUSTOM_DURABILITY = Component.translatable().key("gui.item_custom_durability"); +// TranslatableComponent.Builder GUI_ITEM_ENCHANTMENT = Component.translatable().key("gui.item_enchantment"); +// TranslatableComponent.Builder GUI_ITEM_HEAD64 = Component.translatable().key("gui.item_head64"); +// TranslatableComponent.Builder GUI_ITEM_FLAG = Component.translatable().key("gui.item_item_flag"); +// TranslatableComponent.Builder GUI_ITEM_LORE = Component.translatable().key("gui.item_lore"); +// TranslatableComponent.Builder GUI_ITEM_MATERIAL = Component.translatable().key("gui.item_material"); +// TranslatableComponent.Builder GUI_ITEM_NBT = Component.translatable().key("gui.item_nbt"); +// TranslatableComponent.Builder GUI_ITEM_PREVENT_GRAB = Component.translatable().key("gui.item_prevent_grab"); +// TranslatableComponent.Builder GUI_ITEM_PRICE = Component.translatable().key("gui.item_price"); +// TranslatableComponent.Builder GUI_ITEM_PRICE_BASE = Component.translatable().key("gui.item_price_base"); +// TranslatableComponent.Builder GUI_ITEM_PRICE_BONUS = Component.translatable().key("gui.item_price_bonus"); +// TranslatableComponent.Builder GUI_ITEM_RANDOM_DURABILITY = Component.translatable().key("gui.item_random_durability"); +// TranslatableComponent.Builder GUI_ITEM_SIZE = Component.translatable().key("gui.item_size"); +// TranslatableComponent.Builder GUI_ITEM_STACKABLE = Component.translatable().key("gui.item_stackable"); +// TranslatableComponent.Builder GUI_ITEM_STORED_ENCHANTMENT = Component.translatable().key("gui.item_stored_enchantment"); +// TranslatableComponent.Builder GUI_ITEM_TAG = Component.translatable().key("gui.item_tag"); +// TranslatableComponent.Builder GUI_ITEM_UNBREAKABLE = Component.translatable().key("gui.item_unbreakable"); +// TranslatableComponent.Builder GUI_PAGE_AMOUNT_TITLE = Component.translatable().key("gui.page_amount_title"); +// TranslatableComponent.Builder GUI_PAGE_MODEL_DATA_TITLE = Component.translatable().key("gui.page_model_data_title"); +// TranslatableComponent.Builder GUI_PAGE_DISPLAY_NAME_TITLE = Component.translatable().key("gui.page_display_name_title"); +// TranslatableComponent.Builder GUI_PAGE_NEW_DISPLAY_NAME = Component.translatable().key("gui.page_new_display_name"); +// TranslatableComponent.Builder GUI_PAGE_CUSTOM_DURABILITY_TITLE = Component.translatable().key("gui.page_custom_durability_title"); +// TranslatableComponent.Builder GUI_PAGE_STORED_ENCHANTMENT_TITLE = Component.translatable().key("gui.page_stored_enchantment_title"); +// TranslatableComponent.Builder GUI_PAGE_ENCHANTMENT_TITLE = Component.translatable().key("gui.page_enchantment_title"); +// TranslatableComponent.Builder GUI_PAGE_SELECT_ONE_ENCHANTMENT = Component.translatable().key("gui.page_select_one_enchantment"); +// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_ENCHANTMENT = Component.translatable().key("gui.page_add_new_enchantment"); +// TranslatableComponent.Builder GUI_PAGE_ITEM_FLAG_TITLE = Component.translatable().key("gui.page_item_flag_title"); +// TranslatableComponent.Builder GUI_PAGE_LORE_TITLE = Component.translatable().key("gui.page_lore_title"); +// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_LORE = Component.translatable().key("gui.page_add_new_lore"); +// TranslatableComponent.Builder GUI_PAGE_SELECT_ONE_LORE = Component.translatable().key("gui.page_select_one_lore"); +// TranslatableComponent.Builder GUI_PAGE_MATERIAL_TITLE = Component.translatable().key("gui.page_material_title"); +// TranslatableComponent.Builder GUI_PAGE_NBT_COMPOUND_KEY_TITLE = Component.translatable().key("gui.page_nbt_compound_key_title"); +// TranslatableComponent.Builder GUI_PAGE_NBT_LIST_KEY_TITLE = Component.translatable().key("gui.page_nbt_list_key_title"); +// TranslatableComponent.Builder GUI_PAGE_NBT_KEY_TITLE = Component.translatable().key("gui.page_nbt_key_title"); +// TranslatableComponent.Builder GUI_PAGE_NBT_INVALID_KEY = Component.translatable().key("gui.page_nbt_invalid_key"); +// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_COMPOUND= Component.translatable().key("gui.page_nbt_add_new_compound"); +// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_LIST = Component.translatable().key("gui.page_nbt_add_new_list"); +// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_VALUE = Component.translatable().key("gui.page_nbt_add_new_value"); +// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_KEY = Component.translatable().key("gui.page_add_new_key"); +// TranslatableComponent.Builder GUI_PAGE_NBT_PREVIEW = Component.translatable().key("gui.page_nbt_preview"); +// TranslatableComponent.Builder GUI_PAGE_NBT_BACK_TO_COMPOUND = Component.translatable().key("gui.page_nbt_back_to_compound"); +// TranslatableComponent.Builder GUI_PAGE_NBT_SET_VALUE_TITLE = Component.translatable().key("gui.page_nbt_set_value_title"); +// TranslatableComponent.Builder GUI_PAGE_NBT_EDIT_TITLE = Component.translatable().key("gui.page_nbt_edit_title"); +// TranslatableComponent.Builder GUI_PAGE_NICK_TITLE = Component.translatable().key("gui.page_nick_title"); +// TranslatableComponent.Builder GUI_PAGE_NEW_NICK = Component.translatable().key("gui.page_new_nick"); +// TranslatableComponent.Builder GUI_PAGE_PRICE_TITLE = Component.translatable().key("gui.page_price_title"); +// TranslatableComponent.Builder GUI_PAGE_BASE_PRICE = Component.translatable().key("gui.page_base_price"); +// TranslatableComponent.Builder GUI_PAGE_BASE_BONUS = Component.translatable().key("gui.page_base_bonus"); +// TranslatableComponent.Builder GUI_PAGE_SCORE_TITLE = Component.translatable().key("gui.page_score_title"); +// TranslatableComponent.Builder GUI_PAGE_SIZE_TITLE = Component.translatable().key("gui.page_size_title"); +// TranslatableComponent.Builder GUI_PAGE_SIZE_MIN = Component.translatable().key("gui.page_size_min"); +// TranslatableComponent.Builder GUI_PAGE_SIZE_MAX = Component.translatable().key("gui.page_size_max"); +// TranslatableComponent.Builder GUI_PAGE_SIZE_MAX_NO_LESS_MIN = Component.translatable().key("gui.page_size_max_no_less_min"); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistry.java b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistry.java new file mode 100644 index 00000000..afa376fa --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistry.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.translation.Translator; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +public interface MiniMessageTranslationRegistry extends Translator { + + static @NotNull MiniMessageTranslationRegistry create(final Key name, final MiniMessage miniMessage) { + return new MiniMessageTranslationRegistryImpl(requireNonNull(name, "name"), requireNonNull(miniMessage, "MiniMessage")); + } + + void register(@NotNull String key, @NotNull Locale locale, @NotNull String format); + + void unregister(@NotNull String key); + + boolean contains(@NotNull String key); + + String miniMessageTranslation(@NotNull String key, @NotNull Locale locale); + + void defaultLocale(@NotNull Locale defaultLocale); + + default void registerAll(final @NotNull Locale locale, final @NotNull Map bundle) { + this.registerAll(locale, bundle.keySet(), bundle::get); + } + + default void registerAll(final @NotNull Locale locale, final @NotNull Set keys, final Function function) { + IllegalArgumentException firstError = null; + int errorCount = 0; + for (final String key : keys) { + try { + this.register(key, locale, function.apply(key)); + } catch (final IllegalArgumentException e) { + if (firstError == null) { + firstError = e; + } + errorCount++; + } + } + if (firstError != null) { + if (errorCount == 1) { + throw firstError; + } else if (errorCount > 1) { + throw new IllegalArgumentException(String.format("Invalid key (and %d more)", errorCount - 1), firstError); + } + } + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistryImpl.java b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistryImpl.java new file mode 100644 index 00000000..3652e6a9 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslationRegistryImpl.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.internal.Internals; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.util.TriState; +import net.kyori.examination.Examinable; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +public class MiniMessageTranslationRegistryImpl implements Examinable, MiniMessageTranslationRegistry { + private final Key name; + private final Map translations = new ConcurrentHashMap<>(); + private Locale defaultLocale = Locale.US; + private final MiniMessage miniMessage; + + MiniMessageTranslationRegistryImpl(final Key name, final MiniMessage miniMessage) { + this.name = name; + this.miniMessage = miniMessage; + } + + @Override + public void register(final @NotNull String key, final @NotNull Locale locale, final @NotNull String format) { + this.translations.computeIfAbsent(key, Translation::new).register(locale, format); + } + + @Override + public void unregister(final @NotNull String key) { + this.translations.remove(key); + } + + @Override + public boolean contains(final @NotNull String key) { + return this.translations.containsKey(key); + } + + @Override + public @NotNull Key name() { + return name; + } + + @Override + public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) { + // No need to implement this method + return null; + } + + @Override + public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { + Translation translation = translations.get(component.key()); + if (translation == null) { + return null; + } + String miniMessageString = translation.translate(locale); + if (miniMessageString == null) { + return null; + } + if (miniMessageString.isEmpty()) { + return Component.empty(); + } + final Component resultingComponent; + if (component.arguments().isEmpty()) { + resultingComponent = this.miniMessage.deserialize(miniMessageString); + } else { + resultingComponent = this.miniMessage.deserialize(miniMessageString, new ArgumentTag(component.arguments())); + } + if (component.children().isEmpty()) { + return resultingComponent; + } else { + return resultingComponent.children(component.children()); + } + } + + @Override + public String miniMessageTranslation(@NotNull String key, @NotNull Locale locale) { + Translation translation = translations.get(key); + if (translation == null) { + return null; + } + return translation.translate(locale); + } + + @Override + public @NotNull TriState hasAnyTranslations() { + if (!this.translations.isEmpty()) { + return TriState.TRUE; + } + return TriState.FALSE; + } + + @Override + public void defaultLocale(final @NotNull Locale defaultLocale) { + this.defaultLocale = requireNonNull(defaultLocale, "defaultLocale"); + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("translations", this.translations)); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof MiniMessageTranslationRegistryImpl that)) return false; + return this.name.equals(that.name) + && this.translations.equals(that.translations) + && this.defaultLocale.equals(that.defaultLocale); + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.translations, this.defaultLocale); + } + + @Override + public String toString() { + return Internals.toString(this); + } + + public static class ArgumentTag implements TagResolver { + private static final String NAME = "argument"; + private static final String NAME_1 = "arg"; + + private final List argumentComponents; + + public ArgumentTag(final @NotNull List argumentComponents) { + this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents"); + } + + @Override + public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException { + final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); + + if (index < 0 || index >= argumentComponents.size()) { + throw ctx.newException("Invalid argument number", arguments); + } + + return Tag.inserting(argumentComponents.get(index)); + } + + @Override + public boolean has(final @NotNull String name) { + return name.equals(NAME) || name.equals(NAME_1); + } + } + + final class Translation implements Examinable { + private final String key; + private final Map formats; + + Translation(final @NotNull String key) { + this.key = requireNonNull(key, "translation key"); + this.formats = new ConcurrentHashMap<>(); + } + + void register(final @NotNull Locale locale, final @NotNull String format) { + if (this.formats.putIfAbsent(requireNonNull(locale, "locale"), requireNonNull(format, "message format")) != null) { + throw new IllegalArgumentException(String.format("Translation already exists: %s for %s", this.key, locale)); + } + } + + @Nullable String translate(final @NotNull Locale locale) { + String format = this.formats.get(requireNonNull(locale, "locale")); + if (format == null) { + format = this.formats.get(new Locale(locale.getLanguage())); // try without country + if (format == null) { + format = this.formats.get(MiniMessageTranslationRegistryImpl.this.defaultLocale); // try local default locale + } + } + return format; + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("key", this.key), + ExaminableProperty.of("formats", this.formats) + ); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof Translation that)) return false; + return this.key.equals(that.key) && + this.formats.equals(that.formats); + } + + @Override + public int hashCode() { + return Objects.hash(this.key, this.formats); + } + + @Override + public String toString() { + return Internals.toString(this); + } + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslator.java b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslator.java new file mode 100644 index 00000000..47c42114 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.translation.Translator; +import net.kyori.examination.Examinable; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +public interface MiniMessageTranslator extends Translator, Examinable { + + static @NotNull MiniMessageTranslator translator() { + return MiniMessageTranslatorImpl.INSTANCE; + } + + static @NotNull TranslatableComponentRenderer renderer() { + return MiniMessageTranslatorImpl.INSTANCE.renderer; + } + + static @NotNull Component render(final @NotNull Component component, final @NotNull Locale locale) { + return renderer().render(component, locale); + } + + @NotNull Iterable sources(); + + boolean addSource(final @NotNull Translator source); + + boolean removeSource(final @NotNull Translator source); +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslatorImpl.java b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslatorImpl.java new file mode 100644 index 00000000..ea55ec5c --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/MiniMessageTranslatorImpl.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.translation.Translator; +import net.kyori.adventure.util.TriState; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +public class MiniMessageTranslatorImpl implements MiniMessageTranslator { + + private static final Key NAME = Key.key("customfishing", "main"); + static final MiniMessageTranslatorImpl INSTANCE = new MiniMessageTranslatorImpl(); + final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(this); + private final Set sources = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + @Override + public @NotNull Key name() { + return NAME; + } + + @Override + public @NotNull TriState hasAnyTranslations() { + if (!this.sources.isEmpty()) { + return TriState.TRUE; + } + return TriState.FALSE; + } + + @Override + public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) { + // No need to implement this method + return null; + } + + @Override + public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { + for (final Translator source : this.sources) { + final Component translation = source.translate(component, locale); + if (translation != null) return translation; + } + return null; + } + + @Override + public @NotNull Iterable sources() { + return Collections.unmodifiableSet(this.sources); + } + + @Override + public boolean addSource(final @NotNull Translator source) { + if (source == this) throw new IllegalArgumentException("MiniMessageTranslationSource"); + return this.sources.add(source); + } + + @Override + public boolean removeSource(final @NotNull Translator source) { + return this.sources.remove(source); + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("sources", this.sources)); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/locale/TranslationManager.java b/common/src/main/java/net/momirealms/customfishing/common/locale/TranslationManager.java new file mode 100644 index 00000000..13f33ab3 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/locale/TranslationManager.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.locale; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.translation.Translator; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Pair; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TranslationManager { + + public static final Locale DEFAULT_LOCALE = Locale.ENGLISH; + private static final List locales = List.of("en"); + private static TranslationManager instance; + + private final CustomFishingPlugin plugin; + private final Set installed = ConcurrentHashMap.newKeySet(); + private MiniMessageTranslationRegistry registry; + private final Path translationsDirectory; + + public TranslationManager(CustomFishingPlugin plugin) { + this.plugin = plugin; + this.translationsDirectory = this.plugin.getConfigDirectory().resolve("translations"); + instance = this; + } + + public void reload() { + // remove any previous registry + if (this.registry != null) { + MiniMessageTranslator.translator().removeSource(this.registry); + this.installed.clear(); + } + + for (String lang : locales) { + this.plugin.getConfigManager().saveResource("translations/" + lang + ".yml"); + } + + this.registry = MiniMessageTranslationRegistry.create(Key.key("customfishing", "main"), AdventureHelper.getMiniMessage()); + this.registry.defaultLocale(DEFAULT_LOCALE); + this.loadFromFileSystem(this.translationsDirectory, false); + MiniMessageTranslator.translator().addSource(this.registry); + } + + public static String miniMessageTranslation(String key) { + return miniMessageTranslation(key, null); + } + + public static String miniMessageTranslation(String key, @Nullable Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + if (locale == null) { + locale = DEFAULT_LOCALE; + } + } + return instance.registry.miniMessageTranslation(key, locale); + } + + public static Component render(Component component) { + return render(component, null); + } + + public static Component render(Component component, @Nullable Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + if (locale == null) { + locale = DEFAULT_LOCALE; + } + } + return MiniMessageTranslator.render(component, locale); + } + + public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) { + List translationFiles; + try (Stream stream = Files.list(directory)) { + translationFiles = stream.filter(TranslationManager::isTranslationFile).collect(Collectors.toList()); + } catch (IOException e) { + translationFiles = Collections.emptyList(); + } + + if (translationFiles.isEmpty()) { + return; + } + + Map> loaded = new HashMap<>(); + for (Path translationFile : translationFiles) { + try { + Pair> result = loadTranslationFile(translationFile); + loaded.put(result.left(), result.right()); + } catch (Exception e) { + if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) { + this.plugin.getPluginLogger().warn("Error loading locale file: " + translationFile.getFileName(), e); + } + } + } + + // try registering the locale without a country code - if we don't already have a registration for that + loaded.forEach((locale, bundle) -> { + Locale localeWithoutCountry = new Locale(locale.getLanguage()); + if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) { + try { + this.registry.registerAll(localeWithoutCountry, bundle); + } catch (IllegalArgumentException e) { + // ignore + } + } + }); + + Locale localLocale = Locale.getDefault(); + if (!this.installed.contains(localLocale)) { + plugin.getPluginLogger().warn(localLocale.toString().toLowerCase(Locale.ENGLISH) + ".yml not exists, using en.yml as default locale."); + } + } + + public static boolean isTranslationFile(Path path) { + return path.getFileName().toString().endsWith(".yml"); + } + + private static boolean isAdventureDuplicatesException(Exception e) { + return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists")); + } + + @SuppressWarnings("unchecked") + private Pair> loadTranslationFile(Path translationFile) { + String fileName = translationFile.getFileName().toString(); + String localeString = fileName.substring(0, fileName.length() - ".yml".length()); + Locale locale = parseLocale(localeString); + + if (locale == null) { + throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register."); + } + + Map bundle = new HashMap<>(); + YamlDocument document = plugin.getConfigManager().loadConfig("translations" + "\\" + translationFile.getFileName(), '@'); + Map map = document.getStringRouteMappedValues(false); + map.remove("config-version"); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof List list) { + List strList = (List) list; + StringJoiner stringJoiner = new StringJoiner(""); + for (String str : strList) { + stringJoiner.add(str); + } + bundle.put(entry.getKey(), stringJoiner.toString()); + } else if (entry.getValue() instanceof String str) { + bundle.put(entry.getKey(), str); + } + } + + this.registry.registerAll(locale, bundle); + this.installed.add(locale); + + return Pair.of(locale, bundle); + } + + public static @Nullable Locale parseLocale(@Nullable String locale) { + return locale == null ? null : Translator.parseLocale(locale); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/plugin/CustomFishingPlugin.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/CustomFishingPlugin.java new file mode 100644 index 00000000..e4fa118b --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/CustomFishingPlugin.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.plugin; + +import net.momirealms.customfishing.common.config.ConfigLoader; +import net.momirealms.customfishing.common.dependency.DependencyManager; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customfishing.common.plugin.logging.PluginLogger; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerAdapter; + +import java.io.InputStream; +import java.nio.file.Path; + +public interface CustomFishingPlugin { + + InputStream getResourceStream(String filePath); + + PluginLogger getPluginLogger(); + + ClassPathAppender getClassPathAppender(); + + SchedulerAdapter getScheduler(); + + Path getDataDirectory(); + + default Path getConfigDirectory() { + return getDataDirectory(); + } + + DependencyManager getDependencyManager(); + + TranslationManager getTranslationManager(); + + ConfigLoader getConfigManager(); + + String getServerVersion(); + + String getPluginVersion(); +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ClassPathAppender.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ClassPathAppender.java index 601853a7..71c41416 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ClassPathAppender.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.classpath; +package net.momirealms.customfishing.common.plugin.classpath; import java.nio.file.Path; diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ReflectionClassPathAppender.java similarity index 87% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ReflectionClassPathAppender.java index 5732f66a..277ebfed 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/ReflectionClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/ReflectionClassPathAppender.java @@ -23,7 +23,9 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.classpath; +package net.momirealms.customfishing.common.plugin.classpath; + +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; import java.net.MalformedURLException; import java.net.URLClassLoader; @@ -40,6 +42,10 @@ public class ReflectionClassPathAppender implements ClassPathAppender { } } + public ReflectionClassPathAppender(CustomFishingPlugin plugin) throws IllegalStateException { + this(plugin.getClass().getClassLoader()); + } + @Override public void addJarToClasspath(Path file) { try { diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/URLClassLoaderAccess.java similarity index 95% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/URLClassLoaderAccess.java index 1054b247..e62907f3 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/URLClassLoaderAccess.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/classpath/URLClassLoaderAccess.java @@ -23,9 +23,9 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.classpath; +package net.momirealms.customfishing.common.plugin.classpath; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -66,7 +66,7 @@ public abstract class URLClassLoaderAccess { * * @param url the URL to add */ - public abstract void addURL(@NonNull URL url); + public abstract void addURL(@NotNull URL url); private static void throwError(Throwable cause) throws UnsupportedOperationException { throw new UnsupportedOperationException("CustomFishing is unable to inject into the plugin URLClassLoader.\n" + @@ -100,7 +100,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { try { ADD_URL_METHOD.invoke(super.classLoader, url); } catch (ReflectiveOperationException e) { @@ -162,7 +162,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { if (this.unopenedURLs == null || this.pathURLs == null) { URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs")); } @@ -182,7 +182,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { URLClassLoaderAccess.throwError(null); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntitySettings.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/feature/Reloadable.java similarity index 71% rename from api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntitySettings.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/feature/Reloadable.java index 57ae5d76..6e7efc82 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/entity/EntitySettings.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/feature/Reloadable.java @@ -15,18 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.mechanic.entity; +package net.momirealms.customfishing.common.plugin.feature; -import java.util.Map; +public interface Reloadable { -public interface EntitySettings { - boolean isPersist(); + default void reload() { + unload(); + load(); + } - double getHorizontalVector(); + default void unload() { + } - double getVerticalVector(); + default void load() { + } - String getEntityID(); - - Map getPropertyMap(); + default void disable() { + unload(); + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/JavaPluginLogger.java similarity index 54% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/logging/JavaPluginLogger.java index 7d8b30ee..70c74c86 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/classpath/JarInJarClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/JavaPluginLogger.java @@ -23,40 +23,40 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.classpath; +package net.momirealms.customfishing.common.plugin.logging; -import net.momirealms.customfishing.libraries.loader.JarInJarClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; -import java.io.IOException; -import java.net.MalformedURLException; -import java.nio.file.Path; +public class JavaPluginLogger implements PluginLogger { + private final Logger logger; -public class JarInJarClassPathAppender implements ClassPathAppender { - private final JarInJarClassLoader classLoader; - - public JarInJarClassPathAppender(ClassLoader classLoader) { - if (!(classLoader instanceof JarInJarClassLoader)) { - throw new IllegalArgumentException("Loader is not a JarInJarClassLoader: " + classLoader.getClass().getName()); - } - this.classLoader = (JarInJarClassLoader) classLoader; + public JavaPluginLogger(Logger logger) { + this.logger = logger; } @Override - public void addJarToClasspath(Path file) { - try { - this.classLoader.addJarToClasspath(file.toUri().toURL()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + public void info(String s) { + this.logger.info(s); } @Override - public void close() { - this.classLoader.deleteJarResource(); - try { - this.classLoader.close(); - } catch (IOException e) { - e.printStackTrace(); - } + public void warn(String s) { + this.logger.warning(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.log(Level.WARNING, s, t); + } + + @Override + public void severe(String s) { + this.logger.severe(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.log(Level.SEVERE, s, t); } } diff --git a/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Log4jPluginLogger.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Log4jPluginLogger.java new file mode 100644 index 00000000..338ab9e5 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Log4jPluginLogger.java @@ -0,0 +1,61 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.plugin.logging; + +import org.apache.logging.log4j.Logger; + +public class Log4jPluginLogger implements PluginLogger { + private final Logger logger; + + public Log4jPluginLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String s) { + this.logger.info(s); + } + + @Override + public void warn(String s) { + this.logger.warn(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.warn(s, t); + } + + @Override + public void severe(String s) { + this.logger.error(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.error(s, t); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/PluginLogger.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/logging/PluginLogger.java index 26528e94..180d63ee 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoadingException.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/PluginLogger.java @@ -23,19 +23,24 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.loader; +package net.momirealms.customfishing.common.plugin.logging; /** - * Runtime exception used if there is a problem during loading + * Represents the logger instance being used by CustomFishing on the platform. + * + *

Messages sent using the logger are sent prefixed with the CustomFishing tag, + * and on some implementations will be colored depending on the message type.

*/ -public class LoadingException extends RuntimeException { +public interface PluginLogger { - public LoadingException(String message) { - super(message); - } + void info(String s); - public LoadingException(String message, Throwable cause) { - super(message, cause); - } + void warn(String s); + + void warn(String s, Throwable t); + + void severe(String s); + + void severe(String s, Throwable t); } diff --git a/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Slf4jPluginLogger.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Slf4jPluginLogger.java new file mode 100644 index 00000000..a77f01a6 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/logging/Slf4jPluginLogger.java @@ -0,0 +1,61 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.plugin.logging; + +import org.slf4j.Logger; + +public class Slf4jPluginLogger implements PluginLogger { + private final Logger logger; + + public Slf4jPluginLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String s) { + this.logger.info(s); + } + + @Override + public void warn(String s) { + this.logger.warn(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.warn(s, t); + } + + @Override + public void severe(String s) { + this.logger.error(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.error(s, t); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/AbstractJavaScheduler.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/AbstractJavaScheduler.java new file mode 100644 index 00000000..f59b3d56 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/AbstractJavaScheduler.java @@ -0,0 +1,132 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.plugin.scheduler; + +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Arrays; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Abstract implementation of {@link SchedulerAdapter} using a {@link ScheduledExecutorService}. + */ +public abstract class AbstractJavaScheduler implements SchedulerAdapter { + private static final int PARALLELISM = 16; + + private final CustomFishingPlugin plugin; + + private final ScheduledThreadPoolExecutor scheduler; + private final ForkJoinPool worker; + + public AbstractJavaScheduler(CustomFishingPlugin plugin) { + this.plugin = plugin; + + this.scheduler = new ScheduledThreadPoolExecutor(4, r -> { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName("customfishing-scheduler"); + return thread; + }); + this.scheduler.setRemoveOnCancelPolicy(true); + this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.worker = new ForkJoinPool(PARALLELISM, new WorkerThreadFactory(), new ExceptionHandler(), false); + } + + @Override + public Executor async() { + return this.worker; + } + + @Override + public SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit) { + ScheduledFuture future = this.scheduler.schedule(() -> this.worker.execute(task), delay, unit); + return () -> future.cancel(false); + } + + @Override + public SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit) { + ScheduledFuture future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(task), delay, interval, unit); + return () -> future.cancel(false); + } + + @Override + public void shutdownScheduler() { + this.scheduler.shutdown(); + try { + if (!this.scheduler.awaitTermination(1, TimeUnit.MINUTES)) { + this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing scheduler to terminate"); + reportRunningTasks(thread -> thread.getName().equals("customfishing-scheduler")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void shutdownExecutor() { + this.worker.shutdown(); + try { + if (!this.worker.awaitTermination(1, TimeUnit.MINUTES)) { + this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing worker thread pool to terminate"); + reportRunningTasks(thread -> thread.getName().startsWith("customfishing-worker-")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void reportRunningTasks(Predicate predicate) { + Thread.getAllStackTraces().forEach((thread, stack) -> { + if (predicate.test(thread)) { + this.plugin.getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" + + Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n")) + ); + } + }); + } + + private static final class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + private static final AtomicInteger COUNT = new AtomicInteger(0); + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + thread.setDaemon(true); + thread.setName("customfishing-worker-" + COUNT.getAndIncrement()); + return thread; + } + } + + private final class ExceptionHandler implements UncaughtExceptionHandler { + @Override + public void uncaughtException(Thread t, Throwable e) { + AbstractJavaScheduler.this.plugin.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e); + } + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/scheduler/CancellableTask.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/RegionExecutor.java similarity index 71% rename from api/src/main/java/net/momirealms/customfishing/api/scheduler/CancellableTask.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/RegionExecutor.java index 257d4a4c..320542af 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/scheduler/CancellableTask.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/RegionExecutor.java @@ -15,19 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.scheduler; +package net.momirealms.customfishing.common.plugin.scheduler; -public interface CancellableTask { +public interface RegionExecutor { - /** - * Cancel the task - */ - void cancel(); + void run(Runnable r, T l); - /** - * Get if the task is cancelled or not - * - * @return cancelled or not - */ - boolean isCancelled(); + SchedulerTask runLater(Runnable r, long delayTicks, T l); + + SchedulerTask runRepeating(Runnable r, long delayTicks, long period, T l); } diff --git a/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerAdapter.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerAdapter.java new file mode 100644 index 00000000..bcab3d0a --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerAdapter.java @@ -0,0 +1,106 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.plugin.scheduler; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * A scheduler for running tasks using the systems provided by the platform + */ +public interface SchedulerAdapter { + + /** + * Gets an async executor instance + * + * @return an async executor instance + */ + Executor async(); + + /** + * Gets a sync executor instance + * + * @return a sync executor instance + */ + RegionExecutor sync(); + + /** + * Executes a task async + * + * @param task the task + */ + default void executeAsync(Runnable task) { + async().execute(task); + } + + /** + * Executes a task sync + * + * @param task the task + */ + default void executeSync(Runnable task, T location) { + sync().run(task, location); + } + + default void executeSync(Runnable task) { + sync().run(task, null); + } + + /** + * Executes the given task with a delay. + * + * @param task the task + * @param delay the delay + * @param unit the unit of delay + * @return the resultant task instance + */ + SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit); + + /** + * Executes the given task repeatedly at a given interval. + * + * @param task the task + * @param interval the interval + * @param unit the unit of interval + * @return the resultant task instance + */ + SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit); + + /** + * Shuts down the scheduler instance. + * + *

{@link #asyncLater(Runnable, long, TimeUnit)} and {@link #asyncRepeating(Runnable, long, long, TimeUnit)}.

+ */ + void shutdownScheduler(); + + /** + * Shuts down the executor instance. + * + *

{@link #async()} and {@link #executeAsync(Runnable)}.

+ */ + void shutdownExecutor(); + +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerTask.java similarity index 84% rename from plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java rename to common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerTask.java index 98ddd06d..685c39ef 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/LoaderBootstrap.java +++ b/common/src/main/java/net/momirealms/customfishing/common/plugin/scheduler/SchedulerTask.java @@ -23,17 +23,16 @@ * SOFTWARE. */ -package net.momirealms.customfishing.libraries.loader; +package net.momirealms.customfishing.common.plugin.scheduler; /** - * Minimal bootstrap plugin, called by the loader plugin. + * Represents a scheduled task */ -public interface LoaderBootstrap { +public interface SchedulerTask { - void onLoad(); - - default void onEnable() {} - - default void onDisable() {} + /** + * Cancels the task. + */ + void cancel(); } diff --git a/common/src/main/java/net/momirealms/customfishing/common/sender/AbstractSender.java b/common/src/main/java/net/momirealms/customfishing/common/sender/AbstractSender.java new file mode 100644 index 00000000..8b7e0dea --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/sender/AbstractSender.java @@ -0,0 +1,116 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.sender; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Tristate; + +import java.util.UUID; + +/** + * Simple implementation of {@link Sender} using a {@link SenderFactory} + * + * @param the command sender type + */ +public final class AbstractSender implements Sender { + private final CustomFishingPlugin plugin; + private final SenderFactory factory; + private final T sender; + + private final UUID uniqueId; + private final String name; + private final boolean isConsole; + + AbstractSender(CustomFishingPlugin plugin, SenderFactory factory, T sender) { + this.plugin = plugin; + this.factory = factory; + this.sender = sender; + this.uniqueId = factory.getUniqueId(this.sender); + this.name = factory.getName(this.sender); + this.isConsole = this.factory.isConsole(this.sender); + } + + @Override + public CustomFishingPlugin getPlugin() { + return this.plugin; + } + + @Override + public UUID getUniqueId() { + return this.uniqueId; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void sendMessage(Component message) { + this.factory.sendMessage(this.sender, message); + } + + @Override + public void sendMessage(Component message, boolean ignoreEmpty) { + if (ignoreEmpty && message.equals(Component.empty())) { + return; + } + sendMessage(message); + } + + @Override + public Tristate getPermissionValue(String permission) { + return (isConsole() && this.factory.consoleHasAllPermissions()) ? Tristate.TRUE : this.factory.getPermissionValue(this.sender, permission); + } + + @Override + public boolean hasPermission(String permission) { + return (isConsole() && this.factory.consoleHasAllPermissions()) || this.factory.hasPermission(this.sender, permission); + } + + @Override + public void performCommand(String commandLine) { + this.factory.performCommand(this.sender, commandLine); + } + + @Override + public boolean isConsole() { + return this.isConsole; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof AbstractSender that)) return false; + return this.getUniqueId().equals(that.getUniqueId()); + } + + @Override + public int hashCode() { + return this.uniqueId.hashCode(); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/sender/DummyConsoleSender.java b/common/src/main/java/net/momirealms/customfishing/common/sender/DummyConsoleSender.java new file mode 100644 index 00000000..381116b8 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/sender/DummyConsoleSender.java @@ -0,0 +1,62 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.sender; + +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; + +import java.util.UUID; + +public abstract class DummyConsoleSender implements Sender { + private final CustomFishingPlugin platform; + + public DummyConsoleSender(CustomFishingPlugin plugin) { + this.platform = plugin; + } + + @Override + public void performCommand(String commandLine) { + } + + @Override + public boolean isConsole() { + return true; + } + + @Override + public CustomFishingPlugin getPlugin() { + return this.platform; + } + + @Override + public UUID getUniqueId() { + return Sender.CONSOLE_UUID; + } + + @Override + public String getName() { + return Sender.CONSOLE_NAME; + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/sender/Sender.java b/common/src/main/java/net/momirealms/customfishing/common/sender/Sender.java new file mode 100644 index 00000000..2805299c --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/sender/Sender.java @@ -0,0 +1,121 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.sender; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Tristate; + +import java.util.UUID; + +/** + * Wrapper interface to represent a CommandSender/CommandSource within the common command implementations. + */ +public interface Sender { + + /** The uuid used by the console sender. */ + UUID CONSOLE_UUID = new UUID(0, 0); // 00000000-0000-0000-0000-000000000000 + /** The name used by the console sender. */ + String CONSOLE_NAME = "Console"; + + /** + * Gets the plugin instance the sender is from. + * + * @return the plugin + */ + CustomFishingPlugin getPlugin(); + + /** + * Gets the sender's username + * + * @return a friendly username for the sender + */ + String getName(); + + /** + * Gets the sender's unique id. + * + *

See {@link #CONSOLE_UUID} for the console's UUID representation.

+ * + * @return the sender's uuid + */ + UUID getUniqueId(); + + /** + * Send a json message to the Sender. + * + * @param message the message to send. + */ + void sendMessage(Component message); + + /** + * Send a json message to the Sender. + * + * @param message the message to send. + * @param ignoreEmpty whether to ignore empty component + */ + void sendMessage(Component message, boolean ignoreEmpty); + + /** + * Gets the tristate a permission is set to. + * + * @param permission the permission to check for + * @return a tristate + */ + Tristate getPermissionValue(String permission); + + /** + * Check if the Sender has a permission. + * + * @param permission the permission to check for + * @return true if the sender has the permission + */ + boolean hasPermission(String permission); + + /** + * Makes the sender perform a command. + * + * @param commandLine the command + */ + void performCommand(String commandLine); + + /** + * Gets whether this sender is the console + * + * @return if the sender is the console + */ + boolean isConsole(); + + /** + * Gets whether this sender is still valid & receiving messages. + * + * @return if this sender is valid + */ + default boolean isValid() { + return true; + } + +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/sender/SenderFactory.java b/common/src/main/java/net/momirealms/customfishing/common/sender/SenderFactory.java new file mode 100644 index 00000000..c9aef1db --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/sender/SenderFactory.java @@ -0,0 +1,82 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.sender; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Tristate; + +import java.util.Objects; +import java.util.UUID; + +/** + * Factory class to make a thread-safe sender instance + * + * @param

the plugin type + * @param the command sender type + */ +public abstract class SenderFactory

implements AutoCloseable { + private final P plugin; + + public SenderFactory(P plugin) { + this.plugin = plugin; + } + + protected P getPlugin() { + return this.plugin; + } + + protected abstract UUID getUniqueId(T sender); + + protected abstract String getName(T sender); + + public abstract Audience getAudience(T sender); + + protected abstract void sendMessage(T sender, Component message); + + protected abstract Tristate getPermissionValue(T sender, String node); + + protected abstract boolean hasPermission(T sender, String node); + + protected abstract void performCommand(T sender, String command); + + protected abstract boolean isConsole(T sender); + + protected boolean consoleHasAllPermissions() { + return true; + } + + public final Sender wrap(T sender) { + Objects.requireNonNull(sender, "sender"); + return new AbstractSender<>(this.plugin, this, sender); + } + + @Override + public void close() { + + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/ArrayUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/ArrayUtils.java new file mode 100644 index 00000000..71b1221d --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/ArrayUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ArrayUtils { + + private ArrayUtils() {} + + public static T[] subArray(T[] array, int index) { + if (index < 0) { + throw new IllegalArgumentException("Index should be a value no lower than 0"); + } + if (array.length <= index) { + @SuppressWarnings("unchecked") + T[] emptyArray = (T[]) Array.newInstance(array.getClass().getComponentType(), 0); + return emptyArray; + } + @SuppressWarnings("unchecked") + T[] subArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - index); + System.arraycopy(array, index, subArray, 0, array.length - index); + return subArray; + } + + public static List splitArray(T[] array, int chunkSize) { + List result = new ArrayList<>(); + for (int i = 0; i < array.length; i += chunkSize) { + int end = Math.min(array.length, i + chunkSize); + @SuppressWarnings("unchecked") + T[] chunk = (T[]) Array.newInstance(array.getClass().getComponentType(), end - i); + System.arraycopy(array, i, chunk, 0, end - i); + result.add(chunk); + } + return result; + } + + public static T[] appendElementToArray(T[] array, T element) { + T[] newArray = Arrays.copyOf(array, array.length + 1); + newArray[array.length] = element; + return newArray; + } + + public static String[] splitValue(String value) { + return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']')) + .replaceAll("\\s", "") + .split(","); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/ClassUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/ClassUtils.java similarity index 98% rename from plugin/src/main/java/net/momirealms/customfishing/util/ClassUtils.java rename to common/src/main/java/net/momirealms/customfishing/common/util/ClassUtils.java index 1de55c96..9330a5ff 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/util/ClassUtils.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/ClassUtils.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.util; +package net.momirealms.customfishing.common.util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/CompletableFutures.java b/common/src/main/java/net/momirealms/customfishing/common/util/CompletableFutures.java similarity index 98% rename from plugin/src/main/java/net/momirealms/customfishing/util/CompletableFutures.java rename to common/src/main/java/net/momirealms/customfishing/common/util/CompletableFutures.java index 7e9dc660..882eaa69 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/util/CompletableFutures.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/CompletableFutures.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.util; +package net.momirealms.customfishing.common.util; import com.google.common.collect.ImmutableList; diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/Either.java b/common/src/main/java/net/momirealms/customfishing/common/util/Either.java new file mode 100644 index 00000000..3464b28d --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/Either.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +public interface Either { + + static @NotNull Either ofPrimary(final @NotNull U value) { + return EitherImpl.of(requireNonNull(value, "value"), null); + } + + static @NotNull Either ofFallback(final @NotNull V value) { + return EitherImpl.of(null, requireNonNull(value, "value")); + } + + @NotNull + Optional primary(); + + @NotNull + Optional fallback(); + + default @Nullable U primaryOrMapFallback(final @NotNull Function mapFallback) { + return this.primary().orElseGet(() -> mapFallback.apply(this.fallback().get())); + } + + default @Nullable V fallbackOrMapPrimary(final @NotNull Function mapPrimary) { + return this.fallback().orElseGet(() -> mapPrimary.apply(this.primary().get())); + } + + default @NotNull R mapEither( + final @NotNull Function mapPrimary, + final @NotNull Function mapFallback + ) { + return this.primary() + .map(mapPrimary) + .orElseGet(() -> this.fallback().map(mapFallback).get()); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/EitherImpl.java b/common/src/main/java/net/momirealms/customfishing/common/util/EitherImpl.java new file mode 100644 index 00000000..ab6ff54b --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/EitherImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +final class EitherImpl implements Either { + private final @Nullable U primary; + private final @Nullable V fallback; + + private EitherImpl(Optional primary, Optional fallback) { + this.primary = primary.orElse(null); + this.fallback = fallback.orElse(null); + } + + private EitherImpl(@Nullable U primary, @Nullable V fallback) { + this.primary = primary; + this.fallback = fallback; + } + + private EitherImpl( + EitherImpl original, + @Nullable U primary, + @Nullable V fallback + ) { + this.primary = primary; + this.fallback = fallback; + } + + @Override + public @NotNull Optional primary() { + return Optional.ofNullable(primary); + } + + @Override + public @NotNull Optional fallback() { + return Optional.ofNullable(fallback); + } + + public final EitherImpl withPrimary(@Nullable U value) { + @Nullable U newValue = value; + if (this.primary == newValue) return this; + return new EitherImpl<>(this, newValue, this.fallback); + } + + public EitherImpl withPrimary(Optional optional) { + @Nullable U value = optional.orElse(null); + if (this.primary == value) return this; + return new EitherImpl<>(this, value, this.fallback); + } + + public EitherImpl withFallback(@Nullable V value) { + @Nullable V newValue = value; + if (this.fallback == newValue) return this; + return new EitherImpl<>(this, this.primary, newValue); + } + + public EitherImpl withFallback(Optional optional) { + @Nullable V value = optional.orElse(null); + if (this.fallback == value) return this; + return new EitherImpl<>(this, this.primary, value); + } + + @Override + public boolean equals(@Nullable Object another) { + if (this == another) return true; + return another instanceof EitherImpl + && equalTo((EitherImpl) another); + } + + private boolean equalTo(EitherImpl another) { + return Objects.equals(primary, another.primary) + && Objects.equals(fallback, another.fallback); + } + + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + Objects.hashCode(primary); + h += (h << 5) + Objects.hashCode(fallback); + return h; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Either{"); + if (primary != null) { + builder.append("primary=").append(primary); + } + if (fallback != null) { + if (builder.length() > 7) builder.append(", "); + builder.append("fallback=").append(fallback); + } + return builder.append("}").toString(); + } + + public static EitherImpl of(Optional primary, Optional fallback) { + return new EitherImpl<>(primary, fallback); + } + + public static EitherImpl of(@Nullable U primary, @Nullable V fallback) { + return new EitherImpl<>(primary, fallback); + } + + public static EitherImpl copyOf(Either instance) { + if (instance instanceof EitherImpl) { + return (EitherImpl) instance; + } + return EitherImpl.of(instance.primary(), instance.fallback()); + } +} diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/FileUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/FileUtils.java new file mode 100644 index 00000000..0cd34962 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/FileUtils.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileUtils { + + private FileUtils() {} + + public static Path createFileIfNotExists(Path path) throws IOException { + if (!Files.exists(path)) { + Files.createFile(path); + } + return path; + } + + public static Path createDirectoryIfNotExists(Path path) throws IOException { + if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { + return path; + } + + try { + Files.createDirectory(path); + } catch (FileAlreadyExistsException e) { + // ignore + } + + return path; + } + + public static Path createDirectoriesIfNotExists(Path path) throws IOException { + if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { + return path; + } + + try { + Files.createDirectories(path); + } catch (FileAlreadyExistsException e) { + // ignore + } + + return path; + } + + public static void deleteDirectory(Path path) throws IOException { + if (!Files.exists(path) || !Files.isDirectory(path)) { + return; + } + + try (DirectoryStream contents = Files.newDirectoryStream(path)) { + for (Path file : contents) { + if (Files.isDirectory(file)) { + deleteDirectory(file); + } else { + Files.delete(file); + } + } + } + + Files.delete(path); + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/common/Key.java b/common/src/main/java/net/momirealms/customfishing/common/util/Key.java similarity index 84% rename from api/src/main/java/net/momirealms/customfishing/api/common/Key.java rename to common/src/main/java/net/momirealms/customfishing/common/util/Key.java index a276f10c..46037c6b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/common/Key.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/Key.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.common; +package net.momirealms.customfishing.common.util; public record Key(String namespace, String value) { @@ -23,11 +23,14 @@ public record Key(String namespace, String value) { return new Key(namespace, value); } + public static Key fromString(String key) { + String[] split = key.split(":", 2); + return of(split[0], split[1]); + } + @Override public int hashCode() { - int result = this.namespace.hashCode(); - result = (31 * result) + this.value.hashCode(); - return result; + return toString().hashCode(); } @Override @@ -43,4 +46,4 @@ public record Key(String namespace, String value) { public String toString() { return namespace + ":" + value; } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/data/EarningData.java b/common/src/main/java/net/momirealms/customfishing/common/util/ListUtils.java similarity index 59% rename from api/src/main/java/net/momirealms/customfishing/api/data/EarningData.java rename to common/src/main/java/net/momirealms/customfishing/common/util/ListUtils.java index 38b4d3d6..5550bd6a 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/data/EarningData.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/ListUtils.java @@ -15,23 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.data; +package net.momirealms.customfishing.common.util; -import com.google.gson.annotations.SerializedName; +import java.util.List; -public class EarningData { +public class ListUtils { - @SerializedName("earnings") - public double earnings; - @SerializedName("date") - public int date; - - public EarningData(double earnings, int date) { - this.earnings = earnings; - this.date = date; + private ListUtils() { } - public static EarningData empty() { - return new EarningData(0d, 0); + @SuppressWarnings("unchecked") + public static List toList(final Object obj) { + if (obj instanceof String s) { + return List.of(s); + } else if (obj instanceof List list) { + return (List) list; + } + throw new IllegalArgumentException("Cannot convert " + obj + " to a list"); } } diff --git a/api/src/main/java/net/momirealms/customfishing/api/common/Pair.java b/common/src/main/java/net/momirealms/customfishing/common/util/Pair.java similarity index 94% rename from api/src/main/java/net/momirealms/customfishing/api/common/Pair.java rename to common/src/main/java/net/momirealms/customfishing/common/util/Pair.java index 2491adcd..d665bb11 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/common/Pair.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/Pair.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.common; +package net.momirealms.customfishing.common.util; public record Pair(L left, R right) { diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/RandomUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/RandomUtils.java new file mode 100644 index 00000000..760ced64 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/RandomUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class RandomUtils { + + private final Random random; + + private RandomUtils() { + random = ThreadLocalRandom.current(); + } + + private static class SingletonHolder { + private static final RandomUtils INSTANCE = new RandomUtils(); + } + + private static RandomUtils getInstance() { + return SingletonHolder.INSTANCE; + } + + public static int generateRandomInt(int min, int max) { + return getInstance().random.nextInt(max - min + 1) + min; + } + + public static double generateRandomDouble(double min, double max) { + return min + (max - min) * getInstance().random.nextDouble(); + } + + public static float generateRandomFloat(float min, float max) { + return min + (max - min) * getInstance().random.nextFloat(); + } + + public static boolean generateRandomBoolean() { + return getInstance().random.nextBoolean(); + } + + public static T getRandomElementFromArray(T[] array) { + int index = getInstance().random.nextInt(array.length); + return array[index]; + } + + public static double triangle(double mode, double deviation) { + return mode + deviation * (generateRandomDouble(0,1) - generateRandomDouble(0,1)); + } + + public static T[] getRandomElementsFromArray(T[] array, int count) { + if (count > array.length) { + throw new IllegalArgumentException("Count cannot be greater than array length"); + } + + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[count]; + + for (int i = 0; i < count; i++) { + int index = getInstance().random.nextInt(array.length - i); + result[i] = array[index]; + array[index] = array[array.length - i - 1]; + } + + return result; + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customfishing/api/manager/CommandManager.java b/common/src/main/java/net/momirealms/customfishing/common/util/TriConsumer.java similarity index 84% rename from api/src/main/java/net/momirealms/customfishing/api/manager/CommandManager.java rename to common/src/main/java/net/momirealms/customfishing/common/util/TriConsumer.java index 975ec5e4..470cb71b 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/manager/CommandManager.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/TriConsumer.java @@ -15,11 +15,8 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.manager; +package net.momirealms.customfishing.common.util; -public interface CommandManager { - - void load(); - - void unload(); -} +public interface TriConsumer { + void accept(K k, V v, S s); +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/TriFunction.java b/common/src/main/java/net/momirealms/customfishing/common/util/TriFunction.java new file mode 100644 index 00000000..7f038164 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/TriFunction.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import java.util.Objects; +import java.util.function.Function; + +public interface TriFunction { + R apply(T var1, U var2, V var3); + + default TriFunction andThen(Function after) { + Objects.requireNonNull(after); + return (t, u, v) -> { + return after.apply(this.apply(t, u, v)); + }; + } +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/Tristate.java b/common/src/main/java/net/momirealms/customfishing/common/util/Tristate.java new file mode 100644 index 00000000..e3e5f1a5 --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/Tristate.java @@ -0,0 +1,98 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.common.util; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents three different states of a setting. + * + *

Possible values:

+ *

+ *
    + *
  • {@link #TRUE} - a positive setting
  • + *
  • {@link #FALSE} - a negative (negated) setting
  • + *
  • {@link #UNDEFINED} - a non-existent setting
  • + *
+ */ +public enum Tristate { + + /** + * A value indicating a positive setting + */ + TRUE(true), + + /** + * A value indicating a negative (negated) setting + */ + FALSE(false), + + /** + * A value indicating a non-existent setting + */ + UNDEFINED(false); + + /** + * Returns a {@link Tristate} from a boolean + * + * @param val the boolean value + * @return {@link #TRUE} or {@link #FALSE}, if the value is true or false, respectively. + */ + public static @NotNull Tristate of(boolean val) { + return val ? TRUE : FALSE; + } + + /** + * Returns a {@link Tristate} from a nullable boolean. + * + *

Unlike {@link #of(boolean)}, this method returns {@link #UNDEFINED} + * if the value is null.

+ * + * @param val the boolean value + * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value + * is null, true or false, respectively. + */ + public static @NotNull Tristate of(Boolean val) { + return val == null ? UNDEFINED : val ? TRUE : FALSE; + } + + private final boolean booleanValue; + + Tristate(boolean booleanValue) { + this.booleanValue = booleanValue; + } + + /** + * Returns the value of the Tristate as a boolean. + * + *

A value of {@link #UNDEFINED} converts to false.

+ * + * @return a boolean representation of the Tristate. + */ + public boolean asBoolean() { + return this.booleanValue; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/NumberUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/Tuple.java similarity index 74% rename from plugin/src/main/java/net/momirealms/customfishing/util/NumberUtils.java rename to common/src/main/java/net/momirealms/customfishing/common/util/Tuple.java index d2f053ec..94ad7fe7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/util/NumberUtils.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/Tuple.java @@ -15,12 +15,11 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.util; +package net.momirealms.customfishing.common.util; -public class NumberUtils { +public record Tuple(L left, M mid, R right) { - public static String money(double money) { - String str = String.format("%.2f", money); - return str.replace(",", "."); + public static Tuple of(final L left, final M mid, final R right) { + return new Tuple<>(left, mid, right); } -} +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customfishing/common/util/UUIDUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/UUIDUtils.java new file mode 100644 index 00000000..3b6d7c5e --- /dev/null +++ b/common/src/main/java/net/momirealms/customfishing/common/util/UUIDUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.common.util; + +import java.math.BigInteger; +import java.util.UUID; + +public class UUIDUtils { + + public static UUID fromUnDashedUUID(String id) { + return id == null ? null : new UUID( + new BigInteger(id.substring(0, 16), 16).longValue(), + new BigInteger(id.substring(16, 32), 16).longValue() + ); + } + + public static String toUnDashedUUID(UUID uuid) { + return uuid.toString().replace("-", ""); + } + + public static UUID uuidFromIntArray(int[] array) { + return new UUID((long)array[0] << 32 | (long)array[1] & 4294967295L, (long)array[2] << 32 | (long)array[3] & 4294967295L); + } + + public static int[] uuidToIntArray(UUID uuid) { + long l = uuid.getMostSignificantBits(); + long m = uuid.getLeastSignificantBits(); + return leastMostToIntArray(l, m); + } + + private static int[] leastMostToIntArray(long uuidMost, long uuidLeast) { + return new int[]{(int)(uuidMost >> 32), (int)uuidMost, (int)(uuidLeast >> 32), (int)uuidLeast}; + } +} diff --git a/api/src/main/java/net/momirealms/customfishing/api/util/WeightUtils.java b/common/src/main/java/net/momirealms/customfishing/common/util/WeightUtils.java similarity index 96% rename from api/src/main/java/net/momirealms/customfishing/api/util/WeightUtils.java rename to common/src/main/java/net/momirealms/customfishing/common/util/WeightUtils.java index 4de91cab..68e770dd 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/util/WeightUtils.java +++ b/common/src/main/java/net/momirealms/customfishing/common/util/WeightUtils.java @@ -15,9 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.api.util; - -import net.momirealms.customfishing.api.common.Pair; +package net.momirealms.customfishing.common.util; import java.util.ArrayList; import java.util.Arrays; @@ -27,8 +25,11 @@ import java.util.Map; /** * Utility class for selecting random items based on weights. */ +@SuppressWarnings("DuplicatedCode") public class WeightUtils { + private WeightUtils() {} + /** * Get a random item from a list of pairs, each associated with a weight. * @@ -81,6 +82,7 @@ public class WeightUtils { * @return A randomly selected item from the list, or null if no item was selected. */ private static T getRandom(double[] weights, List available, int effectiveSize) { + if (available.isEmpty()) return null; double total = Arrays.stream(weights).sum(); double[] weightRatios = new double[effectiveSize]; for (int i = 0; i < effectiveSize; i++){ diff --git a/common/src/main/resources/library-version.properties b/common/src/main/resources/library-version.properties new file mode 100644 index 00000000..1e8ea705 --- /dev/null +++ b/common/src/main/resources/library-version.properties @@ -0,0 +1,27 @@ +config=${config_version} +asm=${asm_version} +asm-commons=${asm_commons_version} +jar-relocator=${jar_relocator_version} +h2-driver=${h2_driver_version} +sqlite-driver=${sqlite_driver_version} +cloud-core=${cloud_core_version} +cloud-brigadier=${cloud_brigadier_version} +cloud-services=${cloud_services_version} +cloud-bukkit=${cloud_bukkit_version} +cloud-paper=${cloud_paper_version} +cloud-minecraft-extras=${cloud_minecraft_extras_version} +boosted-yaml=${boosted_yaml_version} +byte-buddy=${byte_buddy_version} +mongodb-driver-core=${mongodb_driver_version} +mariadb-java-client=${mariadb_driver_version} +mysql-connector-j=${mysql_driver_version} +hikari-cp=${hikari_version} +commons-pool=${commons_pool_version} +bstats-base=${bstats_version} +geantyref=${geantyref_version} +gson=${gson_version} +caffeine=${caffeine_version} +jedis=${jedis_version} +exp4j=${exp4j_version} +slf4j=${slf4j_version} +lz4-java=${lz4_version} \ No newline at end of file diff --git a/compatibility/build.gradle.kts b/compatibility/build.gradle.kts new file mode 100644 index 00000000..bcb26bcc --- /dev/null +++ b/compatibility/build.gradle.kts @@ -0,0 +1,75 @@ +repositories { + maven("https://jitpack.io/") // itemsadder + maven("https://mvn.lumine.io/repository/maven-public/") // mythicmobs + maven("https://nexus.phoenixdevt.fr/repository/maven-public/") // mmoitems + maven("https://papermc.io/repo/repository/maven-public/") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi + maven("https://r.irepo.space/maven/") // neigeitems + maven("https://repo.oraxen.com/releases/") // oraxen + maven("https://repo.auxilor.io/repository/maven-public/") // eco + maven("https://nexus.betonquest.org/repository/betonquest/") // betonquest + maven("https://repo.dmulloy2.net/repository/public/") // betonquest needs packet wrapper? + maven("https://maven.enginehub.org/repo/") // worldguard +} + +dependencies { + compileOnly(project(":common")) + compileOnly(project(":api")) + compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") { + exclude(module = "adventure-bom") + exclude(module = "checker-qual") + exclude(module = "annotations") + } + compileOnly("org.jetbrains:annotations:${rootProject.properties["jetbrains_annotations_version"]}") + // papi + compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}") + // server + compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + // vault + compileOnly("com.github.MilkBowl:VaultAPI:${rootProject.properties["vault_version"]}") + // season + compileOnly("com.github.Xiao-MoMi:Custom-Crops:3.4.8") + compileOnly(files("libs/RealisticSeasons-api.jar")) + compileOnly(files("libs/AdvancedSeasons-API.jar")) + // enchantment + compileOnly(files("libs/AdvancedEnchantments-api.jar")) + // leveler + compileOnly(files("libs/mcMMO-api.jar")) + compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT") + compileOnly("dev.aurelium:auraskills-api-bukkit:2.0.7") + compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") + compileOnly("com.github.Zrips:Jobs:4.17.2") + // quest + compileOnly(files("libs/BattlePass-4.0.6-api.jar")) + compileOnly(files("libs/ClueScrolls-4.8.7-api.jar")) + compileOnly(files("libs/notquests-5.17.1.jar")) + compileOnly("org.betonquest:betonquest:2.0.1") + // item + compileOnly(files("libs/zaphkiel-2.0.24.jar")) + compileOnly("com.github.LoneDev6:API-ItemsAdder:3.6.1") + compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") + compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") + compileOnly("pers.neige.neigeitems:NeigeItems:1.17.13") + compileOnly("io.th0rgal:oraxen:1.168.0") + // entity + compileOnly("io.lumine:Mythic-Dist:5.6.2") + // eco + compileOnly("com.willfp:eco:6.70.1") + compileOnly("com.willfp:EcoJobs:3.56.1") + compileOnly("com.willfp:EcoSkills:3.46.1") + compileOnly("com.willfp:libreforge:4.58.1") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/plugin/libs/AdvancedEnchantments-api.jar b/compatibility/libs/AdvancedEnchantments-api.jar similarity index 100% rename from plugin/libs/AdvancedEnchantments-api.jar rename to compatibility/libs/AdvancedEnchantments-api.jar diff --git a/compatibility/libs/AdvancedSeasons-API.jar b/compatibility/libs/AdvancedSeasons-API.jar new file mode 100644 index 0000000000000000000000000000000000000000..19ddf212c31fbb61fe4c2dfc7a9ee7ecd578e0e0 GIT binary patch literal 64703 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. */ -package net.momirealms.customfishing.compatibility; +package net.momirealms.customfishing.bukkit.integration; import net.milkbowl.vault.economy.Economy; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.plugin.RegisteredServiceProvider; public class VaultHook { @@ -26,7 +27,7 @@ public class VaultHook { private static Economy economy; public static boolean initialize() { - RegisteredServiceProvider rsp = CustomFishingPlugin.getInstance().getServer().getServicesManager().getRegistration(Economy.class); + RegisteredServiceProvider rsp = Bukkit.getServicesManager().getRegistration(Economy.class); if (rsp == null) { return false; } @@ -37,4 +38,16 @@ public class VaultHook { public static Economy getEconomy() { return economy; } + + public static void deposit(OfflinePlayer player, double amount) { + economy.depositPlayer(player, amount); + } + + public static void withdraw(OfflinePlayer player, double amount) { + economy.withdrawPlayer(player, amount); + } + + public static double getBalance(OfflinePlayer player) { + return economy.getBalance(player); + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/block/ItemsAdderBlockImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/ItemsAdderBlockProvider.java similarity index 70% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/block/ItemsAdderBlockImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/ItemsAdderBlockProvider.java index 43da1177..14b4de98 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/block/ItemsAdderBlockImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/ItemsAdderBlockProvider.java @@ -15,35 +15,37 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.block; +package net.momirealms.customfishing.bukkit.integration.block; import dev.lone.itemsadder.api.CustomBlock; +import net.momirealms.customfishing.api.integration.BlockProvider; import net.momirealms.customfishing.api.mechanic.block.BlockDataModifier; -import net.momirealms.customfishing.api.mechanic.block.BlockLibrary; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.List; -public class ItemsAdderBlockImpl implements BlockLibrary { +public class ItemsAdderBlockProvider implements BlockProvider { @Override - public String identification() { + public String identifier() { return "ItemsAdder"; } @Override - public BlockData getBlockData(Player player, String id, List modifiers) { + public BlockData blockData(@NotNull Context context, @NotNull String id, List modifiers) { BlockData blockData = CustomBlock.getBaseBlockData(id); for (BlockDataModifier modifier : modifiers) { - modifier.apply(player, blockData); + modifier.apply(context, blockData); } return blockData; } @Override - public String getBlockID(Block block) { + public String blockID(@NotNull Block block) { CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block); return customBlock == null ? null : customBlock.getId(); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/block/VanillaBlockImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/OraxenBlockProvider.java similarity index 54% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/block/VanillaBlockImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/OraxenBlockProvider.java index 5709759e..2f5701f2 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/block/VanillaBlockImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/block/OraxenBlockProvider.java @@ -15,37 +15,36 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.block; +package net.momirealms.customfishing.bukkit.integration.block; +import io.th0rgal.oraxen.api.OraxenBlocks; +import io.th0rgal.oraxen.mechanics.Mechanic; +import net.momirealms.customfishing.api.integration.BlockProvider; import net.momirealms.customfishing.api.mechanic.block.BlockDataModifier; -import net.momirealms.customfishing.api.mechanic.block.BlockLibrary; -import org.bukkit.Material; +import net.momirealms.customfishing.api.mechanic.context.Context; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Locale; -public class VanillaBlockImpl implements BlockLibrary { +public class OraxenBlockProvider implements BlockProvider { @Override - public String identification() { - return "vanilla"; + public String identifier() { + return "Oraxen"; } @Override - public BlockData getBlockData(Player player, String id, List modifiers) { - BlockData blockData = Material.valueOf(id.toUpperCase(Locale.ENGLISH)).createBlockData(); - for (BlockDataModifier modifier : modifiers) { - modifier.apply(player, blockData); - } - return blockData; + public BlockData blockData(@NotNull Context context, @NotNull String id, List modifiers) { + return null; } @Override - public @Nullable String getBlockID(Block block) { - return block.getType().name(); + public String blockID(@NotNull Block block) { + Mechanic mechanic = OraxenBlocks.getOraxenBlock(block.getBlockData()); + if (mechanic == null) return null; + return mechanic.getItemID(); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/AdvancedEnchantmentsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/AdvancedEnchantmentsProvider.java similarity index 61% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/AdvancedEnchantmentsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/AdvancedEnchantmentsProvider.java index fd5cd1cc..db8c0ab8 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/AdvancedEnchantmentsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/AdvancedEnchantmentsProvider.java @@ -15,23 +15,30 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.enchant; +package net.momirealms.customfishing.bukkit.integration.enchant; import net.advancedplugins.ae.api.AEAPI; -import net.momirealms.customfishing.api.integration.EnchantmentInterface; +import net.momirealms.customfishing.api.integration.EnchantmentProvider; +import net.momirealms.customfishing.common.util.Pair; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Map; -public class AdvancedEnchantmentsImpl implements EnchantmentInterface { +public class AdvancedEnchantmentsProvider implements EnchantmentProvider { @Override - public List getEnchants(ItemStack itemStack) { - List enchants = new ArrayList<>(); + public String identifier() { + return "AdvancedEnchantments"; + } + + @Override + public List> getEnchants(@NotNull ItemStack itemStack) { + List> enchants = new ArrayList<>(); for (Map.Entry entry : AEAPI.getEnchantmentsOnItem(itemStack).entrySet()) { - enchants.add("AE:" + entry.getKey() + ":" + entry.getValue()); + enchants.add(Pair.of("AE:" + entry.getKey(), entry.getValue().shortValue())); } return enchants; } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/VanillaEnchantmentsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/VanillaEnchantmentsProvider.java similarity index 62% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/VanillaEnchantmentsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/VanillaEnchantmentsProvider.java index 55ad72c8..a604f1e1 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/enchant/VanillaEnchantmentsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/enchant/VanillaEnchantmentsProvider.java @@ -15,25 +15,31 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.enchant; +package net.momirealms.customfishing.bukkit.integration.enchant; -import net.momirealms.customfishing.api.integration.EnchantmentInterface; +import net.momirealms.customfishing.api.integration.EnchantmentProvider; +import net.momirealms.customfishing.common.util.Pair; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Map; -public class VanillaEnchantmentsImpl implements EnchantmentInterface { +public class VanillaEnchantmentsProvider implements EnchantmentProvider { @Override - public List getEnchants(ItemStack itemStack) { + public String identifier() { + return "vanilla"; + } + + @Override + public List> getEnchants(@NotNull ItemStack itemStack) { Map enchantments = itemStack.getEnchantments(); - List enchants = new ArrayList<>(enchantments.size()); + List> enchants = new ArrayList<>(enchantments.size()); for (Map.Entry en : enchantments.entrySet()) { - String key = en.getKey().getKey() + ":" + en.getValue(); - enchants.add(key); + enchants.add(Pair.of(en.getKey().getKey().toString(), en.getValue().shortValue())); } return enchants; } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/ItemsAdderEntityImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/ItemsAdderEntityProvider.java similarity index 74% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/ItemsAdderEntityImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/ItemsAdderEntityProvider.java index 09f5223c..2b26586a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/ItemsAdderEntityImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/ItemsAdderEntityProvider.java @@ -15,24 +15,26 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.entity; +package net.momirealms.customfishing.bukkit.integration.entity; import dev.lone.itemsadder.api.CustomEntity; -import net.momirealms.customfishing.api.mechanic.entity.EntityLibrary; +import net.momirealms.customfishing.api.integration.EntityProvider; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; import java.util.Map; -public class ItemsAdderEntityImpl implements EntityLibrary { +public class ItemsAdderEntityProvider implements EntityProvider { @Override - public String identification() { - return "vanilla"; + public String identifier() { + return "ItemsAdder"; } + @NotNull @Override - public Entity spawn(Location location, String id, Map propertyMap) { + public Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map propertyMap) { CustomEntity customEntity = CustomEntity.spawn( id, location, diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/MythicEntityImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/MythicEntityProvider.java similarity index 78% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/MythicEntityImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/MythicEntityProvider.java index 464864ae..78824e7c 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/MythicEntityImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/MythicEntityProvider.java @@ -15,36 +15,33 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.entity; +package net.momirealms.customfishing.bukkit.integration.entity; import io.lumine.mythic.api.adapters.AbstractLocation; import io.lumine.mythic.api.mobs.MythicMob; import io.lumine.mythic.bukkit.MythicBukkit; import io.lumine.mythic.bukkit.utils.serialize.Position; import io.lumine.mythic.core.mobs.ActiveMob; -import net.momirealms.customfishing.api.mechanic.entity.EntityLibrary; -import net.momirealms.customfishing.util.ConfigUtils; +import net.momirealms.customfishing.api.integration.EntityProvider; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; import java.util.Map; import java.util.Optional; -public class MythicEntityImpl implements EntityLibrary { +public class MythicEntityProvider implements EntityProvider { private MythicBukkit mythicBukkit; - public MythicEntityImpl() { - this.mythicBukkit = MythicBukkit.inst(); - } - @Override - public String identification() { + public String identifier() { return "MythicMobs"; } + @NotNull @Override - public Entity spawn(Location location, String id, Map propertyMap) { + public Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map propertyMap) { if (this.mythicBukkit == null || mythicBukkit.isClosed()) { this.mythicBukkit = MythicBukkit.inst(); } @@ -53,7 +50,7 @@ public class MythicEntityImpl implements EntityLibrary { MythicMob theMob = mythicMob.get(); Position position = Position.of(location); AbstractLocation abstractLocation = new AbstractLocation(position); - ActiveMob activeMob = theMob.spawn(abstractLocation, ConfigUtils.getDoubleValue(propertyMap.get("level"))); + ActiveMob activeMob = theMob.spawn(abstractLocation, (double) propertyMap.getOrDefault("level", 0d)); return activeMob.getEntity().getBukkitEntity(); } throw new NullPointerException("MythicMobs: " + id + " doesn't exist."); diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/VanillaEntityImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/VanillaEntityProvider.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/VanillaEntityImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/VanillaEntityProvider.java index da5e9139..90b53afb 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/entity/VanillaEntityImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/entity/VanillaEntityProvider.java @@ -15,25 +15,27 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.entity; +package net.momirealms.customfishing.bukkit.integration.entity; -import net.momirealms.customfishing.api.mechanic.entity.EntityLibrary; +import net.momirealms.customfishing.api.integration.EntityProvider; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; import java.util.Locale; import java.util.Map; -public class VanillaEntityImpl implements EntityLibrary { +public class VanillaEntityProvider implements EntityProvider { @Override - public String identification() { + public String identifier() { return "vanilla"; } + @NotNull @Override - public Entity spawn(Location location, String id, Map propertyMap) { + public Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map propertyMap) { return location.getWorld().spawnEntity(location, EntityType.valueOf(id.toUpperCase(Locale.ENGLISH))); } } diff --git a/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/CustomFishingItemProvider.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/CustomFishingItemProvider.java new file mode 100644 index 00000000..fa51e3ec --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/CustomFishingItemProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.integration.item; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.ItemProvider; +import net.momirealms.customfishing.api.mechanic.context.Context; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +public class CustomFishingItemProvider implements ItemProvider { + + @Override + public String identifier() { + return "CustomFishing"; + } + + @NotNull + @Override + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + String[] split = id.split(":", 2); + String finalID; + if (split.length == 1) { + // CustomFishing:ID + finalID = split[0]; + } else { + // CustomFishing:TYPE:ID + finalID = split[1]; + } + ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player), finalID); + return requireNonNull(itemStack); + } + + @Override + public String itemID(@NotNull ItemStack itemStack) { + return BukkitCustomFishingPlugin.getInstance().getItemManager().getCustomFishingItemID(itemStack); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ItemsAdderItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ItemsAdderItemProvider.java similarity index 55% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ItemsAdderItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ItemsAdderItemProvider.java index 773910ea..a8a5f3f3 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ItemsAdderItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ItemsAdderItemProvider.java @@ -15,35 +15,34 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; import dev.lone.itemsadder.api.CustomStack; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; -import net.momirealms.customfishing.api.util.LogUtils; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class ItemsAdderItemImpl implements ItemLibrary { +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class ItemsAdderItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "ItemsAdder"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { - CustomStack stack = CustomStack.getInstance(id); - if (stack == null) { - LogUtils.severe(id + " doesn't exist in ItemsAdder configs."); - return null; - } + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + CustomStack stack = requireNonNull(CustomStack.getInstance(id)); return stack.getItemStack(); } @Override - public String getItemID(ItemStack itemStack) { - CustomStack customStack = CustomStack.byItemStack(itemStack); - if (customStack == null) return null; - return customStack.getNamespacedID(); + public String itemID(@NotNull ItemStack itemStack) { + return Optional.ofNullable(CustomStack.byItemStack(itemStack)).map(CustomStack::getNamespacedID).orElse(null); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MMOItemsItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MMOItemsItemProvider.java similarity index 66% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MMOItemsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MMOItemsItemProvider.java index 1cf8409a..603fedb1 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MMOItemsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MMOItemsItemProvider.java @@ -15,36 +15,40 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; -import de.tr7zw.changeme.nbtapi.NBTItem; +import io.lumine.mythic.lib.api.item.NBTItem; import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.Locale; -public class MMOItemsItemImpl implements ItemLibrary { +import static java.util.Objects.requireNonNull; + +public class MMOItemsItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "MMOItems"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { String[] split = id.split(":"); MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), split[1].toUpperCase(Locale.ENGLISH)); - return mmoItem == null ? new ItemStack(Material.AIR) : mmoItem.newBuilder().build(); + return mmoItem == null ? new ItemStack(Material.AIR) : requireNonNull(mmoItem.newBuilder().build()); } @Override - public String getItemID(ItemStack itemStack) { - NBTItem nbtItem = new NBTItem(itemStack); + public String itemID(@NotNull ItemStack itemStack) { + NBTItem nbtItem = NBTItem.get(itemStack); if (!nbtItem.hasTag("MMOITEMS_ITEM_ID")) return null; return nbtItem.getString("MMOITEMS_ITEM_ID"); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/McMMOTreasureImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/McMMOTreasureProvider.java similarity index 79% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/McMMOTreasureImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/McMMOTreasureProvider.java index 6ca323b7..b379e48b 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/McMMOTreasureImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/McMMOTreasureProvider.java @@ -15,24 +15,25 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -public class McMMOTreasureImpl implements ItemLibrary { +public class McMMOTreasureProvider implements ItemProvider { private final Method getMcMMOPlayerMethod; private final Method getFishingManagerMethod; private final Method getFishingTreasureMethod; private final Method getItemStackMethod; - public McMMOTreasureImpl() throws ClassNotFoundException, NoSuchMethodException { + public McMMOTreasureProvider() throws ClassNotFoundException, NoSuchMethodException { Class userClass = Class.forName("com.gmail.nossr50.util.player.UserManager"); getMcMMOPlayerMethod = userClass.getMethod("getPlayer", Player.class); Class mcMMOPlayerClass = Class.forName("com.gmail.nossr50.datatypes.player.McMMOPlayer"); @@ -45,12 +46,13 @@ public class McMMOTreasureImpl implements ItemLibrary { } @Override - public String identification() { + public String identifier() { return "mcMMO"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { if (!id.equals("treasure")) return new ItemStack(Material.AIR); ItemStack itemStack = null; int times = 0; @@ -68,11 +70,11 @@ public class McMMOTreasureImpl implements ItemLibrary { times++; } } - return itemStack == null ? new ItemStack(Material.COD) : itemStack; + return itemStack == null ? (Math.random() > 0.5 ? new ItemStack(Material.COD) : (Math.random() > 0.2) ? new ItemStack(Material.SALMON) : new ItemStack(Material.PUFFERFISH)) : itemStack; } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { return null; } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MythicMobsItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MythicMobsItemProvider.java similarity index 64% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MythicMobsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MythicMobsItemProvider.java index 28a45b51..fd277c2d 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/MythicMobsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/MythicMobsItemProvider.java @@ -15,29 +15,26 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; -import de.tr7zw.changeme.nbtapi.NBTItem; import io.lumine.mythic.bukkit.MythicBukkit; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class MythicMobsItemImpl implements ItemLibrary { +public class MythicMobsItemProvider implements ItemProvider { private MythicBukkit mythicBukkit; - public MythicMobsItemImpl() { - this.mythicBukkit = MythicBukkit.inst(); - } - @Override - public String identification() { + public String identifier() { return "MythicMobs"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { if (mythicBukkit == null || mythicBukkit.isClosed()) { this.mythicBukkit = MythicBukkit.inst(); } @@ -45,8 +42,10 @@ public class MythicMobsItemImpl implements ItemLibrary { } @Override - public String getItemID(ItemStack itemStack) { - NBTItem nbtItem = new NBTItem(itemStack); - return nbtItem.hasTag("MYTHIC_TYPE") ? nbtItem.getString("MYTHIC_TYPE") : null; + public String itemID(@NotNull ItemStack itemStack) { + if (mythicBukkit == null || mythicBukkit.isClosed()) { + this.mythicBukkit = MythicBukkit.inst(); + } + return mythicBukkit.getItemManager().getMythicTypeFromItem(itemStack); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/NeigeItemsItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/NeigeItemsItemProvider.java similarity index 68% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/NeigeItemsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/NeigeItemsItemProvider.java index 43047613..e6001a06 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/NeigeItemsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/NeigeItemsItemProvider.java @@ -15,29 +15,33 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import pers.neige.neigeitems.item.ItemInfo; import pers.neige.neigeitems.manager.ItemManager; import pers.neige.neigeitems.utils.ItemUtils; -public class NeigeItemsItemImpl implements ItemLibrary { +import java.util.Objects; + +public class NeigeItemsItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "NeigeItems"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { - return ItemManager.INSTANCE.getItemStack(id, player); + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + return Objects.requireNonNull(ItemManager.INSTANCE.getItemStack(id, player)); } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { ItemInfo itemInfo = ItemUtils.isNiItem(itemStack); if (itemInfo != null) { return itemInfo.getId(); diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/OraxenItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/OraxenItemProvider.java similarity index 74% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/OraxenItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/OraxenItemProvider.java index 85dd40b5..a709ac1d 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/OraxenItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/OraxenItemProvider.java @@ -15,30 +15,32 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; import io.th0rgal.oraxen.api.OraxenItems; import io.th0rgal.oraxen.items.ItemBuilder; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class OraxenItemImpl implements ItemLibrary { +public class OraxenItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "Oraxen"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { ItemBuilder itemBuilder = OraxenItems.getItemById(id); return itemBuilder == null ? new ItemStack(Material.AIR) : itemBuilder.build(); } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { return OraxenItems.getIdByItem(itemStack); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ZaphkielItemImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ZaphkielItemProvider.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ZaphkielItemImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ZaphkielItemProvider.java index d1dc0129..ea2ddb2e 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/ZaphkielItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/item/ZaphkielItemProvider.java @@ -15,35 +15,39 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.integration.item; import ink.ptms.zaphkiel.ZapAPI; import ink.ptms.zaphkiel.Zaphkiel; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; +import net.momirealms.customfishing.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class ZaphkielItemImpl implements ItemLibrary { +import java.util.Objects; + +public class ZaphkielItemProvider implements ItemProvider { private final ZapAPI zapAPI; - public ZaphkielItemImpl() { + public ZaphkielItemProvider() { this.zapAPI = Zaphkiel.INSTANCE.api(); } @Override - public String identification() { + public String identifier() { return "Zaphkiel"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { - return zapAPI.getItemManager().generateItemStack(id, player); + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + return Objects.requireNonNull(zapAPI.getItemManager().generateItemStack(id, player)); } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { if (itemStack == null || itemStack.getType() == Material.AIR) return null; return zapAPI.getItemHandler().getItemId(itemStack); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AuraSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AuraSkillsLevelerProvider.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AuraSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AuraSkillsLevelerProvider.java index 70847a40..3d514064 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AuraSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AuraSkillsLevelerProvider.java @@ -15,23 +15,29 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import dev.aurelium.auraskills.api.AuraSkillsApi; import dev.aurelium.auraskills.api.registry.NamespacedId; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class AuraSkillsImpl implements LevelInterface { +public class AuraSkillsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "AuraSkills"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { AuraSkillsApi.get().getUser(player.getUniqueId()) .addSkillXp(AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target)), amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return AuraSkillsApi.get().getUser(player.getUniqueId()).getSkillLevel( AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target)) ); diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AureliumSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AureliumSkillsProvider.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AureliumSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AureliumSkillsProvider.java index cb25cee6..ef484833 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/AureliumSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/AureliumSkillsProvider.java @@ -15,28 +15,34 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import com.archyx.aureliumskills.api.AureliumAPI; import com.archyx.aureliumskills.leveler.Leveler; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class AureliumSkillsImpl implements LevelInterface { +public class AureliumSkillsProvider implements LevelerProvider { + + @Override + public String identifier() { + return "AureliumSkills"; + } private final Leveler leveler; - public AureliumSkillsImpl() { + public AureliumSkillsProvider() { leveler = AureliumAPI.getPlugin().getLeveler(); } @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { leveler.addXp(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target), amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return AureliumAPI.getSkillLevel(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target)); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoJobsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoJobsLevelerProvider.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoJobsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoJobsLevelerProvider.java index fb0f8308..1edea7dd 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoJobsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoJobsLevelerProvider.java @@ -15,18 +15,19 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import com.willfp.ecojobs.api.EcoJobsAPI; import com.willfp.ecojobs.jobs.Job; import com.willfp.ecojobs.jobs.Jobs; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class EcoJobsImpl implements LevelInterface { +public class EcoJobsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { for (Job job : EcoJobsAPI.getActiveJobs(player)) { if (job.getId().equals(target)) { EcoJobsAPI.giveJobExperience(player, job, amount); @@ -35,9 +36,14 @@ public class EcoJobsImpl implements LevelInterface { } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { Job job = Jobs.getByID(target); if (job == null) return 0; return EcoJobsAPI.getJobLevel(player, job); } + + @Override + public String identifier() { + return "EcoJobs"; + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoSkillsLevelerProvider.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoSkillsLevelerProvider.java index 01dc15f7..e36765f2 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/EcoSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/EcoSkillsLevelerProvider.java @@ -15,24 +15,30 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import com.willfp.ecoskills.api.EcoSkillsAPI; import com.willfp.ecoskills.skills.Skills; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.Objects; -public class EcoSkillsImpl implements LevelInterface { +public class EcoSkillsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { EcoSkillsAPI.gainSkillXP(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target)), amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return EcoSkillsAPI.getSkillLevel(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target))); } + + @Override + public String identifier() { + return "EcoSkills"; + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/JobsRebornImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/JobsRebornLevelerProvider.java similarity index 77% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/JobsRebornImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/JobsRebornLevelerProvider.java index 0302b1a5..a093a83a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/JobsRebornImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/JobsRebornLevelerProvider.java @@ -15,21 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import com.gamingmesh.jobs.Jobs; import com.gamingmesh.jobs.container.Job; import com.gamingmesh.jobs.container.JobProgression; import com.gamingmesh.jobs.container.JobsPlayer; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.List; -public class JobsRebornImpl implements LevelInterface { +public class JobsRebornLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player); Job job = Jobs.getJob(target); if (jobsPlayer != null && jobsPlayer.isInJob(job)) @@ -37,7 +38,7 @@ public class JobsRebornImpl implements LevelInterface { } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player); if (jobsPlayer != null) { List jobs = jobsPlayer.getJobProgression(); @@ -48,4 +49,9 @@ public class JobsRebornImpl implements LevelInterface { } return 0; } + + @Override + public String identifier() { + return "JobsReborn"; + } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/MMOCoreImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/MMOCoreLevelerProvider.java similarity index 70% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/MMOCoreImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/MMOCoreLevelerProvider.java index 2c0691c5..ecbadf20 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/MMOCoreImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/MMOCoreLevelerProvider.java @@ -15,23 +15,29 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.experience.EXPSource; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class MMOCoreImpl implements LevelInterface { +public class MMOCoreLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "MMOCore"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { MMOCore.plugin.professionManager.get(target).giveExperience(PlayerData.get(player), amount, null ,EXPSource.OTHER); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return PlayerData.get(player).getCollectionSkills().getLevel(MMOCore.plugin.professionManager.get(target)); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/McMMOImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/McMMOLevelerProvider.java similarity index 68% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/level/McMMOImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/McMMOLevelerProvider.java index 41ec39f0..174f80a2 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/level/McMMOImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/level/McMMOLevelerProvider.java @@ -15,22 +15,28 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.level; +package net.momirealms.customfishing.bukkit.integration.level; import com.gmail.nossr50.api.ExperienceAPI; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import net.momirealms.customfishing.api.integration.LevelInterface; +import net.momirealms.customfishing.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class McMMOImpl implements LevelInterface { +public class McMMOLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "mcMMO"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { ExperienceAPI.addRawXP(player, target, (float) amount, "UNKNOWN"); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return ExperienceAPI.getLevel(player, PrimarySkillType.valueOf(target)); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CompetitionPapi.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CompetitionPapi.java similarity index 75% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CompetitionPapi.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CompetitionPapi.java index c68b2492..1cdc1904 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CompetitionPapi.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CompetitionPapi.java @@ -15,12 +15,14 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.papi; +package net.momirealms.customfishing.bukkit.integration.papi; import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import net.momirealms.customfishing.setting.CFLocale; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,9 +31,9 @@ import java.util.Optional; public class CompetitionPapi extends PlaceholderExpansion { - private final CustomFishingPlugin plugin; + private final BukkitCustomFishingPlugin plugin; - public CompetitionPapi(CustomFishingPlugin plugin) { + public CompetitionPapi(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; } @@ -55,7 +57,7 @@ public class CompetitionPapi extends PlaceholderExpansion { @Override public @NotNull String getVersion() { - return "2.0"; + return "2.2"; } @Override @@ -70,25 +72,25 @@ public class CompetitionPapi extends PlaceholderExpansion { return String.valueOf(plugin.getCompetitionManager().getOnGoingCompetition() != null); } case "nextseconds" -> { - return String.valueOf(plugin.getCompetitionManager().getNextCompetitionSeconds()); + return String.valueOf(plugin.getCompetitionManager().getNextCompetitionInSeconds()); } case "nextsecond" -> { - return plugin.getCompetitionManager().getNextCompetitionSeconds() % 60 + CFLocale.FORMAT_Second; + return plugin.getCompetitionManager().getNextCompetitionInSeconds() % 60 + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_SECOND.build().key()); } case "nextminute" -> { - int sec = plugin.getCompetitionManager().getNextCompetitionSeconds(); + int sec = plugin.getCompetitionManager().getNextCompetitionInSeconds(); int min = (sec % 3600) / 60; - return sec < 60 ? "" : min + CFLocale.FORMAT_Minute; + return sec < 60 ? "" : min + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_MINUTE.build().key()); } case "nexthour" -> { - int sec = plugin.getCompetitionManager().getNextCompetitionSeconds(); + int sec = plugin.getCompetitionManager().getNextCompetitionInSeconds(); int h = (sec % (3600 * 24)) / 3600; - return sec < 3600 ? "" : h + CFLocale.FORMAT_Hour; + return sec < 3600 ? "" : h + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_HOUR.build().key()); } case "nextday" -> { - int sec = plugin.getCompetitionManager().getNextCompetitionSeconds(); + int sec = plugin.getCompetitionManager().getNextCompetitionInSeconds(); int day = sec / (3600 * 24); - return day == 0 ? "" : day + CFLocale.FORMAT_Day; + return day == 0 ? "" : day + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_DAY.build().key()); } case "rank" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); @@ -98,27 +100,27 @@ public class CompetitionPapi extends PlaceholderExpansion { case "goal" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); if (competition == null) return ""; - else return competition.getGoal().name(); + else return competition.getGoal().toString(); } case "seconds" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); if (competition == null) return ""; - return competition.getCachedPlaceholder("{seconds}"); + return String.valueOf(competition.getPublicContext().arg(ContextKeys.SECONDS)); } case "second" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); if (competition == null) return ""; - return competition.getCachedPlaceholder("{second}"); + return String.valueOf(competition.getPublicContext().arg(ContextKeys.SECOND)); } case "minute" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); if (competition == null) return ""; - return competition.getCachedPlaceholder("{minute}"); + return String.valueOf(competition.getPublicContext().arg(ContextKeys.MINUTE)); } case "hour" -> { FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); if (competition == null) return ""; - return competition.getCachedPlaceholder("{hour}"); + return String.valueOf(competition.getPublicContext().arg(ContextKeys.HOUR)); } } diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CFPapi.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CustomFishingPapi.java similarity index 74% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CFPapi.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CustomFishingPapi.java index d57d56e6..6fe85a2c 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/CFPapi.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/CustomFishingPapi.java @@ -15,22 +15,23 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.papi; +package net.momirealms.customfishing.bukkit.integration.papi; import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.user.OnlineUser; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.storage.user.UserData; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class CFPapi extends PlaceholderExpansion { +public class CustomFishingPapi extends PlaceholderExpansion { - private final CustomFishingPlugin plugin; + private final BukkitCustomFishingPlugin plugin; - public CFPapi(CustomFishingPlugin plugin) { + public CustomFishingPapi(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; } @@ -54,7 +55,7 @@ public class CFPapi extends PlaceholderExpansion { @Override public @NotNull String getVersion() { - return "2.0"; + return "2.2"; } @Override @@ -65,11 +66,9 @@ public class CFPapi extends PlaceholderExpansion { @Override public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { String[] split = params.split("_"); - Player player = offlinePlayer.getPlayer(); if (player == null) return ""; - switch (split[0]) { case "market" -> { if (split.length < 2) @@ -77,46 +76,45 @@ public class CFPapi extends PlaceholderExpansion { switch (split[1]) { case "limit" -> { if (split.length < 3) { - return String.format("%.2f", plugin.getMarketManager().getEarningLimit(player)); + return String.format("%.2f", plugin.getMarketManager().earningLimit(Context.player(player))); } else { Player another = Bukkit.getPlayer(split[2]); if (another == null) { return ""; } - return String.format("%.2f", plugin.getMarketManager().getEarningLimit(another)); + return String.format("%.2f", plugin.getMarketManager().earningLimit(Context.player(another))); } } case "earnings" -> { - OnlineUser user; + UserData user; if (split.length < 3) { - user = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); + user = plugin.getStorageManager().getOnlineUser(player.getUniqueId()).orElse(null); } else { Player another = Bukkit.getPlayer(split[2]); if (another == null) { return ""; } - user = plugin.getStorageManager().getOnlineUser(another.getUniqueId()); + user = plugin.getStorageManager().getOnlineUser(another.getUniqueId()).orElse(null); } if (user == null) return ""; - return String.format("%.2f", user.getEarningData().earnings); + return String.format("%.2f", user.earningData().earnings()); } case "canearn" -> { if (split.length < 3) { - OnlineUser user = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); + UserData user = plugin.getStorageManager().getOnlineUser(player.getUniqueId()).orElse(null); if (user == null) return ""; - return String.format("%.2f", plugin.getMarketManager().getEarningLimit(player) - user.getEarningData().earnings); + return String.format("%.2f", plugin.getMarketManager().earningLimit(Context.player(player)) - user.earningData().earnings()); } else { Player another = Bukkit.getPlayer(split[2]); if (another == null) { return ""; } - - OnlineUser user = plugin.getStorageManager().getOnlineUser(another.getUniqueId()); + UserData user = plugin.getStorageManager().getOnlineUser(another.getUniqueId()).orElse(null); if (user == null) return ""; - return String.format("%.2f", plugin.getMarketManager().getEarningLimit(another) - user.getEarningData().earnings); + return String.format("%.2f", plugin.getMarketManager().earningLimit(Context.player(another)) - user.earningData().earnings()); } } } diff --git a/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/StatisticsPapi.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/StatisticsPapi.java new file mode 100644 index 00000000..b3e6c801 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/papi/StatisticsPapi.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.integration.papi; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.api.storage.user.UserData; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public class StatisticsPapi extends PlaceholderExpansion { + + private final BukkitCustomFishingPlugin plugin; + + public StatisticsPapi(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + super.register(); + } + + public void unload() { + super.unregister(); + } + + @Override + public @NotNull String getIdentifier() { + return "fishingstats"; + } + + @Override + public @NotNull String getAuthor() { + return "XiaoMoMi"; + } + + @Override + public @NotNull String getVersion() { + return "2.2"; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { + Optional onlineUser = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); + return onlineUser.map( + data -> { + FishingStatistics statistics = data.statistics(); + String[] split = params.split("_", 2); + switch (split[0]) { + case "total" -> { + return String.valueOf(statistics.amountOfFishCaught()); + } + case "hascaught" -> { + if (split.length == 1) return "Invalid format"; + return String.valueOf(statistics.getAmount(split[1]) != 0); + } + case "amount" -> { + if (split.length == 1) return "Invalid format"; + return String.valueOf(statistics.getAmount(split[1])); + } + case "size-record" -> { + float size = statistics.getMaxSize(split[1]); + return String.format("%.2f", size < 0 ? 0 : size); + } + case "category" -> { + if (split.length == 1) return "Invalid format"; + String[] categorySplit = split[1].split("_", 2); + if (categorySplit.length == 1) return "Invalid format"; + List category = plugin.getStatisticsManager().getCategoryMembers(categorySplit[1]); + if (categorySplit[0].equals("total")) { + int total = 0; + for (String loot : category) { + total += statistics.getAmount(loot); + } + return String.valueOf(total); + } else if (categorySplit[0].equals("progress")) { + int size = category.size(); + int unlocked = 0; + for (String loot : category) { + if (statistics.getAmount(loot) != 0) unlocked++; + } + double percent = ((double) unlocked * 100) / size; + String progress = String.format("%.1f", percent); + return progress.equals("100.0") ? "100" : progress; + } + } + } + return ""; + } + ).orElse("Data not loaded"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BattlePassHook.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BattlePassQuest.java similarity index 84% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BattlePassHook.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BattlePassQuest.java index 2983460d..2c0f1fee 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BattlePassHook.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BattlePassQuest.java @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.quest; +package net.momirealms.customfishing.bukkit.integration.quest; import io.github.battlepass.BattlePlugin; import io.github.battlepass.api.events.server.PluginReloadEvent; import net.advancedplugins.bp.impl.actions.ActionRegistry; import net.advancedplugins.bp.impl.actions.external.executor.ActionQuestExecutor; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import net.momirealms.customfishing.api.event.FishingResultEvent; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -29,10 +29,10 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; -public class BattlePassHook implements Listener { +public class BattlePassQuest implements Listener { - public BattlePassHook() { - Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.get()); + public BattlePassQuest() { + Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap()); } public void register() { @@ -57,15 +57,15 @@ public class BattlePassHook implements Listener { Player player = event.getPlayer(); // Single Fish Quest - if (event.getLoot().getID() != null) + if (event.getLoot().id() != null) this.executionBuilder("loot") .player(player) - .root(event.getLoot().getID()) + .root(event.getLoot().id()) .progress(event.getAmount()) .buildAndExecute(); // Group Fish Quest - String[] lootGroup = event.getLoot().getLootGroup(); + String[] lootGroup = event.getLoot().lootGroup(); if (event.getLoot() != null && lootGroup != null) for (String group : lootGroup) { this.executionBuilder("group") diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BetonQuestQuest.java similarity index 94% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BetonQuestQuest.java index 15fc7859..e83752ea 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/BetonQuestQuest.java @@ -15,10 +15,9 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.quest; +package net.momirealms.customfishing.bukkit.integration.quest; import net.momirealms.customfishing.api.event.FishingResultEvent; -import net.momirealms.customfishing.api.util.LogUtils; import org.betonquest.betonquest.BetonQuest; import org.betonquest.betonquest.Instruction; import org.betonquest.betonquest.VariableNumber; @@ -39,7 +38,7 @@ import java.util.Collections; import java.util.HashSet; @SuppressWarnings("DuplicatedCode") -public class BetonQuestHook { +public class BetonQuestQuest { public static void register() { BetonQuest.getInstance().registerObjectives("customfishing_loot", IDObjective.class); @@ -80,7 +79,7 @@ public class BetonQuestHook { if (isInvalidLocation(event, onlineProfile)) { return; } - if (this.loot_ids.contains(event.getLoot().getID()) && this.checkConditions(onlineProfile)) { + if (this.loot_ids.contains(event.getLoot().id()) && this.checkConditions(onlineProfile)) { getCountingData(onlineProfile).progress(event.getAmount()); completeIfDoneOrNotify(onlineProfile); } @@ -96,7 +95,7 @@ public class BetonQuestHook { try { targetLocation = playerLocation.getLocation(profile); } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { - LogUtils.warn(e.getMessage()); + e.printStackTrace(); return true; } final int range = rangeVar.getInt(profile); @@ -149,7 +148,7 @@ public class BetonQuestHook { if (isInvalidLocation(event, onlineProfile)) { return; } - String[] groups = event.getLoot().getLootGroup(); + String[] groups = event.getLoot().lootGroup(); if (groups != null) for (String group : groups) { if (this.loot_groups.contains(group) && this.checkConditions(onlineProfile)) { @@ -170,7 +169,7 @@ public class BetonQuestHook { try { targetLocation = playerLocation.getLocation(profile); } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { - LogUtils.warn(e.getMessage()); + e.printStackTrace(); return true; } final int range = rangeVar.getInt(profile); diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/ClueScrollsQuest.java similarity index 73% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/ClueScrollsQuest.java index eb328635..2f753f04 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/quest/ClueScrollsQuest.java @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.quest; +package net.momirealms.customfishing.bukkit.integration.quest; import com.electro2560.dev.cluescrolls.api.*; -import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import net.momirealms.customfishing.api.event.FishingResultEvent; import net.momirealms.customfishing.api.mechanic.loot.Loot; import org.bukkit.Bukkit; @@ -26,18 +26,18 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -public class ClueScrollsHook implements Listener { +public class ClueScrollsQuest implements Listener { private final CustomClue idClue; private final CustomClue groupClue; - public ClueScrollsHook() { - idClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomFishingPlugin.getInstance(), "loot", new ClueConfigData("id", DataType.STRING)); - groupClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomFishingPlugin.getInstance(), "group", new ClueConfigData("group", DataType.STRING)); + public ClueScrollsQuest() { + idClue = ClueScrollsAPI.getInstance().registerCustomClue(BukkitCustomFishingPlugin.getInstance().getBoostrap(), "loot", new ClueConfigData("id", DataType.STRING)); + groupClue = ClueScrollsAPI.getInstance().registerCustomClue(BukkitCustomFishingPlugin.getInstance().getBoostrap(), "group", new ClueConfigData("group", DataType.STRING)); } public void register() { - Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.get()); + Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap()); } @EventHandler @@ -57,12 +57,12 @@ public class ClueScrollsHook implements Listener { idClue.handle( player, event.getAmount(), - new ClueDataPair("id", loot.getID()) + new ClueDataPair("id", loot.id()) ); } - if (loot != null && loot.getLootGroup() != null) { - for (String group : event.getLoot().getLootGroup()) { + if (loot != null && loot.lootGroup() != null) { + for (String group : event.getLoot().lootGroup()) { groupClue.handle( player, event.getAmount(), diff --git a/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/AdvancedSeasonsProvider.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/AdvancedSeasonsProvider.java new file mode 100644 index 00000000..9749a750 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/AdvancedSeasonsProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.integration.season; + +import net.advancedplugins.seasons.api.AdvancedSeasonsAPI; +import net.momirealms.customfishing.api.integration.SeasonProvider; +import net.momirealms.customfishing.api.mechanic.misc.season.Season; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +public class AdvancedSeasonsProvider implements SeasonProvider { + + private final AdvancedSeasonsAPI api; + + public AdvancedSeasonsProvider() { + this.api = new AdvancedSeasonsAPI(); + } + + @NotNull + @Override + public Season getSeason(@NotNull World world) { + return switch (api.getSeason(world)) { + case "SPRING" -> Season.SPRING; + case "WINTER" -> Season.WINTER; + case "SUMMER" -> Season.SUMMER; + case "FALL" -> Season.AUTUMN; + default -> Season.DISABLE; + }; + } + + @Override + public String identifier() { + return "AdvancedSeasons"; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/season/CustomCropsSeasonImpl.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/CustomCropsSeasonProvider.java similarity index 60% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/season/CustomCropsSeasonImpl.java rename to compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/CustomCropsSeasonProvider.java index 837db4d7..ce5a2856 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/season/CustomCropsSeasonImpl.java +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/CustomCropsSeasonProvider.java @@ -15,23 +15,28 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.season; +package net.momirealms.customfishing.bukkit.integration.season; import net.momirealms.customcrops.api.CustomCropsPlugin; import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customfishing.api.integration.SeasonInterface; +import net.momirealms.customfishing.api.integration.SeasonProvider; import org.bukkit.World; import org.jetbrains.annotations.NotNull; import java.util.Locale; -public class CustomCropsSeasonImpl implements SeasonInterface { +public class CustomCropsSeasonProvider implements SeasonProvider { @NotNull @Override - public String getSeason(World world) { - Season season = CustomCropsPlugin.get().getIntegrationManager().getSeason(world); - if (season == null) return "disabled"; - return season.name().toUpperCase(Locale.ENGLISH); + public net.momirealms.customfishing.api.mechanic.misc.season.Season getSeason(@NotNull World world) { + Season season = CustomCropsPlugin.get().getIntegrationManager().getSeasonInterface().getSeason(world); + if (season == null) return net.momirealms.customfishing.api.mechanic.misc.season.Season.DISABLE; + return net.momirealms.customfishing.api.mechanic.misc.season.Season.valueOf(season.name().toUpperCase(Locale.ENGLISH)); + } + + @Override + public String identifier() { + return "CustomCrops"; } } \ No newline at end of file diff --git a/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/RealisticSeasonsProvider.java b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/RealisticSeasonsProvider.java new file mode 100644 index 00000000..f62cc591 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customfishing/bukkit/integration/season/RealisticSeasonsProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.integration.season; + +import me.casperge.realisticseasons.api.SeasonsAPI; +import net.momirealms.customfishing.api.integration.SeasonProvider; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +public class RealisticSeasonsProvider implements SeasonProvider { + + @NotNull + @Override + public net.momirealms.customfishing.api.mechanic.misc.season.Season getSeason(@NotNull World world) { + return switch (SeasonsAPI.getInstance().getSeason(world)) { + case WINTER -> net.momirealms.customfishing.api.mechanic.misc.season.Season.WINTER; + case SPRING -> net.momirealms.customfishing.api.mechanic.misc.season.Season.SPRING; + case SUMMER -> net.momirealms.customfishing.api.mechanic.misc.season.Season.SUMMER; + case FALL -> net.momirealms.customfishing.api.mechanic.misc.season.Season.AUTUMN; + case DISABLED, RESTORE -> net.momirealms.customfishing.api.mechanic.misc.season.Season.DISABLE; + }; + } + + @Override + public String identifier() { + return "RealisticSeasons"; + } +} diff --git a/plugin/.gitignore b/core/.gitignore similarity index 100% rename from plugin/.gitignore rename to core/.gitignore diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000..737b6f68 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,101 @@ +val commitID: String by project + +plugins { + id("io.github.goooler.shadow") version "8.1.7" +} + +repositories { + maven("https://repo.xenondevs.xyz/releases") // invui + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi +} + +dependencies { + // platform + compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + // subprojects + implementation(project(":common")) + implementation(project(":api")) { + exclude("dev.dejvokep", "boosted-yaml") + } + implementation(project(":compatibility")) + // adventure + implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") + implementation("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") + implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}") + implementation("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") { + exclude("com.google.code.gson", "gson") + } + // GUI + implementation("xyz.xenondevs.invui:invui:${rootProject.properties["invui_version"]}") { + exclude("org.jetbrains", "annotations") + } + // tag & component + implementation("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}") + implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") + // nms util + implementation("com.github.Xiao-MoMi:Sparrow-Heart:${rootProject.properties["sparrow_heart_version"]}") +// implementation(files("libs/Sparrow-Heart-${rootProject.properties["sparrow_heart_version"]}.jar")) + // bstats + compileOnly("org.bstats:bstats-bukkit:${rootProject.properties["bstats_version"]}") + // config + compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + // serialization + compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}") + // database + compileOnly("org.xerial:sqlite-jdbc:${rootProject.properties["sqlite_driver_version"]}") + compileOnly("com.h2database:h2:${rootProject.properties["h2_driver_version"]}") + compileOnly("org.mongodb:mongodb-driver-sync:${rootProject.properties["mongodb_driver_version"]}") + compileOnly("com.zaxxer:HikariCP:${rootProject.properties["hikari_version"]}") + compileOnly("redis.clients:jedis:${rootProject.properties["jedis_version"]}") + // cloud command framework + compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") + compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}") + compileOnly("org.incendo:cloud-paper:${rootProject.properties["cloud_paper_version"]}") + // expression + compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}") + // placeholder api + compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}") + // lz4 + compileOnly("org.lz4:lz4-java:${rootProject.properties["lz4_version"]}") +} + +tasks { + shadowJar { + archiveFileName = "CustomFishing-${rootProject.properties["project_version"]}-${commitID}.jar" + destinationDirectory.set(file("$rootDir/target")) + relocate("net.kyori", "net.momirealms.customfishing.libraries") + relocate("org.incendo", "net.momirealms.customfishing.libraries") + relocate("dev.dejvokep", "net.momirealms.customfishing.libraries") + relocate ("org.apache.commons.pool2", "net.momirealms.customfishing.libraries.commonspool2") + relocate ("com.mysql", "net.momirealms.customfishing.libraries.mysql") + relocate ("org.mariadb", "net.momirealms.customfishing.libraries.mariadb") + relocate ("com.zaxxer.hikari", "net.momirealms.customfishing.libraries.hikari") + relocate ("com.mongodb", "net.momirealms.customfishing.libraries.mongodb") + relocate ("org.bson", "net.momirealms.customfishing.libraries.bson") + relocate ("org.bstats", "net.momirealms.customfishing.libraries.bstats") + relocate ("com.github.benmanes.caffeine", "net.momirealms.customfishing.libraries.caffeine") + relocate ("net.momirealms.sparrow.heart", "net.momirealms.customfishing.bukkit.nms") + relocate ("com.saicone.rtag", "net.momirealms.customfishing.libraries.rtag") + relocate ("xyz.xenondevs", "net.momirealms.customfishing.libraries") + relocate ("net.objecthunter.exp4j", "net.momirealms.customfishing.libraries.exp4j") + relocate ("net.jpountz", "net.momirealms.customfishing.libraries.jpountz") //lz4 + } +} + +artifacts { + archives(tasks.shadowJar) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/core/libs/Sparrow-Heart-0.29.jar b/core/libs/Sparrow-Heart-0.29.jar new file mode 100644 index 0000000000000000000000000000000000000000..6bcee3171639f0a57d6706dd6e11f00fc3904077 GIT binary patch literal 214942 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�o^)$f%62X7x64cnto)66*rI|M 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 literal 0 HcmV?d00001 diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitBootstrap.java b/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitBootstrap.java new file mode 100644 index 00000000..5e5441f4 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitBootstrap.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import org.bukkit.plugin.java.JavaPlugin; + +public class BukkitBootstrap extends JavaPlugin { + + private BukkitCustomFishingPlugin plugin; + + @Override + public void onLoad() { + this.plugin = new BukkitCustomFishingPluginImpl(this); + this.plugin.load(); + } + + @Override + public void onEnable() { + this.plugin.enable(); + } + + @Override + public void onDisable() { + this.plugin.disable(); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitCustomFishingPluginImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitCustomFishingPluginImpl.java new file mode 100644 index 00000000..000e088e --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/BukkitCustomFishingPluginImpl.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.CustomFishingReloadEvent; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.misc.cooldown.CoolDownManager; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.bukkit.action.BukkitActionManager; +import net.momirealms.customfishing.bukkit.bag.BukkitBagManager; +import net.momirealms.customfishing.bukkit.block.BukkitBlockManager; +import net.momirealms.customfishing.bukkit.command.BukkitCommandManager; +import net.momirealms.customfishing.bukkit.competition.BukkitCompetitionManager; +import net.momirealms.customfishing.bukkit.config.BukkitConfigManager; +import net.momirealms.customfishing.bukkit.effect.BukkitEffectManager; +import net.momirealms.customfishing.bukkit.entity.BukkitEntityManager; +import net.momirealms.customfishing.bukkit.event.BukkitEventManager; +import net.momirealms.customfishing.bukkit.fishing.BukkitFishingManager; +import net.momirealms.customfishing.bukkit.game.BukkitGameManager; +import net.momirealms.customfishing.bukkit.hook.BukkitHookManager; +import net.momirealms.customfishing.bukkit.integration.BukkitIntegrationManager; +import net.momirealms.customfishing.bukkit.item.BukkitItemManager; +import net.momirealms.customfishing.bukkit.loot.BukkitLootManager; +import net.momirealms.customfishing.bukkit.market.BukkitMarketManager; +import net.momirealms.customfishing.bukkit.requirement.BukkitRequirementManager; +import net.momirealms.customfishing.bukkit.scheduler.BukkitSchedulerAdapter; +import net.momirealms.customfishing.bukkit.sender.BukkitSenderFactory; +import net.momirealms.customfishing.bukkit.statistic.BukkitStatisticsManager; +import net.momirealms.customfishing.bukkit.storage.BukkitStorageManager; +import net.momirealms.customfishing.bukkit.totem.BukkitTotemManager; +import net.momirealms.customfishing.common.dependency.Dependency; +import net.momirealms.customfishing.common.dependency.DependencyManagerImpl; +import net.momirealms.customfishing.common.helper.VersionHelper; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customfishing.common.plugin.classpath.ReflectionClassPathAppender; +import net.momirealms.customfishing.common.plugin.logging.JavaPluginLogger; +import net.momirealms.customfishing.common.plugin.logging.PluginLogger; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; + +public class BukkitCustomFishingPluginImpl extends BukkitCustomFishingPlugin { + + private final ClassPathAppender classPathAppender; + private final PluginLogger logger; + private BukkitCommandManager commandManager; + private Consumer debugger; + + public BukkitCustomFishingPluginImpl(Plugin boostrap) { + super(boostrap); + VersionHelper.init(getServerVersion()); + this.scheduler = new BukkitSchedulerAdapter(this); + this.classPathAppender = new ReflectionClassPathAppender(this); + this.logger = new JavaPluginLogger(getBoostrap().getLogger()); + this.dependencyManager = new DependencyManagerImpl(this); + } + + @Override + public void load() { + this.dependencyManager.loadDependencies( + List.of( + Dependency.BOOSTED_YAML, + Dependency.BSTATS_BASE, Dependency.BSTATS_BUKKIT, + Dependency.CAFFEINE, + Dependency.GEANTY_REF, + Dependency.CLOUD_CORE, Dependency.CLOUD_SERVICES, Dependency.CLOUD_BUKKIT, Dependency.CLOUD_PAPER, Dependency.CLOUD_BRIGADIER, Dependency.CLOUD_MINECRAFT_EXTRAS, + Dependency.GSON, + Dependency.COMMONS_POOL_2, + Dependency.JEDIS, + Dependency.EXP4J, + Dependency.MYSQL_DRIVER, Dependency.MARIADB_DRIVER, + Dependency.SQLITE_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE, + Dependency.H2_DRIVER, + Dependency.MONGODB_DRIVER_CORE, Dependency.MONGODB_DRIVER_SYNC, Dependency.MONGODB_DRIVER_BSON, + Dependency.HIKARI_CP, + Dependency.LZ4 + ) + ); + } + + @Override + public void enable() { + this.eventManager = new BukkitEventManager(this); + this.configManager = new BukkitConfigManager(this); + this.requirementManager = new BukkitRequirementManager(this); + this.actionManager = new BukkitActionManager(this); + this.senderFactory = new BukkitSenderFactory(this); + this.placeholderManager = new BukkitPlaceholderManager(this); + this.itemManager = new BukkitItemManager(this); + this.competitionManager = new BukkitCompetitionManager(this); + this.marketManager = new BukkitMarketManager(this); + this.storageManager = new BukkitStorageManager(this); + this.lootManager = new BukkitLootManager(this); + this.coolDownManager = new CoolDownManager(this); + this.entityManager = new BukkitEntityManager(this); + this.blockManager = new BukkitBlockManager(this); + this.statisticsManager = new BukkitStatisticsManager(this); + this.effectManager = new BukkitEffectManager(this); + this.hookManager = new BukkitHookManager(this); + this.fishingManager = new BukkitFishingManager(this); + this.bagManager = new BukkitBagManager(this); + this.totemManager = new BukkitTotemManager(this); + this.translationManager = new TranslationManager(this); + this.integrationManager = new BukkitIntegrationManager(this); + this.gameManager = new BukkitGameManager(this); + this.commandManager = new BukkitCommandManager(this); + this.commandManager.registerDefaultFeatures(); + + this.reload(); + if (ConfigManager.metrics()) new Metrics((JavaPlugin) getBoostrap(), 16648); + if (ConfigManager.checkUpdate()) { + VersionHelper.UPDATE_CHECKER.apply(this).thenAccept(result -> { + if (!result) this.getPluginLogger().info("You are using the latest version."); + else this.getPluginLogger().warn("Update is available: https://polymart.org/resource/2723"); + }); + } + } + + @Override + public void reload() { + MechanicType.reset(); + + this.itemManager.unload(); + this.eventManager.unload(); + this.entityManager.unload(); + this.lootManager.unload(); + this.blockManager.unload(); + this.effectManager.unload(); + this.hookManager.unload(); + this.totemManager.unload(); + this.gameManager.unload(); + + // before ConfigManager + this.placeholderManager.reload(); + this.configManager.reload(); + // after ConfigManager + this.debugger = ConfigManager.debug() ? (s) -> logger.info("[DEBUG] " + s.toString()) : (s) -> {}; + + this.actionManager.reload(); + this.requirementManager.reload(); + this.coolDownManager.reload(); + this.translationManager.reload(); + this.marketManager.reload(); + this.competitionManager.reload(); + this.statisticsManager.reload(); + this.bagManager.reload(); + this.storageManager.reload(); + this.fishingManager.reload(); + + this.itemManager.load(); + this.eventManager.load(); + this.entityManager.load(); + this.lootManager.load(); + this.blockManager.load(); + this.effectManager.load(); + this.hookManager.load(); + this.totemManager.load(); + this.gameManager.load(); + + EventUtils.fireAndForget(new CustomFishingReloadEvent(this)); + } + + @Override + public void disable() { + this.eventManager.disable(); + this.configManager.disable(); + this.requirementManager.disable(); + this.actionManager.disable(); + this.placeholderManager.disable(); + this.itemManager.disable(); + this.competitionManager.disable(); + this.marketManager.disable(); + this.lootManager.disable(); + this.coolDownManager.disable(); + this.entityManager.disable(); + this.blockManager.disable(); + this.statisticsManager.disable(); + this.effectManager.disable(); + this.hookManager.disable(); + this.bagManager.disable(); + this.integrationManager.disable(); + this.storageManager.disable(); + this.commandManager.unregisterFeatures(); + } + + @Override + public InputStream getResourceStream(String filePath) { + return getBoostrap().getResource(filePath); + } + + @Override + public PluginLogger getPluginLogger() { + return logger; + } + + @Override + public ClassPathAppender getClassPathAppender() { + return classPathAppender; + } + + @Override + public Path getDataDirectory() { + return getBoostrap().getDataFolder().toPath().toAbsolutePath(); + } + + @Override + public String getServerVersion() { + return Bukkit.getServer().getBukkitVersion().split("-")[0]; + } + + @SuppressWarnings("deprecation") + @Override + public String getPluginVersion() { + return getBoostrap().getDescription().getVersion(); + } + + @Override + public void debug(Object message) { + this.debugger.accept(message); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/action/BukkitActionManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/action/BukkitActionManager.java new file mode 100644 index 00000000..635e58bd --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/action/BukkitActionManager.java @@ -0,0 +1,938 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.action; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.action.*; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.bukkit.integration.VaultHook; +import net.momirealms.customfishing.bukkit.util.LocationUtils; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.ClassUtils; +import net.momirealms.customfishing.common.util.ListUtils; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.customfishing.common.util.RandomUtils; +import net.momirealms.sparrow.heart.SparrowHeart; +import net.momirealms.sparrow.heart.feature.armorstand.FakeArmorStand; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import static java.util.Objects.requireNonNull; + +public class BukkitActionManager implements ActionManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap> actionFactoryMap = new HashMap<>(); + private static final String EXPANSION_FOLDER = "expansions/action"; + + public BukkitActionManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.registerBuiltInActions(); + } + + @Override + public void disable() { + this.actionFactoryMap.clear(); + } + + @Override + public void reload() { + this.loadExpansions(); + } + + @Override + public boolean registerAction(String type, ActionFactory actionFactory) { + if (this.actionFactoryMap.containsKey(type)) return false; + this.actionFactoryMap.put(type, actionFactory); + return true; + } + + @Override + public boolean unregisterAction(String type) { + return this.actionFactoryMap.remove(type) != null; + } + + @Nullable + @Override + public ActionFactory getActionFactory(@NotNull String type) { + return actionFactoryMap.get(type); + } + + @Override + public boolean hasAction(@NotNull String type) { + return actionFactoryMap.containsKey(type); + } + + @Override + public Action parseAction(Section section) { + if (section == null) return EmptyAction.INSTANCE; + ActionFactory factory = getActionFactory(section.getString("type")); + if (factory == null) { + plugin.getPluginLogger().warn("Action type: " + section.getString("type") + " doesn't exist."); + return EmptyAction.INSTANCE; + } + return factory.process(section.get("value"), section.getDouble("chance", 1d)); + } + + @NotNull + @Override + @SuppressWarnings("unchecked") + public Action[] parseActions(Section section) { + ArrayList> actionList = new ArrayList<>(); + if (section != null) + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + Action action = parseAction(innerSection); + if (action != null) + actionList.add(action); + } + } + return actionList.toArray(new Action[0]); + } + + @Override + public Action parseAction(@NotNull String type, @NotNull Object args) { + ActionFactory factory = getActionFactory(type); + if (factory == null) { + plugin.getPluginLogger().warn("Action type: " + type + " doesn't exist."); + return EmptyAction.INSTANCE; + } + return factory.process(args, 1); + } + + private void registerBuiltInActions() { + this.registerMessageAction(); + this.registerCommandAction(); + this.registerActionBarAction(); + this.registerCloseInvAction(); + this.registerExpAction(); + this.registerFoodAction(); + this.registerChainAction(); + this.registerMoneyAction(); + this.registerItemAction(); + this.registerPotionAction(); + this.registerFishFindAction(); + this.registerPluginExpAction(); + this.registerSoundAction(); + this.registerHologramAction(); + this.registerFakeItemAction(); + this.registerTitleAction(); + } + + private void registerMessageAction() { + registerAction("message", (args, chance) -> { + List messages = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + List replaced = plugin.getPlaceholderManager().parse(context.getHolder(), messages, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.getHolder()); + for (String text : replaced) { + audience.sendMessage(AdventureHelper.miniMessage(text)); + } + }; + }); + registerAction("random-message", (args, chance) -> { + List messages = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + String random = messages.get(RandomUtils.generateRandomInt(0, messages.size() - 1)); + random = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), random, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.getHolder()); + audience.sendMessage(AdventureHelper.miniMessage(random)); + }; + }); + registerAction("broadcast", (args, chance) -> { + List messages = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + List replaced = plugin.getPlaceholderManager().parse(context.getHolder(), messages, context.placeholderMap()); + for (Player player : Bukkit.getOnlinePlayers()) { + Audience audience = plugin.getSenderFactory().getAudience(player); + for (String text : replaced) { + audience.sendMessage(AdventureHelper.miniMessage(text)); + } + } + }; + }); + registerAction("message-nearby", (args, chance) -> { + if (args instanceof Section section) { + List messages = ListUtils.toList(section.get("message")); + MathValue range = MathValue.auto(section.get("range")); + return context -> { + if (Math.random() > chance) return; + double realRange = range.evaluate(context); + Player owner = context.getHolder(); + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + plugin.getScheduler().sync().run(() -> { + for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= realRange) { + context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName()); + List replaced = BukkitPlaceholderManager.getInstance().parse( + owner, + messages, + context.placeholderMap() + ); + Audience audience = plugin.getSenderFactory().getAudience(player); + for (String text : replaced) { + audience.sendMessage(AdventureHelper.miniMessage(text)); + } + } + } + }, location); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at message-nearby action which should be Section"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerCommandAction() { + registerAction("command", (args, chance) -> { + List commands = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + List replaced = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), commands, context.placeholderMap()); + plugin.getScheduler().sync().run(() -> { + for (String text : replaced) { + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text); + } + }, context.arg(ContextKeys.LOCATION)); + }; + }); + registerAction("player-command", (args, chance) -> { + List commands = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + List replaced = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), commands, context.placeholderMap()); + plugin.getScheduler().sync().run(() -> { + for (String text : replaced) { + context.getHolder().performCommand(text); + } + }, context.arg(ContextKeys.LOCATION)); + }; + }); + registerAction("random-command", (args, chance) -> { + List commands = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + String random = commands.get(ThreadLocalRandom.current().nextInt(commands.size())); + random = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), random, context.placeholderMap()); + String finalRandom = random; + plugin.getScheduler().sync().run(() -> { + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom); + }, context.arg(ContextKeys.LOCATION)); + }; + }); + registerAction("command-nearby", (args, chance) -> { + if (args instanceof Section section) { + List cmd = ListUtils.toList(section.get("command")); + MathValue range = MathValue.auto(section.get("range")); + return context -> { + if (Math.random() > chance) return; + Player owner = context.getHolder(); + double realRange = range.evaluate(context); + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + plugin.getScheduler().sync().run(() -> { + for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= realRange) { + context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName()); + List replaced = BukkitPlaceholderManager.getInstance().parse(owner, cmd, context.placeholderMap()); + for (String text : replaced) { + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text); + } + } + } + }, location); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at command-nearby action which should be Section"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerCloseInvAction() { + registerAction("close-inv", (args, chance) -> condition -> { + if (Math.random() > chance) return; + condition.getHolder().closeInventory(); + }); + } + + private void registerActionBarAction() { + registerAction("actionbar", (args, chance) -> { + String text = (String) args; + return context -> { + if (Math.random() > chance) return; + Audience audience = plugin.getSenderFactory().getAudience(context.getHolder()); + Component component = AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(context.getHolder(), text, context.placeholderMap())); + audience.sendActionBar(component); + }; + }); + registerAction("random-actionbar", (args, chance) -> { + List texts = ListUtils.toList(args); + return context -> { + if (Math.random() > chance) return; + String random = texts.get(RandomUtils.generateRandomInt(0, texts.size() - 1)); + random = plugin.getPlaceholderManager().parse(context.getHolder(), random, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.getHolder()); + audience.sendActionBar(AdventureHelper.miniMessage(random)); + }; + }); + registerAction("actionbar-nearby", (args, chance) -> { + if (args instanceof Section section) { + String actionbar = section.getString("actionbar"); + MathValue range = MathValue.auto(section.get("range")); + return context -> { + if (Math.random() > chance) return; + Player owner = context.getHolder(); + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + double realRange = range.evaluate(context); + plugin.getScheduler().sync().run(() -> { + for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= realRange) { + context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName()); + String replaced = plugin.getPlaceholderManager().parse(owner, actionbar, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(player); + audience.sendActionBar(AdventureHelper.miniMessage(replaced)); + } + } + }, location + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at actionbar-nearby action which should be Section"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerExpAction() { + registerAction("mending", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + final Player player = context.getHolder(); + player.getLocation().getWorld().spawn(player.getLocation().clone().add(0,0.5,0), ExperienceOrb.class, e -> e.setExperience((int) value.evaluate(context))); + }; + }); + registerAction("exp", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + final Player player = context.getHolder(); + player.giveExp((int) Math.round(value.evaluate(context))); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.playSound(audience, Sound.sound(Key.key("minecraft:entity.experience_orb.pickup"), Sound.Source.PLAYER, 1, 1)); + }; + }); + registerAction("level", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + player.setLevel((int) Math.max(0, player.getLevel() + value.evaluate(context))); + }; + }); + } + + private void registerFoodAction() { + registerAction("food", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + player.setFoodLevel((int) (player.getFoodLevel() + value.evaluate(context))); + }; + }); + registerAction("saturation", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + player.setSaturation((float) (player.getSaturation() + value.evaluate(context))); + }; + }); + } + + private void registerItemAction() { + registerAction("item-amount", (args, chance) -> { + if (args instanceof Section section) { + boolean mainOrOff = section.getString("hand", "main").equalsIgnoreCase("main"); + int amount = section.getInt("amount", 1); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + ItemStack itemStack = mainOrOff ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand(); + itemStack.setAmount(Math.max(0, itemStack.getAmount() + amount)); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at item-amount action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + registerAction("durability", (args, chance) -> { + if (args instanceof Section section) { + EquipmentSlot slot = EquipmentSlot.valueOf(section.getString("slot", "hand").toUpperCase(Locale.ENGLISH)); + int amount = section.getInt("amount", 1); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + ItemStack itemStack = player.getInventory().getItem(slot); +// if (amount > 0) { +// ItemUtils.increaseDurability(itemStack, amount, true); +// } else { +// ItemUtils.decreaseDurability(context.getHolder(), itemStack, -amount, true); +// } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at durability action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + registerAction("give-item", (args, chance) -> { + if (args instanceof Section section) { + String id = section.getString("item"); + int amount = section.getInt("amount", 1); + return context -> { + if (Math.random() > chance) return; + Player player = context.getHolder(); + ItemStack itemStack = plugin.getItemManager().buildAny(context, id); + int maxStack = itemStack.getType().getMaxStackSize(); + int amountToGive = amount; + while (amountToGive > 0) { + int perStackSize = Math.min(maxStack, amountToGive); + amountToGive -= perStackSize; + ItemStack more = itemStack.clone(); + more.setAmount(perStackSize); + PlayerUtils.dropItem(player, itemStack, true, true, false); + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at give-item action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerChainAction() { + registerAction("chain", (args, chance) -> { + List> actions = new ArrayList<>(); + if (args instanceof Section section) { + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + actions.add(parseAction(innerSection)); + } + } + } + return context -> { + if (Math.random() > chance) return; + for (Action action : actions) { + action.trigger(context); + } + }; + }); + registerAction("delay", (args, chance) -> { + List> actions = new ArrayList<>(); + int delay; + boolean async; + if (args instanceof Section section) { + delay = section.getInt("delay", 1); + async = section.getBoolean("async", false); + Section actionSection = section.getSection("actions"); + if (actionSection != null) + for (Map.Entry entry : actionSection.getStringRouteMappedValues(false).entrySet()) + if (entry.getValue() instanceof Section innerSection) + actions.add(parseAction(innerSection)); + } else { + delay = 1; + async = false; + } + return context -> { + if (Math.random() > chance) return; + Location location = context.arg(ContextKeys.LOCATION); + if (async) { + plugin.getScheduler().asyncLater(() -> { + for (Action action : actions) + action.trigger(context); + }, delay * 50L, TimeUnit.MILLISECONDS); + } else { + plugin.getScheduler().sync().runLater(() -> { + for (Action action : actions) + action.trigger(context); + }, delay, location); + } + }; + }); + registerAction("timer", (args, chance) -> { + List> actions = new ArrayList<>(); + int delay, duration, period; + boolean async; + if (args instanceof Section section) { + delay = section.getInt("delay", 2); + duration = section.getInt("duration", 20); + period = section.getInt("period", 2); + async = section.getBoolean("async", false); + Section actionSection = section.getSection("actions"); + if (actionSection != null) + for (Map.Entry entry : actionSection.getStringRouteMappedValues(false).entrySet()) + if (entry.getValue() instanceof Section innerSection) + actions.add(parseAction(innerSection)); + } else { + delay = 1; + period = 1; + async = false; + duration = 20; + } + return context -> { + if (Math.random() > chance) return; + Location location = context.arg(ContextKeys.LOCATION); + SchedulerTask task; + if (async) { + task = plugin.getScheduler().asyncRepeating(() -> { + for (Action action : actions) { + action.trigger(context); + } + }, delay * 50L, period * 50L, TimeUnit.MILLISECONDS); + } else { + task = plugin.getScheduler().sync().runRepeating(() -> { + for (Action action : actions) { + action.trigger(context); + } + }, delay, period, location); + } + plugin.getScheduler().asyncLater(task::cancel, duration * 50L, TimeUnit.MILLISECONDS); + }; + }); + registerAction("conditional", (args, chance) -> { + if (args instanceof Section section) { + Action[] actions = parseActions(section.getSection("actions")); + Requirement[] requirements = plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), true); + return condition -> { + if (Math.random() > chance) return; + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied(condition)) { + return; + } + } + for (Action action : actions) { + action.trigger(condition); + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at conditional action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + registerAction("priority", (args, chance) -> { + if (args instanceof Section section) { + List[], Action[]>> conditionActionPairList = new ArrayList<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + Action[] actions = parseActions(inner.getSection("actions")); + Requirement[] requirements = plugin.getRequirementManager().parseRequirements(inner.getSection("conditions"), false); + conditionActionPairList.add(Pair.of(requirements, actions)); + } + } + return context -> { + if (Math.random() > chance) return; + outer: + for (Pair[], Action[]> pair : conditionActionPairList) { + if (pair.left() != null) + for (Requirement requirement : pair.left()) { + if (!requirement.isSatisfied(context)) { + continue outer; + } + } + if (pair.right() != null) + for (Action action : pair.right()) { + action.trigger(context); + } + return; + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at priority action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerMoneyAction() { + registerAction("give-money", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + VaultHook.deposit(context.getHolder(), value.evaluate(context)); + }; + }); + registerAction("take-money", (args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() > chance) return; + VaultHook.withdraw(context.getHolder(), value.evaluate(context)); + }; + }); + } + + private void registerPotionAction() { + registerAction("potion-effect", (args, chance) -> { + if (args instanceof Section section) { + PotionEffect potionEffect = new PotionEffect( + Objects.requireNonNull(PotionEffectType.getByName(section.getString("type", "BLINDNESS").toUpperCase(Locale.ENGLISH))), + section.getInt("duration", 20), + section.getInt("amplifier", 0) + ); + return context -> { + if (Math.random() > chance) return; + context.getHolder().addPotionEffect(potionEffect); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at potion-effect action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerSoundAction() { + registerAction("sound", (args, chance) -> { + if (args instanceof Section section) { + Sound sound = Sound.sound( + Key.key(section.getString("key")), + Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)), + section.getDouble("volume", 1.0).floatValue(), + section.getDouble("pitch", 1.0).floatValue() + ); + return context -> { + if (Math.random() > chance) return; + Audience audience = plugin.getSenderFactory().getAudience(context.getHolder()); + AdventureHelper.playSound(audience, sound); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at sound action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + + private void registerPluginExpAction() { + registerAction("plugin-exp", (args, chance) -> { + if (args instanceof Section section) { + String pluginName = section.getString("plugin"); + MathValue value = MathValue.auto(section.get("exp")); + String target = section.getString("target"); + return context -> { + if (Math.random() > chance) return; + Optional.ofNullable(plugin.getIntegrationManager().getLevelerProvider(pluginName)).ifPresentOrElse(it -> { + it.addXp(context.getHolder(), target, value.evaluate(context)); + }, () -> plugin.getPluginLogger().warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.")); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plugin-exp action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerTitleAction() { + registerAction("title", (args, chance) -> { + if (args instanceof Section section) { + TextValue title = TextValue.auto(section.getString("title", "")); + TextValue subtitle = TextValue.auto(section.getString("subtitle", "")); + int fadeIn = section.getInt("fade-in", 20); + int stay = section.getInt("stay", 30); + int fadeOut = section.getInt("fade-out", 10); + return context -> { + if (Math.random() > chance) return; + final Player player = context.getHolder(); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.sendTitle(audience, + AdventureHelper.miniMessage(title.render(context)), + AdventureHelper.miniMessage(subtitle.render(context)), + fadeIn, stay, fadeOut + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + registerAction("random-title", (args, chance) -> { + if (args instanceof Section section) { + List titles = section.getStringList("titles"); + if (titles.isEmpty()) titles.add(""); + List subtitles = section.getStringList("subtitles"); + if (subtitles.isEmpty()) subtitles.add(""); + int fadeIn = section.getInt("fade-in", 20); + int stay = section.getInt("stay", 30); + int fadeOut = section.getInt("fade-out", 10); + return context -> { + if (Math.random() > chance) return; + TextValue title = TextValue.auto(titles.get(RandomUtils.generateRandomInt(0, titles.size() - 1))); + TextValue subtitle = TextValue.auto(subtitles.get(RandomUtils.generateRandomInt(0, subtitles.size() - 1))); + final Player player = context.getHolder(); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.sendTitle(audience, + AdventureHelper.miniMessage(title.render(context)), + AdventureHelper.miniMessage(subtitle.render(context)), + fadeIn, stay, fadeOut + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at random-title action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + registerAction("title-nearby", (args, chance) -> { + if (args instanceof Section section) { + TextValue title = TextValue.auto(section.getString("title")); + TextValue subtitle = TextValue.auto(section.getString("subtitle")); + int fadeIn = section.getInt("fade-in", 20); + int stay = section.getInt("stay", 30); + int fadeOut = section.getInt("fade-out", 10); + int range = section.getInt("range", 0); + return context -> { + if (Math.random() > chance) return; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + plugin.getScheduler().sync().run(() -> { + for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= range) { + context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName()); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.sendTitle(audience, + AdventureHelper.miniMessage(title.render(context)), + AdventureHelper.miniMessage(subtitle.render(context)), + fadeIn, stay, fadeOut + ); + } + } + }, location + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title-nearby action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + }); + } + + private void registerFakeItemAction() { + registerAction("fake-item", ((args, chance) -> { + if (args instanceof Section section) { + String itemID = section.getString("item", ""); + String[] split = itemID.split(":"); + if (split.length >= 2) itemID = split[split.length - 1]; + MathValue duration = MathValue.auto(section.get("duration", 20)); + boolean position = !section.getString("position", "player").equals("player"); + MathValue x = MathValue.auto(section.get("x", 0)); + MathValue y = MathValue.auto(section.get("y", 0)); + MathValue z = MathValue.auto(section.get("z", 0)); + MathValue yaw = MathValue.auto(section.get("yaw", 0)); + int range = section.getInt("range", 0); + boolean opposite = section.getBoolean("opposite-yaw", false); + String finalItemID = itemID; + return context -> { + if (Math.random() > chance) return; + Player owner = context.getHolder(); + Location location = position ? requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)).clone() : owner.getLocation().clone(); + location.add(x.evaluate(context), y.evaluate(context) - 1, z.evaluate(context)); + if (opposite) location.setYaw(-owner.getLocation().getYaw()); + else location.setYaw((float) yaw.evaluate(context)); + FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location); + armorStand.invisible(true); + armorStand.equipment(EquipmentSlot.HEAD, plugin.getItemManager().buildInternal(context, finalItemID)); + ArrayList viewers = new ArrayList<>(); + if (range > 0) { + for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= range) { + viewers.add((Player) player); + } + } + } else { + viewers.add(owner); + } + for (Player player : viewers) { + armorStand.spawn(player); + } + plugin.getScheduler().asyncLater(() -> { + for (Player player : viewers) { + if (player.isOnline() && player.isValid()) { + armorStand.destroy(player); + } + } + }, (long) (duration.evaluate(context) * 50), TimeUnit.MILLISECONDS); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at fake-item action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + })); + } + + private void registerHologramAction() { + registerAction("hologram", ((args, chance) -> { + if (args instanceof Section section) { + TextValue text = TextValue.auto(section.getString("text", "")); + MathValue duration = MathValue.auto(section.get("duration", 20)); + boolean position = section.getString("position", "other").equals("other"); + MathValue x = MathValue.auto(section.get("x", 0)); + MathValue y = MathValue.auto(section.get("y", 0)); + MathValue z = MathValue.auto(section.get("z", 0)); + int range = section.getInt("range", 16); + return context -> { + if (Math.random() > chance) return; + Player owner = context.getHolder(); + Location location = position ? requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)).clone() : owner.getLocation().clone(); + location.add(x.evaluate(context), y.evaluate(context), z.evaluate(context)); + FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location); + armorStand.invisible(true); + armorStand.small(true); + armorStand.name(AdventureHelper.miniMessageToJson(text.render(context))); + ArrayList viewers = new ArrayList<>(); + if (range > 0) { + for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) { + double distance = LocationUtils.getDistance(player.getLocation(), location); + if (distance <= range) { + viewers.add((Player) player); + } + } + } else { + viewers.add(owner); + } + for (Player player : viewers) { + armorStand.spawn(player); + } + plugin.getScheduler().asyncLater(() -> { + for (Player player : viewers) { + if (player.isOnline() && player.isValid()) { + armorStand.destroy(player); + } + } + }, (long) (duration.evaluate(context) * 50), TimeUnit.MILLISECONDS); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at hologram action which is expected to be `Section`"); + return EmptyAction.INSTANCE; + } + })); + } + + private void registerFishFindAction() { + registerAction("fish-finder", (args, chance) -> { + String surrounding = (String) args; + return context -> { + if (Math.random() > chance) return; + String previous = context.arg(ContextKeys.SURROUNDING); + context.arg(ContextKeys.SURROUNDING, surrounding); + Collection loots = plugin.getLootManager().getWeightedLoots(Effect.newInstance(), context).keySet(); + StringJoiner stringJoiner = new StringJoiner(TranslationManager.miniMessageTranslation(MessageConstants.COMMAND_FISH_FINDER_SPLIT_CHAR.build().key())); + for (String loot : loots) { + plugin.getLootManager().getLoot(loot).ifPresent(lootIns -> { + if (lootIns.showInFinder()) { + if (!lootIns.nick().equals("UNDEFINED")) { + stringJoiner.add(lootIns.nick()); + } + } + }); + } + if (previous == null) { + context.remove(ContextKeys.SURROUNDING); + } else { + context.arg(ContextKeys.SURROUNDING, previous); + } + if (loots.isEmpty()) { + plugin.getSenderFactory().wrap(context.getHolder()).sendMessage(TranslationManager.render(MessageConstants.COMMAND_FISH_FINDER_NO_LOOT.build())); + } else { + plugin.getSenderFactory().wrap(context.getHolder()).sendMessage(TranslationManager.render(MessageConstants.COMMAND_FISH_FINDER_POSSIBLE_LOOTS.arguments(AdventureHelper.miniMessage(stringJoiner.toString())).build())); + } + }; + }); + } + + /** + * Loads custom ActionExpansions from JAR files located in the expansion directory. + * This method scans the expansion folder for JAR files, loads classes that extend ActionExpansion, + * and registers them with the appropriate action type and ActionFactory. + */ + @SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"}) + private void loadExpansions() { + File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); + if (!expansionFolder.exists()) + expansionFolder.mkdirs(); + + List>> classes = new ArrayList<>(); + File[] expansionJars = expansionFolder.listFiles(); + if (expansionJars == null) return; + for (File expansionJar : expansionJars) { + if (expansionJar.getName().endsWith(".jar")) { + try { + Class> expansionClass = (Class>) ClassUtils.findClass(expansionJar, ActionExpansion.class); + classes.add(expansionClass); + } catch (IOException | ClassNotFoundException e) { + plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e); + } + } + } + try { + for (Class> expansionClass : classes) { + ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); + unregisterAction(expansion.getActionType()); + registerAction(expansion.getActionType(), expansion.getActionFactory()); + plugin.getPluginLogger().info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() ); + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { + plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/Languages.java b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/Languages.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/adventure/component/Languages.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/adventure/Languages.java index ed3399d5..346c4879 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/Languages.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/Languages.java @@ -4,7 +4,7 @@ * Copyright (c) 2021 NichtStudioCode */ -package net.momirealms.customfishing.adventure.component; +package net.momirealms.customfishing.bukkit.adventure; import com.google.gson.stream.JsonReader; import org.bukkit.entity.Player; diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentUtils.java b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentUtils.java similarity index 94% rename from plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentUtils.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentUtils.java index 9709b287..233c8b4e 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentUtils.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentUtils.java @@ -4,7 +4,7 @@ * Copyright (c) 2021 NichtStudioCode */ -package net.momirealms.customfishing.adventure.component; +package net.momirealms.customfishing.bukkit.adventure; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentWrapper.java b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentWrapper.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentWrapper.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentWrapper.java index 46e56122..75832f79 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureComponentWrapper.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureComponentWrapper.java @@ -4,7 +4,7 @@ * Copyright (c) 2021 NichtStudioCode */ -package net.momirealms.customfishing.adventure.component; +package net.momirealms.customfishing.bukkit.adventure; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureShadedComponentLocalizer.java b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureShadedComponentLocalizer.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureShadedComponentLocalizer.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureShadedComponentLocalizer.java index 5fcebf65..89c242ef 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedAdventureShadedComponentLocalizer.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedAdventureShadedComponentLocalizer.java @@ -4,7 +4,7 @@ * Copyright (c) 2021 NichtStudioCode */ -package net.momirealms.customfishing.adventure.component; +package net.momirealms.customfishing.bukkit.adventure; import net.kyori.adventure.text.*; diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedComponentLocalizer.java b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedComponentLocalizer.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedComponentLocalizer.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedComponentLocalizer.java index 418c7d94..7e125fba 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/component/ShadedComponentLocalizer.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/adventure/ShadedComponentLocalizer.java @@ -4,7 +4,7 @@ * Copyright (c) 2021 NichtStudioCode */ -package net.momirealms.customfishing.adventure.component; +package net.momirealms.customfishing.bukkit.adventure; import java.util.ArrayList; import java.util.List; diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/bag/BukkitBagManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/bag/BukkitBagManager.java new file mode 100644 index 00000000..bd2e53fa --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/bag/BukkitBagManager.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.bag; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingBagPreCollectEvent; +import net.momirealms.customfishing.api.event.FishingLootSpawnEvent; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.bag.BagManager; +import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.bukkit.config.BukkitConfigManager; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class BukkitBagManager implements BagManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap tempEditMap; + private Action[] collectLootActions; + private Action[] bagFullActions; + private boolean bagStoreLoots; + private boolean bagStoreRods; + private boolean bagStoreBaits; + private boolean bagStoreHooks; + private boolean bagStoreUtils; + private final HashSet storedTypes = new HashSet<>(); + private boolean enable; + private String bagTitle; + private List bagWhiteListItems = new ArrayList<>(); + private Requirement[] collectRequirements; + + public BukkitBagManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.tempEditMap = new HashMap<>(); + } + + @Override + public void load() { + this.loadConfig(); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + storedTypes.clear(); + } + + @Override + public void disable() { + unload(); + this.plugin.getStorageManager().getDataSource().updateManyPlayersData(tempEditMap.values(), true); + } + + @EventHandler + public void onLootSpawn(FishingLootSpawnEvent event) { + if (!enable || !bagStoreLoots) { + return; + } + if (!event.summonEntity()) { + return; + } + if (!(event.getEntity() instanceof Item itemEntity)) { + return; + } + + Player player = event.getPlayer(); + Context context = event.getContext(); + if (!RequirementManager.isSatisfied(context, collectRequirements)) { + return; + } + + Optional onlineUser = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); + if (onlineUser.isEmpty()) { + return; + } + UserData userData = onlineUser.get(); + Inventory inventory = userData.holder().getInventory(); + ItemStack item = itemEntity.getItemStack(); + FishingBagPreCollectEvent preCollectEvent = new FishingBagPreCollectEvent(player, item, inventory); + if (EventUtils.fireAndCheckCancel(preCollectEvent)) { + return; + } + + int cannotPut = PlayerUtils.putItemsToInventory(inventory, item, item.getAmount()); + // some are put into bag + if (cannotPut != item.getAmount()) { + ActionManager.trigger(context, collectLootActions); + } + // all are put + if (cannotPut == 0) { + event.summonEntity(false); + return; + } + item.setAmount(cannotPut); + itemEntity.setItemStack(item); + ActionManager.trigger(context, bagFullActions); + } + + private void loadConfig() { + Section config = BukkitConfigManager.getMainConfig().getSection("mechanics.fishing-bag"); + + enable = config.getBoolean("enable", true); + bagTitle = config.getString("bag-title", ""); + bagStoreLoots = config.getBoolean("can-store-loot", false); + bagStoreRods = config.getBoolean("can-store-rod", true); + bagStoreBaits = config.getBoolean("can-store-bait", true); + bagStoreHooks = config.getBoolean("can-store-hook", true); + bagStoreUtils = config.getBoolean("can-store-util", true); + bagWhiteListItems = config.getStringList("whitelist-items").stream().map(it -> Material.valueOf(it.toUpperCase(Locale.ENGLISH))).toList(); + collectLootActions = plugin.getActionManager().parseActions(config.getSection("collect-actions")); + bagFullActions = plugin.getActionManager().parseActions(config.getSection("full-actions")); + collectRequirements = plugin.getRequirementManager().parseRequirements(config.getSection("collect-requirements"), false); + + if (bagStoreLoots) storedTypes.add(MechanicType.LOOT); + if (bagStoreRods) storedTypes.add(MechanicType.ROD); + if (bagStoreBaits) storedTypes.add(MechanicType.BAIT); + if (bagStoreHooks) storedTypes.add(MechanicType.HOOK); + if (bagStoreUtils) storedTypes.add(MechanicType.UTIL); + } + + @Override + public CompletableFuture openBag(Player viewer, UUID owner) { + CompletableFuture future = new CompletableFuture<>(); + if (enable) { + Optional onlineUser = plugin.getStorageManager().getOnlineUser(owner); + onlineUser.ifPresentOrElse(data -> { + viewer.openInventory(data.holder().getInventory()); + SparrowHeart.getInstance().updateInventoryTitle(viewer, AdventureHelper.componentToJson(AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(Bukkit.getOfflinePlayer(owner), bagTitle, Map.of("{uuid}", owner.toString(), "{player}", data.name()))))); + future.complete(true); + }, () -> plugin.getStorageManager().getOfflineUserData(owner, true).thenAccept(result -> result.ifPresentOrElse(data -> { + if (data.isLocked()) { + future.completeExceptionally(new RuntimeException("Data is locked")); + return; + } + this.tempEditMap.put(viewer.getUniqueId(), data); + viewer.openInventory(data.holder().getInventory()); + SparrowHeart.getInstance().updateInventoryTitle(viewer, AdventureHelper.componentToJson(AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(Bukkit.getOfflinePlayer(owner), bagTitle, Map.of("{uuid}", owner.toString(), "{player}", data.name()))))); + future.complete(true); + }, () -> future.complete(false)))); + } else { + future.complete(false); + } + return future; + } + + /** + * Handles the InventoryCloseEvent to save changes made to an offline player's bag inventory when it's closed. + * + * @param event The InventoryCloseEvent triggered when the inventory is closed. + */ + @EventHandler + public void onInvClose(InventoryCloseEvent event) { + if (!(event.getInventory().getHolder() instanceof FishingBagHolder)) + return; + final Player viewer = (Player) event.getPlayer(); + UserData userData = tempEditMap.remove(viewer.getUniqueId()); + if (userData == null) + return; + this.plugin.getStorageManager().saveUserData(userData, true); + } + + /** + * Handles InventoryClickEvent to prevent certain actions on the Fishing Bag inventory. + * This method cancels the event if specific conditions are met to restrict certain item interactions. + * + * @param event The InventoryClickEvent triggered when an item is clicked in an inventory. + */ + @EventHandler (ignoreCancelled = true) + public void onInvClick(InventoryClickEvent event) { + if (!(event.getInventory().getHolder() instanceof FishingBagHolder)) + return; + ItemStack movedItem = event.getCurrentItem(); + Inventory clicked = event.getClickedInventory(); + if (clicked != event.getWhoClicked().getInventory()) { + if (event.getAction() != InventoryAction.HOTBAR_SWAP && event.getAction() != InventoryAction.HOTBAR_MOVE_AND_READD) { + return; + } + movedItem = event.getWhoClicked().getInventory().getItem(event.getHotbarButton()); + } + if (movedItem == null || movedItem.getType() == Material.AIR || bagWhiteListItems.contains(movedItem.getType())) + return; + String id = plugin.getItemManager().getItemID(movedItem); + List type = MechanicType.getTypeByID(id); + if (type == null) { + event.setCancelled(true); + return; + } + for (MechanicType mechanicType : type) { + if (storedTypes.contains(mechanicType)) { + return; + } + } + event.setCancelled(true); + } + + /** + * Event handler for the PlayerQuitEvent. + * This method is triggered when a player quits the server. + * It checks if the player was in the process of editing an offline player's bag inventory, + * and if so, saves the offline player's data if necessary. + * + * @param event The PlayerQuitEvent triggered when a player quits. + */ + @EventHandler + public void onQuit(PlayerQuitEvent event) { + UserData userData = tempEditMap.remove(event.getPlayer().getUniqueId()); + if (userData == null) + return; + plugin.getStorageManager().saveUserData(userData, true); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/block/BukkitBlockManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/block/BukkitBlockManager.java new file mode 100644 index 00000000..5aa8a20c --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/block/BukkitBlockManager.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.block; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.BlockProvider; +import net.momirealms.customfishing.api.integration.ExternalProvider; +import net.momirealms.customfishing.api.mechanic.block.*; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.customfishing.common.util.RandomUtils; +import net.momirealms.customfishing.common.util.Tuple; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Container; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Rotatable; +import org.bukkit.block.data.type.NoteBlock; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +import static java.util.Objects.requireNonNull; + +public class BukkitBlockManager implements BlockManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap blockProviders = new HashMap<>(); + private final HashMap blocks = new HashMap<>(); + private final HashMap dataFactories = new HashMap<>(); + private final HashMap stateFactories = new HashMap<>(); + private BlockProvider[] blockDetectArray; + + public BukkitBlockManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.registerInbuiltProperties(); + this.registerBlockProvider(new BlockProvider() { + @Override + public String identifier() { + return "vanilla"; + } + @Override + public BlockData blockData(@NotNull Context context, @NotNull String id, List modifiers) { + BlockData blockData = Material.valueOf(id.toUpperCase(Locale.ENGLISH)).createBlockData(); + for (BlockDataModifier modifier : modifiers) + modifier.apply(context, blockData); + return blockData; + } + @NotNull + @Override + public String blockID(@NotNull Block block) { + return block.getType().name(); + } + }); + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + this.resetBlockDetectionOrder(); + for (BlockProvider provider : blockProviders.values()) { + plugin.debug("Registered BlockProvider: " + provider.identifier()); + } + plugin.debug("Loaded " + blocks.size() + " blocks"); + plugin.debug("Block order: " + Arrays.toString(Arrays.stream(blockDetectArray).map(ExternalProvider::identifier).toList().toArray(new String[0]))); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + this.blocks.clear(); + } + + @Override + public void disable() { + this.blockProviders.clear(); + } + + private void resetBlockDetectionOrder() { + ArrayList list = new ArrayList<>(); + for (String plugin : ConfigManager.blockDetectOrder()) { + BlockProvider library = blockProviders.get(plugin); + if (library != null) + list.add(library); + } + this.blockDetectArray = list.toArray(new BlockProvider[0]); + } + + @Override + public boolean registerBlock(@NotNull BlockConfig block) { + if (blocks.containsKey(block.id())) return false; + blocks.put(block.id(), block); + return true; + } + + /** + * Event handler for the EntityChangeBlockEvent. + * This method is triggered when an entity changes a block, typically when a block falls or lands. + */ + @EventHandler + public void onBlockLands(EntityChangeBlockEvent event) { + if (event.isCancelled()) + return; + + // Retrieve a custom string value stored in the entity's persistent data container. + String temp = event.getEntity().getPersistentDataContainer().get( + requireNonNull(NamespacedKey.fromString("block", plugin.getBoostrap())), + PersistentDataType.STRING + ); + + // If the custom string value is not present, return without further action. + if (temp == null) return; + + // "BLOCK;PLAYER" + String[] split = temp.split(";"); + + // If no BlockConfig is found for the specified key, return without further action. + BlockConfig blockConfig= blocks.get(split[0]); + if (blockConfig == null) return; + + // If the player is not online or not found, remove the entity and set the block to air + Player player = Bukkit.getPlayer(split[1]); + if (player == null) { + event.getEntity().remove(); + event.getBlock().setType(Material.AIR); + return; + } + + Context context = Context.player(player); + Location location = event.getBlock().getLocation(); + + // Apply block state modifiers from the BlockConfig to the block 1 tick later. + plugin.getScheduler().sync().runLater(() -> { + BlockState state = location.getBlock().getState(); + for (BlockStateModifier modifier : blockConfig.stateModifiers()) { + modifier.apply(context, state); + } + }, 1, location); + } + + public boolean registerBlockProvider(BlockProvider blockProvider) { + if (this.blockProviders.containsKey(blockProvider.identifier())) return false; + this.blockProviders.put(blockProvider.identifier(), blockProvider); + this.resetBlockDetectionOrder(); + return true; + } + + public boolean unregisterBlockProvider(String identification) { + boolean success = blockProviders.remove(identification) != null; + if (success) + this.resetBlockDetectionOrder(); + return success; + } + + public boolean registerBlockDataModifierBuilder(String type, BlockDataModifierFactory factory) { + if (this.dataFactories.containsKey(type)) return false; + this.dataFactories.put(type, factory); + return true; + } + + public boolean registerBlockStateModifierBuilder(String type, BlockStateModifierFactory factory) { + if (stateFactories.containsKey(type)) return false; + this.stateFactories.put(type, factory); + return true; + } + + public boolean unregisterBlockDataModifierBuilder(String type) { + return this.dataFactories.remove(type) != null; + } + + public boolean unregisterBlockStateModifierBuilder(String type) { + return this.stateFactories.remove(type) != null; + } + + private void registerInbuiltProperties() { + this.registerDirectional(); + this.registerStorage(); + this.registerRotatable(); + this.registerNoteBlock(); + } + + @Override + @NotNull + public FallingBlock summonBlockLoot(@NotNull Context context) { + String id = context.arg(ContextKeys.ID); + BlockConfig config = requireNonNull(blocks.get(id), "Block " + id + " not found"); + String blockID = config.blockID(); + BlockData blockData; + if (blockID.contains(":")) { + String[] split = blockID.split(":", 2); + BlockProvider provider = requireNonNull(blockProviders.get(split[0]), "BlockProvider " + split[0] + " doesn't exist"); + blockData = requireNonNull(provider.blockData(context, split[1], config.dataModifier()), "Block " + split[1] + " doesn't exist"); + } else { + blockData = blockProviders.get("vanilla").blockData(context, blockID, config.dataModifier()); + } + Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)); + Location playerLocation = requireNonNull(context.getHolder()).getLocation(); + FallingBlock fallingBlock = hookLocation.getWorld().spawn(hookLocation, FallingBlock.class, (fb -> fb.setBlockData(blockData))); + fallingBlock.getPersistentDataContainer().set( + requireNonNull(NamespacedKey.fromString("block", plugin.getBoostrap())), + PersistentDataType.STRING, + id + ";" + context.getHolder().getName() + ); + Vector vector = playerLocation.subtract(hookLocation).toVector().multiply(1.2 - 1); + vector = vector.setY((vector.getY() + 0.2) * 1.2); + fallingBlock.setVelocity(vector); + return fallingBlock; + } + + @Override + @NotNull + public String getBlockID(@NotNull Block block) { + for (BlockProvider blockProvider : blockDetectArray) { + String id = blockProvider.blockID(block); + if (id != null) return id; + } + // Should not reach this because vanilla library would always work + return "AIR"; + } + + private void registerDirectional() { + this.registerBlockDataModifierBuilder("directional-4", (args) -> (context, blockData) -> { + boolean arg = (boolean) args; + if (arg && blockData instanceof Directional directional) { + directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 4)]); + } + }); + this.registerBlockDataModifierBuilder("directional-6", (args) -> (context, blockData) -> { + boolean arg = (boolean) args; + if (arg && blockData instanceof Directional directional) { + directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 6)]); + } + }); + } + + private void registerRotatable() { + this.registerBlockDataModifierBuilder("rotatable", (args) -> { + boolean arg = (boolean) args; + return (context, blockData) -> { + if (arg && blockData instanceof Rotatable rotatable) { + rotatable.setRotation(BlockFace.values()[ThreadLocalRandom.current().nextInt(BlockFace.values().length)]); + } + }; + }); + } + + private void registerNoteBlock() { + this.registerBlockDataModifierBuilder("noteblock", (args) -> { + if (args instanceof Section section) { + var instrument = Instrument.valueOf(section.getString("instrument")); + var note = new Note(section.getInt("note")); + return (context, blockData) -> { + if (blockData instanceof NoteBlock noteBlock) { + noteBlock.setNote(note); + noteBlock.setInstrument(instrument); + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at noteblock property which should be Section"); + return EmptyBlockDataModifier.INSTANCE; + } + }); + } + + private void registerStorage() { + this.registerBlockStateModifierBuilder("storage", (args) -> { + if (args instanceof Section section) { + List, String, Pair, MathValue>>> contents = new ArrayList<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof ConfigurationSection inner) { + String item = inner.getString("item"); + String[] split = inner.getString("amount","1~1").split("~"); + Pair, MathValue> amountPair = Pair.of(MathValue.auto(split[0]), MathValue.auto(split[1])); + MathValue chance = MathValue.auto(inner.get("chance", 1d)); + contents.add(Tuple.of(chance, item, amountPair)); + } + } + return (context, blockState) -> { + if (blockState instanceof Container container) { + setInventoryItems(contents, context, container.getInventory()); + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at storage property which should be Section"); + return EmptyBlockStateModifier.INSTANCE; + } + }); + } + + private void setInventoryItems( + List, String, Pair, MathValue>>> contents, + Context context, + Inventory inventory + ) { + LinkedList unused = new LinkedList<>(); + for (int i = 0; i < inventory.getSize(); i++) { + unused.add(i); + } + Collections.shuffle(unused); + for (Tuple, String, Pair, MathValue>> tuple : contents) { + if (tuple.left().evaluate(context) > Math.random()) { + ItemStack itemStack = plugin.getItemManager().buildAny(context, tuple.mid()); + if (itemStack != null) { + itemStack.setAmount(RandomUtils.generateRandomInt((int) tuple.right().left().evaluate(context), (int) (tuple.right().right().evaluate(context)))); + inventory.setItem(unused.pop(), itemStack); + } + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandFeature.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandFeature.java new file mode 100644 index 00000000..62bd90b0 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandFeature.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.common.command.AbstractCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.sender.SenderFactory; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.incendo.cloud.bukkit.data.Selector; + +import java.util.Collection; + +public abstract class BukkitCommandFeature extends AbstractCommandFeature { + + public BukkitCommandFeature(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + @SuppressWarnings("unchecked") + protected SenderFactory getSenderFactory() { + return (SenderFactory) BukkitCustomFishingPlugin.getInstance().getSenderFactory(); + } + + public Pair resolveSelector(Selector selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) { + Collection entities = selector.values(); + if (entities.size() == 1) { + return Pair.of(single, Component.text(entities.iterator().next().getName())); + } else { + return Pair.of(multiple, Component.text(entities.size())); + } + } + + public Pair resolveSelector(Collection selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) { + if (selector.size() == 1) { + return Pair.of(single, Component.text(selector.iterator().next().getName())); + } else { + return Pair.of(multiple, Component.text(selector.size())); + } + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandManager.java new file mode 100644 index 00000000..b62ad251 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/BukkitCommandManager.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command; + +import net.kyori.adventure.util.Index; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.feature.*; +import net.momirealms.customfishing.common.command.AbstractCommandManager; +import net.momirealms.customfishing.common.command.CommandFeature; +import net.momirealms.customfishing.common.sender.Sender; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.SenderMapper; +import org.incendo.cloud.bukkit.CloudBukkitCapabilities; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.LegacyPaperCommandManager; +import org.incendo.cloud.setting.ManagerSetting; + +import java.util.List; + +public class BukkitCommandManager extends AbstractCommandManager { + + private final List> FEATURES = List.of( + new ReloadCommand(this), + new SellFishCommand(this), + new GetItemCommand(this), + new GiveItemCommand(this), + new ImportItemCommand(this), + new EndCompetitionCommand(this), + new StopCompetitionCommand(this), + new StartCompetitionCommand(this), + new OpenMarketCommand(this), + new OpenBagCommand(this), + new FishingBagCommand(this), + new EditOnlineBagCommand(this), + new EditOfflineBagCommand(this), + new UnlockDataCommand(this), + new ImportDataCommand(this), + new ExportDataCommand(this), + new AddStatisticsCommand(this), + new SetStatisticsCommand(this), + new ResetStatisticsCommand(this), + new QueryStatisticsCommand(this) + ); + + private final Index> INDEX = Index.create(CommandFeature::getFeatureID, FEATURES); + + public BukkitCommandManager(BukkitCustomFishingPlugin plugin) { + super(plugin, new LegacyPaperCommandManager<>( + plugin.getBoostrap(), + ExecutionCoordinator.simpleCoordinator(), + SenderMapper.identity() + )); + final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); + manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); + if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { + manager.registerBrigadier(); + manager.brigadierManager().setNativeNumberSuggestions(true); + } else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { + manager.registerAsynchronousCompletions(); + } + } + + @Override + protected Sender wrapSender(CommandSender sender) { + return ((BukkitCustomFishingPlugin) plugin).getSenderFactory().wrap(sender); + } + + @Override + public Index> getFeatures() { + return INDEX; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/AddStatisticsCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/AddStatisticsCommand.java new file mode 100644 index 00000000..18b60da1 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/AddStatisticsCommand.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.parser.standard.DoubleParser; +import org.incendo.cloud.parser.standard.EnumParser; +import org.incendo.cloud.parser.standard.StringParser; + +public class AddStatisticsCommand extends BukkitCommandFeature { + + public AddStatisticsCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("player", PlayerParser.playerParser()) + .required("id", StringParser.stringParser()) + .required("type", EnumParser.enumParser(FishingStatistics.Type.class)) + .required("value", DoubleParser.doubleParser(0)) + .handler(context -> { + Player player = context.get("player"); + String id = context.get("id"); + FishingStatistics.Type type = context.get("type"); + double value = context.get("value"); + BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> { + if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) { + userData.statistics().addAmount(id, (int) value); + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName())); + } else if (type == FishingStatistics.Type.MAX_SIZE) { + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_UNSUPPORTED); + } + }, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED)); + }); + } + + @Override + public String getFeatureID() { + return "statistics_add"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOfflineBagCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOfflineBagCommand.java new file mode 100644 index 00000000..b6488b62 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOfflineBagCommand.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.parser.standard.UUIDParser; + +import java.util.UUID; + +public class EditOfflineBagCommand extends BukkitCommandFeature { + + public EditOfflineBagCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .required("uuid", UUIDParser.uuidParser()) + .handler(context -> { + Player admin = context.sender(); + UUID uuid = context.get("uuid"); + BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(admin, uuid).whenComplete((result, throwable) -> { + if (throwable != null) { + handleFeedback(context, MessageConstants.COMMAND_BAG_EDIT_FAILURE_UNSAFE); + return; + } + if (!result) { + handleFeedback(context, MessageConstants.COMMAND_BAG_EDIT_FAILURE_NEVER_PLAYED); + return; + } + }); + }); + } + + @Override + public String getFeatureID() { + return "edit_offline_bag"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOnlineBagCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOnlineBagCommand.java new file mode 100644 index 00000000..043f58a9 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EditOnlineBagCommand.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; + +public class EditOnlineBagCommand extends BukkitCommandFeature { + + public EditOnlineBagCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .required("player", PlayerParser.playerParser()) + .handler(context -> { + Player admin = context.sender(); + Player online = context.get("player"); + BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(admin, online.getUniqueId()); + }); + } + + @Override + public String getFeatureID() { + return "edit_online_bag"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EndCompetitionCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EndCompetitionCommand.java new file mode 100644 index 00000000..7cd6b068 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/EndCompetitionCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class EndCompetitionCommand extends BukkitCommandFeature { + + public EndCompetitionCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + FishingCompetition competition = BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getOnGoingCompetition(); + if (competition == null) { + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NO_COMPETITION); + } else { + competition.end(true); + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_END_SUCCESS); + } + }); + } + + @Override + public String getFeatureID() { + return "end_competition"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ExportDataCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ExportDataCommand.java new file mode 100644 index 00000000..a14dac23 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ExportDataCommand.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.DataStorageProvider; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.util.CompletableFutures; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; + +public class ExportDataCommand extends BukkitCommandFeature { + + public ExportDataCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(ConsoleCommandSender.class) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + if (!Bukkit.getOnlinePlayers().isEmpty()) { + handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_FAILURE_PLAYER_ONLINE); + return; + } + + BukkitCustomFishingPlugin plugin = BukkitCustomFishingPlugin.getInstance(); + handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_START); + plugin.getScheduler().async().execute(() -> { + + DataStorageProvider storageProvider = plugin.getStorageManager().getDataSource(); + + Set uuids = storageProvider.getUniqueUsers(); + Set> futures = new HashSet<>(); + AtomicInteger userCount = new AtomicInteger(0); + Map out = Collections.synchronizedMap(new TreeMap<>()); + + int amount = uuids.size(); + for (UUID uuid : uuids) { + futures.add(storageProvider.getPlayerData(uuid, false).thenAccept(it -> { + if (it.isPresent()) { + out.put(uuid, plugin.getStorageManager().toJson(it.get())); + userCount.incrementAndGet(); + } + })); + } + + CompletableFuture overallFuture = CompletableFutures.allOf(futures); + + while (true) { + try { + overallFuture.get(3, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + break; + } catch (TimeoutException e) { + handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_PROGRESS, Component.text(userCount.get()), Component.text(amount)); + continue; + } + break; + } + + JsonObject outJson = new JsonObject(); + for (Map.Entry entry : out.entrySet()) { + outJson.addProperty(entry.getKey().toString(), entry.getValue()); + } + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); + String formattedDate = formatter.format(new Date()); + File outFile = new File(plugin.getDataFolder(), "exported-" + formattedDate + ".json.gz"); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(outFile.toPath())), StandardCharsets.UTF_8))) { + new GsonBuilder().disableHtmlEscaping().create().toJson(outJson, writer); + } catch (IOException e) { + throw new RuntimeException("Unexpected issue: ", e); + } + + handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_SUCCESS); + }); + }); + } + + @Override + public String getFeatureID() { + return "data_export"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/FishingBagCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/FishingBagCommand.java new file mode 100644 index 00000000..1300f28f --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/FishingBagCommand.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class FishingBagCommand extends BukkitCommandFeature { + + public FishingBagCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .handler(context -> { + BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(context.sender(), context.sender().getUniqueId()).whenComplete((result, e) -> { + if (!result || e != null) { + handleFeedback(context, MessageConstants.COMMAND_DATA_FAILURE_NOT_LOADED); + } + }); + }); + } + + @Override + public String getFeatureID() { + return "fishingbag"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GetItemCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GetItemCommand.java new file mode 100644 index 00000000..a88f066c --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GetItemCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.concurrent.CompletableFuture; + +@SuppressWarnings("DuplicatedCode") +public class GetItemCommand extends BukkitCommandFeature { + + public GetItemCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getItemManager().getItemIDs().stream().map(Suggestion::suggestion).toList()); + } + })) + .optional("amount", IntegerParser.integerParser(1, 6400)) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + final int amount = context.getOrDefault("amount", 1); + final String id = context.get("id"); + final Player player = context.sender(); + try { + ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player).arg(ContextKeys.ID, id), id); + if (itemStack == null) { + throw new RuntimeException("Unrecognized item id: " + id); + } + int amountToGive = amount; + int maxStack = itemStack.getType().getMaxStackSize(); + while (amountToGive > 0) { + int perStackSize = Math.min(maxStack, amountToGive); + amountToGive -= perStackSize; + ItemStack more = itemStack.clone(); + more.setAmount(perStackSize); + PlayerUtils.dropItem(player, more, false, true, false); + } + handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_SUCCESS, Component.text(amount), Component.text(id)); + } catch (NullPointerException e) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_FAILURE_NOT_EXIST, Component.text(id)); + } + }); + } + + @Override + public String getFeatureID() { + return "get_item"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GiveItemCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GiveItemCommand.java new file mode 100644 index 00000000..8e2dc5a3 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/GiveItemCommand.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.concurrent.CompletableFuture; + +@SuppressWarnings("DuplicatedCode") +public class GiveItemCommand extends BukkitCommandFeature { + + public GiveItemCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .required("player", PlayerParser.playerParser()) + .required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getItemManager().getItemIDs().stream().map(Suggestion::suggestion).toList()); + } + })) + .optional("amount", IntegerParser.integerParser(1, 6400)) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + final Player player = context.get("player"); + final int amount = context.getOrDefault("amount", 1); + final String id = context.get("id"); + try { + ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player).arg(ContextKeys.ID, id), id); + if (itemStack == null) { + throw new RuntimeException("Unrecognized item id: " + id); + } + int amountToGive = amount; + int maxStack = itemStack.getType().getMaxStackSize(); + while (amountToGive > 0) { + int perStackSize = Math.min(maxStack, amountToGive); + amountToGive -= perStackSize; + ItemStack more = itemStack.clone(); + more.setAmount(perStackSize); + PlayerUtils.dropItem(player, more, false, true, false); + } + handleFeedback(context, MessageConstants.COMMAND_ITEM_GIVE_SUCCESS, Component.text(player.getName()), Component.text(amount), Component.text(id)); + } catch (NullPointerException e) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_FAILURE_NOT_EXIST, Component.text(id)); + } + }); + } + + @Override + public String getFeatureID() { + return "give_item"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportDataCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportDataCommand.java new file mode 100644 index 00000000..0f3405eb --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportDataCommand.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.DataStorageProvider; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.util.CompletableFutures; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.parser.standard.StringParser; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPInputStream; + +public class ImportDataCommand extends BukkitCommandFeature { + + public ImportDataCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(ConsoleCommandSender.class) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .required("file", StringParser.greedyFlagYieldingStringParser()) + .handler(context -> { + if (!Bukkit.getOnlinePlayers().isEmpty()) { + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_PLAYER_ONLINE); + return; + } + String fileName = context.get("file"); + BukkitCustomFishingPlugin plugin = BukkitCustomFishingPlugin.getInstance(); + File file = new File(plugin.getDataFolder(), fileName); + if (!file.exists()) { + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_NOT_EXISTS); + return; + } + if (!file.getName().endsWith(".json.gz")) { + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_INVALID_FILE); + return; + } + + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_START); + plugin.getScheduler().async().execute(() -> { + + JsonObject data; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(file.toPath())), StandardCharsets.UTF_8))) { + data = new GsonBuilder().disableHtmlEscaping().create().fromJson(reader, JsonObject.class); + } catch (IOException e) { + throw new RuntimeException("Unexpected issue: ", e); + } + + DataStorageProvider storageProvider = plugin.getStorageManager().getDataSource(); + var entrySet = data.entrySet(); + int amount = entrySet.size(); + AtomicInteger userCount = new AtomicInteger(0); + Set> futures = new HashSet<>(); + + for (Map.Entry entry : entrySet) { + UUID uuid = UUID.fromString(entry.getKey()); + if (entry.getValue() instanceof JsonPrimitive primitive) { + PlayerData playerData = plugin.getStorageManager().fromJson(primitive.getAsString()); + futures.add(storageProvider.updateOrInsertPlayerData(uuid, playerData, true).thenAccept(it -> userCount.incrementAndGet())); + } + } + + CompletableFuture overallFuture = CompletableFutures.allOf(futures); + + while (true) { + try { + overallFuture.get(3, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + break; + } catch (TimeoutException e) { + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_PROGRESS, Component.text(userCount.get()), Component.text(amount)); + continue; + } + break; + } + + handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_SUCCESS); + }); + }); + } + + @Override + public String getFeatureID() { + return "data_import"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportItemCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportItemCommand.java new file mode 100644 index 00000000..e79610f0 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ImportItemCommand.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.bukkit.util.ItemStackUtils; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.parser.standard.StringParser; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +@SuppressWarnings("DuplicatedCode") +public class ImportItemCommand extends BukkitCommandFeature { + + public ImportItemCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .required("id", StringParser.stringParser()) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + Player player = context.sender(); + ItemStack item = player.getInventory().getItemInMainHand(); + String id = context.get("id"); + if (item.getType() == Material.AIR) { + handleFeedback(context, MessageConstants.COMMAND_ITEM_IMPORT_FAILURE_NO_ITEM); + return; + } + File saved = new File(BukkitCustomFishingPlugin.getInstance().getDataFolder(), "imported_items.yml"); + if (!saved.exists()) { + try { + saved.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + YamlDocument document = BukkitCustomFishingPlugin.getInstance().getConfigManager().loadData(saved); + Map map = ItemStackUtils.itemStackToMap(item); + document.set(id, map); + try { + document.save(saved); + handleFeedback(context, MessageConstants.COMMAND_ITEM_IMPORT_SUCCESS, Component.text(id)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public String getFeatureID() { + return "import_item"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenBagCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenBagCommand.java new file mode 100644 index 00000000..19a7b2e5 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenBagCommand.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; + +public class OpenBagCommand extends BukkitCommandFeature { + + public OpenBagCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .required("player", PlayerParser.playerParser()) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + final Player player = context.get("player"); + BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(player, player.getUniqueId()).whenComplete((result, e) -> { + if (!result || e != null) { + handleFeedback(context, MessageConstants.COMMAND_BAG_OPEN_FAILURE_NOT_LOADED, Component.text(player.getName())); + } else { + handleFeedback(context, MessageConstants.COMMAND_BAG_OPEN_SUCCESS, Component.text(player.getName())); + } + }); + }); + } + + @Override + public String getFeatureID() { + return "open_bag"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenMarketCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenMarketCommand.java new file mode 100644 index 00000000..eef0d5f8 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/OpenMarketCommand.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; + +public class OpenMarketCommand extends BukkitCommandFeature { + + public OpenMarketCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .required("player", PlayerParser.playerParser()) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + final Player player = context.get("player"); + if (BukkitCustomFishingPlugin.getInstance().getMarketManager().openMarketGUI(player)) { + handleFeedback(context, MessageConstants.COMMAND_MARKET_OPEN_SUCCESS, Component.text(player.getName())); + } else { + handleFeedback(context, MessageConstants.COMMAND_MARKET_OPEN_FAILURE_NOT_LOADED, Component.text(player.getName())); + } + }); + } + + @Override + public String getFeatureID() { + return "open_market"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/QueryStatisticsCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/QueryStatisticsCommand.java new file mode 100644 index 00000000..9aa053fd --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/QueryStatisticsCommand.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.parser.standard.EnumParser; + +public class QueryStatisticsCommand extends BukkitCommandFeature { + + public QueryStatisticsCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("player", PlayerParser.playerParser()) + .required("type", EnumParser.enumParser(FishingStatistics.Type.class)) + .handler(context -> { + Player player = context.get("player"); + FishingStatistics.Type type = context.get("type"); + BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> { + if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) { + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_QUERY_AMOUNT, Component.text(userData.statistics().amountMap().toString())); + } else if (type == FishingStatistics.Type.MAX_SIZE) { + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_QUERY_SIZE, Component.text(userData.statistics().sizeMap().toString())); + } + }, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED)); + }); + } + + @Override + public String getFeatureID() { + return "statistics_query"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ReloadCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ReloadCommand.java new file mode 100644 index 00000000..67605ebf --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ReloadCommand.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class ReloadCommand extends BukkitCommandFeature { + + public ReloadCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .handler(context -> { + long time1 = System.currentTimeMillis(); + BukkitCustomFishingPlugin.getInstance().reload(); + handleFeedback(context, MessageConstants.COMMAND_RELOAD_SUCCESS, Component.text(System.currentTimeMillis() - time1)); + }); + } + + @Override + public String getFeatureID() { + return "reload"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ResetStatisticsCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ResetStatisticsCommand.java new file mode 100644 index 00000000..eadf2427 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/ResetStatisticsCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; + +public class ResetStatisticsCommand extends BukkitCommandFeature { + + public ResetStatisticsCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("player", PlayerParser.playerParser()) + .handler(context -> { + Player player = context.get("player"); + BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> { + userData.statistics().reset(); + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_RESET_SUCCESS, Component.text(player.getName())); + }, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED)); + }); + } + + @Override + public String getFeatureID() { + return "statistics_reset"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SellFishCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SellFishCommand.java new file mode 100644 index 00000000..b29759c0 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SellFishCommand.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class SellFishCommand extends BukkitCommandFeature { + + public SellFishCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .handler(context -> { + if (!BukkitCustomFishingPlugin.getInstance().getMarketManager().openMarketGUI(context.sender())) { + handleFeedback(context, MessageConstants.COMMAND_DATA_FAILURE_NOT_LOADED); + } + }); + } + + @Override + public String getFeatureID() { + return "sellfish"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SetStatisticsCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SetStatisticsCommand.java new file mode 100644 index 00000000..92e16a89 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/SetStatisticsCommand.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.parser.standard.DoubleParser; +import org.incendo.cloud.parser.standard.EnumParser; +import org.incendo.cloud.parser.standard.StringParser; + +public class SetStatisticsCommand extends BukkitCommandFeature { + + public SetStatisticsCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("player", PlayerParser.playerParser()) + .required("id", StringParser.stringParser()) + .required("type", EnumParser.enumParser(FishingStatistics.Type.class)) + .required("value", DoubleParser.doubleParser(0)) + .handler(context -> { + Player player = context.get("player"); + String id = context.get("id"); + FishingStatistics.Type type = context.get("type"); + double value = context.get("value"); + BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> { + if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) { + userData.statistics().setAmount(id, (int) value); + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName())); + } else if (type == FishingStatistics.Type.MAX_SIZE) { + userData.statistics().setMaxSize(id, (float) value); + handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName())); + } + }, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED)); + }); + } + + @Override + public String getFeatureID() { + return "statistics_set"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StartCompetitionCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StartCompetitionCommand.java new file mode 100644 index 00000000..7d3419c8 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StartCompetitionCommand.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.concurrent.CompletableFuture; + +public class StartCompetitionCommand extends BukkitCommandFeature { + + public StartCompetitionCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getCompetitionIDs().stream().map(Suggestion::suggestion).toList()); + } + })) + .optional("group", StringParser.stringParser()) + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + String id = context.get("id"); + String group = context.getOrDefault("group", null); + if (BukkitCustomFishingPlugin.getInstance().getCompetitionManager().startCompetition(id, true, group)) { + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_START_SUCCESS); + } else { + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NOT_EXIST, Component.text(id)); + } + }); + } + + @Override + public String getFeatureID() { + return "start_competition"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StopCompetitionCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StopCompetitionCommand.java new file mode 100644 index 00000000..02fc38b7 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/StopCompetitionCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class StopCompetitionCommand extends BukkitCommandFeature { + + public StopCompetitionCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s").build()) + .handler(context -> { + FishingCompetition competition = BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getOnGoingCompetition(); + if (competition == null) { + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NO_COMPETITION); + } else { + competition.stop(true); + handleFeedback(context, MessageConstants.COMMAND_COMPETITION_STOP_SUCCESS); + } + }); + } + + @Override + public String getFeatureID() { + return "stop_competition"; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/UnlockDataCommand.java b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/UnlockDataCommand.java new file mode 100644 index 00000000..fee5a3bd --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/command/feature/UnlockDataCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature; +import net.momirealms.customfishing.common.command.CustomFishingCommandManager; +import net.momirealms.customfishing.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.parser.standard.UUIDParser; + +import java.util.UUID; + +public class UnlockDataCommand extends BukkitCommandFeature { + + public UnlockDataCommand(CustomFishingCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("uuid", UUIDParser.uuidParser()) + .handler(context -> { + UUID uuid = context.get("uuid"); + BukkitCustomFishingPlugin.getInstance().getStorageManager().getDataSource().lockOrUnlockPlayerData(uuid, false); + handleFeedback(context, MessageConstants.COMMAND_DATA_UNLOCK_SUCCESS, Component.text(uuid.toString())); + }); + } + + @Override + public String getFeatureID() { + return "data_unlock"; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/BukkitCompetitionManager.java similarity index 51% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/BukkitCompetitionManager.java index 6493ce22..82012412 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionManagerImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/BukkitCompetitionManager.java @@ -15,24 +15,28 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition; +package net.momirealms.customfishing.bukkit.competition; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.manager.CompetitionManager; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.bossbar.BossBar; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.competition.*; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.storage.method.database.nosql.RedisManager; -import net.momirealms.customfishing.util.ConfigUtils; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionManager; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig; +import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.Pair; import org.bukkit.Bukkit; -import org.bukkit.boss.BarColor; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.NotNull; +import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -40,16 +44,16 @@ import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; -public class CompetitionManagerImpl implements CompetitionManager { +public class BukkitCompetitionManager implements CompetitionManager { - private final CustomFishingPlugin plugin; + private final BukkitCustomFishingPlugin plugin; private final HashMap timeConfigMap; private final HashMap commandConfigMap; private Competition currentCompetition; - private CancellableTask timerCheckTask; + private SchedulerTask timerCheckTask; private int nextCompetitionSeconds; - public CompetitionManagerImpl(CustomFishingPlugin plugin) { + public BukkitCompetitionManager(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; this.timeConfigMap = new HashMap<>(); this.commandConfigMap = new HashMap<>(); @@ -57,42 +61,31 @@ public class CompetitionManagerImpl implements CompetitionManager { public void load() { loadConfig(); - this.timerCheckTask = plugin.getScheduler().runTaskAsyncTimer( + this.timerCheckTask = plugin.getScheduler().asyncRepeating( this::timerCheck, 1, 1, TimeUnit.SECONDS ); + plugin.debug("Loaded " + commandConfigMap.size() + " competitions"); } public void unload() { - if (this.timerCheckTask != null && !this.timerCheckTask.isCancelled()) + if (this.timerCheckTask != null) this.timerCheckTask.cancel(); + if (currentCompetition != null && currentCompetition.isOnGoing()) + this.currentCompetition.stop(true); this.commandConfigMap.clear(); this.timeConfigMap.clear(); - if (currentCompetition != null && currentCompetition.isOnGoing()) { - plugin.getScheduler().runTaskAsync(() -> currentCompetition.stop(true)); - } } public void disable() { - if (this.timerCheckTask != null && !this.timerCheckTask.isCancelled()) + if (this.timerCheckTask != null) this.timerCheckTask.cancel(); + if (currentCompetition != null && currentCompetition.isOnGoing()) + this.currentCompetition.stop(false); this.commandConfigMap.clear(); this.timeConfigMap.clear(); - if (currentCompetition != null && currentCompetition.isOnGoing()) - currentCompetition.stop(false); - } - - /** - * Retrieves a set of all competition names. - * - * @return A set of competition names. - */ - @NotNull - @Override - public Set getAllCompetitionKeys() { - return commandConfigMap.keySet(); } @SuppressWarnings("DuplicatedCode") @@ -102,7 +95,7 @@ public class CompetitionManagerImpl implements CompetitionManager { File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); if (!typeFolder.exists()) { if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); + plugin.getBoostrap().saveResource("contents" + File.separator + type + File.separator + "default.yml", false); } fileDeque.push(typeFolder); while (!fileDeque.isEmpty()) { @@ -121,46 +114,52 @@ public class CompetitionManagerImpl implements CompetitionManager { } private void loadSingleFileCompetition(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - - CompetitionConfig.Builder builder = new CompetitionConfig.Builder(entry.getKey()) - .goal(CompetitionGoal.valueOf(section.getString("goal", "TOTAL_SCORE").toUpperCase(Locale.ENGLISH))) + YamlDocument document = plugin.getConfigManager().loadData(file); + for (Map.Entry entry : document.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section section) { + CompetitionConfig.Builder builder = CompetitionConfig.builder() + .key(entry.getKey()) + .goal(CompetitionGoal.index().value(section.getString("goal", "TOTAL_SCORE").toLowerCase(Locale.ENGLISH))) .minPlayers(section.getInt("min-players", 0)) .duration(section.getInt("duration", 300)) - .rewards(getPrizeActions(section.getConfigurationSection("rewards"))) - .requirements(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("participate-requirements"), false)) - .joinActions(plugin.getActionManager().getActions(section.getConfigurationSection("participate-actions"))) - .startActions(plugin.getActionManager().getActions(section.getConfigurationSection("start-actions"))) - .endActions(plugin.getActionManager().getActions(section.getConfigurationSection("end-actions"))) - .skipActions(plugin.getActionManager().getActions(section.getConfigurationSection("skip-actions"))); - + .rewards(getPrizeActions(section.getSection("rewards"))) + .joinRequirements(plugin.getRequirementManager().parseRequirements(section.getSection("participate-requirements"), false)) + .joinActions(plugin.getActionManager().parseActions(section.getSection("participate-actions"))) + .startActions(plugin.getActionManager().parseActions(section.getSection("start-actions"))) + .endActions(plugin.getActionManager().parseActions(section.getSection("end-actions"))) + .skipActions(plugin.getActionManager().parseActions(section.getSection("skip-actions")));; if (section.getBoolean("bossbar.enable", false)) { - builder.bossbar(new BossBarConfig.Builder() - .color(BarColor.valueOf(section.getString("bossbar.color", "WHITE").toUpperCase(Locale.ENGLISH))) - .overlay(BossBarConfig.Overlay.valueOf(section.getString("bossbar.overlay", "PROGRESS").toUpperCase(Locale.ENGLISH))) - .refreshRate(section.getInt("bossbar.refresh-rate", 20)) - .switchInterval(section.getInt("bossbar.switch-interval", 200)) - .showToAll(!section.getBoolean("bossbar.only-show-to-participants", true)) - .text(section.getStringList("bossbar.text").toArray(new String[0])) - .build()); + builder.bossBarConfig( + BossBarConfig.builder() + .enable(true) + .color(BossBar.Color.valueOf(section.getString("bossbar.color", "WHITE").toUpperCase(Locale.ENGLISH))) + .overlay(BossBar.Overlay.valueOf(section.getString("bossbar.overlay", "PROGRESS").toUpperCase(Locale.ENGLISH))) + .refreshRate(section.getInt("bossbar.refresh-rate", 20)) + .switchInterval(section.getInt("bossbar.switch-interval", 200)) + .showToAll(!section.getBoolean("bossbar.only-show-to-participants", true)) + .text(section.getStringList("bossbar.text").toArray(new String[0])) + .build() + ); } - if (section.getBoolean("actionbar.enable", false)) { - builder.actionbar(new ActionBarConfig.Builder() - .refreshRate(section.getInt("actionbar.refresh-rate", 5)) - .switchInterval(section.getInt("actionbar.switch-interval", 200)) - .showToAll(!section.getBoolean("actionbar.only-show-to-participants", true)) - .text(section.getStringList("actionbar.text").toArray(new String[0])) - .build()); + builder.actionBarConfig( + ActionBarConfig.builder() + .enable(true) + .refreshRate(section.getInt("actionbar.refresh-rate", 5)) + .switchInterval(section.getInt("actionbar.switch-interval", 200)) + .showToAll(!section.getBoolean("actionbar.only-show-to-participants", true)) + .text(section.getStringList("actionbar.text").toArray(new String[0])) + .build() + ); } - CompetitionConfig competitionConfig = builder.build(); List> timePairs = section.getStringList("start-time") - .stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, ":")).toList(); - List weekdays = section.getIntegerList("start-weekday"); - if (weekdays.size() == 0) { + .stream().map(it -> { + String[] split = it.split(":"); + return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + List weekdays = section.getIntList("start-weekday"); + if (weekdays.isEmpty()) { weekdays.addAll(List.of(1,2,3,4,5,6,7)); } for (Integer weekday : weekdays) { @@ -180,12 +179,12 @@ public class CompetitionManagerImpl implements CompetitionManager { * @param section The configuration section containing prize actions. * @return A HashMap where keys are action names and values are arrays of Action objects. */ - public HashMap getPrizeActions(ConfigurationSection section) { - HashMap map = new HashMap<>(); + public HashMap[]> getPrizeActions(Section section) { + HashMap[]> map = new HashMap<>(); if (section == null) return map; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - map.put(entry.getKey(), plugin.getActionManager().getActions(innerSection)); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + map.put(entry.getKey(), plugin.getActionManager().parseActions(innerSection)); } } return map; @@ -214,37 +213,10 @@ public class CompetitionManagerImpl implements CompetitionManager { } } - /** - * Retrieves the localization key for a given competition goal. - * - * @param goal The competition goal to retrieve the localization key for. - * @return The localization key for the specified competition goal. - */ - @NotNull - @Override - public String getCompetitionGoalLocale(CompetitionGoal goal) { - switch (goal) { - case MAX_SIZE -> { - return CFLocale.MSG_Max_Size; - } - case CATCH_AMOUNT -> { - return CFLocale.MSG_Catch_Amount; - } - case TOTAL_SCORE -> { - return CFLocale.MSG_Total_Score; - } - case TOTAL_SIZE -> { - return CFLocale.MSG_Total_Size; - } - } - return ""; - } - @Override public boolean startCompetition(String competition, boolean force, String serverGroup) { CompetitionConfig config = commandConfigMap.get(competition); if (config == null) { - LogUtils.warn("Competition " + competition + " doesn't exist."); return false; } return startCompetition(config, force, serverGroup); @@ -266,14 +238,8 @@ public class CompetitionManagerImpl implements CompetitionManager { public boolean startCompetition(CompetitionConfig config, boolean force, @Nullable String serverGroup) { if (!force) { int players = Bukkit.getOnlinePlayers().size(); - if (players < config.getMinPlayersToStart()) { - var actions = config.getSkipActions(); - if (actions != null) { - Condition condition = new Condition(null, null, new HashMap<>()); - for (Action action : actions) { - action.trigger(condition); - } - } + if (players < config.minPlayersToStart()) { + ActionManager.trigger(Context.player(null), config.skipActions()); return false; } start(config); @@ -282,7 +248,12 @@ public class CompetitionManagerImpl implements CompetitionManager { start(config); return true; } else { - RedisManager.getInstance().publishRedisMessage(serverGroup, "start;" + config.getKey()); + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF(serverGroup); + out.writeUTF("competition"); + out.writeUTF("start"); + out.writeUTF(config.key()); + RedisManager.getInstance().publishRedisMessage(Arrays.toString(out.toByteArray())); return true; } } @@ -291,16 +262,16 @@ public class CompetitionManagerImpl implements CompetitionManager { if (getOnGoingCompetition() != null) { // END currentCompetition.end(true); - plugin.getScheduler().runTaskAsyncLater(() -> { + plugin.getScheduler().asyncLater(() -> { // start one second later - this.currentCompetition = new Competition(config); - this.currentCompetition.start(); + this.currentCompetition = new Competition(plugin, config); + this.currentCompetition.start(true); }, 1, TimeUnit.SECONDS); } else { // start instantly - plugin.getScheduler().runTaskAsync(() -> { - this.currentCompetition = new Competition(config); - this.currentCompetition.start(); + plugin.getScheduler().async().execute(() -> { + this.currentCompetition = new Competition(plugin, config); + this.currentCompetition.start(true); }); } } @@ -311,19 +282,18 @@ public class CompetitionManagerImpl implements CompetitionManager { * @return The number of seconds until the next competition. */ @Override - public int getNextCompetitionSeconds() { + public int getNextCompetitionInSeconds() { return nextCompetitionSeconds; } - /** - * Retrieves the configuration for a competition based on its key. - * - * @param key The key of the competition configuration to retrieve. - * @return The {@link CompetitionConfig} for the specified key, or {@code null} if no configuration exists with that key. - */ @Nullable @Override - public CompetitionConfig getConfig(String key) { + public CompetitionConfig getCompetition(String key) { return commandConfigMap.get(key); } + + @Override + public Collection getCompetitionIDs() { + return commandConfigMap.keySet(); + } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/competition/Competition.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/Competition.java new file mode 100644 index 00000000..b3daf267 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/Competition.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.competition; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.CompetitionEvent; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig; +import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.api.mechanic.competition.RankingProvider; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.bukkit.competition.actionbar.ActionBarManager; +import net.momirealms.customfishing.bukkit.competition.bossbar.BossBarManager; +import net.momirealms.customfishing.bukkit.competition.ranking.LocalRankingProvider; +import net.momirealms.customfishing.bukkit.competition.ranking.RedisRankingProvider; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public class Competition implements FishingCompetition { + + private final BukkitCustomFishingPlugin plugin; + private final CompetitionConfig config; + private SchedulerTask competitionTimerTask; + private final CompetitionGoal goal; + private final Context publicContext; + private final RankingProvider rankingProvider; + private float progress; + private int remainingTime; + private long startTime; + private BossBarManager bossBarManager; + private ActionBarManager actionBarManager; + + public Competition(BukkitCustomFishingPlugin plugin, CompetitionConfig config) { + this.config = config; + this.plugin = plugin; + this.goal = config.goal() == CompetitionGoal.RANDOM ? CompetitionGoal.getRandom() : config.goal(); + if (ConfigManager.redisRanking()) this.rankingProvider = new RedisRankingProvider(); + else this.rankingProvider = new LocalRankingProvider(); + this.publicContext = Context.player(null, true); + this.publicContext.arg(ContextKeys.GOAL, goal); + } + + @Override + public void start(boolean triggerEvent) { + this.progress = 1; + this.remainingTime = this.config.durationInSeconds(); + this.startTime = Instant.now().getEpochSecond(); + + this.arrangeTimerTask(); + if (this.config.bossBarConfig() != null && this.config.bossBarConfig().enabled()) { + this.bossBarManager = new BossBarManager(this.config.bossBarConfig(), this); + this.bossBarManager.load(); + } + if (this.config.actionBarConfig() != null && this.config.actionBarConfig().enabled()) { + this.actionBarManager = new ActionBarManager(this.config.actionBarConfig(), this); + this.actionBarManager.load(); + } + + this.updatePublicPlaceholders(); + ActionManager.trigger(this.publicContext, this.config.startActions()); + this.rankingProvider.clear(); + if (triggerEvent) { + this.plugin.getScheduler().async().execute(() -> { + CompetitionEvent competitionStartEvent = new CompetitionEvent(CompetitionEvent.State.START, this); + Bukkit.getPluginManager().callEvent(competitionStartEvent); + }); + } + } + + @Override + public void stop(boolean triggerEvent) { + if (this.competitionTimerTask != null) + this.competitionTimerTask.cancel(); + if (this.bossBarManager != null) + this.bossBarManager.unload(); + if (this.actionBarManager != null) + this.actionBarManager.unload(); + this.rankingProvider.clear(); + this.remainingTime = 0; + if (triggerEvent) { + plugin.getScheduler().async().execute(() -> { + CompetitionEvent competitionEvent = new CompetitionEvent(CompetitionEvent.State.STOP, this); + Bukkit.getPluginManager().callEvent(competitionEvent); + }); + } + } + + @Override + public void end(boolean triggerEvent) { + // mark it as ended + this.remainingTime = 0; + + // cancel some sub tasks + if (competitionTimerTask != null) + this.competitionTimerTask.cancel(); + if (this.bossBarManager != null) + this.bossBarManager.unload(); + if (this.actionBarManager != null) + this.actionBarManager.unload(); + + // give prizes + HashMap[]> rewardsMap = config.rewards(); + if (rankingProvider.getSize() != 0 && rewardsMap != null) { + Iterator> iterator = rankingProvider.getIterator(); + int i = 1; + while (iterator.hasNext()) { + Pair competitionPlayer = iterator.next(); + this.publicContext.arg(ContextKeys.of(i + "_player", String.class), competitionPlayer.left()); + this.publicContext.arg(ContextKeys.of(i + "_score", String.class), String.format("%.2f", competitionPlayer.right())); + if (i < rewardsMap.size()) { + Player player = Bukkit.getPlayer(competitionPlayer.left()); + if (player != null) { + ActionManager.trigger(Context.player(player).combine(this.publicContext), rewardsMap.get(String.valueOf(i))); + } + } else { + Action[] actions = rewardsMap.get("participation"); + if (actions != null) { + Player player = Bukkit.getPlayer(competitionPlayer.left()); { + if (player != null) { + ActionManager.trigger(Context.player(player).combine(this.publicContext), actions); + } + } + } + } + i++; + } + } + + // end actions + ActionManager.trigger(publicContext, config.endActions()); + + // call event + if (triggerEvent) { + plugin.getScheduler().async().execute(() -> { + CompetitionEvent competitionEndEvent = new CompetitionEvent(CompetitionEvent.State.END, this); + Bukkit.getPluginManager().callEvent(competitionEndEvent); + }); + } + + // 1 seconds delay for other servers to read the redis data + plugin.getScheduler().asyncLater(this.rankingProvider::clear, 1, TimeUnit.SECONDS); + } + + private void arrangeTimerTask() { + this.competitionTimerTask = this.plugin.getScheduler().asyncRepeating(() -> { + if (decreaseTime()) { + end(true); + return; + } + updatePublicPlaceholders(); + }, 1, 1, TimeUnit.SECONDS); + } + + private void updatePublicPlaceholders() { + for (int i = 1; i < ConfigManager.placeholderLimit() + 1; i++) { + Optional player = Optional.ofNullable(this.rankingProvider.getPlayerAt(i)); + if (player.isPresent()) { + this.publicContext.arg(ContextKeys.of(i + "_player", String.class), player.get()); + this.publicContext.arg(ContextKeys.of(i + "_score", String.class), String.format("%.2f", this.rankingProvider.getScoreAt(i))); + } else { + this.publicContext.arg(ContextKeys.of(i + "_player", String.class), TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_PLAYER.build().key())); + this.publicContext.arg(ContextKeys.of(i + "_score", String.class), TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_SCORE.build().key())); + } + } + this.publicContext.arg(ContextKeys.HOUR, remainingTime < 3600 ? "" : (remainingTime / 3600) + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_HOUR.build().key())); + this.publicContext.arg(ContextKeys.MINUTE, remainingTime < 60 ? "" : (remainingTime % 3600) / 60 + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_MINUTE.build().key())); + this.publicContext.arg(ContextKeys.SECOND, remainingTime == 0 ? "" : remainingTime % 60 + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_SECOND.build().key())); + this.publicContext.arg(ContextKeys.SECONDS, remainingTime); + } + + @Override + public boolean isOnGoing() { + return remainingTime > 0; + } + + /** + * Decreases the remaining time for the fishing competition and updates the progress. + * + * @return {@code true} if the remaining time becomes zero or less, indicating the competition has ended. + */ + private boolean decreaseTime() { + long current = Instant.now().getEpochSecond(); + int duration = config.durationInSeconds(); + remainingTime = (int) (duration - (current - startTime)); + progress = (float) remainingTime / duration; + return remainingTime <= 0; + } + + @Override + public void refreshData(Player player, double score) { + // if player join for the first time, trigger join actions + if (!hasPlayerJoined(player)) { + ActionManager.trigger(Context.player(player).combine(publicContext), config.joinActions()); + } + + // show competition info + if (this.bossBarManager != null) + this.bossBarManager.showBossBarTo(player); + if (this.actionBarManager != null) + this.actionBarManager.showActionBarTo(player); + + // refresh data + this.goal.refreshScore(rankingProvider, player, score); + } + + @Override + public boolean hasPlayerJoined(OfflinePlayer player) { + return rankingProvider.getPlayerRank(player.getName()) != -1; + } + + @Override + public float getProgress() { + return progress; + } + + @Override + public long getRemainingTime() { + return remainingTime; + } + + @Override + public long getStartTime() { + return startTime; + } + + @NotNull + @Override + public CompetitionConfig getConfig() { + return config; + } + + @NotNull + @Override + public CompetitionGoal getGoal() { + return goal; + } + + @NotNull + @Override + public RankingProvider getRanking() { + return rankingProvider; + } + + @Override + public Context getPublicContext() { + return publicContext; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionSchedule.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/CompetitionSchedule.java similarity index 98% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionSchedule.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/CompetitionSchedule.java index 03564cf3..63eb82b9 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/CompetitionSchedule.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/CompetitionSchedule.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition; +package net.momirealms.customfishing.bukkit.competition; public class CompetitionSchedule { diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarManager.java similarity index 86% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarManager.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarManager.java index 1bb6b546..71cc49be 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarManager.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarManager.java @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition.actionbar; +package net.momirealms.customfishing.bukkit.competition.actionbar; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.competition.ActionBarConfig; -import net.momirealms.customfishing.mechanic.competition.Competition; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig; +import net.momirealms.customfishing.bukkit.competition.Competition; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -47,8 +47,8 @@ public class ActionBarManager implements Listener { * Loads the ActionBar manager, registering events and showing ActionBar messages to online players. */ public void load() { - Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.getInstance()); - if (actionBarConfig.isShowToAll()) { + Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap()); + if (actionBarConfig.showToAll()) { for (Player player : Bukkit.getOnlinePlayers()) { ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition); if (!sender.isVisible()) { @@ -93,9 +93,9 @@ public class ActionBarManager implements Listener { @EventHandler public void onJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - CustomFishingPlugin.getInstance().getScheduler().runTaskAsyncLater(() -> { + BukkitCustomFishingPlugin.getInstance().getScheduler().asyncLater(() -> { boolean hasJoined = competition.hasPlayerJoined(player); - if ((hasJoined || actionBarConfig.isShowToAll()) + if ((hasJoined || actionBarConfig.showToAll()) && !senderMap.containsKey(player.getUniqueId())) { ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition); if (!sender.isVisible()) { @@ -117,8 +117,7 @@ public class ActionBarManager implements Listener { sender = new ActionBarSender(player, actionBarConfig, competition); senderMap.put(player.getUniqueId(), sender); } - if (!sender.isVisible()) { + if (!sender.isVisible()) sender.show(); - } } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarSender.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarSender.java new file mode 100644 index 00000000..1dc3e3fa --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/actionbar/ActionBarSender.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.competition.actionbar; + +import net.kyori.adventure.audience.Audience; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.misc.value.DynamicText; +import net.momirealms.customfishing.bukkit.competition.Competition; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.entity.Player; + +import java.util.concurrent.TimeUnit; + +public class ActionBarSender { + + private final Player player; + private final Audience audience; + private int refreshTimer; + private int switchTimer; + private int counter; + private final DynamicText[] texts; + private SchedulerTask senderTask; + private final ActionBarConfig config; + private boolean isShown; + private final Competition competition; + private final Context privateContext; + + public ActionBarSender(Player player, ActionBarConfig config, Competition competition) { + this.player = player; + this.audience = BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(player); + this.config = config; + this.privateContext = Context.player(player); + this.isShown = false; + this.competition = competition; + this.updatePrivatePlaceholders(); + String[] str = config.texts(); + texts = new DynamicText[str.length]; + for (int i = 0; i < str.length; i++) { + texts[i] = new DynamicText(player, str[i]); + texts[i].update(privateContext.placeholderMap()); + } + } + + @SuppressWarnings("DuplicatedCode") + private void updatePrivatePlaceholders() { + this.privateContext.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", competition.getRanking().getPlayerScore(player.getName()))); + int rank = competition.getRanking().getPlayerRank(player.getName()); + this.privateContext.arg(ContextKeys.RANK, rank != -1 ? String.valueOf(rank) : TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_RANK.build().key())); + this.privateContext.combine(competition.getPublicContext()); + } + + public void show() { + this.isShown = true; + senderTask = BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> { + switchTimer++; + if (switchTimer > config.switchInterval()) { + switchTimer = 0; + counter++; + } + if (refreshTimer < config.refreshRate()){ + refreshTimer++; + } else { + refreshTimer = 0; + DynamicText text = texts[counter % (texts.length)]; + updatePrivatePlaceholders(); + text.update(this.privateContext.placeholderMap()); + audience.sendActionBar(AdventureHelper.miniMessage(text.getLatestValue())); + } + }, 50, 50, TimeUnit.MILLISECONDS); + } + + public void hide() { + if (senderTask != null) + senderTask.cancel(); + this.isShown = false; + } + + public boolean isVisible() { + return this.isShown; + } + + public ActionBarConfig getConfig() { + return config; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarManager.java similarity index 86% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarManager.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarManager.java index 1dccd3d5..7d26fde5 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarManager.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarManager.java @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition.bossbar; +package net.momirealms.customfishing.bukkit.competition.bossbar; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.competition.BossBarConfig; -import net.momirealms.customfishing.mechanic.competition.Competition; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig; +import net.momirealms.customfishing.bukkit.competition.Competition; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -47,8 +47,8 @@ public class BossBarManager implements Listener { * Loads the boss bar manager, registering events and showing boss bars to online players. */ public void load() { - Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.getInstance()); - if (bossBarConfig.isShowToAll()) { + Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap()); + if (bossBarConfig.showToAll()) { for (Player player : Bukkit.getOnlinePlayers()) { BossBarSender sender = new BossBarSender(player, bossBarConfig, competition); if (!sender.isVisible()) { @@ -93,9 +93,9 @@ public class BossBarManager implements Listener { @EventHandler public void onJoin(PlayerJoinEvent event) { final Player player = event.getPlayer(); - CustomFishingPlugin.getInstance().getScheduler().runTaskAsyncLater(() -> { + BukkitCustomFishingPlugin.getInstance().getScheduler().asyncLater(() -> { boolean hasJoined = competition.hasPlayerJoined(player); - if ((hasJoined || bossBarConfig.isShowToAll()) + if ((hasJoined || bossBarConfig.showToAll()) && !senderMap.containsKey(player.getUniqueId())) { BossBarSender sender = new BossBarSender(player, bossBarConfig, competition); if (!sender.isVisible()) { @@ -117,8 +117,7 @@ public class BossBarManager implements Listener { sender = new BossBarSender(player, bossBarConfig, competition); senderMap.put(player.getUniqueId(), sender); } - if (!sender.isVisible()) { + if (!sender.isVisible()) sender.show(); - } } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarSender.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarSender.java new file mode 100644 index 00000000..c6489877 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/bossbar/BossBarSender.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.competition.bossbar; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.bossbar.BossBar; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.misc.value.DynamicText; +import net.momirealms.customfishing.bukkit.competition.Competition; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.locale.MessageConstants; +import net.momirealms.customfishing.common.locale.TranslationManager; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.entity.Player; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class BossBarSender { + + private final Player player; + private final Audience audience; + private int refreshTimer; + private int switchTimer; + private int counter; + private final DynamicText[] texts; + private SchedulerTask senderTask; + private final BossBar bossBar; + private final BossBarConfig config; + private boolean isShown; + private final Competition competition; + private final Context privateContext; + + public BossBarSender(Player player, BossBarConfig config, Competition competition) { + this.player = player; + this.audience = BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(player); + this.config = config; + this.isShown = false; + this.competition = competition; + this.privateContext = Context.player(player); + this.updatePrivatePlaceholders(); + String[] str = config.texts(); + texts = new DynamicText[str.length]; + for (int i = 0; i < str.length; i++) { + texts[i] = new DynamicText(player, str[i]); + texts[i].update(privateContext.placeholderMap()); + } + bossBar = BossBar.bossBar( + AdventureHelper.miniMessage(texts[0].getLatestValue()), + competition.getProgress(), + config.color(), + config.overlay(), + Set.of() + ); + } + + @SuppressWarnings("DuplicatedCode") + private void updatePrivatePlaceholders() { + this.privateContext.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", competition.getRanking().getPlayerScore(player.getName()))); + int rank = competition.getRanking().getPlayerRank(player.getName()); + this.privateContext.arg(ContextKeys.RANK, rank != -1 ? String.valueOf(rank) : TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_RANK.build().key())); + this.privateContext.combine(competition.getPublicContext()); + } + + public void show() { + this.isShown = true; + this.bossBar.addViewer(audience); + this.senderTask = BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> { + switchTimer++; + if (switchTimer > config.switchInterval()) { + switchTimer = 0; + counter++; + } + if (refreshTimer < config.refreshRate()){ + refreshTimer++; + } else { + refreshTimer = 0; + DynamicText text = texts[counter % (texts.length)]; + updatePrivatePlaceholders(); + if (text.update(privateContext.placeholderMap())) { + bossBar.name(AdventureHelper.miniMessage(text.getLatestValue())); + } + bossBar.progress(competition.getProgress()); + } + }, 50, 50, TimeUnit.MILLISECONDS); + } + + public boolean isVisible() { + return this.isShown; + } + + public BossBarConfig getConfig() { + return config; + } + + public void hide() { + this.bossBar.removeViewer(audience); + if (senderTask != null) senderTask.cancel(); + this.isShown = false; + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/LocalRankingImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/LocalRankingProvider.java similarity index 95% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/LocalRankingImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/LocalRankingProvider.java index 8b00564d..64b77f06 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/LocalRankingImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/LocalRankingProvider.java @@ -15,22 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition.ranking; +package net.momirealms.customfishing.bukkit.competition.ranking; -import net.momirealms.customfishing.api.common.Pair; import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer; -import net.momirealms.customfishing.api.mechanic.competition.Ranking; +import net.momirealms.customfishing.api.mechanic.competition.RankingProvider; +import net.momirealms.customfishing.common.util.Pair; import java.util.*; /** * Implementation of the Ranking interface that manages the ranking of competition players locally. */ -public class LocalRankingImpl implements Ranking { +public class LocalRankingProvider implements RankingProvider { private final Set competitionPlayers; - public LocalRankingImpl() { + public LocalRankingProvider() { competitionPlayers = Collections.synchronizedSet(new TreeSet<>()); } @@ -102,9 +102,9 @@ public class LocalRankingImpl implements Ranking { } /** - * Returns an iterator for iterating over pairs of player names and scores. + * Returns an iterator for iterating over items of player names and scores. * - * @return An iterator for pairs of player names and scores. + * @return An iterator for items of player names and scores. */ @Override public Iterator> getIterator() { diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/RedisRankingImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/RedisRankingProvider.java similarity index 76% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/RedisRankingImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/RedisRankingProvider.java index 9c2a96f7..46739669 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/ranking/RedisRankingImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/competition/ranking/RedisRankingProvider.java @@ -15,20 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.competition.ranking; +package net.momirealms.customfishing.bukkit.competition.ranking; -import net.momirealms.customfishing.api.common.Pair; import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer; -import net.momirealms.customfishing.api.mechanic.competition.Ranking; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.storage.method.database.nosql.RedisManager; +import net.momirealms.customfishing.api.mechanic.competition.RankingProvider; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager; +import net.momirealms.customfishing.common.util.Pair; import redis.clients.jedis.Jedis; import redis.clients.jedis.resps.Tuple; import java.util.Iterator; import java.util.List; -public class RedisRankingImpl implements Ranking { +public class RedisRankingProvider implements RankingProvider { /** * Clears the ranking data by removing all players and scores. @@ -36,7 +36,7 @@ public class RedisRankingImpl implements Ranking { @Override public void clear() { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - jedis.del("cf_competition_" + CFConfig.serverGroup); + jedis.del("cf_competition_" + ConfigManager.serverGroup()); } } @@ -49,7 +49,7 @@ public class RedisRankingImpl implements Ranking { @Override public CompetitionPlayer getCompetitionPlayer(String player) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - Double score = jedis.zscore("cf_competition_" + CFConfig.serverGroup, player); + Double score = jedis.zscore("cf_competition_" + ConfigManager.serverGroup(), player); if (score == null || score == 0) return null; return new CompetitionPlayer(player, Float.parseFloat(score.toString())); } @@ -58,8 +58,8 @@ public class RedisRankingImpl implements Ranking { @Override public CompetitionPlayer getCompetitionPlayer(int rank) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - List player = jedis.zrevrangeWithScores("cf_competition_" + CFConfig.serverGroup, rank - 1, rank -1); - if (player == null || player.size() == 0) return null; + List player = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1); + if (player == null || player.isEmpty()) return null; return new CompetitionPlayer(player.get(0).getElement(), player.get(0).getScore()); } } @@ -67,26 +67,26 @@ public class RedisRankingImpl implements Ranking { @Override public void addPlayer(CompetitionPlayer competitionPlayer) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - jedis.zincrby("cf_competition_" + CFConfig.serverGroup, competitionPlayer.getScore(), competitionPlayer.getPlayer()); + jedis.zincrby("cf_competition_" + ConfigManager.serverGroup(), competitionPlayer.getScore(), competitionPlayer.getPlayer()); } } @Override public void removePlayer(String player) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - jedis.zrem("cf_competition_" + CFConfig.serverGroup, player); + jedis.zrem("cf_competition_" + ConfigManager.serverGroup(), player); } } /** - * Returns an iterator for iterating over pairs of player names and scores in descending order. + * Returns an iterator for iterating over items of player names and scores in descending order. * - * @return An iterator for pairs of player names and scores. + * @return An iterator for items of player names and scores. */ @Override public Iterator> getIterator() { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - List players = jedis.zrevrangeWithScores("cf_competition_" + CFConfig.serverGroup, 0, -1); + List players = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), 0, -1); return players.stream().map(it -> Pair.of(it.getElement(), it.getScore())).toList().iterator(); } } @@ -99,7 +99,7 @@ public class RedisRankingImpl implements Ranking { @Override public int getSize() { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - long size = jedis.zcard("cf_competition_" + CFConfig.serverGroup); + long size = jedis.zcard("cf_competition_" + ConfigManager.serverGroup()); return (int) size; } } @@ -113,7 +113,7 @@ public class RedisRankingImpl implements Ranking { @Override public int getPlayerRank(String player) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - Long rank = jedis.zrevrank("cf_competition_" + CFConfig.serverGroup, player); + Long rank = jedis.zrevrank("cf_competition_" + ConfigManager.serverGroup(), player); if (rank == null) return -1; return (int) (rank + 1); @@ -129,7 +129,7 @@ public class RedisRankingImpl implements Ranking { @Override public double getPlayerScore(String player) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - Double rank = jedis.zscore("cf_competition_" + CFConfig.serverGroup, player); + Double rank = jedis.zscore("cf_competition_" + ConfigManager.serverGroup(), player); if (rank == null) return 0; return rank.floatValue(); @@ -145,7 +145,7 @@ public class RedisRankingImpl implements Ranking { @Override public void refreshData(String player, double score) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - jedis.zincrby("cf_competition_" + CFConfig.serverGroup, score, player); + jedis.zincrby("cf_competition_" + ConfigManager.serverGroup(), score, player); } } @@ -158,7 +158,7 @@ public class RedisRankingImpl implements Ranking { @Override public void setData(String player, double score) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - jedis.zadd("cf_competition_" + CFConfig.serverGroup, score, player); + jedis.zadd("cf_competition_" + ConfigManager.serverGroup(), score, player); } } @@ -171,8 +171,8 @@ public class RedisRankingImpl implements Ranking { @Override public String getPlayerAt(int rank) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - List player = jedis.zrevrange("cf_competition_" + CFConfig.serverGroup, rank - 1, rank -1); - if (player == null || player.size() == 0) return null; + List player = jedis.zrevrange("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1); + if (player == null || player.isEmpty()) return null; return player.get(0); } } @@ -186,8 +186,8 @@ public class RedisRankingImpl implements Ranking { @Override public double getScoreAt(int rank) { try (Jedis jedis = RedisManager.getInstance().getJedis()) { - List players = jedis.zrevrangeWithScores("cf_competition_" + CFConfig.serverGroup, rank - 1, rank -1); - if (players == null || players.size() == 0) return 0; + List players = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1); + if (players == null || players.isEmpty()) return 0; return players.get(0).getScore(); } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/config/BukkitConfigManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/config/BukkitConfigManager.java new file mode 100644 index 00000000..af1c2863 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/config/BukkitConfigManager.java @@ -0,0 +1,1081 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.config; + +import com.saicone.rtag.RtagItem; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; +import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; +import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; +import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; +import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.config.ConfigType; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.effect.EffectProperties; +import net.momirealms.customfishing.api.mechanic.event.EventManager; +import net.momirealms.customfishing.api.mechanic.item.ItemEditor; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.api.mechanic.requirement.Requirement; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKeys; +import net.momirealms.customfishing.api.mechanic.totem.TotemModel; +import net.momirealms.customfishing.api.mechanic.totem.TotemParticle; +import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock; +import net.momirealms.customfishing.api.mechanic.totem.block.property.AxisImpl; +import net.momirealms.customfishing.api.mechanic.totem.block.property.FaceImpl; +import net.momirealms.customfishing.api.mechanic.totem.block.property.HalfImpl; +import net.momirealms.customfishing.api.mechanic.totem.block.property.TotemBlockProperty; +import net.momirealms.customfishing.api.mechanic.totem.block.type.TypeCondition; +import net.momirealms.customfishing.api.util.OffsetUtils; +import net.momirealms.customfishing.bukkit.item.damage.CustomDurabilityItem; +import net.momirealms.customfishing.bukkit.totem.particle.DustParticleSetting; +import net.momirealms.customfishing.bukkit.totem.particle.ParticleSetting; +import net.momirealms.customfishing.bukkit.util.ItemStackUtils; +import net.momirealms.customfishing.common.dependency.DependencyProperties; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.item.AbstractItem; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.customfishing.common.util.*; +import org.bukkit.*; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Bisected; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class BukkitConfigManager extends ConfigManager { + + private static YamlDocument MAIN_CONFIG; + + public static YamlDocument getMainConfig() { + return MAIN_CONFIG; + } + + public BukkitConfigManager(BukkitCustomFishingPlugin plugin) { + super(plugin); + this.registerBuiltInItemProperties(); + this.registerBuiltInBaseEffectParser(); + this.registerBuiltInLootParser(); + this.registerBuiltInEntityParser(); + this.registerBuiltInEventParser(); + this.registerBuiltInEffectModifierParser(); + this.registerBuiltInTotemParser(); + this.registerBuiltInHookParser(); + } + + @Override + public void load() { + String configVersion = DependencyProperties.getDependencyVersion("config"); + try (InputStream inputStream = new FileInputStream(resolveConfig("config.yml").toFile())) { + MAIN_CONFIG = YamlDocument.create( + inputStream, + plugin.getResourceStream("config.yml"), + GeneralSettings.builder() + .setRouteSeparator('.') + .setUseDefaults(false) + .build(), + LoaderSettings + .builder() + .setAutoUpdate(true) + .build(), + DumperSettings.DEFAULT, + UpdaterSettings + .builder() + .setVersioning(new BasicVersioning("config-version")) + .addIgnoredRoute(configVersion, "mechanics.mechanic-requirements", '.') + .addIgnoredRoute(configVersion, "mechanics.skip-game-requirements", '.') + .addIgnoredRoute(configVersion, "mechanics.auto-fishing-requirements", '.') + .addIgnoredRoute(configVersion, "mechanics.global-events", '.') + .addIgnoredRoute(configVersion, "mechanics.global-effects", '.') + .addIgnoredRoute(configVersion, "mechanics.fishing-bag.collect-requirements", '.') + .addIgnoredRoute(configVersion, "mechanics.fishing-bag.collect-actions", '.') + .addIgnoredRoute(configVersion, "mechanics.fishing-bag.full-actions", '.') + .addIgnoredRoute(configVersion, "mechanics.market.item-price", '.') + .addIgnoredRoute(configVersion, "mechanics.market.sell-all-icons", '.') + .addIgnoredRoute(configVersion, "mechanics.market.sell-icons", '.') + .addIgnoredRoute(configVersion, "mechanics.market.decorative-icons", '.') + .addIgnoredRoute(configVersion, "other-settings.placeholder-register", '.') + .build() + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.loadSettings(); + this.loadConfigs(); + } + + private void loadSettings() { + YamlDocument config = getMainConfig(); + + metrics = config.getBoolean("metrics", true); + checkUpdate = config.getBoolean("update-checker", true); + debug = config.getBoolean("debug", false); + + overrideVanillaWaitTime = config.getBoolean("mechanics.fishing-wait-time.override-vanilla", false); + waterMinTime = config.getInt("mechanics.fishing-wait-time.min-wait-time", 100); + waterMaxTime = config.getInt("mechanics.fishing-wait-time.max-wait-time", 600); + + enableLavaFishing = config.getBoolean("mechanics.lava-fishing.enable", false); + lavaMinTime = config.getInt("mechanics.lava-fishing.min-wait-time", 100); + lavaMaxTime = config.getInt("mechanics.lava-fishing.max-wait-time", 600); + + enableVoidFishing = config.getBoolean("mechanics.void-fishing.enable", false); + voidMinTime = config.getInt("mechanics.void-fishing.min-wait-time", 100); + voidMaxTime = config.getInt("mechanics.void-fishing.max-wait-time", 600); + + restrictedSizeRange = config.getBoolean("mechanics.size.restricted-size-range", true); + + placeholderLimit = config.getInt("mechanics.competition.placeholder-limit", 3); + serverGroup = config.getString("mechanics.competition.server-group", "default"); + redisRanking = config.getBoolean("mechanics.competition.redis-ranking", false); + + AdventureHelper.legacySupport = config.getBoolean("other-settings.legacy-color-code-support", true); + dataSaveInterval = config.getInt("other-settings.data-save-interval", 600); + logDataSaving = config.getBoolean("other-settings.log-data-saving", true); + lockData = config.getBoolean("other-settings.lock-data", true); + + durabilityLore = new ArrayList<>(config.getStringList("other-settings.custom-durability-format").stream().map(it -> "" + it).toList()); + + itemDetectOrder = config.getStringList("other-settings.item-detection-order").toArray(new String[0]); + blockDetectOrder = config.getStringList("other-settings.block-detection-order").toArray(new String[0]); + + allowMultipleTotemType = config.getBoolean("mechanics.totem.allow-multiple-type", true); + allowSameTotemType = config.getBoolean("mechanics.totem.allow-same-type", false); + + eventPriority = EventPriority.valueOf(config.getString("other-settings.event-priority", "NORMAL").toUpperCase(Locale.ENGLISH)); + + mechanicRequirements = plugin.getRequirementManager().parseRequirements(config.getSection("mechanics.mechanic-requirements"), true); + skipGameRequirements = plugin.getRequirementManager().parseRequirements(config.getSection("mechanics.skip-game-requirements"), true); + autoFishingRequirements = plugin.getRequirementManager().parseRequirements(config.getSection("mechanics.auto-fishing-requirements"), true); + + enableBag = config.getBoolean("mechanics.fishing-bag.enable", true); + + baitAnimation = config.getBoolean("mechanics.bait-animation", true); + + multipleLootSpawnDelay = config.getInt("mechanics.multiple-loot-spawn-delay", 4); + + Loot.DefaultProperties.DEFAULT_DISABLE_GAME = config.getBoolean("mechanics.global-loot-property.disable-game", false); + Loot.DefaultProperties.DEFAULT_DISABLE_STATS = config.getBoolean("mechanics.global-loot-property.disable-stat", false); + Loot.DefaultProperties.DEFAULT_INSTANT_GAME = config.getBoolean("mechanics.global-loot-property.instant-game", false); + Loot.DefaultProperties.DEFAULT_SHOW_IN_FINDER = config.getBoolean("mechanics.global-loot-property.show-in-fishfinder", true); + + Section placeholderSection = config.getSection("other-settings.placeholder-register"); + if (placeholderSection != null) { + for (Map.Entry entry : placeholderSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof String original) { + plugin.getPlaceholderManager().registerCustomPlaceholder(entry.getKey(), original); + } + } + } + + OffsetUtils.load(config.getSection("other-settings.offset-characters")); + + globalEffects = new ArrayList<>(); + Section globalEffectSection = config.getSection("mechanics.global-effects"); + if (globalEffectSection != null) { + for (Map.Entry entry : globalEffectSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + globalEffects.add(parseEffect(innerSection)); + } + } + } + + EventManager.GLOBAL_ACTIONS.clear(); + EventManager.GLOBAL_TIMES_ACTION.clear(); + Section globalEvents = config.getSection("mechanics.global-events"); + if (globalEvents != null) { + for (Map.Entry entry : globalEvents.getStringRouteMappedValues(false).entrySet()) { + MechanicType type = MechanicType.index().value(entry.getKey()); + if (entry.getValue() instanceof Section inner) { + Map[]> actionMap = new HashMap<>(); + for (Map.Entry innerEntry : inner.getStringRouteMappedValues(false).entrySet()) { + if (innerEntry.getValue() instanceof Section actionSection) { + actionMap.put(ActionTrigger.valueOf(innerEntry.getKey().toUpperCase(Locale.ENGLISH)), plugin.getActionManager().parseActions(actionSection)); + } + } + EventManager.GLOBAL_ACTIONS.put(type, actionMap); + } + } + } + } + + private void loadConfigs() { + Deque fileDeque = new ArrayDeque<>(); + for (ConfigType type : ConfigType.values()) { + File typeFolder = new File(plugin.getDataFolder(), "contents" + File.separator + type.path()); + if (!typeFolder.exists()) { + if (!typeFolder.mkdirs()) return; + plugin.getBoostrap().saveResource("contents" + File.separator + type.path() + File.separator + "default.yml", false); + } + fileDeque.push(typeFolder); + while (!fileDeque.isEmpty()) { + File file = fileDeque.pop(); + File[] files = file.listFiles(); + if (files == null) continue; + for (File subFile : files) { + if (subFile.isDirectory()) { + fileDeque.push(subFile); + } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { + YamlDocument document = plugin.getConfigManager().loadData(subFile); + for (Map.Entry entry : document.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section section) { + type.parse(entry.getKey(), section, formatFunctions); + } + } + } + } + } + } + } + + private Map getEnchantments(Section section) { + Map map = new HashMap<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + int level = Math.min(255, Math.max(1, (int) entry.getValue())); + if (Registry.ENCHANTMENT.get(Objects.requireNonNull(NamespacedKey.fromString(entry.getKey()))) != null) { + map.put(Key.fromString(entry.getKey()), (short) level); + } + } + return map; + } + + private List> getPossibleEnchantments(Section section) { + List> list = new ArrayList<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + Tuple tuple = Tuple.of( + inner.getDouble("chance"), + inner.getString("enchant"), + Short.valueOf(String.valueOf(inner.getInt("level"))) + ); + list.add(tuple); + } + } + return list; + } + + private Pair getEnchantmentPair(String enchantmentWithLevel) { + String[] split = enchantmentWithLevel.split(":", 3); + return Pair.of(Key.of(split[0], split[1]), Short.parseShort(split[2])); + } + + private void registerBuiltInItemProperties() { + Function, Context>> f1 = arg -> { + Section section = (Section) arg; + boolean stored = Objects.equals(section.getNameAsString(), "stored-enchantment-pool"); + Section amountSection = section.getSection("amount"); + Section enchantSection = section.getSection("pool"); + List>> amountList = new ArrayList<>(); + for (Map.Entry entry : amountSection.getStringRouteMappedValues(false).entrySet()) { + amountList.add(Pair.of(Integer.parseInt(entry.getKey()), MathValue.auto(entry.getValue()))); + } + List, MathValue>> enchantPoolPair = new ArrayList<>(); + for (Map.Entry entry : enchantSection.getStringRouteMappedValues(false).entrySet()) { + enchantPoolPair.add(Pair.of(getEnchantmentPair(entry.getKey()), MathValue.auto(entry.getValue()))); + } + if (amountList.isEmpty() || enchantPoolPair.isEmpty()) { + throw new RuntimeException("Both `pool` and `amount` should not be empty"); + } + return (item, context) -> { + List> parsedAmountPair = new ArrayList<>(amountList.size()); + for (Pair> rawValue : amountList) { + parsedAmountPair.add(Pair.of(rawValue.left(), rawValue.right().evaluate(context))); + } + int amount = WeightUtils.getRandom(parsedAmountPair); + if (amount <= 0) return; + HashSet addedEnchantments = new HashSet<>(); + List, Double>> cloned = new ArrayList<>(enchantPoolPair.size()); + for (Pair, MathValue> rawValue : enchantPoolPair) { + cloned.add(Pair.of(rawValue.left(), rawValue.right().evaluate(context))); + } + int i = 0; + outer: + while (i < amount && !cloned.isEmpty()) { + Pair enchantPair = WeightUtils.getRandom(cloned); + Enchantment enchantment = Registry.ENCHANTMENT.get(Objects.requireNonNull(NamespacedKey.fromString(enchantPair.left().toString()))); + if (enchantment == null) { + plugin.getPluginLogger().warn("Enchantment: " + enchantPair.left() + " doesn't exist."); + return; + } + if (!stored) { + for (Enchantment added : addedEnchantments) { + if (enchantment.conflictsWith(added)) { + cloned.removeIf(pair -> pair.left().left().equals(enchantPair.left())); + continue outer; + } + } + } + if (stored) { + item.addStoredEnchantment(enchantPair.left(), enchantPair.right()); + } else { + item.addEnchantment(enchantPair.left(), enchantPair.right()); + } + addedEnchantments.add(enchantment); + cloned.removeIf(pair -> pair.left().left().equals(enchantPair.left())); + i++; + } + }; + }; + this.registerItemParser(f1, 4800, "stored-enchantment-pool"); + this.registerItemParser(f1, 4700, "enchantment-pool"); + Function, Context>> f2 = arg -> { + Section section = (Section) arg; + boolean stored = Objects.equals(section.getNameAsString(), "stored-random-enchantments"); + List> enchantments = getPossibleEnchantments(section); + return (item, context) -> { + HashSet ids = new HashSet<>(); + for (Tuple pair : enchantments) { + if (Math.random() < pair.left() && !ids.contains(pair.mid())) { + if (stored) { + item.addStoredEnchantment(Key.fromString(pair.mid()), pair.right()); + } else { + item.addEnchantment(Key.fromString(pair.mid()), pair.right()); + } + ids.add(pair.mid()); + } + } + }; + }; + this.registerItemParser(f2, 4850, "random-stored-enchantments"); + this.registerItemParser(f2, 4750, "random-enchantments"); + this.registerItemParser(arg -> { + Section section = (Section) arg; + Map map = getEnchantments(section); + return (item, context) -> item.storedEnchantments(map); + }, 4600, "stored-enchantments"); + this.registerItemParser(arg -> { + Section section = (Section) arg; + Map map = getEnchantments(section); + return (item, context) -> item.enchantments(map); + }, 4500, "enchantments"); + this.registerItemParser(arg -> { + String base64 = (String) arg; + return (item, context) -> item.skull(base64); + }, 5200, "head"); + this.registerItemParser(arg -> { + List args = ListUtils.toList(arg); + return (item, context) -> item.itemFlags(args); + }, 5100, "item-flags"); + this.registerItemParser(arg -> { + MathValue mathValue = MathValue.auto(arg); + return (item, context) -> item.customModelData((int) mathValue.evaluate(context)); + }, 5000, "custom-model-data"); + this.registerItemParser(arg -> { + TextValue textValue = TextValue.auto("" + arg); + return (item, context) -> { + item.displayName(AdventureHelper.miniMessageToJson(textValue.render(context))); + }; + }, 4000, "display", "name"); + this.registerItemParser(arg -> { + List list = ListUtils.toList(arg); + List> lore = new ArrayList<>(); + for (String text : list) { + lore.add(TextValue.auto("" + text)); + } + return (item, context) -> { + item.lore(lore.stream() + .map(it -> AdventureHelper.miniMessageToJson(it.render(context))) + .toList()); + }; + }, 3_000, "display", "lore"); + this.registerItemParser(arg -> { + boolean enable = (boolean) arg; + return (item, context) -> { + if (!enable) return; + item.setTag(context.arg(ContextKeys.ID), "CustomFishing", "id"); + }; + }, 2_000, "tag"); + this.registerItemParser(arg -> { + boolean enable = (boolean) arg; + return (item, context) -> { + if (enable) return; + item.setTag(UUID.randomUUID(), "CustomFishing", "uuid"); + }; + }, 2_222, "stackable"); + this.registerItemParser(arg -> { + String sizePair = (String) arg; + String[] split = sizePair.split("~", 2); + MathValue min = MathValue.auto(split[0]); + MathValue max = MathValue.auto(split[1]); + return (item, context) -> { + double minSize = min.evaluate(context); + double maxSize = max.evaluate(context); + float size = (float) RandomUtils.generateRandomDouble(minSize, maxSize); + item.setTag(size, "CustomFishing", "size"); + context.arg(ContextKeys.SIZE, size); + context.arg(ContextKeys.SIZE_FORMATTED, String.format("%.2f", size)); + }; + }, 1_000, "size"); + this.registerItemParser(arg -> { + Section section = (Section) arg; + MathValue base = MathValue.auto(section.get("base", "0")); + MathValue bonus = MathValue.auto(section.get("bonus", "0")); + return (item, context) -> { + double basePrice = base.evaluate(context); + double bonusPrice = bonus.evaluate(context); + float size = Optional.ofNullable(context.arg(ContextKeys.SIZE)).orElse(0f); + double price = basePrice + bonusPrice * size; + item.setTag(price, "Price"); + context.arg(ContextKeys.PRICE, price); + context.arg(ContextKeys.PRICE_FORMATTED, String.format("%.2f", price)); + }; + }, 1_500, "price"); + this.registerItemParser(arg -> { + boolean random = (boolean) arg; + return (item, context) -> { + if (!random) return; + if (item.hasTag("CustomFishing", "max_dur")) { + CustomDurabilityItem durabilityItem = new CustomDurabilityItem(item); + durabilityItem.damage(RandomUtils.generateRandomInt(0, durabilityItem.maxDamage() - 1)); + } else { + item.damage(RandomUtils.generateRandomInt(0, item.maxDamage().get() - 1)); + } + }; + }, 3200, "random-durability"); + this.registerItemParser(arg -> { + MathValue mathValue = MathValue.auto(arg); + return (item, context) -> { + int max = (int) mathValue.evaluate(context); + item.setTag(max, "CustomFishing", "max_dur"); + item.setTag(max, "CustomFishing", "cur_dur"); + CustomDurabilityItem customDurabilityItem = new CustomDurabilityItem(item); + customDurabilityItem.damage(0); + }; + }, 3100, "max-durability"); + this.registerItemParser(arg -> { + Section section = (Section) arg; + ArrayList editors = new ArrayList<>(); + ItemStackUtils.sectionToTagEditor(section, editors); + return (item, context) -> { + for (ItemEditor editor : editors) { + editor.apply(((AbstractItem) item).getRTagItem(), context); + } + }; + }, 10_050, "nbt"); + this.registerItemParser(arg -> { + Section section = (Section) arg; + ArrayList editors = new ArrayList<>(); + ItemStackUtils.sectionToComponentEditor(section, editors); + return (item, context) -> { + for (ItemEditor editor : editors) { + editor.apply(((AbstractItem) item).getRTagItem(), context); + } + }; + }, 10_075, "components"); + } + + private void registerBuiltInEffectModifierParser() { + this.registerEffectModifierParser(object -> { + Section section = (Section) object; + return builder -> builder.requirements(List.of(plugin.getRequirementManager().parseRequirements(section, true))); + }, "requirements"); + this.registerEffectModifierParser(object -> { + Section section = (Section) object; + ArrayList, Integer>> property = new ArrayList<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + property.add(parseEffect(innerSection)); + } + } + return builder -> { + builder.modifiers(property); + }; + }, "effects"); + } + + private TriConsumer, Integer> parseEffect(Section section) { + switch (section.getString("type")) { + case "lava-fishing" -> { + return (((effect, context, phase) -> { + if (phase == 0) effect.properties().put(EffectProperties.LAVA_FISHING, true); + })); + } + case "void-fishing" -> { + return (((effect, context, phase) -> { + if (phase == 0) effect.properties().put(EffectProperties.VOID_FISHING, true); + })); + } + case "weight-mod" -> { + var op = parseWeightOperation(section.getStringList("value")); + return (((effect, context, phase) -> { + if (phase == 1) effect.weightOperations(op); + })); + } + case "weight-mod-ignore-conditions" -> { + var op = parseWeightOperation(section.getStringList("value")); + return (((effect, context, phase) -> { + if (phase == 1) effect.weightOperationsIgnored(op); + })); + } + case "group-mod" -> { + var op = parseGroupWeightOperation(section.getStringList("value")); + return (((effect, context, phase) -> { + if (phase == 1) effect.weightOperations(op); + })); + } + case "group-mod-ignore-conditions" -> { + var op = parseGroupWeightOperation(section.getStringList("value")); + return (((effect, context, phase) -> { + if (phase == 1) effect.weightOperationsIgnored(op); + })); + } + case "wait-time" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.waitTimeAdder(effect.waitTimeAdder() + value.evaluate(context)); + })); + } + case "hook-time", "wait-time-multiplier" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.waitTimeMultiplier(effect.waitTimeMultiplier() - 1 + value.evaluate(context)); + })); + } + case "difficulty" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.difficultyAdder(effect.difficultyAdder() + value.evaluate(context)); + })); + } + case "difficulty-multiplier", "difficulty-bonus" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.difficultyMultiplier(effect.difficultyMultiplier() - 1 + value.evaluate(context)); + })); + } + case "size" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.sizeAdder(effect.sizeAdder() + value.evaluate(context)); + })); + } + case "size-multiplier", "size-bonus" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.sizeMultiplier(effect.sizeMultiplier() - 1 + value.evaluate(context)); + })); + } + case "game-time" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.gameTimeAdder(effect.gameTimeAdder() + value.evaluate(context)); + })); + } + case "game-time-multiplier", "game-time-bonus" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.gameTimeMultiplier(effect.gameTimeMultiplier() - 1 + value.evaluate(context)); + })); + } + case "score" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.scoreAdder(effect.scoreAdder() + value.evaluate(context)); + })); + } + case "score-multiplier", "score-bonus" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.scoreMultiplier(effect.scoreMultiplier() - 1 + value.evaluate(context)); + })); + } + case "multiple-loot" -> { + MathValue value = MathValue.auto(section.get("value")); + return (((effect, context, phase) -> { + if (phase == 2) effect.multipleLootChance(effect.multipleLootChance() + value.evaluate(context)); + })); + } + case "conditional" -> { + Requirement[] requirements = plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), true); + Section effectSection = section.getSection("effects"); + ArrayList, Integer>> effects = new ArrayList<>(); + if (effectSection != null) + for (Map.Entry entry : effectSection.getStringRouteMappedValues(false).entrySet()) + if (entry.getValue() instanceof Section inner) + effects.add(parseEffect(inner)); + return (((effect, context, phase) -> { + if (!RequirementManager.isSatisfied(context, requirements)) return; + for (TriConsumer, Integer> consumer : effects) { + consumer.accept(effect, context, phase); + } + })); + } + default -> { + return (((effect, context, phase) -> {})); + } + } + } + + private BiFunction, Double, Double> parseWeightOperation(String op) { + switch (op.charAt(0)) { + case '/' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> weight / arg.evaluate(context); + } + case '*' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> weight * arg.evaluate(context); + } + case '-' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> weight - arg.evaluate(context); + } + case '%' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> weight % arg.evaluate(context); + } + case '+' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> weight + arg.evaluate(context); + } + case '=' -> { + MathValue arg = MathValue.auto(op.substring(1)); + return (context, weight) -> { + context.arg(ContextKeys.WEIGHT, weight); + return arg.evaluate(context); + }; + } + default -> throw new IllegalArgumentException("Invalid weight operation: " + op); + } + } + + @Override + public List, Double, Double>>> parseWeightOperation(List ops) { + List, Double, Double>>> result = new ArrayList<>(); + for (String op : ops) { + String[] split = op.split(":", 2); + result.add(Pair.of(split[0], parseWeightOperation(split[1]))); + } + return result; + } + + @Override + public List, Double, Double>>> parseGroupWeightOperation(List gops) { + List, Double, Double>>> result = new ArrayList<>(); + for (String gop : gops) { + String[] split = gop.split(":", 2); + BiFunction, Double, Double> operation = parseWeightOperation(split[1]); + for (String member : plugin.getLootManager().getGroupMembers(split[0])) { + result.add(Pair.of(member, operation)); + } + } + return result; + } + + private void registerBuiltInHookParser() { + this.registerHookParser(object -> { + List lore = ListUtils.toList(object); + return builder -> builder.lore(lore.stream().map(it -> "" + it).toList()); + }, "lore-on-rod"); + } + + private void registerBuiltInTotemParser() { + this.registerTotemParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.radius(mathValue); + }, "radius"); + this.registerTotemParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.duration(mathValue); + }, "duration"); + this.registerTotemParser(object -> { + Section section = (Section) object; + TotemParticle[] particles = getParticleSettings(section); + return builder -> builder.particleSettings(particles); + }, "particles"); + this.registerTotemParser(object -> { + Section section = (Section) object; + TotemModel[] models = getTotemModels(section); + return builder -> builder.totemModels(models); + }, "pattern"); + } + + private void registerBuiltInBaseEffectParser() { + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.difficultyAdder(mathValue); + }, "base-effects", "difficulty-adder"); + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.difficultyMultiplier(mathValue); + }, "base-effects", "difficulty-multiplier"); + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.gameTimeAdder(mathValue); + }, "base-effects", "game-time-adder"); + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.gameTimeMultiplier(mathValue); + }, "base-effects", "game-time-multiplier"); + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.waitTimeAdder(mathValue); + }, "base-effects", "wait-time-adder"); + this.registerBaseEffectParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.waitTimeMultiplier(mathValue); + }, "base-effects", "wait-time-multiplier"); + } + + private void registerBuiltInEntityParser() { + this.registerEntityParser(object -> { + String entity = (String) object; + return builder -> builder.entityID(entity); + }, "entity"); + this.registerEntityParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.horizontalVector(mathValue); + }, "velocity", "horizontal"); + this.registerEntityParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.verticalVector(mathValue); + }, "velocity", "vertical"); + this.registerEntityParser(object -> { + Section section = (Section) object; + return builder -> builder.propertyMap(section.getStringRouteMappedValues(false)); + }, "properties"); + } + + private void registerBuiltInEventParser() { + this.registerEventParser(object -> { + boolean disable = (boolean) object; + return builder -> builder.disableGlobalActions(disable); + }, "disable-global-event"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.LURE, actions); + }, "events", "lure"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.ESCAPE, actions); + }, "events", "escape"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.SUCCESS, actions); + }, "events", "success"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.ACTIVATE, actions); + }, "events", "activate"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.FAILURE, actions); + }, "events", "failure"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.HOOK, actions); + }, "events", "hook"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.CONSUME, actions); + }, "events", "consume"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.CAST, actions); + }, "events", "cast"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.BITE, actions); + }, "events", "bite"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.LAND, actions); + }, "events", "land"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.TIMER, actions); + }, "events", "timer"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.INTERACT, actions); + }, "events", "interact"); + this.registerEventParser(object -> { + Section section = (Section) object; + Action[] actions = plugin.getActionManager().parseActions(section); + return builder -> builder.action(ActionTrigger.NEW_SIZE_RECORD, actions); + }, "events", "new_size_record"); + } + + private void registerBuiltInLootParser() { + this.registerLootParser(object -> { + boolean value = (boolean) object; + return builder -> builder.preventGrabbing(value); + }, "prevent-grabbing"); + this.registerLootParser(object -> { + String string = (String) object; + return builder -> builder.nick(string); + }, "nick"); + this.registerLootParser(object -> { + boolean value = (boolean) object; + return builder -> builder.showInFinder(value); + }, "show-in-fishfinder"); + this.registerLootParser(object -> { + boolean value = (boolean) object; + return builder -> builder.disableStatistics(value); + }, "disable-stat"); + this.registerLootParser(object -> { + boolean value = (boolean) object; + return builder -> builder.disableGame(value); + }, "disable-game"); + this.registerLootParser(object -> { + boolean value = (boolean) object; + return builder -> builder.instantGame(value); + }, "instant-game"); + this.registerLootParser(object -> { + MathValue mathValue = MathValue.auto(object); + return builder -> builder.score(mathValue); + }, "score"); + this.registerLootParser(object -> { + List args = ListUtils.toList(object); + return builder -> builder.groups(args.toArray(new String[0])); + }, "group"); + this.registerLootParser(object -> { + Section section = (Section) object; + StatisticsKeys keys = new StatisticsKeys( + section.getString("amount"), + section.getString("size") + ); + return builder -> builder.statisticsKeys(keys); + }, "statistics"); + } + + @Override + public void saveResource(String filePath) { + if (!new File(plugin.getDataFolder(), filePath).exists()) { + plugin.getBoostrap().saveResource(filePath, false); + } + } + + private ParticleSetting[] getParticleSettings(Section section) { + List particleSettings = new ArrayList<>(); + if (section != null) + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + particleSettings.add(getParticleSetting(innerSection)); + } + } + return particleSettings.toArray(new ParticleSetting[0]); + } + + private ParticleSetting getParticleSetting(Section section) { + Particle particle = Particle.valueOf(section.getString("type","REDSTONE")); + String formulaHorizontal = section.getString("polar-coordinates-formula.horizontal"); + String formulaVertical = section.getString("polar-coordinates-formula.vertical"); + List> ranges = section.getStringList("theta.range") + .stream().map(it -> { + String[] split = it.split("~"); + return Pair.of(Double.parseDouble(split[0]) * Math.PI / 180, Double.parseDouble(split[1]) * Math.PI / 180); + }).toList(); + + double interval = section.getDouble("theta.draw-interval", 3d); + int delay = section.getInt("task.delay", 0); + int period = section.getInt("task.period", 0); + if (particle == Particle.REDSTONE) { + String color = section.getString("options.color","0,0,0"); + String[] colorSplit = color.split(","); + return new DustParticleSetting( + formulaHorizontal, + formulaVertical, + particle, + interval, + ranges, + delay, + period, + new Particle.DustOptions( + Color.fromRGB( + Integer.parseInt(colorSplit[0]), + Integer.parseInt(colorSplit[1]), + Integer.parseInt(colorSplit[2]) + ), + section.getDouble("options.scale", 1.0).floatValue() + ) + ); + } else if (particle == Particle.DUST_COLOR_TRANSITION) { + String color = section.getString("options.from","0,0,0"); + String[] colorSplit = color.split(","); + String toColor = section.getString("options.to","255,255,255"); + String[] toColorSplit = toColor.split(","); + return new DustParticleSetting( + formulaHorizontal, + formulaVertical, + particle, + interval, + ranges, + delay, + period, + new Particle.DustTransition( + Color.fromRGB( + Integer.parseInt(colorSplit[0]), + Integer.parseInt(colorSplit[1]), + Integer.parseInt(colorSplit[2]) + ), + Color.fromRGB( + Integer.parseInt(toColorSplit[0]), + Integer.parseInt(toColorSplit[1]), + Integer.parseInt(toColorSplit[2]) + ), + section.getDouble("options.scale", 1.0).floatValue() + ) + ); + } else { + return new ParticleSetting( + formulaHorizontal, + formulaVertical, + particle, + interval, + ranges, + delay, + period + ); + } + } + + private TotemModel[] getTotemModels(Section section) { + TotemModel originalModel = parseModel(section); + List modelList = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + originalModel = originalModel.deepClone().rotate90(); + modelList.add(originalModel); + if (i % 2 == 0) { + modelList.add(originalModel.mirrorVertically()); + } else { + modelList.add(originalModel.mirrorHorizontally()); + } + } + return modelList.toArray(new TotemModel[0]); + } + + @SuppressWarnings("unchecked") + private TotemModel parseModel(Section section) { + Section layerSection = section.getSection("layer"); + List totemBlocksList = new ArrayList<>(); + if (layerSection != null) { + var set = layerSection.getStringRouteMappedValues(false).entrySet(); + TotemBlock[][][][] totemBlocks = new TotemBlock[set.size()][][][]; + for (Map.Entry entry : set) { + if (entry.getValue() instanceof List list) { + totemBlocks[Integer.parseInt(entry.getKey())-1] = parseLayer((List) list); + } + } + totemBlocksList.addAll(List.of(totemBlocks)); + } + + String[] core = section.getString("core","1,1,1").split(","); + int x = Integer.parseInt(core[2]) - 1; + int z = Integer.parseInt(core[1]) - 1; + int y = Integer.parseInt(core[0]) - 1; + return new TotemModel( + x,y,z, + totemBlocksList.toArray(new TotemBlock[0][][][]) + ); + } + + private TotemBlock[][][] parseLayer(List lines) { + List totemBlocksList = new ArrayList<>(); + for (String line : lines) { + totemBlocksList.add(parseSingleLine(line)); + } + return totemBlocksList.toArray(new TotemBlock[0][][]); + } + + private TotemBlock[][] parseSingleLine(String line) { + List totemBlocksList = new ArrayList<>(); + String[] splits = line.split("\\s+"); + for (String split : splits) { + totemBlocksList.add(parseSingleElement(split)); + } + return totemBlocksList.toArray(new TotemBlock[0][]); + } + + private TotemBlock[] parseSingleElement(String element) { + String[] orBlocks = element.split("\\|\\|"); + List totemBlockList = new ArrayList<>(); + for (String block : orBlocks) { + int index = block.indexOf("{"); + List propertyList = new ArrayList<>(); + if (index == -1) { + index = block.length(); + } else { + String propertyStr = block.substring(index+1, block.length()-1); + String[] properties = propertyStr.split(";"); + for (String property : properties) { + String[] split = property.split("="); + if (split.length < 2) continue; + String key = split[0]; + String value = split[1]; + switch (key) { + // Block face + case "face" -> { + BlockFace blockFace = BlockFace.valueOf(value.toUpperCase(Locale.ENGLISH)); + propertyList.add(new FaceImpl(blockFace)); + } + // Block axis + case "axis" -> { + Axis axis = Axis.valueOf(value.toUpperCase(Locale.ENGLISH)); + propertyList.add(new AxisImpl(axis)); + } + // Slab, Stair half + case "half" -> { + Bisected.Half half = Bisected.Half.valueOf(value.toUpperCase(Locale.ENGLISH)); + propertyList.add(new HalfImpl(half)); + } + } + } + } + String type = block.substring(0, index); + TotemBlock totemBlock = new TotemBlock( + TypeCondition.getTypeCondition(type), + propertyList.toArray(new TotemBlockProperty[0]) + ); + totemBlockList.add(totemBlock); + } + return totemBlockList.toArray(new TotemBlock[0]); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/effect/BukkitEffectManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/effect/BukkitEffectManager.java new file mode 100644 index 00000000..27e29a71 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/effect/BukkitEffectManager.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.effect; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.effect.EffectManager; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; + +import java.util.HashMap; +import java.util.Optional; + +public class BukkitEffectManager implements EffectManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap effectModifiers = new HashMap<>(); + + public BukkitEffectManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void unload() { + this.effectModifiers.clear(); + } + + @Override + public void load() { + plugin.debug("Loaded " + effectModifiers.size() + " effects"); + } + + @Override + public boolean registerEffectModifier(EffectModifier effect, MechanicType type) { + if (effectModifiers.containsKey(effect.id())) return false; + this.effectModifiers.put(type.getType() + ":" + effect.id(), effect); + return true; + } + + @Override + public Optional getEffectModifier(String id, MechanicType type) { + return Optional.ofNullable(this.effectModifiers.get(type.getType() + ":" + id)); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/entity/BukkitEntityManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/entity/BukkitEntityManager.java new file mode 100644 index 00000000..e1478bb4 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/entity/BukkitEntityManager.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.entity; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.EntityProvider; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.entity.EntityConfig; +import net.momirealms.customfishing.api.mechanic.entity.EntityManager; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class BukkitEntityManager implements EntityManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap entityProviders = new HashMap<>(); + private final HashMap entities = new HashMap<>(); + + public BukkitEntityManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.registerEntityProvider(new EntityProvider() { + @Override + public String identifier() { + return "vanilla"; + } + @NotNull + @Override + public Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map propertyMap) { + return location.getWorld().spawnEntity(location, EntityType.valueOf(id.toUpperCase(Locale.ENGLISH))); + } + }); + } + + @Override + public void load() { + for (EntityProvider provider : entityProviders.values()) { + plugin.debug("Registered EntityProvider: " + provider.identifier()); + } + plugin.debug("Loaded " + entities.size() + " entities"); + } + + @Override + public void unload() { + this.entities.clear(); + } + + @Override + public void disable() { + unload(); + this.entityProviders.clear(); + } + + @Override + public Optional getEntity(String id) { + return Optional.ofNullable(this.entities.get(id)); + } + + @Override + public boolean registerEntity(EntityConfig entity) { + if (entities.containsKey(entity.id())) return false; + this.entities.put(entity.id(), entity); + return true; + } + + public boolean registerEntityProvider(EntityProvider entityProvider) { + if (entityProviders.containsKey(entityProvider.identifier())) return false; + else entityProviders.put(entityProvider.identifier(), entityProvider); + return true; + } + + public boolean unregisterEntityProvider(String id) { + return entityProviders.remove(id) != null; + } + + @NotNull + @Override + public Entity summonEntityLoot(Context context) { + String id = context.arg(ContextKeys.ID); + EntityConfig config = requireNonNull(entities.get(id), "Entity " + id + " not found"); + Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)); + Location playerLocation = requireNonNull(context.getHolder().getLocation()); + String entityID = config.entityID(); + Entity entity; + if (entityID.contains(":")) { + String[] split = entityID.split(":", 2); + EntityProvider provider = requireNonNull(entityProviders.get(split[0]), "EntityProvider " + split[0] + " doesn't exist"); + entity = requireNonNull(provider.spawn(hookLocation, split[1], config.propertyMap()), "Entity " + entityID + " doesn't exist"); + } else { + entity = entityProviders.get("vanilla").spawn(hookLocation, entityID, config.propertyMap()); + } + double d0 = playerLocation.getX() - hookLocation.getX(); + double d1 = playerLocation.getY() - hookLocation.getY(); + double d2 = playerLocation.getZ() - hookLocation.getZ(); + double d3 = config.horizontalVector().evaluate(context); + double d4 = config.verticalVector().evaluate(context); + Vector vector = new Vector(d0 * 0.1D * d3, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D * d4, d2 * 0.1D * d3); + entity.setVelocity(vector); + return entity; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/event/BukkitEventManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/event/BukkitEventManager.java new file mode 100644 index 00000000..ea20a7c0 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/event/BukkitEventManager.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.event; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.event.EventCarrier; +import net.momirealms.customfishing.api.mechanic.event.EventManager; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Optional; + +public class BukkitEventManager implements EventManager, Listener { + + private final HashMap carriers = new HashMap<>(); + private final BukkitCustomFishingPlugin plugin; + + public BukkitEventManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void unload() { + this.carriers.clear(); + HandlerList.unregisterAll(this); + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, this.plugin.getBoostrap()); + } + + @Override + public Optional getEventCarrier(String id, MechanicType type) { + return Optional.ofNullable(this.carriers.get(type.getType() + ":" + id)); + } + + @Override + public boolean registerEventCarrier(EventCarrier carrier) { + if (this.carriers.containsKey(carrier.id())) return false; + this.carriers.put(carrier.type().getType() + ":" + carrier.id(), carrier); + return true; + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND) + return; + if (event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_AIR && event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) + return; + ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand(); + if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + String id = this.plugin.getItemManager().getItemID(itemStack); + Context context = Context.player(event.getPlayer()); + Block clicked = event.getClickedBlock(); + context.arg(ContextKeys.OTHER_LOCATION, clicked == null ? event.getPlayer().getLocation() : clicked.getLocation()); + trigger(context, id, MechanicType.UTIL, ActionTrigger.INTERACT); + } + + @EventHandler (ignoreCancelled = true) + public void onConsumeItem(PlayerItemConsumeEvent event) { + Context context = Context.player(event.getPlayer()); + trigger(context, plugin.getItemManager().getItemID(event.getItem()), MechanicType.LOOT, ActionTrigger.CONSUME); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/fishing/BukkitFishingManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/fishing/BukkitFishingManager.java new file mode 100644 index 00000000..e1728658 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/fishing/BukkitFishingManager.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.fishing; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingHookStateEvent; +import net.momirealms.customfishing.api.event.RodCastEvent; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook; +import net.momirealms.customfishing.api.mechanic.fishing.FishingGears; +import net.momirealms.customfishing.api.mechanic.fishing.FishingManager; +import net.momirealms.customfishing.api.mechanic.fishing.hook.VanillaMechanic; +import net.momirealms.customfishing.api.mechanic.game.AbstractGamingPlayer; +import net.momirealms.customfishing.api.mechanic.game.GamingPlayer; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.util.EventUtils; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.*; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class BukkitFishingManager implements FishingManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final ConcurrentHashMap castHooks = new ConcurrentHashMap<>(); + + public BukkitFishingManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + } + + @Override + public Optional getFishHook(Player player) { + return getFishHook(player.getUniqueId()); + } + + @Override + public Optional getFishHook(UUID player) { + return Optional.ofNullable(castHooks.get(player)); + } + + @Override + public Optional getOwner(FishHook hook) { + if (hook.getOwnerUniqueId() != null) { + return Optional.ofNullable(Bukkit.getPlayer(hook.getOwnerUniqueId())); + } + return Optional.empty(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onFishMONITOR(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.MONITOR) return; + this.selectState(event); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onFishHIGHEST(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.HIGHEST) return; + this.selectState(event); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onFishHIGH(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.HIGH) return; + this.selectState(event); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onFishNORMAL(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.NORMAL) return; + this.selectState(event); + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onFishLOW(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.LOW) return; + this.selectState(event); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onFishLOWEST(PlayerFishEvent event) { + if (ConfigManager.eventPriority() != EventPriority.LOWEST) return; + this.selectState(event); + } + + @EventHandler(ignoreCancelled = true) + public void onItemHeldChange(PlayerItemHeldEvent event) { + if (getFishHook(event.getPlayer()).isPresent()) { + this.destroy(event.getPlayer().getUniqueId()); + } + } + + @EventHandler(ignoreCancelled = true) + public void onSwapItem(PlayerSwapHandItemsEvent event) { + getFishHook(event.getPlayer()).ifPresent(hook -> { + Optional optionalGamingPlayer = hook.getGamingPlayer(); + if (optionalGamingPlayer.isPresent()) { + optionalGamingPlayer.get().handleSwapHand(); + event.setCancelled(true); + } else { + this.destroy(event.getPlayer().getUniqueId()); + } + }); + } + + @EventHandler(ignoreCancelled = true) + public void onJump(PlayerMoveEvent event) { + final Player player = event.getPlayer(); + if (event.getFrom().getY() < event.getTo().getY() && player.isOnGround()) { + getFishHook(player).flatMap(CustomFishingHook::getGamingPlayer).ifPresent(gamingPlayer -> { + if (gamingPlayer.handleJump()) { + event.setCancelled(true); + } + }); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.destroy(event.getPlayer().getUniqueId()); + } + + @EventHandler (ignoreCancelled = false) + public void onLeftClick(PlayerInteractEvent event) { + if (event.getAction() != Action.LEFT_CLICK_AIR) + return; + if (event.getMaterial() != Material.FISHING_ROD) + return; + if (event.getHand() != EquipmentSlot.HAND) + return; + getFishHook(event.getPlayer()).ifPresent(hook -> { + Optional optionalGamingPlayer = hook.getGamingPlayer(); + if (optionalGamingPlayer.isPresent()) { + if (((AbstractGamingPlayer) optionalGamingPlayer.get()).internalLeftClick()) { + event.setCancelled(true); + } + } + }); + } + + @EventHandler (ignoreCancelled = true) + public void onSneak(PlayerToggleSneakEvent event) { + if (!event.isSneaking()) return; + getFishHook(event.getPlayer()).ifPresent(hook -> { + Optional optionalGamingPlayer = hook.getGamingPlayer(); + if (optionalGamingPlayer.isPresent()) { + if (optionalGamingPlayer.get().handleSneak()) { + event.setCancelled(true); + } + } + }); + } + + @EventHandler + public void onChat(AsyncPlayerChatEvent event) { + if (event.isCancelled()) return; + getFishHook(event.getPlayer()).ifPresent(hook -> { + Optional optionalGamingPlayer = hook.getGamingPlayer(); + if (optionalGamingPlayer.isPresent()) { + if (optionalGamingPlayer.get().handleChat(event.getMessage())) { + event.setCancelled(true); + } + } + }); + } + + private void selectState(PlayerFishEvent event) { + switch (event.getState()) { + case FISHING -> onCastRod(event); + case REEL_IN, CAUGHT_FISH -> onReelIn(event); + case CAUGHT_ENTITY -> onCaughtEntity(event); + //case CAUGHT_FISH -> onCaughtFish(event); + case BITE -> onBite(event); + case IN_GROUND -> onInGround(event); + case FAILED_ATTEMPT -> onFailedAttempt(event); + // case LURED 1.20.5+ + } + } + + // for vanilla mechanics + private void onFailedAttempt(PlayerFishEvent event) { + Player player = event.getPlayer(); + getFishHook(player).ifPresent(hook -> { + if (hook.getCurrentHookMechanic() instanceof VanillaMechanic vanillaMechanic) { + vanillaMechanic.onFailedAttempt(); + } + }); + } + + // for vanilla mechanics + private void onBite(PlayerFishEvent event) { + Player player = event.getPlayer(); + getFishHook(player).ifPresent(hook -> { + if (hook.getCurrentHookMechanic() instanceof VanillaMechanic vanillaMechanic) { + vanillaMechanic.onBite(); + } + }); + } + + private void onCaughtEntity(PlayerFishEvent event) { + final Player player = event.getPlayer(); + Optional hook = getFishHook(player); + if (hook.isPresent()) { + Entity entity = event.getCaught(); + if (entity != null && entity.getPersistentDataContainer().get( + Objects.requireNonNull(NamespacedKey.fromString("temp-entity", plugin.getBoostrap())), + PersistentDataType.STRING + ) != null) { + event.setCancelled(true); + hook.get().onReelIn(); + return; + } + } + + if (player.getGameMode() != GameMode.CREATIVE) { + ItemStack itemStack = player.getInventory().getItemInMainHand(); + if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand(); + if (plugin.getItemManager().hasCustomDurability(itemStack)) { + event.getHook().pullHookedEntity(); + event.getHook().remove(); + event.setCancelled(true); + plugin.getItemManager().decreaseDurability(player, itemStack, event.getCaught() instanceof Item ? 3 : 5, true); + } + } + } + + private void onReelIn(PlayerFishEvent event) { + Player player = event.getPlayer(); + getFishHook(player).ifPresent(hook -> { + event.setCancelled(true); + Optional gamingPlayer = hook.getGamingPlayer(); + if (gamingPlayer.isPresent()) { + ((AbstractGamingPlayer) gamingPlayer.get()).internalRightClick(); + return; + } + hook.onReelIn(); + }); + } + +// private void onCaughtFish(PlayerFishEvent event) { +// Player player = event.getPlayer(); +// getFishHook(player).ifPresent(hook -> { +// Optional gamingPlayer = hook.getGamingPlayer(); +// if (gamingPlayer.isPresent()) { +// if (gamingPlayer.get().handleRightClick()) { +// event.setCancelled(true); +// } +// return; +// } +// event.setCancelled(true); +// hook.onReelIn(); +// }); +// } + + private void onCastRod(PlayerFishEvent event) { + FishHook hook = event.getHook(); + Player player = event.getPlayer(); + Context context = Context.player(player); + FishingGears gears = new FishingGears(context); + if (!RequirementManager.isSatisfied(context, ConfigManager.mechanicRequirements())) { + this.destroy(player.getUniqueId()); + return; + } + if (!gears.canFish()) { + event.setCancelled(true); + return; + } + if (EventUtils.fireAndCheckCancel(new RodCastEvent(event, gears))) { + return; + } + plugin.debug(context); + CustomFishingHook customHook = new CustomFishingHook(plugin, hook, gears, context); + this.castHooks.put(player.getUniqueId(), customHook); + } + + private void onInGround(PlayerFishEvent event) { + final Player player = event.getPlayer(); + if (player.getGameMode() != GameMode.CREATIVE) { + ItemStack itemStack = player.getInventory().getItemInMainHand(); + if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand(); + if (itemStack.getType() == Material.FISHING_ROD) { + if (plugin.getItemManager().hasCustomDurability(itemStack)) { + event.setCancelled(true); + event.getHook().remove(); + plugin.getItemManager().decreaseDurability(player, itemStack, 2, true); + } + } + } + } + + @EventHandler + public void onHookStateChange(FishingHookStateEvent event) { + Player player = event.getPlayer(); + getFishHook(player).ifPresent(hook -> { + switch (event.getState()) { + case BITE -> hook.onBite(); + case LAND -> hook.onLand(); + case ESCAPE -> hook.onEscape(); + case LURE -> hook.onLure(); + } + }); + } + + @Override + public void destroy(UUID uuid) { + this.getFishHook(uuid).ifPresent(hook -> { + hook.destroy(); + this.castHooks.remove(uuid); + }); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/game/BukkitGameManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/game/BukkitGameManager.java new file mode 100644 index 00000000..16774b1a --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/game/BukkitGameManager.java @@ -0,0 +1,1057 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.game; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook; +import net.momirealms.customfishing.api.mechanic.game.*; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.api.mechanic.requirement.ConditionalElement; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.util.OffsetUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.util.*; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; + +@SuppressWarnings("DuplicatedCode") +public class BukkitGameManager implements GameManager { + + private final BukkitCustomFishingPlugin plugin; + private final Map gameFactoryMap = new HashMap<>(); + private final Map gameMap = new HashMap<>(); + private final LinkedHashMap, Double, Double>>>, Player>> gameConditions = new LinkedHashMap<>(); + private static final String EXPANSION_FOLDER = "expansions/minigame"; + + public BukkitGameManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.registerHoldGame(); + this.registerHoldV2Game(); + this.registerClickGame(); + this.registerTensionGame(); + this.registerDanceGame(); + this.registerAccurateClickGame(); + this.registerAccurateClickV2Game(); + this.registerAccurateClickV3Game(); + } + + @Override + public void load() { + this.loadExpansions(); + File file = new File(plugin.getDataFolder(), "game-conditions.yml"); + if (!file.exists()) { + plugin.getBoostrap().saveResource("game-conditions.yml", false); + } + YamlDocument lootConditionsConfig = plugin.getConfigManager().loadData(file); + for (Map.Entry entry : lootConditionsConfig.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section section) { + gameConditions.put(entry.getKey(), parseGameConditions(section)); + } + } + } + + @Override + public void unload() { + this.gameMap.clear(); + } + + private ConditionalElement, Double, Double>>>, Player> parseGameConditions(Section section) { + Section subSection = section.getSection("sub-groups"); + if (subSection == null) { + return new ConditionalElement<>( + plugin.getConfigManager().parseWeightOperation(section.getStringList("list")), + Map.of(), + plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false) + ); + } else { + HashMap, Double, Double>>>, Player>> subElements = new HashMap<>(); + for (Map.Entry entry : subSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + subElements.put(entry.getKey(), parseGameConditions(innerSection)); + } + } + return new ConditionalElement<>( + plugin.getConfigManager().parseWeightOperation(section.getStringList("list")), + subElements, + plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false) + ); + } + } + + @Override + public boolean registerGameType(String type, GameFactory gameFactory) { + if (gameFactoryMap.containsKey(type)) return false; + gameFactoryMap.put(type, gameFactory); + return true; + } + + @Override + public boolean unregisterGameType(String type) { + return gameFactoryMap.remove(type) != null; + } + + @Nullable + @Override + public GameFactory getGameFactory(String type) { + return gameFactoryMap.get(type); + } + + @Override + public Optional getGame(String id) { + return Optional.ofNullable(gameMap.get(id)); + } + + @Override + public boolean registerGame(Game game) { + if (gameMap.containsKey(game.id())) return false; + gameMap.put(game.id(), game); + return true; + } + + @Nullable + @Override + public Game getNextGame(Effect effect, Context context) { + HashMap lootWeightMap = new HashMap<>(); + for (ConditionalElement, Double, Double>>>, Player> conditionalElement : gameConditions.values()) { + modifyWeightMap(lootWeightMap, context, conditionalElement); + } + String gameID = WeightUtils.getRandom(lootWeightMap); + return Optional.ofNullable(gameID) + .map(id -> getGame(gameID).orElseThrow(() -> new RuntimeException("Could not find game " + gameID))) + .orElse(null); + } + + private void modifyWeightMap(Map weightMap, Context context, ConditionalElement, Double, Double>>>, Player> conditionalElement) { + if (conditionalElement == null) return; + if (RequirementManager.isSatisfied(context, conditionalElement.getRequirements())) { + for (Pair, Double, Double>> modifierPair : conditionalElement.getElement()) { + double previous = weightMap.getOrDefault(modifierPair.left(), 0d); + weightMap.put(modifierPair.left(), modifierPair.right().apply(context, previous)); + } + for (ConditionalElement, Double, Double>>>, Player> sub : conditionalElement.getSubElements().values()) { + modifyWeightMap(weightMap, context, sub); + } + } + } + + private GameBasics getGameBasics(Section section) { + GameBasics.Builder builder = GameBasics.builder(); + Object difficulty = section.get("difficulty", "20~80"); + if (difficulty instanceof String str) { + String[] split = str.split("~"); + builder.difficulty(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + } else if (difficulty instanceof Integer integer) { + builder.difficulty(integer); + } + Object time = section.get("time", 15); + if (time instanceof String str) { + String[] split = str.split("~"); + builder.time(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + } else if (time instanceof Integer integer) { + builder.time(integer); + } + return builder.build(); + } + + private void registerHoldGame() { + this.registerGameType("hold", (id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final int[] timeRequirements = section.getIntList("hold-time-requirements").stream().mapToInt(Integer::intValue).toArray(); + private final String judgementAreaImage = section.getString("subtitle.judgment-area"); + private final String pointerImage = section.getString("subtitle.pointer"); + private final int barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); + private final int judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); + private final int judgementAreaWidth = section.getInt("arguments.judgment-area-width"); + private final int pointerIconWidth = section.getInt("arguments.pointer-icon-width"); + private final double punishment = section.getDouble("arguments.punishment"); + private final String[] progress = section.getStringList("progress").toArray(new String[0]); + private final double waterResistance = section.getDouble("arguments.water-resistance", 0.15); + private final double pullingStrength = section.getDouble("arguments.pulling-strength", 0.45); + private final double looseningLoss = section.getDouble("arguments.loosening-strength-loss", 0.3); + private final TextValue title = TextValue.auto(section.getString("title","{progress}")); + private final String font = section.getString("subtitle.font"); + private final String barImage = section.getString("subtitle.bar"); + private final String tip = section.getString("tip"); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private double hold_time; + private double judgement_position; + private double fish_position; + private double judgement_velocity; + private double fish_velocity; + private int timer; + private final int time_requirement = timeRequirements[ThreadLocalRandom.current().nextInt(timeRequirements.length)] * 1000; + private boolean played; + + @Override + public void arrangeTask() { + this.judgement_position = (double) (barEffectiveWidth - judgementAreaWidth) / 2; + this.task = plugin.getScheduler().asyncRepeating(this, 50, 33, TimeUnit.MILLISECONDS); + } + + @Override + public void tick() { + if (getPlayer().isSneaking()) addV(); + else reduceV(); + if (timer < 40 - (settings.difficulty() / 10)) { + timer++; + } else { + timer = 0; + if (Math.random() > ((double) 25 / (settings.difficulty() + 100))) { + burst(); + } + } + judgement_position += judgement_velocity; + fish_position += fish_velocity; + fraction(); + calibrate(); + if (fish_position >= judgement_position && fish_position + pointerIconWidth <= judgement_position + judgementAreaWidth) { + hold_time += 33; + } else { + hold_time -= punishment * 33; + } + if (hold_time >= time_requirement) { + setGameResult(true); + endGame(); + return; + } + hold_time = Math.max(0, Math.min(hold_time, time_requirement)); + showUI(); + } + + private void burst() { + if (Math.random() < (judgement_position / barEffectiveWidth)) { + judgement_velocity = -1 - 0.8 * Math.random() * ((double) settings.difficulty() / 15); + } else { + judgement_velocity = 1 + 0.8 * Math.random() * ((double) settings.difficulty() / 15); + } + } + + private void fraction() { + if (judgement_velocity > 0) { + judgement_velocity -= waterResistance; + if (judgement_velocity < 0) judgement_velocity = 0; + } else { + judgement_velocity += waterResistance; + if (judgement_velocity > 0) judgement_velocity = 0; + } + } + + private void reduceV() { + fish_velocity -= looseningLoss; + } + + private void addV() { + played = true; + fish_velocity += pullingStrength; + } + + private void calibrate() { + if (fish_position < 0) { + fish_position = 0; + fish_velocity = 0; + } + if (fish_position + pointerIconWidth > barEffectiveWidth) { + fish_position = barEffectiveWidth - pointerIconWidth; + fish_velocity = 0; + } + if (judgement_position < 0) { + judgement_position = 0; + judgement_velocity = 0; + } + if (judgement_position + judgementAreaWidth > barEffectiveWidth) { + judgement_position = barEffectiveWidth - judgementAreaWidth; + judgement_velocity = 0; + } + } + + private void showUI() { + String bar = AdventureHelper.surroundWithMiniMessageFont(barImage, font) + + OffsetUtils.getOffsetChars((int) (judgementAreaOffset + judgement_position)) + + AdventureHelper.surroundWithMiniMessageFont(judgementAreaImage, font) + + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - judgement_position - judgementAreaWidth)) + + OffsetUtils.getOffsetChars((int) (-barEffectiveWidth - 1 + fish_position)) + + AdventureHelper.surroundWithMiniMessageFont(pointerImage, font) + + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - fish_position - pointerIconWidth + 1)); + customFishingHook.getContext().arg(ContextKeys.PROGRESS, progress[(int) ((hold_time / time_requirement) * progress.length)]); + SparrowHeart.getInstance().sendTitle(super.getPlayer(), AdventureHelper.miniMessageToJson(tip != null && !played ? tip : title.render(customFishingHook.getContext())), AdventureHelper.jsonToMiniMessage(bar), 0, 20, 0); + } + }; + } + }; + }); + } + + private void registerHoldV2Game() { + this.registerGameType("hold_v2", (id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final int[] timeRequirements = section.getIntList("hold-time-requirements").stream().mapToInt(Integer::intValue).toArray(); + private final String judgementAreaImage = section.getString("subtitle.judgment-area"); + private final String pointerImage = section.getString("subtitle.pointer"); + private final int barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); + private final int judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); + private final int judgementAreaWidth = section.getInt("arguments.judgment-area-width"); + private final int pointerIconWidth = section.getInt("arguments.pointer-icon-width"); + private final double punishment = section.getDouble("arguments.punishment"); + private final String[] progress = section.getStringList("progress").toArray(new String[0]); + private final double waterResistance = section.getDouble("arguments.water-resistance", 0.15); + private final double pullingStrength = section.getDouble("arguments.pulling-strength", 3d); + private final double looseningLoss = section.getDouble("arguments.loosening-strength-loss", 0.5); + private final TextValue title = TextValue.auto(section.getString("title", "{progress}")); + private final String font = section.getString("subtitle.font"); + private final String barImage = section.getString("subtitle.bar"); + private final String tip = section.getString("tip"); + private final boolean left = section.getBoolean("left-click", false); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + private double hold_time; + private double judgement_position; + private double fish_position; + private double judgement_velocity; + private double fish_velocity; + private int timer; + private final int time_requirement = timeRequirements[ThreadLocalRandom.current().nextInt(timeRequirements.length)] * 1000; + private boolean played; + + @Override + public void arrangeTask() { + this.judgement_position = (double) (barEffectiveWidth - judgementAreaWidth) / 2; + this.task = plugin.getScheduler().asyncRepeating(this, 50, 33, TimeUnit.MILLISECONDS); + } + + @Override + public void tick() { + if (timer < 40 - (settings.difficulty() / 10)) { + timer++; + } else { + timer = 0; + if (Math.random() > ((double) 25 / (settings.difficulty() + 100))) { + burst(); + } + } + judgement_position += judgement_velocity; + fish_position += fish_velocity; + fraction(); + calibrate(); + if (fish_position >= judgement_position && fish_position + pointerIconWidth <= judgement_position + judgementAreaWidth) { + hold_time += 33; + } else { + hold_time -= punishment * 33; + } + if (hold_time >= time_requirement) { + setGameResult(true); + endGame(); + return; + } + hold_time = Math.max(0, Math.min(hold_time, time_requirement)); + showUI(); + } + + private void burst() { + if (Math.random() < (judgement_position / barEffectiveWidth)) { + judgement_velocity = -1 - 0.8 * Math.random() * ((double) settings.difficulty() / 15); + } else { + judgement_velocity = 1 + 0.8 * Math.random() * ((double) settings.difficulty() / 15); + } + } + + private void fraction() { + if (judgement_velocity > 0) { + judgement_velocity -= waterResistance; + if (judgement_velocity < 0) judgement_velocity = 0; + } else { + judgement_velocity += waterResistance; + if (judgement_velocity > 0) judgement_velocity = 0; + } + fish_velocity -= looseningLoss; + if (fish_velocity < -10 * looseningLoss) { + fish_velocity = -10 * looseningLoss; + } + } + + private void calibrate() { + if (fish_position < 0) { + fish_position = 0; + fish_velocity = 0; + } + if (fish_position + pointerIconWidth > barEffectiveWidth) { + fish_position = barEffectiveWidth - pointerIconWidth; + fish_velocity = 0; + } + if (judgement_position < 0) { + judgement_position = 0; + judgement_velocity = 0; + } + if (judgement_position + judgementAreaWidth > barEffectiveWidth) { + judgement_position = barEffectiveWidth - judgementAreaWidth; + judgement_velocity = 0; + } + } + + @Override + public void handleRightClick() { + if (left) { + setGameResult(false); + endGame(); + return; + } + played = true; + fish_velocity = pullingStrength; + return; + } + + @Override + public boolean handleLeftClick() { + if (left) { + played = true; + fish_velocity = pullingStrength; + } + return false; + } + + private void showUI() { + String bar = AdventureHelper.surroundWithMiniMessageFont(barImage, font) + + OffsetUtils.getOffsetChars((int) (judgementAreaOffset + judgement_position)) + + AdventureHelper.surroundWithMiniMessageFont(judgementAreaImage, font) + + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - judgement_position - judgementAreaWidth)) + + OffsetUtils.getOffsetChars((int) (-barEffectiveWidth - 1 + fish_position)) + + AdventureHelper.surroundWithMiniMessageFont(pointerImage, font) + + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - fish_position - pointerIconWidth + 1)); + hook.getContext().arg(ContextKeys.PROGRESS, progress[(int) ((hold_time / time_requirement) * progress.length)]); + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(tip != null && !played ? tip : title.render(hook.getContext())), AdventureHelper.miniMessageToJson(bar), 0, 20, 0); + } + }; + } + }; + }); + } + + private void registerClickGame() { + this.registerGameType("click", ((id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final TextValue title = TextValue.auto(section.getString("title","{progress}")); + private final TextValue subtitle = TextValue.auto(section.getString("subtitle", "Click {clicks} times to win. Time left {time_left}s")); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + private int clickedTimes; + private final int requiredTimes = settings.difficulty(); + + @Override + public void arrangeTask() { + hook.getContext().arg(ContextKeys.REQUIRED_TIMES, requiredTimes); + super.arrangeTask(); + } + + @Override + protected void tick() { + showUI(); + } + + @Override + public void handleRightClick() { + handleClicks(); + } + + @Override + public boolean internalLeftClick() { + handleClicks(); + return true; + } + + private void handleClicks() { + clickedTimes++; + if (clickedTimes >= requiredTimes) { + showUI(); + setGameResult(true); + endGame(); + } + } + + private void showUI() { + hook.getContext().arg(ContextKeys.CLICKS_LEFT, requiredTimes - clickedTimes); + hook.getContext().arg(ContextKeys.PROGRESS, String.valueOf(clickedTimes)); + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(title.render(hook.getContext())), AdventureHelper.miniMessageToJson(subtitle.render(hook.getContext())), 0, 20, 0); + } + }; + } + }; + })); + } + + private void registerTensionGame() { + this.registerGameType("tension", ((id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final int fishIconWidth = section.getInt("arguments.fish-icon-width"); + private final String fishImage = section.getString("subtitle.fish"); + private final String[] tension = section.getStringList("tension").toArray(new String[0]); + private final String[] strugglingFishImage = section.getStringList("subtitle.struggling-fish").toArray(new String[0]); + private final int barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); + private final int fishOffset = section.getInt("arguments.fish-offset"); + private final int fishStartPosition = section.getInt("arguments.fish-start-position"); + private final int successPosition = section.getInt("arguments.success-position"); + private final double ultimateTension = section.getDouble("arguments.ultimate-tension", 50d); + private final double normalIncrease = section.getDouble("arguments.normal-pull-tension-increase", 1d); + private final double strugglingIncrease = section.getDouble("arguments.struggling-tension-increase", 2d); + private final double tensionLoss = section.getDouble("arguments.loosening-tension-loss", 2d); + private final TextValue title = TextValue.auto(section.getString("title","{progress}")); + private final String font = section.getString("subtitle.font"); + private final String barImage = section.getString("subtitle.bar"); + private final String tip = section.getString("tip"); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private int fish_position = fishStartPosition; + private double strain; + private int struggling_time; + private boolean played; + + @Override + public void arrangeTask() { + this.task = plugin.getScheduler().asyncRepeating(this, 50, 40, TimeUnit.MILLISECONDS); + } + + @Override + protected void tick() { + if (struggling_time <= 0) { + if (Math.random() < ((double) settings.difficulty() / 4000)) { + struggling_time = (int) (10 + Math.random() * (settings.difficulty() / 4)); + } + } else { + struggling_time--; + } + if (getPlayer().isSneaking()) pull(); + else loosen(); + if (fish_position < successPosition - fishIconWidth - 1) { + setGameResult(true); + endGame(); + return; + } + if (fish_position + fishIconWidth > barEffectiveWidth || strain >= ultimateTension) { + setGameResult(false); + endGame(); + return; + } + showUI(); + } + + private void pull() { + played = true; + if (struggling_time > 0) { + strain += (strugglingIncrease + ((double) settings.difficulty() / 50)); + fish_position -= 1; + } else { + strain += normalIncrease; + fish_position -= 2; + } + } + + private void loosen() { + fish_position++; + strain -= tensionLoss; + } + + private void showUI() { + String bar = AdventureHelper.surroundWithMiniMessageFont(barImage, font) + + OffsetUtils.getOffsetChars(fishOffset + fish_position) + + AdventureHelper.surroundWithMiniMessageFont((struggling_time > 0 ? strugglingFishImage[struggling_time % strugglingFishImage.length] : fishImage), font) + + OffsetUtils.getOffsetChars(barEffectiveWidth - fish_position - fishIconWidth); + strain = Math.max(0, Math.min(strain, ultimateTension)); + hook.getContext().arg(ContextKeys.PROGRESS, tension[(int) ((strain / ultimateTension) * tension.length)]); + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(tip != null && !played ? tip : title.render(hook.getContext())), AdventureHelper.miniMessageToJson(bar), 0, 20, 0); + } + }; + } + }; + })); + } + + private void registerDanceGame() { + this.registerGameType("dance", ((id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + private final TextValue subtitle = TextValue.auto(section.getString("subtitle", "Dance to win. Time left: {time_left}s")); + private final String leftNot = section.getString("title.left-button"); + private final String leftCorrect = section.getString("title.left-button-correct"); + private final String leftWrong = section.getString("title.left-button-wrong"); + private final String leftCurrent = section.getString("title.left-button-current"); + private final String rightNot = section.getString("title.right-button"); + private final String rightCorrect = section.getString("title.right-button-correct"); + private final String rightWrong = section.getString("title.right-button-wrong"); + private final String rightCurrent = section.getString("title.right-button-current"); + private final String upNot = section.getString("title.up-button"); + private final String upCorrect = section.getString("title.up-button-correct"); + private final String upWrong = section.getString("title.up-button-wrong"); + private final String upCurrent = section.getString("title.up-button-current"); + private final String downNot = section.getString("title.down-button"); + private final String downCorrect = section.getString("title.down-button-correct"); + private final String downWrong = section.getString("title.down-button-wrong"); + private final String downCurrent = section.getString("title.down-button-current"); + private final int maxShown = section.getInt("title.display-amount", 7); + private final String tip = section.getString("tip"); + private final boolean easy = section.getBoolean("easy", false); + private final String correctSound = section.getString("sound.correct", "minecraft:block.amethyst_block.hit"); + private final String wrongSound = section.getString("sound.wrong", "minecraft:block.anvil.land"); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private int clickedTimes; + private int requiredTimes; + // 0 = left / 1 = right / 2 = up / 3 = down + private int[] order; + boolean fail = false; + + @Override + public void arrangeTask() { + requiredTimes = settings.difficulty() / 4; + order = new int[requiredTimes]; + for (int i = 0; i < requiredTimes; i++) { + order[i] = ThreadLocalRandom.current().nextInt(0, easy ? 2 : 4); + } + this.task = plugin.getScheduler().asyncRepeating(this, 50, 50, TimeUnit.MILLISECONDS); + } + + @Override + protected void tick() { + showUI(); + if (tip != null) { + SparrowHeart.getInstance().sendActionBar(getPlayer(), AdventureHelper.miniMessageToJson(tip)); + } + } + + @Override + public boolean handleLeftClick() { + if (order[clickedTimes] != 0) { + handleWrongAction(); + return true; + } + handleCorrectAction(); + return true; + } + + @Override + public void handleRightClick() { + if (order[clickedTimes] != 1) { + handleWrongAction(); + return; + } + handleCorrectAction(); + } + + @Override + public boolean handleJump() { + if (order[clickedTimes] != 2) { + handleWrongAction(); + return false; + } + handleCorrectAction(); + return false; + } + + @Override + public boolean handleSneak() { + if (order[clickedTimes] != 3) { + handleWrongAction(); + return false; + } + handleCorrectAction(); + return false; + } + + private void handleCorrectAction() { + plugin.getSenderFactory().getAudience(getPlayer()).playSound(Sound.sound(Key.key(correctSound), Sound.Source.PLAYER, 1,1)); + clickedTimes++; + if (clickedTimes >= requiredTimes) { + setGameResult(true); + showUI(); + endGame(); + } + } + + private void handleWrongAction() { + setGameResult(false); + fail = true; + showUI(); + plugin.getSenderFactory().getAudience(getPlayer()).playSound(Sound.sound(Key.key(wrongSound), Sound.Source.PLAYER, 1,1)); + endGame(); + } + + private void showUI() { + try { + if (requiredTimes <= maxShown) { + StringBuilder sb = new StringBuilder(); + for (int x = 0; x < requiredTimes; x++) { + if (x < clickedTimes) { + switch (order[x]) { + case 0 -> sb.append(leftCorrect); + case 1 -> sb.append(rightCorrect); + case 2 -> sb.append(upCorrect); + case 3 -> sb.append(downCorrect); + } + } else if (clickedTimes == x) { + switch (order[x]) { + case 0 -> sb.append(fail ? leftWrong : leftCurrent); + case 1 -> sb.append(fail ? rightWrong : rightCurrent); + case 2 -> sb.append(fail ? upWrong : upCurrent); + case 3 -> sb.append(fail ? downWrong : downCurrent); + } + } else { + switch (order[x]) { + case 0 -> sb.append(leftNot); + case 1 -> sb.append(rightNot); + case 2 -> sb.append(upNot); + case 3 -> sb.append(downNot); + } + } + } + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(sb.toString()), AdventureHelper.miniMessageToJson(subtitle.render(hook.getContext())), 0, 20, 0); + } else { + int half = (maxShown - 1) / 2; + int low = clickedTimes - half; + int high = clickedTimes + half; + if (low < 0) { + high += (-low); + low = 0; + } else if (high >= requiredTimes) { + low -= (high - requiredTimes + 1); + high = requiredTimes - 1; + } + StringBuilder sb = new StringBuilder(); + for (int x = low; x < high + 1; x++) { + if (x < clickedTimes) { + switch (order[x]) { + case 0 -> sb.append(leftCorrect); + case 1 -> sb.append(rightCorrect); + case 2 -> sb.append(upCorrect); + case 3 -> sb.append(downCorrect); + } + } else if (clickedTimes == x) { + switch (order[x]) { + case 0 -> sb.append(fail ? leftWrong : leftCurrent); + case 1 -> sb.append(fail ? rightWrong : rightCurrent); + case 2 -> sb.append(fail ? upWrong : upCurrent); + case 3 -> sb.append(fail ? downWrong : downCurrent); + } + } else { + switch (order[x]) { + case 0 -> sb.append(leftNot); + case 1 -> sb.append(rightNot); + case 2 -> sb.append(upNot); + case 3 -> sb.append(downNot); + } + } + } + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(sb.toString()), subtitle.render(hook.getContext()), 0, 20, 0); + } + } catch (Exception e) { + plugin.getPluginLogger().warn("Failed to show `dance` UI", e); + } + } + }; + } + }; + })); + } + + private void registerAccurateClickGame() { + this.registerGameType("accurate_click", ((id, section) -> { + GameBasics basics = getGameBasics(section); + + Set chances = Objects.requireNonNull(section.getSection("success-rate-sections")).getRoutesAsStrings(false); + double[] successRate = new double[chances.size()]; + for(int i = 0; i < chances.size(); i++) + successRate[i] = section.getDouble("success-rate-sections." + (i + 1)); + + return new AbstractGame(id, basics) { + + private final int widthPerSection = section.getInt("arguments.width-per-section", 16); + private final int totalWidth = chances.size() * widthPerSection - 1; + private final int pointerOffset = section.getInt("arguments.pointer-offset"); + private final int pointerWidth = section.getInt("arguments.pointer-width"); + private final List title = ListUtils.toList(section.get("title")); + private final String font = section.getString("subtitle.font"); + private final String barImage = section.getString("subtitle.bar"); + private final String pointerImage = section.getString("subtitle.pointer"); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private int progress = -1; + private boolean face = true; + private final TextValue sendTitle = TextValue.auto(title.get(RandomUtils.generateRandomInt(0, title.size() - 1))); + + @Override + public void arrangeTask() { + var period = Math.min(200, ((double) 10*(200-settings.difficulty()))/((double) (1+4*settings.difficulty()))); + this.task = plugin.getScheduler().asyncRepeating(this, 10, (long) 10, TimeUnit.MILLISECONDS); + } + + @Override + protected void tick() { + if (face) progress++; + else progress--; + if (progress > totalWidth) { + face = !face; + progress = 2 * totalWidth - progress; + } else if (progress < 0) { + face = !face; + progress = -progress; + } + if (!isValid()) return; + showUI(); + } + + @Override + public boolean isSuccessful() { + if (isTimeOut) return false; + int last = progress / widthPerSection; + return (Math.random() < successRate[last]); + } + + private void showUI() { + String bar = AdventureHelper.surroundWithMiniMessageFont(barImage, font) + + OffsetUtils.getOffsetChars(pointerOffset + progress) + + AdventureHelper.surroundWithMiniMessageFont(pointerImage, font) + + OffsetUtils.getOffsetChars(totalWidth - progress - pointerWidth); + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(sendTitle.render(hook.getContext())), AdventureHelper.miniMessageToJson(bar), 0, 10, 0); + } + }; + } + }; + })); + } + + private void registerAccurateClickV2Game() { + this.registerGameType("accurate_click_v2", ((id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final String barWidth = section.getString("title.total-width", "15~20"); + private final String barSuccess = section.getString("title.success-width","3~4"); + private final String barBody = section.getString("title.body",""); + private final String left = section.getString("title.left",""); + private final String right = section.getString("title.right",""); + private final String barPointer = section.getString("title.pointer", ""); + private final String barTarget = section.getString("title.target",""); + private final String subtitle = section.getString("subtitle", "Reel in at the most critical moment"); + + @Override + public BiFunction gamingPlayerProvider() { + + int minWidth = Integer.parseInt(barWidth.split("~")[0]); + int maxWidth = Integer.parseInt(barWidth.split("~")[1]); + int minSuccess = Integer.parseInt(barSuccess.split("~")[0]); + int maxSuccess = Integer.parseInt(barSuccess.split("~")[1]); + + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private final int totalWidth = RandomUtils.generateRandomInt(minWidth, maxWidth); + private final int successWidth = RandomUtils.generateRandomInt(minSuccess, maxSuccess); + private final int successPosition = ThreadLocalRandom.current().nextInt((totalWidth - successWidth + 1)) + 1; + private int currentIndex = 0; + private int timer = 0; + private boolean face = true; + + @Override + protected void tick() { + timer++; + if (timer % (21 - settings.difficulty() / 5) == 0) { + movePointer(); + } + showUI(); + } + + private void movePointer() { + if (face) { + currentIndex++; + if (currentIndex >= totalWidth - 1) { + face = false; + } + } else { + currentIndex--; + if (currentIndex <= 0) { + face = true; + } + } + } + + private void showUI() { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 1; i <= totalWidth; i++) { + if (i == currentIndex + 1) { + stringBuilder.append(barPointer); + continue; + } + if (i >= successPosition && i <= successPosition + successWidth - 1) { + stringBuilder.append(barTarget); + continue; + } + stringBuilder.append(barBody); + } + + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(left + stringBuilder + right), AdventureHelper.miniMessageToJson(subtitle), 0, 20, 0); + } + + @Override + public boolean isSuccessful() { + if (isTimeOut) return false; + return currentIndex + 1 <= successPosition + successWidth - 1 && currentIndex + 1 >= successPosition; + } + }; + } + }; + })); + } + + private void registerAccurateClickV3Game() { + this.registerGameType("accurate_click_v3", ((id, section) -> { + GameBasics basics = getGameBasics(section); + return new AbstractGame(id, basics) { + + private final String font = section.getString("subtitle.font"); + private final String pointerImage = section.getString("subtitle.pointer"); + private final String barImage = section.getString("subtitle.bar"); + private final String judgementAreaImage = section.getString("subtitle.judgment-area"); + private final List titles = ListUtils.toList(section.get("title")); + private final int barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); + private final int judgementAreaWidth = section.getInt("arguments.judgment-area-width"); + private final int judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); + private final int pointerIconWidth = section.getInt("arguments.pointer-icon-width"); + private final int pointerOffset = section.getInt("arguments.pointer-offset"); + + @Override + public BiFunction gamingPlayerProvider() { + return (customFishingHook, gameSetting) -> new AbstractGamingPlayer(customFishingHook, gameSetting) { + + private int progress = -1; + private boolean face = true; + private final int judgement_position = RandomUtils.generateRandomInt(0, barEffectiveWidth - judgementAreaWidth); + private final TextValue title = TextValue.auto(titles.get(RandomUtils.generateRandomInt(0, titles.size() - 1))); + + @Override + public void arrangeTask() { + var period = ((double) 10*(200-settings.difficulty()))/((double) (1+4*settings.difficulty())); + this.task = plugin.getScheduler().asyncRepeating(this, 50, (long) period, TimeUnit.MILLISECONDS); + } + + @Override + protected void tick() { + if (face) { + progress++; + if (progress >= barEffectiveWidth - 1) { + face = false; + } + } else { + progress--; + if (progress <= 0) { + face = true; + } + } + showUI(); + } + + private void showUI() { + String bar = AdventureHelper.surroundWithMiniMessageFont(barImage, font) + + OffsetUtils.getOffsetChars(judgementAreaOffset + judgement_position) + + AdventureHelper.surroundWithMiniMessageFont(judgementAreaImage, font) + + OffsetUtils.getOffsetChars(barEffectiveWidth - judgement_position - judgementAreaWidth) + + OffsetUtils.getOffsetChars(progress + pointerOffset) + + AdventureHelper.surroundWithMiniMessageFont(pointerImage, font) + + OffsetUtils.getOffsetChars(barEffectiveWidth - progress - pointerIconWidth + 1); + SparrowHeart.getInstance().sendTitle(getPlayer(), AdventureHelper.miniMessageToJson(title.render(hook.getContext())), AdventureHelper.miniMessageToJson(bar), 0, 20, 0); + } + + @Override + public boolean isSuccessful() { + if (isTimeOut) return false; + return progress < judgement_position + judgementAreaWidth && progress >= judgement_position; + } + }; + } + }; + })); + } + + /** + * Loads minigame expansions from the expansion folder. + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + private void loadExpansions() { + File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); + if (!expansionFolder.exists()) + expansionFolder.mkdirs(); + List> classes = new ArrayList<>(); + File[] expansionJars = expansionFolder.listFiles(); + if (expansionJars == null) return; + for (File expansionJar : expansionJars) { + if (expansionJar.getName().endsWith(".jar")) { + try { + Class expansionClass = ClassUtils.findClass(expansionJar, GameExpansion.class); + classes.add(expansionClass); + } catch (IOException | ClassNotFoundException e) { + plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e); + } + } + } + try { + for (Class expansionClass : classes) { + GameExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); + unregisterGameType(expansion.getGameType()); + registerGameType(expansion.getGameType(), expansion.getGameFactory()); + plugin.getPluginLogger().info("Loaded minigame expansion: " + expansion.getGameType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() ); + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { + plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e); + } + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/hook/BukkitHookManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/hook/BukkitHookManager.java new file mode 100644 index 00000000..f883ada4 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/hook/BukkitHookManager.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.hook; + +import com.saicone.rtag.item.ItemTagStream; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ScoreComponent; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.hook.HookConfig; +import net.momirealms.customfishing.api.mechanic.hook.HookManager; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.bukkit.item.damage.CustomDurabilityItem; +import net.momirealms.customfishing.bukkit.item.damage.DurabilityItem; +import net.momirealms.customfishing.bukkit.item.damage.VanillaDurabilityItem; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +public class BukkitHookManager implements HookManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap hooks = new HashMap<>(); + private LZ4Factory factory; + + public BukkitHookManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.factory = LZ4Factory.fastestInstance(); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + plugin.debug("Loaded " + hooks.size() + " hooks"); + } + + @Override + public boolean registerHook(HookConfig hook) { + if (hooks.containsKey(hook.id())) return false; + hooks.put(hook.id(), hook); + return true; + } + + @NotNull + @Override + public Optional getHook(String id) { + return Optional.ofNullable(hooks.get(id)); + } + + @Override + public Optional getHookID(ItemStack rod) { + if (rod == null || rod.getType() != Material.FISHING_ROD || rod.getAmount() == 0) + return Optional.empty(); + + Item wrapped = plugin.getItemManager().wrap(rod); + return wrapped.getTag("CustomFishing", "hook_id").map(o -> (String) o); + } + + @EventHandler (ignoreCancelled = true) + public void onDragDrop(InventoryClickEvent event) { + final Player player = (Player) event.getWhoClicked(); + if (event.getClickedInventory() != player.getInventory()) + return; + if (player.getGameMode() != GameMode.SURVIVAL) + return; + ItemStack clicked = event.getCurrentItem(); + if (clicked == null || clicked.getType() != Material.FISHING_ROD) + return; + if (plugin.getFishingManager().getFishHook(player).isPresent()) + return; + ItemStack cursor = event.getCursor(); + if (cursor.getType() == Material.AIR) { + if (event.getClick() != ClickType.RIGHT) { + return; + } + Item wrapped = plugin.getItemManager().wrap(clicked); + if (!wrapped.hasTag("CustomFishing", "hook_id")) { + return; + } + event.setCancelled(true); + String id = (String) wrapped.getTag("CustomFishing", "hook_id").orElseThrow(); + byte[] hookItemBase64 = (byte[]) wrapped.getTag("CustomFishing", "hook_stack").orElse(null); + int damage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0); + ItemStack itemStack; + if (hookItemBase64 != null) { + itemStack = bytesToHook(hookItemBase64); + } else { + itemStack = plugin.getItemManager().buildInternal(Context.player(player), id); + } + plugin.getItemManager().setDurability(player, itemStack, damage); + + wrapped.removeTag("CustomFishing", "hook_id"); + wrapped.removeTag("CustomFishing", "hook_stack"); + wrapped.removeTag("CustomFishing", "hook_damage"); + wrapped.removeTag("CustomFishing", "hook_max_damage"); + + event.setCursor(itemStack); + + List previousLore = wrapped.lore().orElse(new ArrayList<>()); + List newLore = new ArrayList<>(); + for (String previous : previousLore) { + Component component = AdventureHelper.jsonToComponent(previous); + if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf") && scoreComponent.objective().equals("hook")) { + continue; + } + newLore.add(previous); + } + wrapped.lore(newLore); + + wrapped.load(); + return; + } + + String hookID = plugin.getItemManager().getItemID(cursor); + Optional setting = getHook(hookID); + if (setting.isEmpty()) { + return; + } + + Context context = Context.player(player); + HookConfig hookConfig = setting.get(); + Optional modifier = plugin.getEffectManager().getEffectModifier(hookID, MechanicType.HOOK); + if (modifier.isPresent()) { + if (!RequirementManager.isSatisfied(context, modifier.get().requirements())) { + return; + } + } + event.setCancelled(true); + + ItemStack clonedHook = cursor.clone(); + clonedHook.setAmount(1); + cursor.setAmount(cursor.getAmount() - 1); + + Item wrapped = plugin.getItemManager().wrap(clicked); + String previousHookID = (String) wrapped.getTag("CustomFishing", "hook_id").orElse(null); + if (previousHookID != null) { + int previousHookDamage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0); + ItemStack previousItemStack; + byte[] stackBytes = (byte[]) wrapped.getTag("CustomFishing", "hook_stack").orElse(null); + if (stackBytes != null) { + previousItemStack = bytesToHook(stackBytes); + } else { + previousItemStack = plugin.getItemManager().buildInternal(Context.player(player), previousHookID); + } + if (previousItemStack != null) { + plugin.getItemManager().setDurability(player, previousItemStack, previousHookDamage); + if (cursor.getAmount() == 0) { + event.setCursor(previousItemStack); + } else { + PlayerUtils.giveItem(player, previousItemStack, 1); + } + } + } + + Item wrappedHook = plugin.getItemManager().wrap(clonedHook); + DurabilityItem durabilityItem; + if (wrappedHook.hasTag("CustomFishing", "max_dur")) { + durabilityItem = new CustomDurabilityItem(wrappedHook); + } else { + durabilityItem = new VanillaDurabilityItem(wrappedHook); + } + + wrapped.setTag(hookID, "CustomFishing", "hook_id"); + wrapped.setTag(hookToBytes(clonedHook), "CustomFishing", "hook_stack"); + wrapped.setTag(durabilityItem.damage(), "CustomFishing", "hook_damage"); + wrapped.setTag(durabilityItem.maxDamage(), "CustomFishing", "hook_max_damage"); + + List previousLore = wrapped.lore().orElse(new ArrayList<>()); + List newLore = new ArrayList<>(); + List durabilityLore = new ArrayList<>(); + for (String previous : previousLore) { + Component component = AdventureHelper.jsonToComponent(previous); + if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) { + if (scoreComponent.objective().equals("hook")) { + continue; + } else if (scoreComponent.objective().equals("durability")) { + durabilityLore.add(previous); + continue; + } + } + newLore.add(previous); + } + for (String lore : hookConfig.lore()) { + ScoreComponent.Builder builder = Component.score().name("cf").objective("hook"); + builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(durabilityItem.maxDamage() - durabilityItem.damage())).replace("{max}", String.valueOf(durabilityItem.maxDamage())))); + newLore.add(AdventureHelper.componentToJson(builder.build())); + } + newLore.addAll(durabilityLore); + wrapped.lore(newLore); + wrapped.load(); + } + + private byte[] hookToBytes(ItemStack hook) { + try { + byte[] data = ItemTagStream.INSTANCE.toBytes(hook); + int decompressedLength = data.length; + LZ4Compressor compressor = factory.fastCompressor(); + int maxCompressedLength = compressor.maxCompressedLength(decompressedLength); + byte[] compressed = new byte[maxCompressedLength]; + int compressedLength = compressor.compress(data, 0, decompressedLength, compressed, 0, maxCompressedLength); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream); + outputStream.writeInt(decompressedLength); + outputStream.write(compressed, 0, compressedLength); + outputStream.close(); + + return byteArrayOutputStream.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private ItemStack bytesToHook(byte[] bytes) { + try { + DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(bytes)); + int decompressedLength = inputStream.readInt(); + byte[] compressed = new byte[inputStream.available()]; + inputStream.readFully(compressed); + + LZ4FastDecompressor decompressor = factory.fastDecompressor(); + byte[] restored = new byte[decompressedLength]; + decompressor.decompress(compressed, 0, restored, 0, decompressedLength); + + return ItemTagStream.INSTANCE.fromBytes(restored); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/integration/BukkitIntegrationManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/integration/BukkitIntegrationManager.java new file mode 100644 index 00000000..71b9aae2 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/integration/BukkitIntegrationManager.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.integration; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.*; +import net.momirealms.customfishing.bukkit.block.BukkitBlockManager; +import net.momirealms.customfishing.bukkit.entity.BukkitEntityManager; +import net.momirealms.customfishing.bukkit.integration.block.ItemsAdderBlockProvider; +import net.momirealms.customfishing.bukkit.integration.block.OraxenBlockProvider; +import net.momirealms.customfishing.bukkit.integration.enchant.AdvancedEnchantmentsProvider; +import net.momirealms.customfishing.bukkit.integration.enchant.VanillaEnchantmentsProvider; +import net.momirealms.customfishing.bukkit.integration.entity.ItemsAdderEntityProvider; +import net.momirealms.customfishing.bukkit.integration.entity.MythicEntityProvider; +import net.momirealms.customfishing.bukkit.integration.item.*; +import net.momirealms.customfishing.bukkit.integration.level.*; +import net.momirealms.customfishing.bukkit.integration.papi.CompetitionPapi; +import net.momirealms.customfishing.bukkit.integration.papi.CustomFishingPapi; +import net.momirealms.customfishing.bukkit.integration.papi.StatisticsPapi; +import net.momirealms.customfishing.bukkit.integration.quest.BattlePassQuest; +import net.momirealms.customfishing.bukkit.integration.quest.BetonQuestQuest; +import net.momirealms.customfishing.bukkit.integration.quest.ClueScrollsQuest; +import net.momirealms.customfishing.bukkit.integration.season.AdvancedSeasonsProvider; +import net.momirealms.customfishing.bukkit.integration.season.CustomCropsSeasonProvider; +import net.momirealms.customfishing.bukkit.integration.season.RealisticSeasonsProvider; +import net.momirealms.customfishing.bukkit.item.BukkitItemManager; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class BukkitIntegrationManager implements IntegrationManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap levelerProviders = new HashMap<>(); + private final HashMap enchantmentProviders = new HashMap<>(); + private SeasonProvider seasonProvider; + + public BukkitIntegrationManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.load(); + } + + @Override + public void disable() { + this.enchantmentProviders.clear(); + this.levelerProviders.clear(); + } + + @Override + public void load() { + registerEnchantmentProvider(new VanillaEnchantmentsProvider()); + if (isHooked("ItemsAdder")) { + registerItemProvider(new ItemsAdderItemProvider()); + registerBlockProvider(new ItemsAdderBlockProvider()); + registerEntityProvider(new ItemsAdderEntityProvider()); + } + if (isHooked("MMOItems")) { + registerItemProvider(new MMOItemsItemProvider()); + } + if (isHooked("Oraxen")) { + registerItemProvider(new OraxenItemProvider()); + registerBlockProvider(new OraxenBlockProvider()); + } + if (isHooked("Zaphkiel")) { + registerItemProvider(new ZaphkielItemProvider()); + } + if (isHooked("NeigeItems")) { + registerItemProvider(new NeigeItemsItemProvider()); + } + if (isHooked("MythicMobs")) { + registerItemProvider(new MythicMobsItemProvider()); + registerEntityProvider(new MythicEntityProvider()); + } + if (isHooked("EcoJobs")) { + registerLevelerProvider(new EcoJobsLevelerProvider()); + } + if (isHooked("EcoSkills")) { + registerLevelerProvider(new EcoSkillsLevelerProvider()); + } + if (isHooked("Jobs")) { + registerLevelerProvider(new JobsRebornLevelerProvider()); + } + if (isHooked("MMOCore")) { + registerLevelerProvider(new MMOCoreLevelerProvider()); + } + if (isHooked("mcMMO")) { + try { + registerItemProvider(new McMMOTreasureProvider()); + } catch (ClassNotFoundException | NoSuchMethodException e) { + plugin.getPluginLogger().warn("Failed to initialize mcMMO Treasure"); + } + registerLevelerProvider(new McMMOLevelerProvider()); + } + if (isHooked("AureliumSkills")) { + registerLevelerProvider(new AureliumSkillsProvider()); + } + if (isHooked("AuraSkills")) { + registerLevelerProvider(new AuraSkillsLevelerProvider()); + } + if (isHooked("AdvancedEnchantments")) { + registerEnchantmentProvider(new AdvancedEnchantmentsProvider()); + } + if (isHooked("RealisticSeasons")) { + registerSeasonProvider(new RealisticSeasonsProvider()); + } else if (isHooked("AdvancedSeasons")) { + registerSeasonProvider(new AdvancedSeasonsProvider()); + } else if (isHooked("CustomCrops")) { + registerSeasonProvider(new CustomCropsSeasonProvider()); + } + if (isHooked("Vault")) { + VaultHook.initialize(); + } + if (isHooked("BattlePass")){ + BattlePassQuest battlePassQuest = new BattlePassQuest(); + battlePassQuest.register(); + } + if (isHooked("ClueScrolls")) { + ClueScrollsQuest clueScrollsQuest = new ClueScrollsQuest(); + clueScrollsQuest.register(); + } + if (isHooked("BetonQuest")) { + BetonQuestQuest.register(); + } + if (isHooked("PlaceholderAPI")) { + new CustomFishingPapi(plugin).load(); + new CompetitionPapi(plugin).load(); + new StatisticsPapi(plugin).load(); + } + } + + private boolean isHooked(String hooked) { + if (Bukkit.getPluginManager().getPlugin(hooked) != null) { + plugin.getPluginLogger().info(hooked + " hooked!"); + return true; + } + return false; + } + + @Override + public boolean registerLevelerProvider(@NotNull LevelerProvider leveler) { + if (levelerProviders.containsKey(leveler.identifier())) return false; + levelerProviders.put(leveler.identifier(), leveler); + return true; + } + + @Override + public boolean unregisterLevelerProvider(@NotNull String id) { + return levelerProviders.remove(id) != null; + } + + @Override + public boolean registerEnchantmentProvider(@NotNull EnchantmentProvider enchantment) { + if (enchantmentProviders.containsKey(enchantment.identifier())) return false; + enchantmentProviders.put(enchantment.identifier(), enchantment); + return true; + } + + @Override + public boolean unregisterEnchantmentProvider(@NotNull String id) { + return enchantmentProviders.remove(id) != null; + } + + @Override + @Nullable + public LevelerProvider getLevelerProvider(String plugin) { + return levelerProviders.get(plugin); + } + + @Override + @Nullable + public EnchantmentProvider getEnchantmentProvider(String id) { + return enchantmentProviders.get(id); + } + + @Override + public List> getEnchantments(ItemStack itemStack) { + ArrayList> list = new ArrayList<>(); + for (EnchantmentProvider enchantmentProvider : enchantmentProviders.values()) { + list.addAll(enchantmentProvider.getEnchants(itemStack)); + } + return list; + } + + @Nullable + @Override + public SeasonProvider getSeasonProvider() { + return seasonProvider; + } + + @Override + public boolean registerSeasonProvider(@NotNull SeasonProvider season) { + if (this.seasonProvider != null) return false; + this.seasonProvider = season; + return true; + } + + @Override + public boolean unregisterSeasonProvider() { + if (this.seasonProvider == null) return false; + this.seasonProvider = null; + return true; + } + + @Override + public boolean registerEntityProvider(@NotNull EntityProvider entity) { + return ((BukkitEntityManager) plugin.getEntityManager()).registerEntityProvider(entity); + } + + @Override + public boolean unregisterEntityProvider(@NotNull String id) { + return ((BukkitEntityManager) plugin.getEntityManager()).unregisterEntityProvider(id); + } + + @Override + public boolean registerItemProvider(@NotNull ItemProvider item) { + return ((BukkitItemManager) plugin.getItemManager()).registerItemProvider(item); + } + + @Override + public boolean unregisterItemProvider(@NotNull String id) { + return ((BukkitItemManager) plugin.getItemManager()).unregisterItemProvider(id); + } + + @Override + public boolean registerBlockProvider(@NotNull BlockProvider block) { + return ((BukkitBlockManager) plugin.getBlockManager()).registerBlockProvider(block); + } + + @Override + public boolean unregisterBlockProvider(@NotNull String id) { + return ((BukkitBlockManager) plugin.getBlockManager()).unregisterBlockProvider(id); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemFactory.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemFactory.java new file mode 100644 index 00000000..4a88371b --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemFactory.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item; + +import com.saicone.rtag.RtagItem; +import net.momirealms.customfishing.bukkit.item.impl.ComponentItemFactory; +import net.momirealms.customfishing.bukkit.item.impl.UniversalItemFactory; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.customfishing.common.item.ItemFactory; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import org.bukkit.inventory.ItemStack; + +import java.util.Objects; +import java.util.Optional; + +public abstract class BukkitItemFactory extends ItemFactory { + + protected BukkitItemFactory(CustomFishingPlugin plugin) { + super(plugin); + } + + public static BukkitItemFactory create(CustomFishingPlugin plugin) { + Objects.requireNonNull(plugin, "plugin"); + switch (plugin.getServerVersion()) { + case "1.17", "1.17.1", + "1.18", "1.18.1", "1.18.2", + "1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4", + "1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4" -> { + return new UniversalItemFactory(plugin); + } + case "1.20.5", "1.20.6", + "1.21", "1.21.1", "1.21.2" -> { + return new ComponentItemFactory(plugin); + } + default -> throw new IllegalStateException("Unsupported server version: " + plugin.getServerVersion()); + } + } + + public Item wrap(ItemStack item) { + Objects.requireNonNull(item, "item"); + return wrap(new RtagItem(item)); + } + + @Override + protected void setTag(RtagItem item, Object value, Object... path) { + item.set(value, path); + } + + @Override + protected Optional getTag(RtagItem item, Object... path) { + return Optional.ofNullable(item.get(path)); + } + + @Override + protected boolean hasTag(RtagItem item, Object... path) { + return item.hasTag(path); + } + + @Override + protected boolean removeTag(RtagItem item, Object... path) { + return item.remove(path); + } + + @Override + protected void update(RtagItem item) { + item.update(); + } + + @Override + protected ItemStack load(RtagItem item) { + return item.load(); + } + + @Override + protected ItemStack getItem(RtagItem item) { + return item.getItem(); + } + + @Override + protected ItemStack loadCopy(RtagItem item) { + return item.loadCopy(); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemManager.java new file mode 100644 index 00000000..ecc5b3d8 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/BukkitItemManager.java @@ -0,0 +1,484 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.ExternalProvider; +import net.momirealms.customfishing.api.integration.ItemProvider; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.item.ItemManager; +import net.momirealms.customfishing.api.util.EventUtils; +import net.momirealms.customfishing.bukkit.integration.item.CustomFishingItemProvider; +import net.momirealms.customfishing.bukkit.item.damage.CustomDurabilityItem; +import net.momirealms.customfishing.bukkit.item.damage.DurabilityItem; +import net.momirealms.customfishing.bukkit.item.damage.VanillaDurabilityItem; +import net.momirealms.customfishing.bukkit.util.ItemStackUtils; +import net.momirealms.customfishing.bukkit.util.LocationUtils; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.Skull; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.*; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.player.PlayerAttemptPickupItemEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.event.player.PlayerItemMendEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +import static java.util.Objects.requireNonNull; + +public class BukkitItemManager implements ItemManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap itemProviders = new HashMap<>(); + private final HashMap items = new HashMap<>(); + private final BukkitItemFactory factory; + private ItemProvider[] itemDetectArray; + + public BukkitItemManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.factory = BukkitItemFactory.create(plugin); + this.registerItemProvider(new ItemProvider() { + @NotNull + @Override + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH))); + } + @NotNull + @Override + public String itemID(@NotNull ItemStack itemStack) { + return itemStack.getType().name(); + } + @Override + public String identifier() { + return "vanilla"; + } + }); + this.registerItemProvider(new CustomFishingItemProvider()); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + this.items.clear(); + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + this.resetItemDetectionOrder(); + for (ItemProvider provider : itemProviders.values()) { + plugin.debug("Registered ItemProvider: " + provider.identifier()); + } + plugin.debug("Loaded " + items.size() + " items"); + plugin.debug("Item order: " + Arrays.toString(Arrays.stream(itemDetectArray).map(ExternalProvider::identifier).toList().toArray(new String[0]))); + } + + @Override + public boolean registerItem(@NotNull CustomFishingItem item) { + if (items.containsKey(item.id())) return false; + items.put(item.id(), item); + return true; + } + + @Nullable + @Override + public ItemStack buildInternal(@NotNull Context context, @NotNull String id) { +// CustomFishingItem item = requireNonNull(items.get(id), () -> "No item found for " + id); + CustomFishingItem item = items.get(id); + if (item == null) return null; + return build(context, item); + } + + @NotNull + @Override + public ItemStack build(@NotNull Context context, @NotNull CustomFishingItem item) { + ItemStack itemStack = getOriginalStack(context.getHolder(), item.material()); + if (itemStack.getType() == Material.AIR) return itemStack; + Item wrappedItemStack = factory.wrap(itemStack); + for (BiConsumer, Context> consumer : item.tagConsumers()) { + consumer.accept(wrappedItemStack, context); + } + return wrappedItemStack.load(); + } + + @Override + public ItemStack buildAny(@NotNull Context context, @NotNull String item) { + return getOriginalStack(context.getHolder(), item); + } + + @NotNull + @Override + public String getItemID(@NotNull ItemStack itemStack) { + if (itemStack.getType() == Material.AIR) + return "AIR"; + for (ItemProvider library : itemDetectArray) { + String id = library.itemID(itemStack); + if (id != null) + return id; + } + // should not reach this because vanilla library would always work + return "AIR"; + } + + @Override + public String getCustomFishingItemID(@NotNull ItemStack itemStack) { + return (String) factory.wrap(itemStack).getTag("CustomFishing", "id").orElse(null); + } + + @Nullable + @Override + public org.bukkit.entity.Item dropItemLoot(@NotNull Context context, ItemStack rod, FishHook hook) { + String id = requireNonNull(context.arg(ContextKeys.ID)); + ItemStack itemStack; + if (id.equals("vanilla")) { + itemStack = SparrowHeart.getInstance().getFishingLoot(context.getHolder(), hook, rod).stream().findAny().orElseThrow(() -> new RuntimeException("new EntityItem would throw if for whatever reason (mostly shitty datapacks) the fishing loot turns out to be empty")); + } else { + itemStack = requireNonNull(buildInternal(context, id)); + } + + if (itemStack.getType() == Material.AIR) { + return null; + } + + Player player = context.getHolder(); + Location playerLocation = player.getLocation(); + Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)); + + double d0 = playerLocation.getX() - hookLocation.getX(); + double d1 = playerLocation.getY() - hookLocation.getY(); + double d2 = playerLocation.getZ() - hookLocation.getZ(); + Vector vector = new Vector(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); + + org.bukkit.entity.Item itemEntity = hookLocation.getWorld().dropItem(hookLocation, itemStack); + + itemEntity.setInvulnerable(true); + // prevent from being killed by lava + plugin.getScheduler().asyncLater(() -> { + if (itemEntity.isValid()) + itemEntity.setInvulnerable(false); + }, 1, TimeUnit.SECONDS); + + itemEntity.setVelocity(vector); + + return itemEntity; + } + + private ItemStack getOriginalStack(Player player, String material) { + if (!material.contains(":")) { + try { + return new ItemStack(Material.valueOf(material.toUpperCase(Locale.ENGLISH))); + } catch (IllegalArgumentException e) { + plugin.getPluginLogger().severe("material " + material + " not exists", e); + return new ItemStack(Material.PAPER); + } + } else { + String[] split = material.split(":", 2); + ItemProvider provider = requireNonNull(itemProviders.get(split[0]), "Item provider: " + split[0] + " not found"); + return requireNonNull(provider.buildItem(player, split[0]), "Item: " + split[0] + " not found"); + } + } + + private void resetItemDetectionOrder() { + ArrayList list = new ArrayList<>(); + for (String plugin : ConfigManager.itemDetectOrder()) { + ItemProvider provider = itemProviders.get(plugin); + if (provider != null) + list.add(provider); + } + this.itemDetectArray = list.toArray(new ItemProvider[0]); + } + + public boolean registerItemProvider(ItemProvider item) { + if (itemProviders.containsKey(item.identifier())) return false; + itemProviders.put(item.identifier(), item); + this.resetItemDetectionOrder(); + return true; + } + + public boolean unregisterItemProvider(String id) { + boolean success = itemProviders.remove(id) != null; + if (success) + this.resetItemDetectionOrder(); + return success; + } + + @Override + public boolean hasCustomDurability(ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return false; + Item wrapped = factory.wrap(itemStack); + return wrapped.hasTag("CustomFishing", "max_dur"); + } + + @Override + public void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean incorrectUsage) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + if (!incorrectUsage) { + int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY); + if (Math.random() > (double) 1 / (unBreakingLevel + 1)) { + return; + } + } + Item wrapped = factory.wrap(itemStack); + if (wrapped.unbreakable()) + return; + + ItemMeta previousMeta = itemStack.getItemMeta().clone(); + PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(player, itemStack, amount); + if (EventUtils.fireAndCheckCancel(itemDamageEvent)) { + plugin.debug("Another plugin modified the item from `PlayerItemDamageEvent` called by CustomFishing"); + return; + } + if (!itemStack.getItemMeta().equals(previousMeta)) { + return; + } + + DurabilityItem durabilityItem = wrapDurabilityItem(wrapped); + int damage = durabilityItem.damage(); + if (damage + amount >= durabilityItem.maxDamage()) { + plugin.getSenderFactory().getAudience(player).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1)); + itemStack.setAmount(0); + return; + } + + durabilityItem.damage(damage + amount); + wrapped.load(); + } + + @Override + public void setDurability(Player player, ItemStack itemStack, int damage) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + Item wrapped = factory.wrap(itemStack); + if (wrapped.unbreakable()) + return; + DurabilityItem wrappedDurability = wrapDurabilityItem(wrapped); + if (damage >= wrappedDurability.maxDamage()) { + if (player != null) + plugin.getSenderFactory().getAudience(player).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1)); + itemStack.setAmount(0); + return; + } + wrappedDurability.damage(damage); + wrapped.load(); + } + + public DurabilityItem wrapDurabilityItem(Item wrapped) { + if (wrapped.hasTag("CustomFishing", "max_dur")) { + return new CustomDurabilityItem(wrapped); + } else { + return new VanillaDurabilityItem(wrapped); + } + } + + @EventHandler (ignoreCancelled = true) + public void onMending(PlayerItemMendEvent event) { + ItemStack itemStack = event.getItem(); + if (!hasCustomDurability(itemStack)) { + return; + } + event.setCancelled(true); + Item wrapped = factory.wrap(itemStack); + if (wrapped.unbreakable()) + return; + DurabilityItem wrappedDurability = wrapDurabilityItem(wrapped); + setDurability(event.getPlayer(), itemStack, Math.max(wrappedDurability.damage() - event.getRepairAmount(), 0)); + } + + @EventHandler (ignoreCancelled = true) + public void onAnvil(PrepareAnvilEvent event) { + AnvilInventory anvil = event.getInventory(); + ItemStack first = anvil.getFirstItem(); + ItemStack second = anvil.getSecondItem(); + if (first != null && second != null + && first.getType() == Material.FISHING_ROD && second.getType() == Material.FISHING_ROD && event.getResult() != null + && hasCustomDurability(first)) { + Item wrapped1 = factory.wrap(anvil.getResult()); + DurabilityItem wrappedDurability1 = wrapDurabilityItem(wrapped1); + + Item wrapped2 = factory.wrap(second); + DurabilityItem wrappedDurability2 = wrapDurabilityItem(wrapped2); + + int durability2 = wrappedDurability2.maxDamage() - wrappedDurability2.damage(); + int damage1 = Math.max(wrappedDurability1.damage() - durability2, 0); + wrappedDurability1.damage(damage1); + event.setResult(wrapped1.load()); + } + } + + @EventHandler(ignoreCancelled = true) + public void onInvPickItem(InventoryPickupItemEvent event) { + ItemStack itemStack = event.getItem().getItemStack(); + Item wrapped = factory.wrap(itemStack); + if (wrapped.hasTag("owner")) { + wrapped.removeTag("owner"); + itemStack.setItemMeta(wrapped.getItem().getItemMeta()); + } + } + + @EventHandler (ignoreCancelled = true) + public void onPlaceBlock(BlockPlaceEvent event) { + ItemStack itemStack = event.getItemInHand(); + if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0 || !itemStack.hasItemMeta()) { + return; + } + + Item wrapped = factory.wrap(itemStack); + if (wrapped.hasTag("CustomFishing")) { + if (!wrapped.hasTag("CustomFishing", "placeable") || ((int) wrapped.getTag("CustomFishing", "placeable").get()) != 1) { + event.setCancelled(true); + return; + } + Block block = event.getBlock(); + if (block.getState() instanceof Skull) { + PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); + ItemStack cloned = itemStack.clone(); + cloned.setAmount(1); + pdc.set(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING, ItemStackUtils.toBase64(cloned)); + } else { + event.setCancelled(true); + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onBreakBlock(BlockBreakEvent event) { + final Block block = event.getBlock(); + if (block.getState() instanceof Skull) { + PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); + String base64 = pdc.get(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING); + if (base64 != null) { + ItemStack itemStack = ItemStackUtils.fromBase64(base64); + event.setDropItems(false); + block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack); + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onPiston(BlockPistonExtendEvent event) { + handlePiston(event, event.getBlocks()); + } + + @EventHandler (ignoreCancelled = true) + public void onPiston(BlockPistonRetractEvent event) { + handlePiston(event, event.getBlocks()); + } + + private void handlePiston(Cancellable event, List blockList) { + for (Block block : blockList) { + if (block.getState() instanceof Skull) { + PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); + if (pdc.has(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING)) { + event.setCancelled(true); + return; + } + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onExplosion(BlockExplodeEvent event) { + handleExplosion(event.blockList()); + } + + @EventHandler (ignoreCancelled = true) + public void onExplosion(EntityExplodeEvent event) { + handleExplosion(event.blockList()); + } + + @EventHandler (ignoreCancelled = true) + public void onPickUpItem(PlayerAttemptPickupItemEvent event) { + String owner = event.getItem().getPersistentDataContainer().get(requireNonNull(NamespacedKey.fromString("owner", plugin.getBoostrap())), PersistentDataType.STRING); + if (owner != null) { + if (!owner.equals(event.getPlayer().getName())) { + event.setCancelled(true); + } + } + } + + private void handleExplosion(List blocks) { + ArrayList blockToRemove = new ArrayList<>(); + for (Block block : blocks) { + if (block.getState() instanceof Skull) { + PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); + var nk = new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())); + String base64 = pdc.get(nk, PersistentDataType.STRING); + if (base64 != null) { + ItemStack itemStack = ItemStackUtils.fromBase64(base64); + block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack); + blockToRemove.add(block); + block.setType(Material.AIR); + pdc.remove(nk); + } + } + } + blocks.removeAll(blockToRemove); + } + + @Override + public BukkitItemFactory getFactory() { + return factory; + } + + @Override + public ItemProvider[] getItemProviders() { + return itemProviders.values().toArray(new ItemProvider[0]); + } + + @Override + public Collection getItemIDs() { + return items.keySet(); + } + + @Override + public Item wrap(ItemStack itemStack) { + return factory.wrap(itemStack); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/CustomDurabilityItem.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/CustomDurabilityItem.java new file mode 100644 index 00000000..5028cbf4 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/CustomDurabilityItem.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item.damage; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ScoreComponent; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.item.Item; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class CustomDurabilityItem implements DurabilityItem { + + private final Item item; + + public CustomDurabilityItem(Item item) { + this.item = item; + } + + @Override + public void damage(int value) { + int customMaxDamage = (int) item.getTag("CustomFishing", "max_dur").get(); + int maxDamage = item.maxDamage().get(); + double ratio = (double) maxDamage / (double) customMaxDamage; + int fakeDamage = (int) (value * ratio); + item.damage(fakeDamage); + item.setTag(customMaxDamage - value, "CustomFishing", "cur_dur"); + List durabilityLore = ConfigManager.durabilityLore(); + List previousLore = item.lore().orElse(new ArrayList<>()); + List newLore = new ArrayList<>(); + for (String previous : previousLore) { + Component component = AdventureHelper.jsonToComponent(previous); + if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) { + if (scoreComponent.objective().equals("durability")) { + continue; + } + } + newLore.add(previous); + } + for (String lore : durabilityLore) { + ScoreComponent.Builder builder = Component.score().name("cf").objective("durability"); + builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(customMaxDamage - value)).replace("{max}", String.valueOf(customMaxDamage)))); + newLore.add(AdventureHelper.componentToJson(builder.build())); + } + item.lore(newLore); + } + + @Override + public int damage() { + int customMaxDamage = (int) item.getTag("CustomFishing", "max_dur").get(); + return customMaxDamage - (int) item.getTag("CustomFishing", "cur_dur").orElse(0); + } + + @Override + public int maxDamage() { + return (int) item.getTag("CustomFishing", "max_dur").get(); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/DurabilityItem.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/DurabilityItem.java new file mode 100644 index 00000000..944f7d4a --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/DurabilityItem.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item.damage; + +public interface DurabilityItem { + + void damage(int value); + + int damage(); + + int maxDamage(); +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/VanillaItemImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/VanillaDurabilityItem.java similarity index 58% rename from plugin/src/main/java/net/momirealms/customfishing/compatibility/item/VanillaItemImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/VanillaDurabilityItem.java index d502b47a..e22db0e7 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/VanillaItemImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/damage/VanillaDurabilityItem.java @@ -15,29 +15,31 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.compatibility.item; +package net.momirealms.customfishing.bukkit.item.damage; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; -import org.bukkit.Material; -import org.bukkit.entity.Player; +import net.momirealms.customfishing.common.item.Item; import org.bukkit.inventory.ItemStack; -import java.util.Locale; +public class VanillaDurabilityItem implements DurabilityItem { -public class VanillaItemImpl implements ItemLibrary { + private final Item item; - @Override - public String identification() { - return "vanilla"; + public VanillaDurabilityItem(Item item) { + this.item = item; } @Override - public ItemStack buildItem(Player player, String id) { - return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH))); + public void damage(int value) { + item.damage(value); } @Override - public String getItemID(ItemStack itemStack) { - return itemStack.getType().name(); + public int damage() { + return item.damage().orElse(0); + } + + @Override + public int maxDamage() { + return item.maxDamage().get(); } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/ComponentItemFactory.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/ComponentItemFactory.java new file mode 100644 index 00000000..d6103dca --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/ComponentItemFactory.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item.impl; + +import com.saicone.rtag.RtagItem; +import com.saicone.rtag.data.ComponentType; +import net.momirealms.customfishing.bukkit.item.BukkitItemFactory; +import net.momirealms.customfishing.common.item.ComponentKeys; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Key; +import net.momirealms.sparrow.heart.SparrowHeart; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@SuppressWarnings("UnstableApiUsage") +public class ComponentItemFactory extends BukkitItemFactory { + + public ComponentItemFactory(CustomFishingPlugin plugin) { + super(plugin); + } + + @Override + protected void customModelData(RtagItem item, Integer data) { + if (data == null) { + item.removeComponent(ComponentKeys.CUSTOM_MODEL_DATA); + } else { + item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA, data); + } + } + + @Override + protected Optional customModelData(RtagItem item) { + if (!item.hasComponent(ComponentKeys.CUSTOM_MODEL_DATA)) return Optional.empty(); + return Optional.ofNullable( + (Integer) ComponentType.encodeJava( + ComponentKeys.CUSTOM_MODEL_DATA, + item.getComponent(ComponentKeys.CUSTOM_MODEL_DATA) + ).orElse(null) + ); + } + + @Override + protected void displayName(RtagItem item, String json) { + if (json == null) { + item.removeComponent(ComponentKeys.CUSTOM_NAME); + } else { + item.setComponent(ComponentKeys.CUSTOM_NAME, json); + } + } + + @Override + protected Optional displayName(RtagItem item) { + if (!item.hasComponent(ComponentKeys.CUSTOM_NAME)) return Optional.empty(); + return Optional.ofNullable( + (String) ComponentType.encodeJava( + ComponentKeys.CUSTOM_NAME, + item.getComponent(ComponentKeys.CUSTOM_NAME) + ).orElse(null) + ); + } + + @Override + protected void skull(RtagItem item, String skullData) { + final Map profile = Map.of( + "properties", List.of( + Map.of( + "name", "textures", + "value", skullData + ) + ) + ); + item.setComponent("minecraft:profile", profile); + } + + @SuppressWarnings("unchecked") + @Override + protected Optional> lore(RtagItem item) { + if (!item.hasComponent(ComponentKeys.LORE)) return Optional.empty(); + return Optional.ofNullable( + (List) ComponentType.encodeJava( + ComponentKeys.LORE, + item.getComponent(ComponentKeys.LORE) + ).orElse(null) + ); + } + + @Override + protected void lore(RtagItem item, List lore) { + if (lore == null || lore.isEmpty()) { + item.removeComponent(ComponentKeys.LORE); + } else { + item.setComponent(ComponentKeys.LORE, lore); + } + } + + @Override + protected boolean unbreakable(RtagItem item) { + return item.isUnbreakable(); + } + + @Override + protected void unbreakable(RtagItem item, boolean unbreakable) { + item.setUnbreakable(unbreakable); + } + + @Override + protected Optional glint(RtagItem item) { + return Optional.ofNullable((Boolean) item.getComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE)); + } + + @Override + protected void glint(RtagItem item, Boolean glint) { + item.setComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE, glint); + } + + @Override + protected Optional damage(RtagItem item) { + if (!item.hasComponent(ComponentKeys.DAMAGE)) return Optional.empty(); + return Optional.ofNullable( + (Integer) ComponentType.encodeJava( + ComponentKeys.DAMAGE, + item.getComponent(ComponentKeys.DAMAGE) + ).orElse(null) + ); + } + + @Override + protected void damage(RtagItem item, Integer damage) { + if (damage == null) damage = 0; + item.setComponent(ComponentKeys.DAMAGE, damage); + } + + @Override + protected Optional maxDamage(RtagItem item) { + if (!item.hasComponent(ComponentKeys.MAX_DAMAGE)) return Optional.of((int) item.getItem().getType().getMaxDurability()); + return Optional.ofNullable( + (Integer) ComponentType.encodeJava( + ComponentKeys.MAX_DAMAGE, + item.getComponent(ComponentKeys.MAX_DAMAGE) + ).orElse(null) + ); + } + + @Override + protected void maxDamage(RtagItem item, Integer damage) { + if (damage == null) { + item.removeComponent(ComponentKeys.MAX_DAMAGE); + } else { + item.setComponent(ComponentKeys.MAX_DAMAGE, damage); + } + } + + @Override + protected void enchantments(RtagItem item, Map enchantments) { + Map enchants = new HashMap<>(); + for (Map.Entry entry : enchantments.entrySet()) { + enchants.put(entry.getKey().toString(), Integer.valueOf(entry.getValue())); + } + item.setComponent(ComponentKeys.ENCHANTMENTS, enchants); + } + + @Override + protected void storedEnchantments(RtagItem item, Map enchantments) { + Map enchants = new HashMap<>(); + for (Map.Entry entry : enchantments.entrySet()) { + enchants.put(entry.getKey().toString(), Integer.valueOf(entry.getValue())); + } + item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, enchants); + } + + @Override + protected void addEnchantment(RtagItem item, Key enchantment, int level) { + Object enchant = item.getComponent(ComponentKeys.ENCHANTMENTS); + Map map = SparrowHeart.getInstance().itemEnchantmentsToMap(enchant); + map.put(enchantment.toString(), level); + item.setComponent(ComponentKeys.ENCHANTMENTS, map); + } + + @Override + protected void addStoredEnchantment(RtagItem item, Key enchantment, int level) { + Object enchant = item.getComponent(ComponentKeys.STORED_ENCHANTMENTS); + Map map = SparrowHeart.getInstance().itemEnchantmentsToMap(enchant); + map.put(enchantment.toString(), level); + item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, map); + } + + @Override + protected void itemFlags(RtagItem item, List flags) { + throw new UnsupportedOperationException("This feature is not available on 1.20.5+"); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/UniversalItemFactory.java b/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/UniversalItemFactory.java new file mode 100644 index 00000000..0f8d8638 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/item/impl/UniversalItemFactory.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.item.impl; + +import com.saicone.rtag.RtagItem; +import com.saicone.rtag.tag.TagBase; +import com.saicone.rtag.tag.TagCompound; +import com.saicone.rtag.tag.TagList; +import net.momirealms.customfishing.bukkit.item.BukkitItemFactory; +import net.momirealms.customfishing.common.plugin.CustomFishingPlugin; +import net.momirealms.customfishing.common.util.Key; +import org.bukkit.inventory.ItemFlag; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class UniversalItemFactory extends BukkitItemFactory { + + public UniversalItemFactory(CustomFishingPlugin plugin) { + super(plugin); + } + + @Override + protected void displayName(RtagItem item, String json) { + if (json != null) { + item.set(json, "display", "Name"); + } else { + item.remove("display", "Name"); + } + } + + @Override + protected Optional displayName(RtagItem item) { + if (!item.hasTag("display", "Name")) return Optional.empty(); + return Optional.of(item.get("display", "Name")); + } + + @Override + protected void customModelData(RtagItem item, Integer data) { + if (data == null) { + item.remove("CustomModelData"); + } else { + item.set(data, "CustomModelData"); + } + } + + @Override + protected Optional customModelData(RtagItem item) { + if (!item.hasTag("CustomModelData")) return Optional.empty(); + return Optional.of(item.get("CustomModelData")); + } + + @Override + protected void skull(RtagItem item, String skullData) { + if (skullData == null) { + item.remove("SkullOwner"); + } else { + item.set(List.of(Map.of("Value", skullData)), "SkullOwner", "Properties", "textures"); + } + } + + @Override + protected Optional> lore(RtagItem item) { + if (!item.hasTag("display", "Lore")) return Optional.empty(); + return Optional.of(item.get("display", "Lore")); + } + + @Override + protected void lore(RtagItem item, List lore) { + if (lore == null || lore.isEmpty()) { + item.remove("display", "Lore"); + } else { + item.set(lore, "display", "Lore"); + } + } + + @Override + protected boolean unbreakable(RtagItem item) { + return item.isUnbreakable(); + } + + @Override + protected void unbreakable(RtagItem item, boolean unbreakable) { + item.setUnbreakable(unbreakable); + } + + @Override + protected Optional glint(RtagItem item) { + return Optional.of(false); + } + + @Override + protected void glint(RtagItem item, Boolean glint) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected Optional damage(RtagItem item) { + if (!item.hasTag("Damage")) return Optional.empty(); + return Optional.of(item.get("Damage")); + } + + @Override + protected void damage(RtagItem item, Integer damage) { + item.set(damage, "Damage"); + } + + @Override + protected Optional maxDamage(RtagItem item) { +// if (!item.hasTag("CustomFishing", "max_dur")) return Optional.empty(); +// return Optional.of(item.get("CustomFishing", "max_dur")); + return Optional.of((int) item.getItem().getType().getMaxDurability()); + } + + @Override + protected void maxDamage(RtagItem item, Integer damage) { +// if (damage == null) { +// item.remove("CustomFishing", "max_dur"); +// } else { +// item.set(damage, "CustomFishing", "max_dur"); +// } + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected void enchantments(RtagItem item, Map enchantments) { + ArrayList tags = new ArrayList<>(); + for (Map.Entry entry : enchantments.entrySet()) { + tags.add((Map.of("id", entry.getKey().toString(), "lvl", entry.getValue()))); + } + item.set(tags, "Enchantments"); + } + + @Override + protected void storedEnchantments(RtagItem item, Map enchantments) { + ArrayList tags = new ArrayList<>(); + for (Map.Entry entry : enchantments.entrySet()) { + tags.add((Map.of("id", entry.getKey().toString(), "lvl", entry.getValue()))); + } + item.set(tags, "StoredEnchantments"); + } + + @Override + protected void addEnchantment(RtagItem item, Key enchantment, int level) { + Object enchantments = item.getExact("Enchantments"); + if (enchantments != null) { + for (Object enchant : TagList.getValue(enchantments)) { + if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) { + TagCompound.set(enchant, "lvl", TagBase.newTag(level)); + return; + } + } + item.add(Map.of("id", enchantment.toString(), "lvl", (short) level), "Enchantments"); + } else { + item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) level)), "Enchantments"); + } + } + + @Override + protected void addStoredEnchantment(RtagItem item, Key enchantment, int level) { + Object enchantments = item.getExact("StoredEnchantments"); + if (enchantments != null) { + for (Object enchant : TagList.getValue(enchantments)) { + if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) { + TagCompound.set(enchant, "lvl", TagBase.newTag(level)); + return; + } + } + item.add(Map.of("id", enchantment.toString(), "lvl", (short) level), "StoredEnchantments"); + } else { + item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) level)), "StoredEnchantments"); + } + } + + @Override + protected void itemFlags(RtagItem item, List flags) { + if (flags == null || flags.isEmpty()) { + item.remove("HideFlags"); + return; + } + int f = 0; + for (String flag : flags) { + ItemFlag itemFlag = ItemFlag.valueOf(flag); + f = f | 1 << itemFlag.ordinal(); + } + item.set(f, "HideFlags"); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/loot/BukkitLootManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/loot/BukkitLootManager.java new file mode 100644 index 00000000..baf31ac1 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/loot/BukkitLootManager.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.loot; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.Effect; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootManager; +import net.momirealms.customfishing.api.mechanic.requirement.ConditionalElement; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.customfishing.common.util.WeightUtils; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; +import java.util.function.BiFunction; + +@SuppressWarnings("DuplicatedCode") +public class BukkitLootManager implements LootManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap lootMap = new HashMap<>(); + private final HashMap> groupMembersMap = new HashMap<>(); + private final LinkedHashMap, Double, Double>>>, Player>> lootConditions = new LinkedHashMap<>(); + + public BukkitLootManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void unload() { + this.lootMap.clear(); + this.groupMembersMap.clear(); + this.lootConditions.clear(); + } + + @Override + public void load() { + plugin.debug("Loaded " + lootMap.size() + " loots"); + for (Map.Entry> entry : groupMembersMap.entrySet()) { + plugin.debug("Group: {" + entry.getKey() + "} Members: " + entry.getValue()); + } + File file = new File(plugin.getDataFolder(), "loot-conditions.yml"); + if (!file.exists()) { + plugin.getBoostrap().saveResource("loot-conditions.yml", false); + } + YamlDocument lootConditionsConfig = plugin.getConfigManager().loadData(file); + for (Map.Entry entry : lootConditionsConfig.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section section) { + lootConditions.put(entry.getKey(), parseLootConditions(section)); + } + } + } + + private ConditionalElement, Double, Double>>>, Player> parseLootConditions(Section section) { + Section subSection = section.getSection("sub-groups"); + if (subSection == null) { + return new ConditionalElement<>( + plugin.getConfigManager().parseWeightOperation(section.getStringList("list")), + Map.of(), + plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false) + ); + } else { + HashMap, Double, Double>>>, Player>> subElements = new HashMap<>(); + for (Map.Entry entry : subSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + subElements.put(entry.getKey(), parseLootConditions(innerSection)); + } + } + return new ConditionalElement<>( + plugin.getConfigManager().parseWeightOperation(section.getStringList("list")), + subElements, + plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false) + ); + } + } + + @Override + public boolean registerLoot(@NotNull Loot loot) { + if (lootMap.containsKey(loot.id())) return false; + this.lootMap.put(loot.id(), loot); + for (String group : loot.lootGroup()) { + addGroupMember(group, loot.id()); + } + return true; + } + + private void addGroupMember(String group, String member) { + List members = groupMembersMap.get(group); + if (members == null) { + members = new ArrayList<>(List.of(member)); + groupMembersMap.put(group, members); + } else { + members.add(member); + } + } + + @NotNull + @Override + public List getGroupMembers(String key) { + return Optional.ofNullable(groupMembersMap.get(key)).orElse(List.of()); + } + + @NotNull + @Override + public Optional getLoot(String key) { + return Optional.ofNullable(lootMap.get(key)); + } + + @Override + public HashMap getWeightedLoots(Effect effect, Context context) { + HashMap lootWeightMap = new HashMap<>(); + for (ConditionalElement, Double, Double>>>, Player> conditionalElement : lootConditions.values()) { + modifyWeightMap(lootWeightMap, context, conditionalElement); + } + for (Pair, Double, Double>> pair : effect.weightOperations()) { + double previous = lootWeightMap.getOrDefault(pair.left(), 0d); + if (previous > 0) + lootWeightMap.put(pair.left(), pair.right().apply(context, previous)); + } + for (Pair, Double, Double>> pair : effect.weightOperationsIgnored()) { + double previous = lootWeightMap.getOrDefault(pair.left(), 0d); + lootWeightMap.put(pair.left(), pair.right().apply(context, previous)); + } + return lootWeightMap; + } + + @Nullable + @Override + public Loot getNextLoot(Effect effect, Context context) { + HashMap lootWeightMap = new HashMap<>(); + for (ConditionalElement, Double, Double>>>, Player> conditionalElement : lootConditions.values()) { + modifyWeightMap(lootWeightMap, context, conditionalElement); + } + for (Pair, Double, Double>> pair : effect.weightOperations()) { + double previous = lootWeightMap.getOrDefault(pair.left(), 0d); + if (previous > 0) + lootWeightMap.put(pair.left(), pair.right().apply(context, previous)); + } + for (Pair, Double, Double>> pair : effect.weightOperationsIgnored()) { + double previous = lootWeightMap.getOrDefault(pair.left(), 0d); + lootWeightMap.put(pair.left(), pair.right().apply(context, previous)); + } + String lootID = WeightUtils.getRandom(lootWeightMap); + return Optional.ofNullable(lootID) + .map(id -> getLoot(lootID).orElseThrow(() -> new RuntimeException("Could not find loot " + lootID))) + .orElseThrow(() -> new RuntimeException("No loot available. " + context)); + } + + private void modifyWeightMap(Map weightMap, Context context, ConditionalElement, Double, Double>>>, Player> conditionalElement) { + if (conditionalElement == null) return; + if (RequirementManager.isSatisfied(context, conditionalElement.getRequirements())) { + for (Pair, Double, Double>> modifierPair : conditionalElement.getElement()) { + double previous = weightMap.getOrDefault(modifierPair.left(), 0d); + weightMap.put(modifierPair.left(), modifierPair.right().apply(context, previous)); + } + for (ConditionalElement, Double, Double>>>, Player> sub : conditionalElement.getSubElements().values()) { + modifyWeightMap(weightMap, context, sub); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/market/BukkitMarketManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/market/BukkitMarketManager.java new file mode 100644 index 00000000..d6ab950b --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/market/BukkitMarketManager.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.market; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.config.GUIItemParser; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem; +import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder; +import net.momirealms.customfishing.api.mechanic.market.MarketManager; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.api.storage.data.EarningData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.bukkit.config.BukkitConfigManager; +import net.momirealms.customfishing.bukkit.item.BukkitItemFactory; +import net.momirealms.customfishing.common.item.Item; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.ShulkerBox; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.*; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.BundleMeta; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("DuplicatedCode") +public class BukkitMarketManager implements MarketManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + + private final HashMap> priceMap; + private String formula; + private MathValue earningsLimit; + private boolean allowItemWithNoPrice; + protected boolean sellFishingBag; + + protected TextValue title; + protected String[] layout; + protected final HashMap decorativeIcons; + protected final ConcurrentHashMap marketGUICache; + + protected char itemSlot; + protected char sellSlot; + protected char sellAllSlot; + + protected CustomFishingItem sellIconAllowItem; + protected CustomFishingItem sellIconDenyItem; + protected CustomFishingItem sellIconLimitItem; + protected CustomFishingItem sellAllIconAllowItem; + protected CustomFishingItem sellAllIconDenyItem; + protected CustomFishingItem sellAllIconLimitItem; + protected Action[] sellDenyActions; + protected Action[] sellAllowActions; + protected Action[] sellLimitActions; + protected Action[] sellAllDenyActions; + protected Action[] sellAllAllowActions; + protected Action[] sellAllLimitActions; + + private SchedulerTask resetEarningsTask; + private int cachedDate; + + private boolean allowBundle; + private boolean allowShulkerBox; + + public BukkitMarketManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.priceMap = new HashMap<>(); + this.decorativeIcons = new HashMap<>(); + this.marketGUICache = new ConcurrentHashMap<>(); + this.cachedDate = getRealTimeDate(); + } + + @Override + public void load() { + this.loadConfig(); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + this.resetEarningsTask = plugin.getScheduler().asyncRepeating(() -> { + int now = getRealTimeDate(); + if (this.cachedDate != now) { + this.cachedDate = now; + for (UserData userData : plugin.getStorageManager().getOnlineUsers()) { + userData.earningData().refresh(); + } + } + }, 1, 1, TimeUnit.SECONDS); + } + + private int getRealTimeDate() { + Calendar calendar = Calendar.getInstance(); + return (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + this.priceMap.clear(); + this.decorativeIcons.clear(); + if (this.resetEarningsTask != null) + this.resetEarningsTask.cancel(); + } + + private void loadConfig() { + Section config = BukkitConfigManager.getMainConfig().getSection("mechanics.market"); + + this.formula = config.getString("price-formula", "{base} + {bonus} * {size}"); + this.layout = config.getStringList("layout").toArray(new String[0]); + this.title = TextValue.auto(config.getString("title", "market.title")); + this.itemSlot = config.getString("item-slot.symbol", "I").charAt(0); + this.allowItemWithNoPrice = config.getBoolean("item-slot.allow-items-with-no-price", true); + this.allowBundle = config.getBoolean("allow-bundle", true); + this.allowShulkerBox = config.getBoolean("allow-shulker-box", true); + + Section sellAllSection = config.getSection("sell-all-icons"); + if (sellAllSection != null) { + this.sellAllSlot = sellAllSection.getString("symbol", "S").charAt(0); + this.sellFishingBag = sellAllSection.getBoolean("fishingbag", true); + + this.sellAllIconAllowItem = new GUIItemParser("allow", sellAllSection.getSection("allow-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + this.sellAllIconDenyItem = new GUIItemParser("deny", sellAllSection.getSection("deny-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + this.sellAllIconLimitItem = new GUIItemParser("limit", sellAllSection.getSection("limit-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + + this.sellAllAllowActions = plugin.getActionManager().parseActions(sellAllSection.getSection("allow-icon.action")); + this.sellAllDenyActions = plugin.getActionManager().parseActions(sellAllSection.getSection("deny-icon.action")); + this.sellAllLimitActions = plugin.getActionManager().parseActions(sellAllSection.getSection("limit-icon.action")); + } + + Section sellSection = config.getSection("sell-icons"); + if (sellSection == null) { + // for old config compatibility + sellSection = config.getSection("functional-icons"); + } + if (sellSection != null) { + this.sellSlot = sellSection.getString("symbol", "B").charAt(0); + + this.sellIconAllowItem = new GUIItemParser("allow", sellSection.getSection("allow-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + this.sellIconDenyItem = new GUIItemParser("deny", sellSection.getSection("deny-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + this.sellIconLimitItem = new GUIItemParser("limit", sellSection.getSection("limit-icon"), plugin.getConfigManager().getFormatFunctions()).getItem(); + + this.sellAllowActions = plugin.getActionManager().parseActions(sellSection.getSection("allow-icon.action")); + this.sellDenyActions = plugin.getActionManager().parseActions(sellSection.getSection("deny-icon.action")); + this.sellLimitActions = plugin.getActionManager().parseActions(sellSection.getSection("limit-icon.action")); + } + + this.earningsLimit = config.getBoolean("limitation.enable", true) ? MathValue.auto(config.getString("limitation.earnings", "10000")) : MathValue.plain(-1); + + // Load item prices from the configuration + Section priceSection = config.getSection("item-price"); + if (priceSection != null) { + for (Map.Entry entry : priceSection.getStringRouteMappedValues(false).entrySet()) { + this.priceMap.put(entry.getKey(), MathValue.auto(entry.getValue())); + } + } + + // Load decorative icons from the configuration + Section decorativeSection = config.getSection("decorative-icons"); + if (decorativeSection != null) { + for (Map.Entry entry : decorativeSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section innerSection) { + char symbol = Objects.requireNonNull(innerSection.getString("symbol")).charAt(0); + decorativeIcons.put(symbol, new GUIItemParser("gui", innerSection, plugin.getConfigManager().getFormatFunctions()).getItem()); + } + } + } + } + + /** + * Open the market GUI for a player + * + * @param player player + */ + @Override + public boolean openMarketGUI(Player player) { + Optional optionalUserData = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); + if (optionalUserData.isEmpty()) { + plugin.getPluginLogger().warn("Player " + player.getName() + "'s market data has not been loaded yet."); + return false; + } + Context context = Context.player(player); + MarketGUI gui = new MarketGUI(this, context, optionalUserData.get().earningData()); + gui.addElement(new MarketGUIElement(itemSlot, new ItemStack(Material.AIR))); + gui.addElement(new MarketDynamicGUIElement(sellSlot, new ItemStack(Material.AIR))); + gui.addElement(new MarketDynamicGUIElement(sellAllSlot, new ItemStack(Material.AIR))); + for (Map.Entry entry : decorativeIcons.entrySet()) { + gui.addElement(new MarketGUIElement(entry.getKey(), entry.getValue().build(context))); + } + gui.build().refresh().show(); + marketGUICache.put(player.getUniqueId(), gui); + return true; + } + + /** + * This method handles the closing of an inventory. + * + * @param event The InventoryCloseEvent that triggered this method. + */ + @EventHandler + public void onCloseInv(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player player)) + return; + if (!(event.getInventory().getHolder() instanceof MarketGUIHolder)) + return; + MarketGUI gui = marketGUICache.remove(player.getUniqueId()); + if (gui != null) + gui.returnItems(); + } + + /** + * This method handles a player quitting the server. + * + * @param event The PlayerQuitEvent that triggered this method. + */ + @EventHandler + public void onQuit(PlayerQuitEvent event) { + MarketGUI gui = marketGUICache.remove(event.getPlayer().getUniqueId()); + if (gui != null) + gui.returnItems(); + } + + /** + * This method handles dragging items in an inventory. + * + * @param event The InventoryDragEvent that triggered this method. + */ + @EventHandler + public void onDragInv(InventoryDragEvent event) { + if (event.isCancelled()) + return; + Inventory inventory = event.getInventory(); + if (!(inventory.getHolder() instanceof MarketGUIHolder)) + return; + Player player = (Player) event.getWhoClicked(); + MarketGUI gui = marketGUICache.get(player.getUniqueId()); + if (gui == null) { + event.setCancelled(true); + player.closeInventory(); + return; + } + + MarketGUIElement element = gui.getElement(itemSlot); + if (element == null) { + event.setCancelled(true); + return; + } + + List slots = element.getSlots(); + for (int dragSlot : event.getRawSlots()) { + if (!slots.contains(dragSlot)) { + event.setCancelled(true); + return; + } + } + + plugin.getScheduler().sync().runLater(gui::refresh, 1, player.getLocation()); + } + + /** + * This method handles inventory click events. + * + * @param event The InventoryClickEvent that triggered this method. + */ + @EventHandler (ignoreCancelled = true) + public void onClickInv(InventoryClickEvent event) { + Inventory clickedInv = event.getClickedInventory(); + if (clickedInv == null) return; + + Player player = (Player) event.getWhoClicked(); + + // Check if the clicked inventory is a MarketGUI + if (!(event.getInventory().getHolder() instanceof MarketGUIHolder)) + return; + + MarketGUI gui = marketGUICache.get(player.getUniqueId()); + if (gui == null) { + event.setCancelled(true); + player.closeInventory(); + return; + } + + EarningData earningData = gui.earningData; + earningData.refresh(); + double earningLimit = earningLimit(gui.context); + + if (clickedInv != player.getInventory()) { + int slot = event.getSlot(); + MarketGUIElement element = gui.getElement(slot); + if (element == null) { + event.setCancelled(true); + return; + } + + if (element.getSymbol() == itemSlot) { + if (!allowItemWithNoPrice) { + if (event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD || event.getAction() == InventoryAction.HOTBAR_SWAP) { + ItemStack moved = player.getInventory().getItem(event.getHotbarButton()); + double price = getItemPrice(gui.context, moved); + if (price <= 0) { + event.setCancelled(true); + return; + } + } + } + } else { + event.setCancelled(true); + } + + if (element.getSymbol() == sellSlot) { + + Pair pair = getItemsToSell(gui.context, gui.getItemsInGUI()); + double totalWorth = pair.right(); + gui.context.arg(ContextKeys.MONEY, money(totalWorth)) + .arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth)) + .arg(ContextKeys.REST, money(earningLimit - earningData.earnings)) + .arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))) + .arg(ContextKeys.SOLD_ITEM_AMOUNT, pair.left()); + + if (totalWorth > 0) { + if (earningLimit != -1 && (earningLimit - earningData.earnings) < totalWorth) { + // Can't earn more money + ActionManager.trigger(gui.context, sellLimitActions); + } else { + // Clear items and update earnings + clearWorthyItems(gui.context, gui.getItemsInGUI()); + earningData.earnings += totalWorth; + gui.context.arg(ContextKeys.REST, money(earningLimit - earningData.earnings)); + gui.context.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))); + ActionManager.trigger(gui.context, sellAllowActions); + } + } else { + // Nothing to sell + ActionManager.trigger(gui.context, sellDenyActions); + } + } else if (element.getSymbol() == sellAllSlot) { + ArrayList itemStacksToSell = new ArrayList<>(List.of(gui.context.getHolder().getInventory().getStorageContents())); + if (sellFishingBag) { + Optional optionalUserData = BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(gui.context.getHolder().getUniqueId()); + optionalUserData.ifPresent(userData -> itemStacksToSell.addAll(List.of(userData.holder().getInventory().getStorageContents()))); + } + Pair pair = getItemsToSell(gui.context, itemStacksToSell); + double totalWorth = pair.right(); + gui.context.arg(ContextKeys.MONEY, money(totalWorth)) + .arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth)) + .arg(ContextKeys.REST, money(earningLimit - earningData.earnings)) + .arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))) + .arg(ContextKeys.SOLD_ITEM_AMOUNT, pair.left()); + + if (totalWorth > 0) { + if (earningLimit != -1 && (earningLimit - earningData.earnings) < totalWorth) { + // Can't earn more money + ActionManager.trigger(gui.context, sellAllLimitActions); + } else { + // Clear items and update earnings + clearWorthyItems(gui.context, itemStacksToSell); + earningData.earnings += totalWorth; + gui.context.arg(ContextKeys.REST, money(earningLimit - earningData.earnings)); + gui.context.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))); + ActionManager.trigger(gui.context, sellAllAllowActions); + } + } else { + // Nothing to sell + ActionManager.trigger(gui.context, sellAllDenyActions); + } + } + } else { + // Handle interactions with the player's inventory + ItemStack current = event.getCurrentItem(); + if (!allowItemWithNoPrice) { + double price = getItemPrice(gui.context, current); + if (price <= 0) { + event.setCancelled(true); + return; + } + } + + if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT) + && (current != null && current.getType() != Material.AIR)) { + event.setCancelled(true); + MarketGUIElement element = gui.getElement(itemSlot); + if (element == null) return; + for (int slot : element.getSlots()) { + ItemStack itemStack = gui.inventory.getItem(slot); + if (itemStack != null && itemStack.getType() != Material.AIR) { + if (current.getType() == itemStack.getType() + && itemStack.getAmount() != itemStack.getType().getMaxStackSize() + && current.getItemMeta().equals(itemStack.getItemMeta()) + ) { + int left = itemStack.getType().getMaxStackSize() - itemStack.getAmount(); + if (current.getAmount() <= left) { + itemStack.setAmount(itemStack.getAmount() + current.getAmount()); + current.setAmount(0); + break; + } else { + current.setAmount(current.getAmount() - left); + itemStack.setAmount(itemStack.getType().getMaxStackSize()); + } + } + } else { + gui.inventory.setItem(slot, current.clone()); + current.setAmount(0); + break; + } + } + } + } + + // Refresh the GUI + plugin.getScheduler().sync().runLater(gui::refresh, 1, player.getLocation()); + } + + @Override + @SuppressWarnings("UnstableApiUsage") + public double getItemPrice(Context context, ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR) + return 0; + + Item wrapped = ((BukkitItemFactory) plugin.getItemManager().getFactory()).wrap(itemStack); + double price = (double) wrapped.getTag("Price").orElse(0d); + if (price != 0) { + // If a custom price is defined in the ItemStack's NBT data, use it. + return price * itemStack.getAmount(); + } + + if (allowBundle && itemStack.getItemMeta() instanceof BundleMeta bundleMeta) { + Pair pair = getItemsToSell(context, bundleMeta.getItems()); + return pair.right(); + } + + if (allowShulkerBox && itemStack.getItemMeta() instanceof BlockStateMeta stateMeta) { + if (stateMeta.getBlockState() instanceof ShulkerBox shulkerBox) { + Pair pair = getItemsToSell(context, Arrays.stream(shulkerBox.getInventory().getStorageContents()).filter(Objects::nonNull).toList()); + return pair.right(); + } + } + + // If no custom price is defined, attempt to fetch the price from a predefined price map. + String itemID = itemStack.getType().name(); + Optional optionalCMD = wrapped.customModelData(); + if (optionalCMD.isPresent()) { + itemID = itemID + ":" + optionalCMD.get(); + } + + MathValue formula = priceMap.get(itemID); + if (formula == null) return 0; + + return formula.evaluate(context) * itemStack.getAmount(); + } + + @Override + public String getFormula() { + return formula; + } + + @Override + public double earningLimit(Context context) { + return earningsLimit.evaluate(context); + } + + public Pair getItemsToSell(Context context, List itemStacks) { + int amount = 0; + double worth = 0d; + for (ItemStack itemStack : itemStacks) { + double price = getItemPrice(context, itemStack); + if (price > 0 && itemStack != null) { + amount += itemStack.getAmount(); + worth += price; + } + } + return Pair.of(amount, worth); + } + + @SuppressWarnings("UnstableApiUsage") + public void clearWorthyItems(Context context, List itemStacks) { + for (ItemStack itemStack : itemStacks) { + double price = getItemPrice(context, itemStack); + if (price > 0 && itemStack != null) { + if (allowBundle && itemStack.getItemMeta() instanceof BundleMeta bundleMeta) { + clearWorthyItems(context, bundleMeta.getItems()); + itemStack.setItemMeta(bundleMeta); + continue; + } + if (allowShulkerBox && itemStack.getItemMeta() instanceof BlockStateMeta stateMeta) { + if (stateMeta.getBlockState() instanceof ShulkerBox shulkerBox) { + clearWorthyItems(context, Arrays.stream(shulkerBox.getInventory().getStorageContents()).filter(Objects::nonNull).toList()); + stateMeta.setBlockState(shulkerBox); + itemStack.setItemMeta(stateMeta); + continue; + } + } + itemStack.setAmount(0); + } + } + } + + protected String money(double money) { + String str = String.format("%.2f", money); + return str.replace(",", "."); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketDynamicGUIElement.java b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketDynamicGUIElement.java similarity index 94% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketDynamicGUIElement.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketDynamicGUIElement.java index f64e7556..0b3e4236 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketDynamicGUIElement.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketDynamicGUIElement.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.market; +package net.momirealms.customfishing.bukkit.market; import org.bukkit.inventory.ItemStack; diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUI.java b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUI.java new file mode 100644 index 00000000..3b103c47 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUI.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.market; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder; +import net.momirealms.customfishing.api.storage.data.EarningData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.bukkit.util.PlayerUtils; +import net.momirealms.customfishing.common.helper.AdventureHelper; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +@SuppressWarnings("DuplicatedCode") +public class MarketGUI { + + private final HashMap itemsCharMap; + private final HashMap itemsSlotMap; + private final BukkitMarketManager manager; + protected final Inventory inventory; + protected final Context context; + protected final EarningData earningData; + + public MarketGUI(BukkitMarketManager manager, Context context, EarningData earningData) { + this.manager = manager; + this.context = context; + this.earningData = earningData; + this.itemsCharMap = new HashMap<>(); + this.itemsSlotMap = new HashMap<>(); + var holder = new MarketGUIHolder(); + this.inventory = Bukkit.createInventory(holder, manager.layout.length * 9); + holder.setInventory(this.inventory); + } + + private void init() { + int line = 0; + for (String content : manager.layout) { + for (int index = 0; index < 9; index++) { + char symbol; + if (index < content.length()) symbol = content.charAt(index); + else symbol = ' '; + MarketGUIElement element = itemsCharMap.get(symbol); + if (element != null) { + element.addSlot(index + line * 9); + itemsSlotMap.put(index + line * 9, element); + } + } + line++; + } + for (Map.Entry entry : itemsSlotMap.entrySet()) { + this.inventory.setItem(entry.getKey(), entry.getValue().getItemStack().clone()); + } + } + + @SuppressWarnings("UnusedReturnValue") + public MarketGUI addElement(MarketGUIElement... elements) { + for (MarketGUIElement element : elements) { + itemsCharMap.put(element.getSymbol(), element); + } + return this; + } + + public MarketGUI build() { + init(); + return this; + } + + public void show() { + context.getHolder().openInventory(inventory); + SparrowHeart.getInstance().updateInventoryTitle(context.getHolder(), AdventureHelper.componentToJson(AdventureHelper.miniMessage(manager.title.render(context)))); + } + + @Nullable + public MarketGUIElement getElement(int slot) { + return itemsSlotMap.get(slot); + } + + @Nullable + public MarketGUIElement getElement(char slot) { + return itemsCharMap.get(slot); + } + + /** + * Refresh the GUI, updating the display based on current data. + * @return The MarketGUI instance. + */ + public MarketGUI refresh() { + double earningLimit = manager.earningLimit(context); + MarketDynamicGUIElement sellElement = (MarketDynamicGUIElement) getElement(manager.sellSlot); + if (sellElement != null && !sellElement.getSlots().isEmpty()) { + Pair pair = manager.getItemsToSell(context, getItemsInGUI()); + double totalWorth = pair.right(); + int soldAmount = pair.left(); + context.arg(ContextKeys.MONEY, manager.money(totalWorth)) + .arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth)) + .arg(ContextKeys.REST, manager.money(earningLimit - earningData.earnings)) + .arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))) + .arg(ContextKeys.SOLD_ITEM_AMOUNT, soldAmount); + if (totalWorth <= 0) { + sellElement.setItemStack(manager.sellIconDenyItem.build(context)); + } else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) { + sellElement.setItemStack(manager.sellIconLimitItem.build(context)); + } else { + sellElement.setItemStack(manager.sellIconAllowItem.build(context)); + } + } + + MarketDynamicGUIElement sellAllElement = (MarketDynamicGUIElement) getElement(manager.sellAllSlot); + if (sellAllElement != null && !sellAllElement.getSlots().isEmpty()) { + ArrayList itemStacksToSell = new ArrayList<>(List.of(context.getHolder().getInventory().getStorageContents())); + if (manager.sellFishingBag) { + Optional optionalUserData = BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(context.getHolder().getUniqueId()); + optionalUserData.ifPresent(userData -> itemStacksToSell.addAll(List.of(userData.holder().getInventory().getStorageContents()))); + } + Pair pair = manager.getItemsToSell(context, itemStacksToSell); + double totalWorth = pair.right(); + int soldAmount = pair.left(); + context.arg(ContextKeys.MONEY, manager.money(totalWorth)) + .arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth)) + .arg(ContextKeys.REST, manager.money(earningLimit - earningData.earnings)) + .arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings))) + .arg(ContextKeys.SOLD_ITEM_AMOUNT, soldAmount); + if (totalWorth <= 0) { + sellAllElement.setItemStack(manager.sellAllIconAllowItem.build(context)); + } else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) { + sellAllElement.setItemStack(manager.sellAllIconLimitItem.build(context)); + } else { + sellAllElement.setItemStack(manager.sellAllIconAllowItem.build(context)); + } + } + + for (Map.Entry entry : itemsSlotMap.entrySet()) { + if (entry.getValue() instanceof MarketDynamicGUIElement dynamicGUIElement) { + this.inventory.setItem(entry.getKey(), dynamicGUIElement.getItemStack().clone()); + } + } + return this; + } + + public List getItemsInGUI() { + MarketGUIElement itemElement = getElement(manager.itemSlot); + if (itemElement == null) return List.of(); + return itemElement.getSlots().stream().map(inventory::getItem).filter(Objects::nonNull).toList(); + } + + public int getEmptyItemSlot() { + MarketGUIElement itemElement = getElement(manager.itemSlot); + if (itemElement == null) { + return -1; + } + for (int slot : itemElement.getSlots()) { + ItemStack itemStack = inventory.getItem(slot); + if (itemStack == null || itemStack.getType() == Material.AIR) { + return slot; + } + } + return -1; + } + + public void returnItems() { + MarketGUIElement itemElement = getElement(manager.itemSlot); + if (itemElement == null) { + return; + } + for (int slot : itemElement.getSlots()) { + ItemStack itemStack = inventory.getItem(slot); + if (itemStack != null && itemStack.getType() != Material.AIR) { + PlayerUtils.giveItem(context.getHolder(), itemStack, itemStack.getAmount()); + inventory.setItem(slot, new ItemStack(Material.AIR)); + } + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUIElement.java b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUIElement.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUIElement.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUIElement.java index 774b12cb..6242d211 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUIElement.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/market/MarketGUIElement.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.market; +package net.momirealms.customfishing.bukkit.market; import org.bukkit.inventory.ItemStack; diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/requirement/BukkitRequirementManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/requirement/BukkitRequirementManager.java new file mode 100644 index 00000000..dff85941 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/requirement/BukkitRequirementManager.java @@ -0,0 +1,1210 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.requirement; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.integration.LevelerProvider; +import net.momirealms.customfishing.api.integration.SeasonProvider; +import net.momirealms.customfishing.api.mechanic.action.Action; +import net.momirealms.customfishing.api.mechanic.action.ActionManager; +import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.effect.EffectProperties; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.misc.season.Season; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.api.mechanic.requirement.*; +import net.momirealms.customfishing.api.util.MoonPhase; +import net.momirealms.customfishing.bukkit.integration.VaultHook; +import net.momirealms.customfishing.common.util.ClassUtils; +import net.momirealms.customfishing.common.util.ListUtils; +import net.momirealms.customfishing.common.util.Pair; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +import static java.util.Objects.requireNonNull; + +public class BukkitRequirementManager implements RequirementManager { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap> requirementFactoryMap = new HashMap<>(); + private static final String EXPANSION_FOLDER = "expansions/requirement"; + + public BukkitRequirementManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.registerBuiltInRequirements(); + } + + @Override + public void reload() { + this.loadExpansions(); + } + + @Override + public void disable() { + this.requirementFactoryMap.clear(); + } + + @Override + public boolean registerRequirement(@NotNull String type, @NotNull RequirementFactory requirementFactory) { + if (this.requirementFactoryMap.containsKey(type)) return false; + this.requirementFactoryMap.put(type, requirementFactory); + return true; + } + + @Override + public boolean unregisterRequirement(@NotNull String type) { + return this.requirementFactoryMap.remove(type) != null; + } + + @Nullable + @Override + public RequirementFactory getRequirementFactory(@NotNull String type) { + return requirementFactoryMap.get(type); + } + + @Override + public boolean hasRequirement(@NotNull String type) { + return requirementFactoryMap.containsKey(type); + } + + @NotNull + @Override + @SuppressWarnings("unchecked") + public Requirement[] parseRequirements(Section section, boolean runActions) { + List> requirements = new ArrayList<>(); + if (section != null) + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + String typeOrName = entry.getKey(); + if (hasRequirement(typeOrName)) { + requirements.add(parseRequirement(typeOrName, entry.getValue())); + } else { + requirements.add(parseRequirement(section.getSection(typeOrName), runActions)); + } + } + return requirements.toArray(new Requirement[0]); + } + + @NotNull + @Override + public Requirement parseRequirement(@NotNull Section section, boolean runActions) { + List> actionList = new ArrayList<>(); + if (runActions && section.contains("not-met-actions")) { + actionList.addAll(List.of(plugin.getActionManager().parseActions(requireNonNull(section.getSection("not-met-actions"))))); + } + String type = section.getString("type"); + if (type == null) { + plugin.getPluginLogger().warn("No requirement type found at " + section.getRouteAsString()); + return EmptyRequirement.INSTANCE; + } + var factory = getRequirementFactory(type); + if (factory == null) { + plugin.getPluginLogger().warn("Requirement type: " + type + " not exists"); + return EmptyRequirement.INSTANCE; + } + return factory.process(section.get("value"), actionList, runActions); + } + + @NotNull + @Override + public Requirement parseRequirement(@NotNull String type, @NotNull Object value) { + RequirementFactory factory = getRequirementFactory(type); + if (factory == null) { + plugin.getPluginLogger().warn("Requirement type: " + type + " doesn't exist."); + return EmptyRequirement.INSTANCE; + } + return factory.process(value); + } + + private void registerBuiltInRequirements() { + this.registerTimeRequirement(); + this.registerYRequirement(); + this.registerInWaterRequirement(); + this.registerInVoidRequirement(); + this.registerInLavaRequirement(); + this.registerAndRequirement(); + this.registerOrRequirement(); + this.registerGroupRequirement(); + this.registerRodRequirement(); + this.registerPAPIRequirement(); + this.registerSeasonRequirement(); + this.registerPermissionRequirement(); + this.registerMoonPhaseRequirement(); + this.registerCoolDownRequirement(); + this.registerDateRequirement(); + this.registerWeatherRequirement(); + this.registerBiomeRequirement(); + this.registerWorldRequirement(); + this.registerMoneyRequirement(); + this.registerLevelRequirement(); + this.registerRandomRequirement(); + this.registerIceFishingRequirement(); + this.registerOpenWaterRequirement(); + this.registerBaitRequirement(); + this.registerLootRequirement(); + this.registerSizeRequirement(); + this.registerLootTypeRequirement(); + this.registerHasStatsRequirement(); + this.registerHookRequirement(); + this.registerEnvironmentRequirement(); + this.registerListRequirement(); + this.registerInBagRequirement(); + this.registerCompetitionRequirement(); + this.registerPluginLevelRequirement(); + this.registerItemInHandRequirement(); + this.registerImpossibleRequirement(); + } + + private void registerImpossibleRequirement() { + registerRequirement("impossible", ((args, actions, runActions) -> context -> { + if (runActions) ActionManager.trigger(context, actions); + return false; + })); + } + + private void registerCompetitionRequirement() { + registerRequirement("competition", (args, actions, runActions) -> { + if (args instanceof Section section) { + boolean onCompetition = section.getBoolean("ongoing", true); + List ids = ListUtils.toList(section.get("id")); + return context -> { + if (ids.isEmpty()) { + if (plugin.getCompetitionManager().getOnGoingCompetition() != null == onCompetition) { + return true; + } + } else { + FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); + if (onCompetition) { + if (competition != null) + if (ids.contains(competition.getConfig().key())) + return true; + } else { + if (competition == null) + return true; + } + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at competition requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerInBagRequirement() { + registerRequirement("in-fishingbag", (args, actions, runActions) -> { + boolean arg = (boolean) args; + return context -> { + boolean inBag = Optional.ofNullable(context.arg(ContextKeys.IN_BAG)).orElse(false); + if (inBag == arg) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerItemInHandRequirement() { + registerRequirement("item-in-hand", (args, actions, runActions) -> { + if (args instanceof Section section) { + boolean mainOrOff = section.getString("hand","main").equalsIgnoreCase("main"); + int amount = section.getInt("amount", 1); + List items = ListUtils.toList(section.get("item")); + return context -> { + ItemStack itemStack = mainOrOff ? + context.getHolder().getInventory().getItemInMainHand() + : context.getHolder().getInventory().getItemInOffHand(); + String id = plugin.getItemManager().getItemID(itemStack); + if (items.contains(id) && itemStack.getAmount() >= amount) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at item-in-hand requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerPluginLevelRequirement() { + registerRequirement("plugin-level", (args, actions, runActions) -> { + if (args instanceof Section section) { + String pluginName = section.getString("plugin"); + int level = section.getInt("level"); + String target = section.getString("target"); + return context -> { + LevelerProvider levelerProvider = plugin.getIntegrationManager().getLevelerProvider(pluginName); + if (levelerProvider == null) { + plugin.getPluginLogger().warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."); + return true; + } + if (levelerProvider.getLevel(context.getHolder(), target) >= level) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plugin-level requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerTimeRequirement() { + registerRequirement("time", (args, actions, runActions) -> { + List list = ListUtils.toList(args); + List> timePairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long time = location.getWorld().getTime(); + for (Pair pair : timePairs) + if (time >= pair.left() && time <= pair.right()) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerYRequirement() { + registerRequirement("ypos", (args, actions, runActions) -> { + List list = ListUtils.toList(args); + List> posPairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Double.parseDouble(split[0]), Double.parseDouble(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + double y = location.getY(); + for (Pair pair : posPairs) + if (y >= pair.left() && y <= pair.right()) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerOrRequirement() { + registerRequirement("||", (args, actions, runActions) -> { + if (args instanceof Section section) { + Requirement[] requirements = parseRequirements(section, runActions); + return context -> { + for (Requirement requirement : requirements) + if (requirement.isSatisfied(context)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at || requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerAndRequirement() { + registerRequirement("&&", (args, actions, runActions) -> { + if (args instanceof Section section) { + Requirement[] requirements = parseRequirements(section, runActions); + return context -> { + outer: { + for (Requirement requirement : requirements) + if (!requirement.isSatisfied(context)) + break outer; + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at && requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerInWaterRequirement() { + registerRequirement("in-water", (args, actions, runActions) -> { + boolean inWater = (boolean) args; + return context -> { + boolean in_water = Optional.ofNullable(context.arg(ContextKeys.SURROUNDING)).orElse("").equals(EffectProperties.WATER_FISHING.key()); + if (in_water == inWater) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerInVoidRequirement() { + registerRequirement("in-void", (args, actions, runActions) -> { + boolean inVoid = (boolean) args; + return context -> { + boolean in_void = Optional.ofNullable(context.arg(ContextKeys.SURROUNDING)).orElse("").equals(EffectProperties.VOID_FISHING.key()); + if (in_void == inVoid) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerInLavaRequirement() { + // Deprecated requirement type + registerRequirement("lava-fishing", (args, actions, runActions) -> { + boolean inLava = (boolean) args; + if (!inLava) { + // in water + return context -> { + boolean in_water = Optional.ofNullable(context.arg(ContextKeys.SURROUNDING)).orElse("").equals(EffectProperties.WATER_FISHING.key()); + if (in_water) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } + // in lava + return context -> { + boolean in_lava = Optional.ofNullable(context.arg(ContextKeys.SURROUNDING)).orElse("").equals(EffectProperties.LAVA_FISHING.key()); + if (in_lava) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("in-lava", (args, actions, runActions) -> { + boolean inLava = (boolean) args; + return context -> { + boolean in_lava = Optional.ofNullable(context.arg(ContextKeys.SURROUNDING)).orElse("").equals(EffectProperties.LAVA_FISHING.key()); + if (in_lava == inLava) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerRodRequirement() { + registerRequirement("rod", (args, actions, runActions) -> { + HashSet rods = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.ROD); + if (rods.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!rod", (args, actions, runActions) -> { + HashSet rods = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.ROD); + if (!rods.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerGroupRequirement() { + registerRequirement("group", (args, actions, runActions) -> { + HashSet groups = new HashSet<>(ListUtils.toList(args)); + return context -> { + String lootID = context.arg(ContextKeys.ID); + Optional loot = plugin.getLootManager().getLoot(lootID); + if (loot.isEmpty()) return false; + String[] group = loot.get().lootGroup(); + if (group != null) + for (String x : group) + if (groups.contains(x)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!group", (args, actions, runActions) -> { + HashSet groups = new HashSet<>(ListUtils.toList(args)); + return context -> { + String lootID = context.arg(ContextKeys.ID); + Optional loot = plugin.getLootManager().getLoot(lootID); + if (loot.isEmpty()) return false; + String[] group = loot.get().lootGroup(); + if (group == null) + return true; + outer: { + for (String x : group) + if (groups.contains(x)) + break outer; + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerLootRequirement() { + registerRequirement("loot", (args, actions, runActions) -> { + HashSet arg = new HashSet<>(ListUtils.toList(args)); + return context -> { + String lootID = context.arg(ContextKeys.ID); + if (arg.contains(lootID)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!loot", (args, actions, runActions) -> { + HashSet arg = new HashSet<>(ListUtils.toList(args)); + return context -> { + String lootID = context.arg(ContextKeys.ID); + if (!arg.contains(lootID)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerHookRequirement() { + registerRequirement("hook", (args, actions, runActions) -> { + HashSet hooks = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.HOOK); + if (hooks.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!hook", (args, actions, runActions) -> { + HashSet hooks = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.HOOK); + if (!hooks.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("has-hook", (args, actions, runActions) -> { + boolean has = (boolean) args; + return context -> { + String id = context.arg(ContextKeys.HOOK); + if (id != null && has) return true; + if (id == null && !has) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerBaitRequirement() { + registerRequirement("bait", (args, actions, runActions) -> { + HashSet arg = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.BAIT); + if (arg.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!bait", (args, actions, runActions) -> { + HashSet arg = new HashSet<>(ListUtils.toList(args)); + return context -> { + String id = context.arg(ContextKeys.BAIT); + if (!arg.contains(id)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("has-bait", (args, actions, runActions) -> { + boolean has = (boolean) args; + return context -> { + String id = context.arg(ContextKeys.BAIT); + if (id != null && has) return true; + if (id == null && !has) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerSizeRequirement() { + registerRequirement("has-size", (args, actions, runActions) -> { + boolean has = (boolean) args; + return context -> { + float size = Optional.ofNullable(context.arg(ContextKeys.SIZE)).orElse(-1f); + if (size != -1 && has) return true; + if (size == -1 && !has) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerOpenWaterRequirement() { + registerRequirement("open-water", (args, actions, runActions) -> { + boolean openWater = (boolean) args; + return context -> { + boolean current = Optional.ofNullable(context.arg(ContextKeys.OPEN_WATER)).orElse(false); + if (openWater == current) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerHasStatsRequirement() { + registerRequirement("has-stats", (args, actions, runActions) -> { + boolean has = (boolean) args; + return context -> { + String loot = context.arg(ContextKeys.ID); + Optional lootInstance = plugin.getLootManager().getLoot(loot); + if (lootInstance.isPresent()) { + if (!lootInstance.get().disableStats() && has) return true; + if (lootInstance.get().disableStats() && !has) return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerLootTypeRequirement() { + registerRequirement("loot-type", (args, actions, runActions) -> { + List types = ListUtils.toList(args); + return context -> { + String loot = context.arg(ContextKeys.ID); + Optional lootInstance = plugin.getLootManager().getLoot(loot); + if (lootInstance.isPresent()) { + if (types.contains(lootInstance.get().type().name().toLowerCase(Locale.ENGLISH))) + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!loot-type", (args, actions, runActions) -> { + List types = ListUtils.toList(args); + return context -> { + String loot = context.arg(ContextKeys.ID); + Optional lootInstance = plugin.getLootManager().getLoot(loot); + if (lootInstance.isPresent()) { + if (!types.contains(lootInstance.get().type().name().toLowerCase(Locale.ENGLISH))) + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerListRequirement() { + registerRequirement("list", (args, actions, runActions) -> { + plugin.getPluginLogger().severe("It seems that you made a mistake where you put \"list\" into \"conditions\" section."); + plugin.getPluginLogger().warn("list:"); + for (String e : ListUtils.toList(args)) { + plugin.getPluginLogger().warn(" - " + e); + } + return EmptyRequirement.INSTANCE; + }); + } + + private void registerEnvironmentRequirement() { + registerRequirement("environment", (args, actions, runActions) -> { + List environments = ListUtils.toList(args); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + var name = location.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); + if (environments.contains(name)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!environment", (args, actions, runActions) -> { + List environments = ListUtils.toList(args); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + var name = location.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); + if (!environments.contains(name)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerIceFishingRequirement() { + registerRequirement("ice-fishing", (args, actions, runActions) -> { + boolean iceFishing = (boolean) args; + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)); + int water = 0, ice = 0; + for (int i = -2; i <= 2; i++) + for (int j = -1; j <= 2; j++) + for (int k = -2; k <= 2; k++) { + Block block = location.clone().add(i, j, k).getBlock(); + Material material = block.getType(); + switch (material) { + case ICE -> ice++; + case WATER -> water++; + } + } + if ((ice >= 16 && water >= 25) == iceFishing) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerLevelRequirement() { + registerRequirement("level", (args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + int current = context.getHolder().getLevel(); + if (current >= value.evaluate(context)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerMoneyRequirement() { + registerRequirement("money", (args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + double current = VaultHook.getBalance(context.getHolder()); + if (current >= value.evaluate(context)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerRandomRequirement() { + registerRequirement("random", (args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() < value.evaluate(context)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerBiomeRequirement() { + registerRequirement("biome", (args, actions, runActions) -> { + HashSet biomes = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(Optional.ofNullable(context.arg(ContextKeys.OTHER_LOCATION)).orElse(context.arg(ContextKeys.LOCATION))); + String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(location); + if (biomes.contains(currentBiome)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!biome", (args, actions, runActions) -> { + HashSet biomes = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(Optional.ofNullable(context.arg(ContextKeys.OTHER_LOCATION)).orElse(context.arg(ContextKeys.LOCATION))); + String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(location); + if (!biomes.contains(currentBiome)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerMoonPhaseRequirement() { + registerRequirement("moon-phase", (args, actions, runActions) -> { + HashSet moonPhases = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long days = location.getWorld().getFullTime() / 24_000; + if (moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!moon-phase", (args, actions, runActions) -> { + HashSet moonPhases = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long days = location.getWorld().getFullTime() / 24_000; + if (!moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerWorldRequirement() { + registerRequirement("world", (args, actions, runActions) -> { + HashSet worlds = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + if (worlds.contains(location.getWorld().getName())) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!world", (args, actions, runActions) -> { + HashSet worlds = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + if (!worlds.contains(location.getWorld().getName())) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerWeatherRequirement() { + registerRequirement("weather", (args, actions, runActions) -> { + HashSet weathers = new HashSet<>(ListUtils.toList(args)); + return context -> { + String currentWeather; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + World world = location.getWorld(); + if (world.isClearWeather()) currentWeather = "clear"; + else if (world.isThundering()) currentWeather = "thunder"; + else currentWeather = "rain"; + if (weathers.contains(currentWeather)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerCoolDownRequirement() { + registerRequirement("cooldown", (args, actions, runActions) -> { + if (args instanceof Section section) { + String key = section.getString("key"); + int time = section.getInt("time"); + return context -> { + if (!plugin.getCoolDownManager().isCoolDown(context.getHolder().getUniqueId(), key, time)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at cooldown requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerDateRequirement() { + registerRequirement("date", (args, actions, runActions) -> { + HashSet dates = new HashSet<>(ListUtils.toList(args)); + return context -> { + Calendar calendar = Calendar.getInstance(); + String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE); + if (dates.contains(current)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerPermissionRequirement() { + registerRequirement("permission", (args, actions, runActions) -> { + List perms = ListUtils.toList(args); + return context -> { + for (String perm : perms) + if (context.getHolder().hasPermission(perm)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + registerRequirement("!permission", (args, actions, runActions) -> { + List perms = ListUtils.toList(args); + return context -> { + for (String perm : perms) + if (context.getHolder().hasPermission(perm)) { + if (runActions) ActionManager.trigger(context, actions); + return false; + } + return true; + }; + }); + } + + private void registerSeasonRequirement() { + registerRequirement("season", (args, actions, runActions) -> { + List seasons = ListUtils.toList(args); + return context -> { + SeasonProvider seasonProvider = plugin.getIntegrationManager().getSeasonProvider(); + if (seasonProvider == null) return true; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + World world = location.getWorld(); + Season season = seasonProvider.getSeason(world); + if (seasons.contains(season.name().toLowerCase(Locale.ENGLISH))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + private void registerPAPIRequirement() { + registerRequirement("<", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) < v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at < requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("<=", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) <= v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at <= requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!=", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) != v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at != requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("==", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) == v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at == requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement(">=", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) >= v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at >= requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement(">", (args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context) > v2.evaluate(context)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at > requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("regex", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("papi", "")); + String v2 = section.getString("regex", ""); + return context -> { + if (v1.render(context).matches(v2)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at regex requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("startsWith", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context).startsWith(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at startsWith requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!startsWith", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context).startsWith(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !startsWith requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("endsWith", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context).endsWith(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at endsWith requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!endsWith", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context).endsWith(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !endsWith requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("contains", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context).contains(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at contains requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!contains", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context).contains(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !contains requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("in-list", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue papi = TextValue.auto(section.getString("papi", "")); + List values = ListUtils.toList(section.get("values")); + return context -> { + if (values.contains(papi.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at in-list requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!in-list", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue papi = TextValue.auto(section.getString("papi", "")); + List values = ListUtils.toList(section.get("values")); + return context -> { + if (!values.contains(papi.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !in-list requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("equals", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context).equals(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at equals requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + registerRequirement("!equals", (args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context).equals(v2.render(context))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !equals requirement which is expected be `Section`"); + return EmptyRequirement.INSTANCE; + } + }); + } + + private void registerPotionEffectRequirement() { + registerRequirement("potion-effect", (args, actions, runActions) -> { + String potions = (String) args; + String[] split = potions.split("(<=|>=|<|>|==)", 2); + PotionEffectType type = PotionEffectType.getByName(split[0]); + if (type == null) { + plugin.getPluginLogger().warn("Potion effect doesn't exist: " + split[0]); + return EmptyRequirement.INSTANCE; + } + int required = Integer.parseInt(split[1]); + String operator = potions.substring(split[0].length(), potions.length() - split[1].length()); + return context -> { + int level = -1; + PotionEffect potionEffect = context.getHolder().getPotionEffect(type); + if (potionEffect != null) { + level = potionEffect.getAmplifier(); + } + boolean result = false; + switch (operator) { + case ">=" -> { + if (level >= required) result = true; + } + case ">" -> { + if (level > required) result = true; + } + case "==" -> { + if (level == required) result = true; + } + case "!=" -> { + if (level != required) result = true; + } + case "<=" -> { + if (level <= required) result = true; + } + case "<" -> { + if (level < required) result = true; + } + } + if (result) { + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }); + } + + /** + * Loads requirement expansions from external JAR files located in the expansion folder. + * Each expansion JAR should contain classes that extends the RequirementExpansion class. + * Expansions are registered and used to create custom requirements. + */ + @SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"}) + private void loadExpansions() { + File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); + if (!expansionFolder.exists()) + expansionFolder.mkdirs(); + List>> classes = new ArrayList<>(); + File[] expansionJars = expansionFolder.listFiles(); + if (expansionJars == null) return; + for (File expansionJar : expansionJars) { + if (expansionJar.getName().endsWith(".jar")) { + try { + Class> expansionClass = (Class>) ClassUtils.findClass(expansionJar, RequirementExpansion.class); + classes.add(expansionClass); + } catch (IOException | ClassNotFoundException e) { + plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e); + } + } + } + try { + for (Class> expansionClass : classes) { + RequirementExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); + unregisterRequirement(expansion.getRequirementType()); + registerRequirement(expansion.getRequirementType(), expansion.getRequirementFactory()); + plugin.getPluginLogger().info("Loaded requirement expansion: " + expansion.getRequirementType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor()); + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { + plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/BukkitSchedulerAdapter.java b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/BukkitSchedulerAdapter.java new file mode 100644 index 00000000..a78cb4c7 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/BukkitSchedulerAdapter.java @@ -0,0 +1,52 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.bukkit.scheduler; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.bukkit.scheduler.impl.BukkitExecutor; +import net.momirealms.customfishing.bukkit.scheduler.impl.FoliaExecutor; +import net.momirealms.customfishing.common.helper.VersionHelper; +import net.momirealms.customfishing.common.plugin.scheduler.AbstractJavaScheduler; +import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor; +import org.bukkit.Location; + +public class BukkitSchedulerAdapter extends AbstractJavaScheduler { + protected RegionExecutor sync; + + public BukkitSchedulerAdapter(BukkitCustomFishingPlugin plugin) { + super(plugin); + if (VersionHelper.isFolia()) { + this.sync = new FoliaExecutor(plugin.getBoostrap()); + } else { + this.sync = new BukkitExecutor(plugin.getBoostrap()); + } + } + + @Override + public RegionExecutor sync() { + return this.sync; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/BukkitExecutor.java b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/BukkitExecutor.java new file mode 100644 index 00000000..af583f14 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/BukkitExecutor.java @@ -0,0 +1,64 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.bukkit.scheduler.impl; + +import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.Plugin; + +public class BukkitExecutor implements RegionExecutor { + + private final Plugin plugin; + + public BukkitExecutor(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void run(Runnable r, Location l) { + Bukkit.getScheduler().runTask(plugin, r); + } + + @Override + public SchedulerTask runLater(Runnable r, long delayTicks, Location l) { + if (delayTicks == 0) { + if (Bukkit.isPrimaryThread()) { + r.run(); + return () -> {}; + } else { + return Bukkit.getScheduler().runTask(plugin, r)::cancel; + } + } + return Bukkit.getScheduler().runTaskLater(plugin, r, delayTicks)::cancel; + } + + @Override + public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) { + return Bukkit.getScheduler().runTaskTimer(plugin, r, delayTicks, period)::cancel; + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/FoliaExecutor.java b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/FoliaExecutor.java new file mode 100644 index 00000000..29d3d4c6 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/scheduler/impl/FoliaExecutor.java @@ -0,0 +1,74 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.bukkit.scheduler.impl; + +import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.plugin.Plugin; + +import java.util.Optional; + +public class FoliaExecutor implements RegionExecutor { + + private final Plugin plugin; + + public FoliaExecutor(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void run(Runnable r, Location l) { + Optional.ofNullable(l).ifPresentOrElse(loc -> Bukkit.getRegionScheduler().execute(plugin, loc, r), () -> Bukkit.getGlobalRegionScheduler().execute(plugin, r)); + } + + @Override + public SchedulerTask runLater(Runnable r, long delayTicks, Location l) { + if (l == null) { + if (delayTicks == 0) { + return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> r.run(), delayTicks)::cancel; + } else { + return Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> r.run())::cancel; + } + } else { + if (delayTicks == 0) { + return Bukkit.getRegionScheduler().run(plugin, l, scheduledTask -> r.run())::cancel; + } else { + return Bukkit.getRegionScheduler().runDelayed(plugin, l, scheduledTask -> r.run(), delayTicks)::cancel; + } + } + } + + @Override + public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) { + if (l == null) { + return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> r.run(), delayTicks, period)::cancel; + } else { + return Bukkit.getRegionScheduler().runAtFixedRate(plugin, l, scheduledTask -> r.run(), delayTicks, period)::cancel; + } + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/sender/BukkitSenderFactory.java b/core/src/main/java/net/momirealms/customfishing/bukkit/sender/BukkitSenderFactory.java new file mode 100644 index 00000000..bb71b1f2 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/sender/BukkitSenderFactory.java @@ -0,0 +1,112 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customfishing.bukkit.sender; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.common.sender.Sender; +import net.momirealms.customfishing.common.sender.SenderFactory; +import net.momirealms.customfishing.common.util.Tristate; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class BukkitSenderFactory extends SenderFactory { + private final BukkitAudiences audiences; + + public BukkitSenderFactory(BukkitCustomFishingPlugin plugin) { + super(plugin); + this.audiences = BukkitAudiences.create(plugin.getBoostrap()); + } + + @Override + protected String getName(CommandSender sender) { + if (sender instanceof Player) { + return sender.getName(); + } + return Sender.CONSOLE_NAME; + } + + @Override + protected UUID getUniqueId(CommandSender sender) { + if (sender instanceof Player) { + return ((Player) sender).getUniqueId(); + } + return Sender.CONSOLE_UUID; + } + + @Override + public Audience getAudience(CommandSender sender) { + return this.audiences.sender(sender); + } + + @Override + protected void sendMessage(CommandSender sender, Component message) { + // we can safely send async for players and the console - otherwise, send it sync + if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) { + getAudience(sender).sendMessage(message); + } else { + getPlugin().getScheduler().executeSync(() -> getAudience(sender).sendMessage(message)); + } + } + + @Override + protected Tristate getPermissionValue(CommandSender sender, String node) { + if (sender.hasPermission(node)) { + return Tristate.TRUE; + } else if (sender.isPermissionSet(node)) { + return Tristate.FALSE; + } else { + return Tristate.UNDEFINED; + } + } + + @Override + protected boolean hasPermission(CommandSender sender, String node) { + return sender.hasPermission(node); + } + + @Override + protected void performCommand(CommandSender sender, String command) { + getPlugin().getBoostrap().getServer().dispatchCommand(sender, command); + } + + @Override + protected boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender; + } + + @Override + public void close() { + super.close(); + this.audiences.close(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/statistic/StatisticsManagerImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/statistic/BukkitStatisticsManager.java similarity index 59% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/statistic/StatisticsManagerImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/statistic/BukkitStatisticsManager.java index 732dd3bf..93025ef4 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/statistic/StatisticsManagerImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/statistic/BukkitStatisticsManager.java @@ -15,54 +15,38 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.statistic; +package net.momirealms.customfishing.bukkit.statistic; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import net.momirealms.customfishing.api.manager.StatisticsManager; -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.statistic.StatisticsManager; import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.*; -public class StatisticsManagerImpl implements StatisticsManager { +public class BukkitStatisticsManager implements StatisticsManager { - private final CustomFishingPlugin plugin; - private final HashMap> categoryMap; + private final BukkitCustomFishingPlugin plugin; + private final Map> categoryMap = new HashMap<>(); - public StatisticsManagerImpl(CustomFishingPlugin plugin) { + public BukkitStatisticsManager(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; - this.categoryMap = new HashMap<>(); } + @Override public void load() { this.loadCategoriesFromPluginFolder(); + for (Map.Entry> entry : categoryMap.entrySet()) { + plugin.debug("Category: {" + entry.getKey() + "} Members: " + entry.getValue()); + } } + @Override public void unload() { this.categoryMap.clear(); } - - public void disable() { - unload(); - } - - /** - * Get the statistics for a player with the given UUID. - * - * @param uuid The UUID of the player for whom statistics are retrieved. - * @return The player's statistics or null if the player is not found. - */ - @Override - @Nullable - public Statistics getStatistics(UUID uuid) { - OnlineUser onlineUser = plugin.getStorageManager().getOnlineUser(uuid); - if (onlineUser == null) return null; - return onlineUser.getStatistics(); - } - + @SuppressWarnings("DuplicatedCode") public void loadCategoriesFromPluginFolder() { Deque fileDeque = new ArrayDeque<>(); @@ -70,7 +54,7 @@ public class StatisticsManagerImpl implements StatisticsManager { File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); if (!typeFolder.exists()) { if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); + plugin.getBoostrap().saveResource("contents" + File.separator + type + File.separator + "default.yml", false); } fileDeque.push(typeFolder); while (!fileDeque.isEmpty()) { @@ -95,15 +79,9 @@ public class StatisticsManagerImpl implements StatisticsManager { } } - /** - * Get a list of strings associated with a specific key in a category map. - * - * @param key The key to look up in the category map. - * @return A list of strings associated with the key or null if the key is not found. - */ + @NotNull @Override - @Nullable - public List getCategory(String key) { - return categoryMap.get(key); + public List getCategoryMembers(String key) { + return categoryMap.getOrDefault(key, List.of()); } } diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/storage/BukkitStorageManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/BukkitStorageManager.java new file mode 100644 index 00000000..380c5635 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/BukkitStorageManager.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.storage; + +import com.google.gson.JsonSyntaxException; +import dev.dejvokep.boostedyaml.YamlDocument; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.storage.DataStorageProvider; +import net.momirealms.customfishing.api.storage.StorageManager; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.bukkit.storage.method.database.nosql.MongoDBProvider; +import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager; +import net.momirealms.customfishing.bukkit.storage.method.database.sql.H2Provider; +import net.momirealms.customfishing.bukkit.storage.method.database.sql.MariaDBProvider; +import net.momirealms.customfishing.bukkit.storage.method.database.sql.MySQLProvider; +import net.momirealms.customfishing.bukkit.storage.method.database.sql.SQLiteProvider; +import net.momirealms.customfishing.bukkit.storage.method.file.JsonProvider; +import net.momirealms.customfishing.bukkit.storage.method.file.YAMLProvider; +import net.momirealms.customfishing.common.helper.GsonHelper; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class BukkitStorageManager implements StorageManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private DataStorageProvider dataSource; + private StorageType previousType; + private final ConcurrentHashMap onlineUserMap; + private final HashSet locked; + private boolean hasRedis; + private RedisManager redisManager; + private String serverID; + private SchedulerTask timerSaveTask; + + public BukkitStorageManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + this.locked = new HashSet<>(); + this.onlineUserMap = new ConcurrentHashMap<>(); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + } + + @Override + public void reload() { + YamlDocument config = plugin.getConfigManager().loadConfig("database.yml"); + this.serverID = config.getString("unique-server-id", "default"); + + // Check if storage type has changed and reinitialize if necessary + StorageType storageType = StorageType.valueOf(config.getString("data-storage-method", "H2")); + if (storageType != previousType) { + if (this.dataSource != null) this.dataSource.disable(); + this.previousType = storageType; + switch (storageType) { + case H2 -> this.dataSource = new H2Provider(plugin); + case JSON -> this.dataSource = new JsonProvider(plugin); + case YAML -> this.dataSource = new YAMLProvider(plugin); + case SQLite -> this.dataSource = new SQLiteProvider(plugin); + case MySQL -> this.dataSource = new MySQLProvider(plugin); + case MariaDB -> this.dataSource = new MariaDBProvider(plugin); + case MongoDB -> this.dataSource = new MongoDBProvider(plugin); + } + if (this.dataSource != null) this.dataSource.initialize(config); + else plugin.getPluginLogger().severe("No storage type is set."); + } + + // Handle Redis configuration + if (!this.hasRedis && config.getBoolean("Redis.enable", false)) { + this.hasRedis = true; + this.redisManager = new RedisManager(plugin); + this.redisManager.initialize(config); + } + + // Disable Redis if it was enabled but is now disabled + if (this.hasRedis && !config.getBoolean("Redis.enable", false) && this.redisManager != null) { + this.redisManager.disable(); + this.redisManager = null; + } + + // Cancel any existing timerSaveTask + if (this.timerSaveTask != null) { + this.timerSaveTask.cancel(); + } + + // Schedule periodic data saving if dataSaveInterval is configured + if (ConfigManager.dataSaveInterval() > 0) + this.timerSaveTask = this.plugin.getScheduler().asyncRepeating( + () -> { + long time1 = System.currentTimeMillis(); + this.dataSource.updateManyPlayersData(this.onlineUserMap.values(), !ConfigManager.lockData()); + if (ConfigManager.logDataSaving()) + plugin.getPluginLogger().info("Data Saved for online players. Took " + (System.currentTimeMillis() - time1) + "ms."); + }, + ConfigManager.dataSaveInterval(), + ConfigManager.dataSaveInterval(), + TimeUnit.SECONDS + ); + } + + /** + * Disables the storage manager and cleans up resources. + */ + @Override + public void disable() { + HandlerList.unregisterAll(this); + this.dataSource.updateManyPlayersData(onlineUserMap.values(), true); + this.onlineUserMap.clear(); + if (this.dataSource != null) + this.dataSource.disable(); + if (this.redisManager != null) + this.redisManager.disable(); + } + + @NotNull + @Override + public String getServerID() { + return serverID; + } + + @NotNull + @Override + public Optional getOnlineUser(UUID uuid) { + return Optional.ofNullable(onlineUserMap.get(uuid)); + } + + @NotNull + @Override + public Collection getOnlineUsers() { + return onlineUserMap.values(); + } + + @Override + public CompletableFuture> getOfflineUserData(UUID uuid, boolean lock) { + CompletableFuture> optionalDataFuture = dataSource.getPlayerData(uuid, lock); + return optionalDataFuture.thenCompose(optionalUser -> { + if (optionalUser.isEmpty()) { + return CompletableFuture.completedFuture(Optional.empty()); + } + PlayerData data = optionalUser.get(); + return CompletableFuture.completedFuture(Optional.of(UserData.builder() + .data(data) + .build())); + }); + } + + @Override + public CompletableFuture saveUserData(UserData userData, boolean unlock) { + return dataSource.updatePlayerData(userData.uuid(), userData.toPlayerData(), unlock); + } + + @NotNull + @Override + public DataStorageProvider getDataSource() { + return dataSource; + } + + /** + * Event handler for when a player joins the server. + * Locks the player's data and initiates data retrieval if Redis is not used, + * otherwise, it starts a Redis data retrieval task. + */ + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + locked.add(uuid); + if (!hasRedis) { + waitLock(uuid, 1); + } else { + plugin.getScheduler().asyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(changeServer -> { + if (!changeServer) { + waitLock(uuid, 3); + } else { + new RedisGetDataTask(uuid); + } + }), 500, TimeUnit.MILLISECONDS); + } + } + + /** + * Event handler for when a player quits the server. + * If the player is not locked, it removes their OnlineUser instance, + * updates the player's data in Redis and the data source. + */ + @EventHandler + public void onQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + if (locked.contains(uuid)) + return; + + UserData onlineUser = onlineUserMap.remove(uuid); + if (onlineUser == null) return; + PlayerData data = onlineUser.toPlayerData(); + + if (hasRedis) { + redisManager.setChangeServer(uuid).thenRun( + () -> redisManager.updatePlayerData(uuid, data, true).thenRun( + () -> dataSource.updatePlayerData(uuid, data, true).thenAccept( + result -> { + if (result) locked.remove(uuid); + }))); + } else { + dataSource.updatePlayerData(uuid, data, true).thenAccept( + result -> { + if (result) locked.remove(uuid); + }); + } + } + + /** + * Runnable task for asynchronously retrieving data from Redis. + * Retries up to 6 times and cancels the task if the player is offline. + */ + private class RedisGetDataTask implements Runnable { + + private final UUID uuid; + private int triedTimes; + private final SchedulerTask task; + + public RedisGetDataTask(UUID uuid) { + this.uuid = uuid; + this.task = plugin.getScheduler().asyncRepeating(this, 0, 333, TimeUnit.MILLISECONDS); + } + + @Override + public void run() { + triedTimes++; + Player player = Bukkit.getPlayer(uuid); + if (player == null || !player.isOnline()) { + // offline + task.cancel(); + return; + } + if (triedTimes >= 6) { + waitLock(uuid, 3); + return; + } + redisManager.getPlayerData(uuid, false).thenAccept(optionalData -> { + if (optionalData.isPresent()) { + addOnlineUser(player, optionalData.get()); + task.cancel(); + if (ConfigManager.lockData()) dataSource.lockOrUnlockPlayerData(uuid, true); + } + }); + } + } + + /** + * Waits for data lock release with a delay and a maximum of three retries. + * + * @param uuid The UUID of the player. + * @param times The number of times this method has been retried. + */ + private void waitLock(UUID uuid, int times) { + plugin.getScheduler().asyncLater(() -> { + var player = Bukkit.getPlayer(uuid); + if (player == null || !player.isOnline()) + return; + if (times > 3) { + plugin.getPluginLogger().warn("Tried 3 times when getting data for " + uuid + ". Giving up."); + return; + } + this.dataSource.getPlayerData(uuid, ConfigManager.lockData()).thenAccept(optionalData -> { + // Data should not be empty + if (optionalData.isEmpty()) { + plugin.getPluginLogger().severe("Unexpected error: Data is null"); + return; + } + + if (optionalData.get().locked()) { + waitLock(uuid, times + 1); + } else { + try { + addOnlineUser(player, optionalData.get()); + } catch (Exception e) { + plugin.getPluginLogger().severe("Unexpected error: " + e.getMessage(), e); + } + } + }); + }, 1, TimeUnit.SECONDS); + } + + private void addOnlineUser(Player player, PlayerData playerData) { + this.locked.remove(player.getUniqueId()); + this.onlineUserMap.put(player.getUniqueId(), UserData.builder() + .data(playerData) + // update the name + .name(player.getName()) + .build()); + } + + @Override + public boolean isRedisEnabled() { + return hasRedis; + } + + @Nullable + public RedisManager getRedisManager() { + return redisManager; + } + + @NotNull + @Override + public byte[] toBytes(@NotNull PlayerData data) { + return toJson(data).getBytes(StandardCharsets.UTF_8); + } + + @Override + @NotNull + public String toJson(@NotNull PlayerData data) { + return GsonHelper.get().toJson(data); + } + + @NotNull + @Override + public PlayerData fromJson(String json) { + try { + return GsonHelper.get().fromJson(json, PlayerData.class); + } catch (JsonSyntaxException e) { + plugin.getPluginLogger().severe("Failed to parse PlayerData from json"); + plugin.getPluginLogger().info("Json: " + json); + throw new RuntimeException(e); + } + } + + @Override + @NotNull + public PlayerData fromBytes(byte[] data) { + return fromJson(new String(data, StandardCharsets.UTF_8)); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/AbstractStorage.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/AbstractStorage.java similarity index 65% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/AbstractStorage.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/AbstractStorage.java index d9306d06..8d74782b 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/AbstractStorage.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/AbstractStorage.java @@ -15,12 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method; +package net.momirealms.customfishing.bukkit.storage.method; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.DataStorageInterface; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.user.OfflineUser; +import dev.dejvokep.boostedyaml.YamlDocument; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.DataStorageProvider; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; import java.time.Instant; import java.util.Collection; @@ -30,16 +31,16 @@ import java.util.concurrent.CompletableFuture; /** * An abstract class that implements the DataStorageInterface and provides common functionality for data storage. */ -public abstract class AbstractStorage implements DataStorageInterface { +public abstract class AbstractStorage implements DataStorageProvider { - protected CustomFishingPlugin plugin; + protected BukkitCustomFishingPlugin plugin; - public AbstractStorage(CustomFishingPlugin plugin) { + public AbstractStorage(BukkitCustomFishingPlugin plugin) { this.plugin = plugin; } @Override - public void initialize() { + public void initialize(YamlDocument config) { // This method can be overridden in subclasses to perform initialization tasks specific to the storage type. } @@ -58,19 +59,12 @@ public abstract class AbstractStorage implements DataStorageInterface { } @Override - public void updateManyPlayersData(Collection users, boolean unlock) { - // Update data for multiple players by iterating through the collection of OfflineUser objects. - for (OfflineUser user : users) { - this.updatePlayerData(user.getUUID(), user.getPlayerData(), unlock); + public void updateManyPlayersData(Collection users, boolean unlock) { + for (UserData user : users) { + this.updatePlayerData(user.uuid(), user.toPlayerData(), unlock); } } - /** - * Lock or unlock player data based on the provided UUID and lock flag. - * - * @param uuid The UUID of the player. - * @param lock True to lock the player data, false to unlock it. - */ public void lockOrUnlockPlayerData(UUID uuid, boolean lock) { // Note: Only remote database would override this method } diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/MongoDBImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/MongoDBProvider.java similarity index 66% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/MongoDBImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/MongoDBProvider.java index 1bfd7562..cc490fc6 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/MongoDBImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/MongoDBProvider.java @@ -15,52 +15,44 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.nosql; +package net.momirealms.customfishing.bukkit.storage.method.database.nosql; import com.mongodb.*; import com.mongodb.client.*; import com.mongodb.client.model.*; import com.mongodb.client.result.UpdateResult; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.storage.method.AbstractStorage; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage; import org.bson.Document; import org.bson.UuidRepresentation; import org.bson.conversions.Bson; import org.bson.types.Binary; import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; import java.util.*; import java.util.concurrent.CompletableFuture; -/** - * An implementation of AbstractStorage that uses MongoDB for player data storage. - */ -public class MongoDBImpl extends AbstractStorage { +public class MongoDBProvider extends AbstractStorage { private MongoClient mongoClient; private MongoDatabase database; private String collectionPrefix; - public MongoDBImpl(CustomFishingPlugin plugin) { + public MongoDBProvider(BukkitCustomFishingPlugin plugin) { super(plugin); } - /** - * Initialize the MongoDB connection and configuration based on the plugin's YAML configuration. - */ @Override - public void initialize() { - YamlConfiguration config = plugin.getConfig("database.yml"); - ConfigurationSection section = config.getConfigurationSection("MongoDB"); + public void initialize(YamlDocument config) { + Section section = config.getSection("MongoDB"); if (section == null) { - LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); + plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); return; } @@ -89,9 +81,6 @@ public class MongoDBImpl extends AbstractStorage { this.database = mongoClient.getDatabase(section.getString("database", "minecraft")); } - /** - * Disable the MongoDB connection by closing the MongoClient. - */ @Override public void disable() { if (this.mongoClient != null) { @@ -123,51 +112,41 @@ public class MongoDBImpl extends AbstractStorage { return StorageType.MongoDB; } - /** - * Asynchronously retrieve player data from the MongoDB database. - * - * @param uuid The UUID of the player. - * @param lock Flag indicating whether to lock the data. - * @return A CompletableFuture with an optional PlayerData. - */ @Override public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { var future = new CompletableFuture>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { MongoCollection collection = database.getCollection(getCollectionName("data")); Document doc = collection.find(Filters.eq("uuid", uuid)).first(); if (doc == null) { if (Bukkit.getPlayer(uuid) != null) { if (lock) lockOrUnlockPlayerData(uuid, true); - future.complete(Optional.of(PlayerData.empty())); + var data = PlayerData.empty(); + data.uuid(uuid); + future.complete(Optional.of(data)); } else { future.complete(Optional.empty()); } } else { - if (doc.getInteger("lock") != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= doc.getInteger("lock")) { - future.complete(Optional.of(PlayerData.LOCKED)); + Binary binary = (Binary) doc.get("data"); + PlayerData data = plugin.getStorageManager().fromBytes(binary.getData()); + data.uuid(uuid); + if (doc.getInteger("lock") != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= doc.getInteger("lock")) { + data.locked(true); + future.complete(Optional.of(data)); return; } - Binary binary = (Binary) doc.get("data"); if (lock) lockOrUnlockPlayerData(uuid, true); - future.complete(Optional.of(plugin.getStorageManager().fromBytes(binary.getData()))); + future.complete(Optional.of(data)); } }); return future; } - /** - * Asynchronously update player data in the MongoDB database. - * - * @param uuid The UUID of the player. - * @param playerData The player's data to update. - * @param unlock Flag indicating whether to unlock the data. - * @return A CompletableFuture indicating the update result. - */ @Override public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { MongoCollection collection = database.getCollection(getCollectionName("data")); try { Document query = new Document("uuid", uuid); @@ -184,39 +163,27 @@ public class MongoDBImpl extends AbstractStorage { return future; } - /** - * Asynchronously update data for multiple players in the MongoDB database. - * - * @param users A collection of OfflineUser instances to update. - * @param unlock Flag indicating whether to unlock the data. - */ @Override - public void updateManyPlayersData(Collection users, boolean unlock) { + public void updateManyPlayersData(Collection users, boolean unlock) { MongoCollection collection = database.getCollection(getCollectionName("data")); try { int lock = unlock ? 0 : getCurrentSeconds(); var list = users.stream().map(it -> new UpdateOneModel( - new Document("uuid", it.getUUID()), + new Document("uuid", it.uuid()), Updates.combine( Updates.set("lock", lock), - Updates.set("data", new Binary(plugin.getStorageManager().toBytes(it.getPlayerData()))) + Updates.set("data", new Binary(plugin.getStorageManager().toBytes(it.toPlayerData()))) ), new UpdateOptions().upsert(true) ) ).toList(); - if (list.size() == 0) return; + if (list.isEmpty()) return; collection.bulkWrite(list); } catch (MongoException e) { - LogUtils.warn("Failed to update data for online players", e); + plugin.getPluginLogger().warn("Failed to update data for online players", e); } } - /** - * Lock or unlock player data in the MongoDB database. - * - * @param uuid The UUID of the player. - * @param lock Flag indicating whether to lock or unlock the data. - */ @Override public void lockOrUnlockPlayerData(UUID uuid, boolean lock) { MongoCollection collection = database.getCollection(getCollectionName("data")); @@ -226,18 +193,12 @@ public class MongoDBImpl extends AbstractStorage { UpdateOptions options = new UpdateOptions().upsert(true); collection.updateOne(query, updates, options); } catch (MongoException e) { - LogUtils.warn("Failed to lock data for " + uuid, e); + plugin.getPluginLogger().warn("Failed to lock data for " + uuid, e); } } - /** - * Get a set of unique player UUIDs from the MongoDB database. - * - * @param legacy Flag indicating whether to retrieve legacy data. - * @return A set of unique player UUIDs. - */ @Override - public Set getUniqueUsers(boolean legacy) { + public Set getUniqueUsers() { // no legacy files Set uuids = new HashSet<>(); MongoCollection collection = database.getCollection(getCollectionName("data")); @@ -249,7 +210,7 @@ public class MongoDBImpl extends AbstractStorage { } } } catch (MongoException e) { - LogUtils.warn("Failed to get unique data.", e); + plugin.getPluginLogger().warn("Failed to get unique data.", e); } return uuids; } diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/RedisManager.java similarity index 73% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/RedisManager.java index 3fb89303..f65b45ff 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/nosql/RedisManager.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/nosql/RedisManager.java @@ -15,16 +15,17 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.nosql; +package net.momirealms.customfishing.bukkit.storage.method.database.nosql; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.storage.method.AbstractStorage; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage; import org.jetbrains.annotations.NotNull; import redis.clients.jedis.*; import redis.clients.jedis.exceptions.JedisException; @@ -36,9 +37,6 @@ import java.time.Duration; import java.util.*; import java.util.concurrent.CompletableFuture; -/** - * A RedisManager class responsible for managing interactions with a Redis server for data storage. - */ public class RedisManager extends AbstractStorage { private static RedisManager instance; @@ -51,7 +49,7 @@ public class RedisManager extends AbstractStorage { private BlockingThreadTask threadTask; private boolean isNewerThan5; - public RedisManager(CustomFishingPlugin plugin) { + public RedisManager(BukkitCustomFishingPlugin plugin) { super(plugin); instance = this; } @@ -78,11 +76,10 @@ public class RedisManager extends AbstractStorage { * Initialize the Redis connection and configuration based on the plugin's YAML configuration. */ @Override - public void initialize() { - YamlConfiguration config = plugin.getConfig("database.yml"); - ConfigurationSection section = config.getConfigurationSection("Redis"); + public void initialize(YamlDocument config) { + Section section = config.getSection("Redis"); if (section == null) { - LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); + plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); return; } @@ -90,7 +87,7 @@ public class RedisManager extends AbstractStorage { jedisPoolConfig.setTestWhileIdle(true); jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000)); jedisPoolConfig.setNumTestsPerEvictionRun(-1); - jedisPoolConfig.setMinEvictableIdleTime(Duration.ofMillis(section.getInt("MinEvictableIdleTimeMillis",1800000))); + jedisPoolConfig.setMinEvictableIdleDuration(Duration.ofMillis(section.getInt("MinEvictableIdleTimeMillis", 1800000))); jedisPoolConfig.setMaxTotal(section.getInt("MaxTotal",8)); jedisPoolConfig.setMaxIdle(section.getInt("MaxIdle",8)); jedisPoolConfig.setMinIdle(section.getInt("MinIdle",1)); @@ -109,9 +106,9 @@ public class RedisManager extends AbstractStorage { String info; try (Jedis jedis = jedisPool.getResource()) { info = jedis.info(); - LogUtils.info("Redis server connected."); + plugin.getPluginLogger().info("Redis server connected."); } catch (JedisException e) { - LogUtils.warn("Failed to connect redis.", e); + plugin.getPluginLogger().warn("Failed to connect redis.", e); return; } @@ -141,12 +138,9 @@ public class RedisManager extends AbstractStorage { /** * Send a message to Redis on a specified channel. * - * @param server The Redis channel to send the message to. * @param message The message to send. */ - public void publishRedisMessage(@NotNull String server, @NotNull String message) { - message = server + ";" + message; - plugin.debug("Sent Redis message: " + message); + public void publishRedisMessage(@NotNull String message) { if (isNewerThan5) { try (Jedis jedis = jedisPool.getResource()) { HashMap messages = new HashMap<>(); @@ -189,30 +183,28 @@ public class RedisManager extends AbstractStorage { thread.start(); } - private static void handleMessage(String message) { - CustomFishingPlugin.get().debug("Received Redis message: " + message); - String[] split = message.split(";"); - String server = split[0]; - if (!CFConfig.serverGroup.contains(server)) { + private void handleMessage(String message) { + ByteArrayDataInput input = ByteStreams.newDataInput(message.getBytes(StandardCharsets.UTF_8)); + String server = input.readUTF(); + if (!ConfigManager.serverGroup().equals(server)) return; - } - String action = split[1]; - CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> { + String type = input.readUTF(); + if (type.equals("competition")) { + String action = input.readUTF(); switch (action) { case "start" -> { - // start competition for all the servers that connected to redis - CustomFishingPlugin.get().getCompetitionManager().startCompetition(split[2], true, null); + plugin.getCompetitionManager().startCompetition(input.readUTF(), true, null); } case "end" -> { - if (CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition() != null) - CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().end(true); + if (plugin.getCompetitionManager().getOnGoingCompetition() != null) + plugin.getCompetitionManager().getOnGoingCompetition().end(true); } case "stop" -> { - if (CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition() != null) - CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition().stop(true); + if (plugin.getCompetitionManager().getOnGoingCompetition() != null) + plugin.getCompetitionManager().getOnGoingCompetition().stop(true); } } - }); + } } @Override @@ -228,7 +220,7 @@ public class RedisManager extends AbstractStorage { */ public CompletableFuture setChangeServer(UUID uuid) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try (Jedis jedis = jedisPool.getResource()) { jedis.setex( getRedisKey("cf_server", uuid), @@ -237,7 +229,6 @@ public class RedisManager extends AbstractStorage { ); } future.complete(null); - plugin.debug("Server data set for " + uuid); }); return future; } @@ -250,16 +241,14 @@ public class RedisManager extends AbstractStorage { */ public CompletableFuture getChangeServer(UUID uuid) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try (Jedis jedis = jedisPool.getResource()) { byte[] key = getRedisKey("cf_server", uuid); if (jedis.get(key) != null) { jedis.del(key); future.complete(true); - plugin.debug("Server data retrieved for " + uuid + "; value: true"); } else { future.complete(false); - plugin.debug("Server data retrieved for " + uuid + "; value: false"); } } }); @@ -276,21 +265,18 @@ public class RedisManager extends AbstractStorage { @Override public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { var future = new CompletableFuture>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try (Jedis jedis = jedisPool.getResource()) { byte[] key = getRedisKey("cf_data", uuid); byte[] data = jedis.get(key); jedis.del(key); if (data != null) { future.complete(Optional.of(plugin.getStorageManager().fromBytes(data))); - plugin.debug("Redis data retrieved for " + uuid + "; normal data"); } else { future.complete(Optional.empty()); - plugin.debug("Redis data retrieved for " + uuid + "; empty data"); } } catch (Exception e) { future.complete(Optional.empty()); - LogUtils.warn("Failed to get redis data for " + uuid, e); } }); return future; @@ -307,7 +293,7 @@ public class RedisManager extends AbstractStorage { @Override public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try (Jedis jedis = jedisPool.getResource()) { jedis.setex( getRedisKey("cf_data", uuid), @@ -315,24 +301,15 @@ public class RedisManager extends AbstractStorage { plugin.getStorageManager().toBytes(playerData) ); future.complete(true); - plugin.debug("Redis data set for " + uuid); } catch (Exception e) { future.complete(false); - LogUtils.warn("Failed to set redis data for player " + uuid, e); } }); return future; } - /** - * Get a set of unique player UUIDs from Redis (Returns an empty set). - * This method is designed for importing and exporting so it would not actually be called. - * - * @param legacy Flag indicating whether to retrieve legacy data (not used). - * @return An empty set of UUIDs. - */ @Override - public Set getUniqueUsers(boolean legacy) { + public Set getUniqueUsers() { return new HashSet<>(); } @@ -351,17 +328,17 @@ public class RedisManager extends AbstractStorage { return STREAM; } - private static boolean isRedisNewerThan5(String version) { + private boolean isRedisNewerThan5(String version) { String[] split = version.split("\\."); int major = Integer.parseInt(split[0]); if (major < 7) { - LogUtils.warn(String.format("Detected that you are running an outdated Redis server. v%s. ", version)); - LogUtils.warn("It's recommended to update to avoid security vulnerabilities!"); + plugin.getPluginLogger().warn(String.format("Detected that you are running an outdated Redis server. v%s. ", version)); + plugin.getPluginLogger().warn("It's recommended to update to avoid security vulnerabilities!"); } return major >= 5; } - private static String parseRedisVersion(String info) { + private String parseRedisVersion(String info) { for (String line : info.split("\n")) { if (line.startsWith("redis_version:")) { return line.split(":")[1]; @@ -388,7 +365,7 @@ public class RedisManager extends AbstractStorage { if (connection != null) { var messages = connection.xread(XReadParams.xReadParams().count(1).block(2000), map); connection.close(); - if (messages != null && messages.size() != 0) { + if (messages != null && !messages.isEmpty()) { for (Map.Entry> message : messages) { if (message.getKey().equals(getStream())) { var value = message.getValue().get(0).getFields().get("value"); @@ -400,7 +377,7 @@ public class RedisManager extends AbstractStorage { Thread.sleep(2000); } } catch (Exception e) { - LogUtils.warn("Failed to connect redis. Try reconnecting 10s later",e); + plugin.getPluginLogger().warn("Failed to connect redis. Try reconnecting 10s later",e); try { Thread.sleep(10000); } catch (InterruptedException ex) { diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractHikariDatabase.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractHikariDatabase.java new file mode 100644 index 00000000..cc8c46d4 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractHikariDatabase.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.storage.method.database.sql; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +public abstract class AbstractHikariDatabase extends AbstractSQLDatabase { + + private HikariDataSource dataSource; + private final String driverClass; + private final String sqlBrand; + + public AbstractHikariDatabase(BukkitCustomFishingPlugin plugin) { + super(plugin); + this.driverClass = getStorageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver"; + this.sqlBrand = getStorageType() == StorageType.MariaDB ? "MariaDB" : "MySQL"; + try { + Class.forName(this.driverClass); + } catch (ClassNotFoundException e1) { + if (getStorageType() == StorageType.MariaDB) { + plugin.getPluginLogger().warn("No MariaDB driver is found"); + } else if (getStorageType() == StorageType.MySQL) { + try { + Class.forName("com.mysql.jdbc.Driver"); + } catch (ClassNotFoundException e2) { + plugin.getPluginLogger().warn("No MySQL driver is found"); + } + } + } + } + + @Override + public void initialize(YamlDocument config) { + Section section = config.getSection(sqlBrand); + + if (section == null) { + plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); + return; + } + + super.tablePrefix = section.getString("table-prefix", "customfishing"); + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setUsername(section.getString("user", "root")); + hikariConfig.setPassword(section.getString("password", "pa55w0rd")); + hikariConfig.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", + sqlBrand.toLowerCase(Locale.ENGLISH), + section.getString("host", "localhost"), + section.getString("port", "3306"), + section.getString("database", "minecraft"), + section.getString("connection-parameters") + )); + hikariConfig.setDriverClassName(driverClass); + hikariConfig.setMaximumPoolSize(section.getInt("Pool-Settings.max-pool-size", 10)); + hikariConfig.setMinimumIdle(section.getInt("Pool-Settings.min-idle", 10)); + hikariConfig.setMaxLifetime(section.getLong("Pool-Settings.max-lifetime", 180000L)); + hikariConfig.setConnectionTimeout(section.getLong("Pool-Settings.time-out", 20000L)); + hikariConfig.setPoolName("CustomFishingHikariPool"); + try { + hikariConfig.setKeepaliveTime(section.getLong("Pool-Settings.keep-alive-time", 60000L)); + } catch (NoSuchMethodError ignored) { + } + + final Properties properties = new Properties(); + properties.putAll( + Map.of("cachePrepStmts", "true", + "prepStmtCacheSize", "250", + "prepStmtCacheSqlLimit", "2048", + "useServerPrepStmts", "true", + "useLocalSessionState", "true", + "useLocalTransactionState", "true" + )); + properties.putAll( + Map.of( + "rewriteBatchedStatements", "true", + "cacheResultSetMetadata", "true", + "cacheServerConfiguration", "true", + "elideSetAutoCommits", "true", + "maintainTimeStats", "false") + ); + hikariConfig.setDataSourceProperties(properties); + dataSource = new HikariDataSource(hikariConfig); + super.createTableIfNotExist(); + } + + @Override + public void disable() { + if (dataSource != null && !dataSource.isClosed()) + dataSource.close(); + } + + @Override + public Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractSQLDatabase.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractSQLDatabase.java similarity index 70% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractSQLDatabase.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractSQLDatabase.java index ca8bfee9..8bbfb10e 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractSQLDatabase.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/AbstractSQLDatabase.java @@ -15,14 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.sql; +package net.momirealms.customfishing.bukkit.storage.method.database.sql; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.storage.method.AbstractStorage; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; @@ -40,7 +39,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { protected String tablePrefix; - public AbstractSQLDatabase(CustomFishingPlugin plugin) { + public AbstractSQLDatabase(BukkitCustomFishingPlugin plugin) { super(plugin); } @@ -63,12 +62,12 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { statement.execute(tableCreationStatement); } } catch (SQLException e) { - LogUtils.warn("Failed to create tables", e); + plugin.getPluginLogger().warn("Failed to create tables", e); } } catch (SQLException e) { - LogUtils.warn("Failed to get sql connection", e); + plugin.getPluginLogger().warn("Failed to get sql connection", e); } catch (IOException e) { - LogUtils.warn("Failed to get schema resource", e); + plugin.getPluginLogger().warn("Failed to get schema resource", e); } } @@ -80,7 +79,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { * @throws IOException If there is an error reading the schema resource. */ private String[] getSchema(@NotNull String fileName) throws IOException { - return replaceSchemaPlaceholder(new String(Objects.requireNonNull(plugin.getResource("schema/" + fileName + ".sql")) + return replaceSchemaPlaceholder(new String(Objects.requireNonNull(plugin.getBoostrap().getResource("schema/" + fileName + ".sql")) .readAllBytes(), StandardCharsets.UTF_8)).split(";"); } @@ -113,18 +112,11 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { return tablePrefix; } - /** - * Retrieve a player's data from the SQL database. - * - * @param uuid The UUID of the player. - * @param lock Whether to lock the player data during retrieval. - * @return A CompletableFuture containing the optional player data. - */ @SuppressWarnings("DuplicatedCode") @Override public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { var future = new CompletableFuture>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try ( Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data"))) @@ -132,47 +124,44 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { statement.setString(1, uuid.toString()); ResultSet rs = statement.executeQuery(); if (rs.next()) { - if (lock) { - int lockValue = rs.getInt(2); - if (lockValue != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lockValue) { - connection.close(); - future.complete(Optional.of(PlayerData.LOCKED)); - LogUtils.warn("Player " + uuid + "'s data is locked. Retrying..."); - return; - } - } final Blob blob = rs.getBlob("data"); final byte[] dataByteArray = blob.getBytes(1, (int) blob.length()); blob.free(); + PlayerData data = plugin.getStorageManager().fromBytes(dataByteArray); + data.uuid(uuid); + if (lock) { + int lockValue = rs.getInt(2); + if (lockValue != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= lockValue) { + connection.close(); + data.locked(true); + future.complete(Optional.of(data)); + plugin.getPluginLogger().warn("Player " + uuid + "'s data is locked. Retrying..."); + return; + } + } if (lock) lockOrUnlockPlayerData(uuid, true); - future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray))); + future.complete(Optional.of(data)); } else if (Bukkit.getPlayer(uuid) != null) { + // the player is online var data = PlayerData.empty(); + data.uuid(uuid); insertPlayerData(uuid, data, lock); future.complete(Optional.of(data)); } else { future.complete(Optional.empty()); } } catch (SQLException e) { - LogUtils.warn("Failed to get " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e); future.completeExceptionally(e); } }); return future; } - /** - * Update a player's data in the SQL database. - * - * @param uuid The UUID of the player. - * @param playerData The player data to update. - * @param unlock Whether to unlock the player data after updating. - * @return A CompletableFuture indicating the success of the update. - */ @Override public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try ( Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"))) @@ -182,51 +171,37 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { statement.setString(3, uuid.toString()); statement.executeUpdate(); future.complete(true); - plugin.debug("SQL data saved for " + uuid + "; unlock: " + unlock); } catch (SQLException e) { - LogUtils.warn("Failed to update " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to update " + uuid + "'s data.", e); future.completeExceptionally(e); } }); return future; } - /** - * Update data for multiple players in the SQL database. - * - * @param users A collection of OfflineUser objects representing players. - * @param unlock Whether to unlock the player data after updating. - */ @Override - public void updateManyPlayersData(Collection users, boolean unlock) { + public void updateManyPlayersData(Collection users, boolean unlock) { String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")); try (Connection connection = getConnection()) { connection.setAutoCommit(false); try (PreparedStatement statement = connection.prepareStatement(sql)) { - for (OfflineUser user : users) { + for (UserData user : users) { statement.setInt(1, unlock ? 0 : getCurrentSeconds()); - statement.setBlob(2, new ByteArrayInputStream(plugin.getStorageManager().toBytes(user.getPlayerData()))); - statement.setString(3, user.getUUID().toString()); + statement.setBlob(2, new ByteArrayInputStream(plugin.getStorageManager().toBytes(user.toPlayerData()))); + statement.setString(3, user.uuid().toString()); statement.addBatch(); } statement.executeBatch(); connection.commit(); } catch (SQLException e) { connection.rollback(); - LogUtils.warn("Failed to update data for online players", e); + plugin.getPluginLogger().warn("Failed to update data for online players", e); } } catch (SQLException e) { - LogUtils.warn("Failed to get connection when saving online players' data", e); + plugin.getPluginLogger().warn("Failed to get connection when saving online players' data", e); } } - /** - * Insert a new player's data into the SQL database. - * - * @param uuid The UUID of the player. - * @param playerData The player data to insert. - * @param lock Whether to lock the player data upon insertion. - */ public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) { try ( Connection connection = getConnection(); @@ -237,16 +212,10 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { statement.setBlob(3, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData))); statement.execute(); } catch (SQLException e) { - LogUtils.warn("Failed to insert " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to insert " + uuid + "'s data.", e); } } - /** - * Lock or unlock a player's data in the SQL database. - * - * @param uuid The UUID of the player. - * @param lock Whether to lock or unlock the player data. - */ @Override public void lockOrUnlockPlayerData(UUID uuid, boolean lock) { try ( @@ -257,22 +226,14 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { statement.setString(2, uuid.toString()); statement.execute(); } catch (SQLException e) { - LogUtils.warn("Failed to lock " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to lock " + uuid + "'s data.", e); } } - /** - * Update or insert a player's data into the SQL database. - * - * @param uuid The UUID of the player. - * @param playerData The player data to update or insert. - * @param unlock Whether to unlock the player data after updating or inserting. - * @return A CompletableFuture indicating the success of the operation. - */ @Override public CompletableFuture updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try ( Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data"))) @@ -286,23 +247,17 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { future.complete(true); } } catch (SQLException e) { - LogUtils.warn("Failed to get " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e); } }); return future; } - /** - * Get a set of unique user UUIDs from the SQL database. - * - * @param legacy Whether to include legacy data in the retrieval. - * @return A set of unique user UUIDs. - */ @Override - public Set getUniqueUsers(boolean legacy) { + public Set getUniqueUsers() { Set uuids = new HashSet<>(); try (Connection connection = getConnection(); - PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_ALL_UUID, legacy ? getTableName("fishingbag") : getTableName("data")))) { + PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_ALL_UUID, getTableName("data")))) { try (ResultSet rs = statement.executeQuery()) { while (rs.next()) { UUID uuid = UUID.fromString(rs.getString("uuid")); @@ -310,7 +265,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage { } } } catch (SQLException e) { - LogUtils.warn("Failed to get unique data.", e); + plugin.getPluginLogger().warn("Failed to get unique data.", e); } return uuids; } diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/H2Provider.java similarity index 76% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/H2Provider.java index f28e029c..b64a711a 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/H2Impl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/H2Provider.java @@ -15,13 +15,12 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.sql; +package net.momirealms.customfishing.bukkit.storage.method.database.sql; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.libraries.dependencies.Dependency; -import org.bukkit.configuration.file.YamlConfiguration; +import dev.dejvokep.boostedyaml.YamlDocument; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.common.dependency.Dependency; import java.io.File; import java.lang.reflect.Method; @@ -31,13 +30,13 @@ import java.util.EnumSet; /** * An implementation of AbstractSQLDatabase that uses the H2 embedded database for player data storage. */ -public class H2Impl extends AbstractSQLDatabase { +public class H2Provider extends AbstractSQLDatabase { private Object connectionPool; private Method disposeMethod; private Method getConnectionMethod; - public H2Impl(CustomFishingPlugin plugin) { + public H2Provider(BukkitCustomFishingPlugin plugin) { super(plugin); } @@ -45,13 +44,12 @@ public class H2Impl extends AbstractSQLDatabase { * Initialize the H2 database and connection pool based on the configuration. */ @Override - public void initialize() { - YamlConfiguration config = plugin.getConfig("database.yml"); + public void initialize(YamlDocument config) { File databaseFile = new File(plugin.getDataFolder(), config.getString("H2.file", "data.db")); super.tablePrefix = config.getString("H2.table-prefix", "customfishing"); final String url = String.format("jdbc:h2:%s", databaseFile.getAbsolutePath()); - ClassLoader classLoader = ((CustomFishingPluginImpl) plugin).getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER)); + ClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER)); try { Class connectionClass = classLoader.loadClass("org.h2.jdbcx.JdbcConnectionPool"); Method createPoolMethod = connectionClass.getMethod("create", String.class, String.class, String.class); @@ -65,9 +63,6 @@ public class H2Impl extends AbstractSQLDatabase { super.createTableIfNotExist(); } - /** - * Disable the H2 database by disposing of the connection pool. - */ @Override public void disable() { if (connectionPool != null) { diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MariaDBImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MariaDBProvider.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MariaDBImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MariaDBProvider.java index b209b168..90a85178 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MariaDBImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MariaDBProvider.java @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.sql; +package net.momirealms.customfishing.bukkit.storage.method.database.sql; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.StorageType; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; -public class MariaDBImpl extends AbstractHikariDatabase { +public class MariaDBProvider extends AbstractHikariDatabase { - public MariaDBImpl(CustomFishingPlugin plugin) { + public MariaDBProvider(BukkitCustomFishingPlugin plugin) { super(plugin); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MySQLImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MySQLProvider.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MySQLImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MySQLProvider.java index ff644f84..33d05333 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/MySQLImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/MySQLProvider.java @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.sql; +package net.momirealms.customfishing.bukkit.storage.method.database.sql; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.StorageType; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; -public class MySQLImpl extends AbstractHikariDatabase { +public class MySQLProvider extends AbstractHikariDatabase { - public MySQLImpl(CustomFishingPlugin plugin) { + public MySQLProvider(BukkitCustomFishingPlugin plugin) { super(plugin); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/SQLiteImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/SQLiteProvider.java similarity index 65% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/SQLiteImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/SQLiteProvider.java index 9e4ae73e..dc6095ad 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/SQLiteImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/database/sql/SQLiteProvider.java @@ -15,18 +15,16 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.database.sql; +package net.momirealms.customfishing.bukkit.storage.method.database.sql; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.libraries.dependencies.Dependency; -import net.momirealms.customfishing.setting.CFConfig; +import dev.dejvokep.boostedyaml.YamlDocument; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.user.UserData; +import net.momirealms.customfishing.common.dependency.Dependency; import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; import java.lang.reflect.Constructor; @@ -37,25 +35,19 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.CompletableFuture; -/** - * An implementation of AbstractSQLDatabase that uses the SQLite database for player data storage. - */ -public class SQLiteImpl extends AbstractSQLDatabase { +public class SQLiteProvider extends AbstractSQLDatabase { private Connection connection; private File databaseFile; private Constructor connectionConstructor; - public SQLiteImpl(CustomFishingPlugin plugin) { + public SQLiteProvider(BukkitCustomFishingPlugin plugin) { super(plugin); } - /** - * Initialize the SQLite database and connection based on the configuration. - */ @Override - public void initialize() { - ClassLoader classLoader = ((CustomFishingPluginImpl) plugin).getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.SQLITE_DRIVER, Dependency.SLF4J_SIMPLE, Dependency.SLF4J_API)); + public void initialize(YamlDocument config) { + ClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.SQLITE_DRIVER, Dependency.SLF4J_SIMPLE, Dependency.SLF4J_API)); try { Class connectionClass = classLoader.loadClass("org.sqlite.jdbc4.JDBC4Connection"); connectionConstructor = connectionClass.getConstructor(String.class, String.class, Properties.class); @@ -63,15 +55,11 @@ public class SQLiteImpl extends AbstractSQLDatabase { throw new RuntimeException(e); } - YamlConfiguration config = plugin.getConfig("database.yml"); this.databaseFile = new File(plugin.getDataFolder(), config.getString("SQLite.file", "data") + ".db"); super.tablePrefix = config.getString("SQLite.table-prefix", "customfishing"); super.createTableIfNotExist(); } - /** - * Disable the SQLite database by closing the connection. - */ @Override public void disable() { try { @@ -113,18 +101,11 @@ public class SQLiteImpl extends AbstractSQLDatabase { } } - /** - * Asynchronously retrieve player data from the SQLite database. - * - * @param uuid The UUID of the player. - * @param lock Flag indicating whether to lock the data. - * @return A CompletableFuture with an optional PlayerData. - */ @SuppressWarnings("DuplicatedCode") @Override public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { var future = new CompletableFuture>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try ( Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data"))) @@ -132,42 +113,38 @@ public class SQLiteImpl extends AbstractSQLDatabase { statement.setString(1, uuid.toString()); ResultSet rs = statement.executeQuery(); if (rs.next()) { + final byte[] dataByteArray = rs.getBytes("data"); + PlayerData data = plugin.getStorageManager().fromBytes(dataByteArray); + data.uuid(uuid); int lockValue = rs.getInt(2); - if (lockValue != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lockValue) { + if (lockValue != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= lockValue) { connection.close(); - future.complete(Optional.of(PlayerData.LOCKED)); + data.locked(true); + future.complete(Optional.of(data)); return; } - final byte[] dataByteArray = rs.getBytes("data"); if (lock) lockOrUnlockPlayerData(uuid, true); - future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray))); + future.complete(Optional.of(data)); } else if (Bukkit.getPlayer(uuid) != null) { var data = PlayerData.empty(); + data.uuid(uuid); insertPlayerData(uuid, data, lock); future.complete(Optional.of(data)); } else { future.complete(Optional.empty()); } } catch (SQLException e) { - LogUtils.warn("Failed to get " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e); future.completeExceptionally(e); } }); return future; } - /** - * Asynchronously update player data in the SQLite database. - * - * @param uuid The UUID of the player. - * @param playerData The player's data to update. - * @param unlock Flag indicating whether to unlock the data. - * @return A CompletableFuture indicating the update result. - */ @Override public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) { var future = new CompletableFuture(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try ( Connection connection = getConnection(); PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"))) @@ -178,49 +155,36 @@ public class SQLiteImpl extends AbstractSQLDatabase { statement.executeUpdate(); future.complete(true); } catch (SQLException e) { - LogUtils.warn("Failed to update " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to update " + uuid + "'s data.", e); future.completeExceptionally(e); } }); return future; } - /** - * Asynchronously update data for multiple players in the SQLite database. - * - * @param users A collection of OfflineUser instances to update. - * @param unlock Flag indicating whether to unlock the data. - */ @Override - public void updateManyPlayersData(Collection users, boolean unlock) { + public void updateManyPlayersData(Collection users, boolean unlock) { String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")); try (Connection connection = getConnection()) { connection.setAutoCommit(false); try (PreparedStatement statement = connection.prepareStatement(sql)) { - for (OfflineUser user : users) { + for (UserData user : users) { statement.setInt(1, unlock ? 0 : getCurrentSeconds()); - statement.setBytes(2, plugin.getStorageManager().toBytes(user.getPlayerData())); - statement.setString(3, user.getUUID().toString()); + statement.setBytes(2, plugin.getStorageManager().toBytes(user.toPlayerData())); + statement.setString(3, user.uuid().toString()); statement.addBatch(); } statement.executeBatch(); connection.commit(); } catch (SQLException e) { connection.rollback(); - LogUtils.warn("Failed to update bag data for online players", e); + plugin.getPluginLogger().warn("Failed to update bag data for online players", e); } } catch (SQLException e) { - LogUtils.warn("Failed to get connection when saving online players' data", e); + plugin.getPluginLogger().warn("Failed to get connection when saving online players' data", e); } } - /** - * Insert player data into the SQLite database. - * - * @param uuid The UUID of the player. - * @param playerData The player's data to insert. - * @param lock Flag indicating whether to lock the data. - */ @Override public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) { try ( @@ -232,7 +196,7 @@ public class SQLiteImpl extends AbstractSQLDatabase { statement.setBytes(3, plugin.getStorageManager().toBytes(playerData)); statement.execute(); } catch (SQLException e) { - LogUtils.warn("Failed to insert " + uuid + "'s data.", e); + plugin.getPluginLogger().warn("Failed to insert " + uuid + "'s data.", e); } } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/file/JsonImpl.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/JsonProvider.java similarity index 89% rename from plugin/src/main/java/net/momirealms/customfishing/storage/method/file/JsonImpl.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/JsonProvider.java index 9f4b2f7f..4432c4d4 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/file/JsonImpl.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/JsonProvider.java @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.storage.method.file; +package net.momirealms.customfishing.bukkit.storage.method.file; import com.google.gson.Gson; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.storage.method.AbstractStorage; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage; import org.bukkit.Bukkit; import java.io.File; @@ -38,10 +38,10 @@ import java.util.concurrent.CompletableFuture; /** * A data storage implementation that uses JSON files to store player data. */ -public class JsonImpl extends AbstractStorage { +public class JsonProvider extends AbstractStorage { @SuppressWarnings("ResultOfMethodCallIgnored") - public JsonImpl(CustomFishingPlugin plugin) { + public JsonProvider(BukkitCustomFishingPlugin plugin) { super(plugin); File folder = new File(plugin.getDataFolder(), "data"); if (!folder.exists()) folder.mkdirs(); @@ -60,6 +60,7 @@ public class JsonImpl extends AbstractStorage { playerData = readFromJsonFile(file, PlayerData.class); } else if (Bukkit.getPlayer(uuid) != null) { playerData = PlayerData.empty(); + playerData.uuid(uuid); } else { playerData = null; } @@ -130,7 +131,7 @@ public class JsonImpl extends AbstractStorage { // Retrieve a set of unique user UUIDs based on JSON data files in the 'data' folder. @Override - public Set getUniqueUsers(boolean legacy) { + public Set getUniqueUsers() { // No legacy files File folder = new File(plugin.getDataFolder(), "data"); Set uuids = new HashSet<>(); diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/YAMLProvider.java b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/YAMLProvider.java new file mode 100644 index 00000000..e844d779 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/storage/method/file/YAMLProvider.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.storage.method.file; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.storage.StorageType; +import net.momirealms.customfishing.api.storage.data.EarningData; +import net.momirealms.customfishing.api.storage.data.InventoryData; +import net.momirealms.customfishing.api.storage.data.PlayerData; +import net.momirealms.customfishing.api.storage.data.StatisticData; +import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class YAMLProvider extends AbstractStorage { + + @SuppressWarnings("ResultOfMethodCallIgnored") + public YAMLProvider(BukkitCustomFishingPlugin plugin) { + super(plugin); + File folder = new File(plugin.getDataFolder(), "data"); + if (!folder.exists()) folder.mkdirs(); + } + + @Override + public StorageType getStorageType() { + return StorageType.YAML; + } + + /** + * Get the file associated with a player's UUID for storing YAML data. + * + * @param uuid The UUID of the player. + * @return The file for the player's data. + */ + public File getPlayerDataFile(UUID uuid) { + return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".yml"); + } + + @Override + public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { + File dataFile = getPlayerDataFile(uuid); + if (!dataFile.exists()) { + if (Bukkit.getPlayer(uuid) != null) { + var data = PlayerData.empty(); + data.uuid(uuid); + return CompletableFuture.completedFuture(Optional.of(data)); + } else { + return CompletableFuture.completedFuture(Optional.empty()); + } + } + YamlDocument data = plugin.getConfigManager().loadData(dataFile); + PlayerData playerData = PlayerData.builder() + .bag(new InventoryData(data.getString("bag", ""), data.getInt("size", 9))) + .earnings(new EarningData(data.getDouble("earnings"), data.getInt("date"))) + .statistics(getStatistics(data.getSection("stats"))) + .name(data.getString("name", "")) + .build(); + return CompletableFuture.completedFuture(Optional.of(playerData)); + } + + @Override + public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) { + YamlConfiguration data = new YamlConfiguration(); + data.set("name", playerData.name()); + data.set("bag", playerData.bagData().serialized); + data.set("size", playerData.bagData().size); + data.set("date", playerData.earningData().date); + data.set("earnings", playerData.earningData().earnings); + ConfigurationSection section = data.createSection("stats"); + ConfigurationSection amountSection = section.createSection("amount"); + ConfigurationSection sizeSection = section.createSection("size"); + for (Map.Entry entry : playerData.statistics().amountMap.entrySet()) { + amountSection.set(entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : playerData.statistics().sizeMap.entrySet()) { + sizeSection.set(entry.getKey(), entry.getValue()); + } + try { + data.save(getPlayerDataFile(uuid)); + } catch (IOException e) { + plugin.getPluginLogger().warn("Failed to save player data", e); + } + return CompletableFuture.completedFuture(true); + } + + @Override + public Set getUniqueUsers() { + File folder = new File(plugin.getDataFolder(), "data"); + Set uuids = new HashSet<>(); + if (folder.exists()) { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + uuids.add(UUID.fromString(file.getName().substring(0, file.getName().length() - 4))); + } + } + } + return uuids; + } + + private StatisticData getStatistics(Section section) { + HashMap amountMap = new HashMap<>(); + HashMap sizeMap = new HashMap<>(); + if (section == null) { + return new StatisticData(amountMap, sizeMap); + } + Section amountSection = section.getSection("amount"); + if (amountSection != null) { + for (Map.Entry entry : amountSection.getStringRouteMappedValues(false).entrySet()) { + amountMap.put(entry.getKey(), (Integer) entry.getValue()); + } + } + Section sizeSection = section.getSection("size"); + if (sizeSection != null) { + for (Map.Entry entry : sizeSection.getStringRouteMappedValues(false).entrySet()) { + sizeMap.put(entry.getKey(), ((Double) entry.getValue()).floatValue()); + } + } + return new StatisticData(amountMap, sizeMap); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/totem/ActivatedTotem.java b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/ActivatedTotem.java new file mode 100644 index 00000000..2bd8d67b --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/ActivatedTotem.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.totem; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; +import net.momirealms.customfishing.api.mechanic.totem.TotemParticle; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class ActivatedTotem { + + private final List subTasks; + private final Location coreLocation; + private final TotemConfig totemConfig; + private final long expireTime; + private final Context context; + private final double radius; + + public ActivatedTotem(Player activator, Location coreLocation, TotemConfig config) { + this.context = Context.player(activator, true) + .arg(ContextKeys.LOCATION, coreLocation) + .arg(ContextKeys.X, coreLocation.getBlockX()) + .arg(ContextKeys.Y, coreLocation.getBlockY()) + .arg(ContextKeys.Z, coreLocation.getBlockZ()) + .arg(ContextKeys.ID, config.id()); + this.subTasks = new ArrayList<>(); + this.expireTime = (long) (System.currentTimeMillis() + config.duration().evaluate(context) * 1000L); + this.coreLocation = coreLocation.clone().add(0.5,0,0.5); + this.totemConfig = config; + this.radius = config.radius().evaluate(context); + for (TotemParticle particleSetting : config.particleSettings()) { + this.subTasks.add(particleSetting.start(coreLocation, radius)); + } + } + + public TotemConfig getTotemConfig() { + return totemConfig; + } + + public Location getCoreLocation() { + return coreLocation; + } + + public void cancel() { + for (SchedulerTask task : this.subTasks) { + task.cancel(); + } + this.subTasks.clear(); + } + + public long getExpireTime() { + return this.expireTime; + } + + public double getRadius() { + return radius; + } + + public void doTimerAction() { + this.context.arg(ContextKeys.TIME_LEFT, String.valueOf((expireTime - System.currentTimeMillis())/1000)); + BukkitCustomFishingPlugin.getInstance().getEventManager().getEventCarrier(totemConfig.id(), MechanicType.TOTEM) + .ifPresent(carrier -> carrier.trigger(context, ActionTrigger.TIMER)); + } +} diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/totem/BukkitTotemManager.java b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/BukkitTotemManager.java new file mode 100644 index 00000000..f1597f31 --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/BukkitTotemManager.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.totem; + +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.event.TotemActivateEvent; +import net.momirealms.customfishing.api.mechanic.MechanicType; +import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; +import net.momirealms.customfishing.api.mechanic.config.ConfigManager; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; +import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager; +import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; +import net.momirealms.customfishing.api.mechanic.totem.TotemManager; +import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock; +import net.momirealms.customfishing.api.util.SimpleLocation; +import net.momirealms.customfishing.bukkit.util.LocationUtils; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class BukkitTotemManager implements TotemManager, Listener { + + private final BukkitCustomFishingPlugin plugin; + private final HashMap> block2Totem = new HashMap<>(); + private final HashMap id2Totem = new HashMap<>(); + private final List allMaterials = Arrays.stream(Material.values()).map(Enum::name).toList(); + private final ConcurrentHashMap activatedTotems = new ConcurrentHashMap<>(); + private SchedulerTask timerCheckTask; + + public BukkitTotemManager(BukkitCustomFishingPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + this.timerCheckTask = plugin.getScheduler().asyncRepeating(() -> { + long time = System.currentTimeMillis(); + ArrayList removed = new ArrayList<>(); + for (Map.Entry entry : activatedTotems.entrySet()) { + if (time > entry.getValue().getExpireTime()) { + removed.add(entry.getKey()); + entry.getValue().cancel(); + } else { + entry.getValue().doTimerAction(); + } + } + for (SimpleLocation simpleLocation : removed) { + activatedTotems.remove(simpleLocation); + } + }, 1, 1, TimeUnit.SECONDS); + plugin.debug("Loaded " + id2Totem.size() + " totems"); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + for (ActivatedTotem activatedTotem : this.activatedTotems.values()) + activatedTotem.cancel(); + this.activatedTotems.clear(); + if (this.timerCheckTask != null) + this.timerCheckTask.cancel(); + this.block2Totem.clear(); + } + + @Override + public Collection getActivatedTotems(Location location) { + Collection activated = new ArrayList<>(); + double nearest = Double.MAX_VALUE; + String nearestTotemID = null; + for (ActivatedTotem activatedTotem : activatedTotems.values()) { + double distance = LocationUtils.getDistance(activatedTotem.getCoreLocation(), location); + if (distance < activatedTotem.getRadius()) { + activated.add(activatedTotem.getTotemConfig().id()); + if (nearest > distance) { + nearest = distance; + nearestTotemID = activatedTotem.getTotemConfig().id(); + } + } + } + if (nearestTotemID == null) return List.of(); + if (!ConfigManager.allowMultipleTotemType()) { + if (ConfigManager.allowSameTotemType()) { + String finalNearestTotemID = nearestTotemID; + activated.removeIf(element -> !element.equals(finalNearestTotemID)); + return activated; + } else { + return List.of(nearestTotemID); + } + } else { + if (ConfigManager.allowSameTotemType()) { + return activated; + } else { + return new HashSet<>(activated); + } + } + } + + @EventHandler + public void onBreakTotemCore(BlockBreakEvent event) { + if (event.isCancelled()) + return; + Location location = event.getBlock().getLocation(); + SimpleLocation simpleLocation = SimpleLocation.of(location); + ActivatedTotem activatedTotem = activatedTotems.remove(simpleLocation); + if (activatedTotem != null) + activatedTotem.cancel(); + } + + @EventHandler (ignoreCancelled = true) + public void onInteractBlock(PlayerInteractEvent event) { + if ( + event.isBlockInHand() || + event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK || + event.getHand() != EquipmentSlot.HAND + ) + return; + + Block block = event.getClickedBlock(); + assert block != null; + String id = plugin.getBlockManager().getBlockID(block); + List configs = block2Totem.get(id); + if (configs == null) + return; + TotemConfig config = null; + for (TotemConfig temp : configs) { + if (temp.isRightPattern(block.getLocation())) { + config = temp; + break; + } + } + if (config == null) + return; + + String totemID = config.id(); + final Player player = event.getPlayer();; + Context context = Context.player(player); + Optional optionalEffectModifier = plugin.getEffectManager().getEffectModifier(totemID, MechanicType.TOTEM); + if (optionalEffectModifier.isPresent()) { + if (!RequirementManager.isSatisfied(context, optionalEffectModifier.get().requirements())) { + return; + } + } + + TotemActivateEvent totemActivateEvent = new TotemActivateEvent(player, block.getLocation(), config); + Bukkit.getPluginManager().callEvent(totemActivateEvent); + if (totemActivateEvent.isCancelled()) { + return; + } + + plugin.getEventManager().trigger(context, totemID, MechanicType.TOTEM, ActionTrigger.ACTIVATE); + + Location location = block.getLocation(); + ActivatedTotem activatedTotem = new ActivatedTotem(player, location, config); + SimpleLocation simpleLocation = SimpleLocation.of(location); + ActivatedTotem previous = this.activatedTotems.put(simpleLocation, activatedTotem); + if (previous != null) { + previous.cancel(); + } + } + + @Override + public boolean registerTotem(TotemConfig totem) { + if (id2Totem.containsKey(totem.id())) { + return false; + } + HashSet coreMaterials = new HashSet<>(); + for (TotemBlock totemBlock : totem.totemCore()) { + String text = totemBlock.getTypeCondition().getRawText(); + if (text.startsWith("*")) { + String sub = text.substring(1); + coreMaterials.addAll(allMaterials.stream().filter(it -> it.endsWith(sub)).toList()); + } else if (text.endsWith("*")) { + String sub = text.substring(0, text.length() - 1); + coreMaterials.addAll(allMaterials.stream().filter(it -> it.startsWith(sub)).toList()); + } else { + coreMaterials.add(text); + } + } + for (String material : coreMaterials) { + List configs = this.block2Totem.getOrDefault(material, new ArrayList<>()); + configs.add(totem); + this.block2Totem.put(material, configs); + } + id2Totem.put(totem.id(), totem); + return true; + } + + @NotNull + @Override + public Optional getTotem(String id) { + return Optional.ofNullable(id2Totem.get(id)); + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/DustParticleSetting.java b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/DustParticleSetting.java similarity index 84% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/DustParticleSetting.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/DustParticleSetting.java index ebc34e5b..d738ef93 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/DustParticleSetting.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/DustParticleSetting.java @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.totem.particle; +package net.momirealms.customfishing.bukkit.totem.particle; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.scheduler.CancellableTask; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.Pair; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; @@ -46,9 +46,9 @@ public class DustParticleSetting extends ParticleSetting { } @SuppressWarnings("DuplicatedCode") - public CancellableTask start(Location location, double radius) { + public SchedulerTask start(Location location, double radius) { World world = location.getWorld(); - return CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { + return BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> { for (Pair range : ranges) { for (double theta = range.left(); theta <= range.right(); theta += interval) { double r = expressionHorizontal.setVariable("theta", theta).setVariable("radius", radius).evaluate(); diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/ParticleSetting.java b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/ParticleSetting.java similarity index 87% rename from plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/ParticleSetting.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/ParticleSetting.java index 0ec64ac1..c29239a4 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/particle/ParticleSetting.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/totem/particle/ParticleSetting.java @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.mechanic.totem.particle; +package net.momirealms.customfishing.bukkit.totem.particle; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; import net.momirealms.customfishing.api.mechanic.totem.TotemParticle; -import net.momirealms.customfishing.api.scheduler.CancellableTask; +import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customfishing.common.util.Pair; import net.objecthunter.exp4j.Expression; import net.objecthunter.exp4j.ExpressionBuilder; import org.bukkit.Location; @@ -63,9 +63,9 @@ public class ParticleSetting implements TotemParticle { } @SuppressWarnings("DuplicatedCode") - public CancellableTask start(Location location, double radius) { + public SchedulerTask start(Location location, double radius) { World world = location.getWorld(); - return CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { + return BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> { for (Pair range : ranges) { for (double theta = range.left(); theta <= range.right(); theta += interval) { double r = expressionHorizontal.setVariable("theta", theta).setVariable("radius", radius).evaluate(); diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/util/ItemStackUtils.java b/core/src/main/java/net/momirealms/customfishing/bukkit/util/ItemStackUtils.java new file mode 100644 index 00000000..60e470fb --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/util/ItemStackUtils.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.util; + +import com.saicone.rtag.item.ItemTagStream; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customfishing.api.mechanic.item.ItemEditor; +import net.momirealms.customfishing.api.mechanic.item.tag.TagMap; +import net.momirealms.customfishing.api.mechanic.item.tag.TagValueType; +import net.momirealms.customfishing.api.mechanic.misc.value.MathValue; +import net.momirealms.customfishing.api.mechanic.misc.value.TextValue; +import net.momirealms.customfishing.common.util.ArrayUtils; +import net.momirealms.customfishing.common.util.Pair; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +import static net.momirealms.customfishing.api.util.TagUtils.toTypeAndData; +import static net.momirealms.customfishing.common.util.ArrayUtils.splitValue; + +public class ItemStackUtils { + + private ItemStackUtils() {} + + public static ItemStack fromBase64(String base64) { + if (base64 == null || base64.isEmpty()) + return new ItemStack(Material.AIR); + ByteArrayInputStream inputStream; + try { + inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64)); + } catch (IllegalArgumentException e) { + return new ItemStack(Material.AIR); + } + ItemStack stack = null; + try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) { + stack = (ItemStack) dataInput.readObject(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + return stack; + } + + public static String toBase64(ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR) + return ""; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) { + dataOutput.writeObject(itemStack); + byte[] byteArr = outputStream.toByteArray(); + dataOutput.close(); + outputStream.close(); + return Base64Coder.encodeLines(byteArr); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + public static Map itemStackToMap(ItemStack itemStack) { + Map map = ItemTagStream.INSTANCE.toMap(itemStack); + map.remove("rtagDataVersion"); + map.remove("count"); + map.remove("id"); + map.put("material", itemStack.getType().name().toLowerCase(Locale.ENGLISH)); + map.put("amount", itemStack.getAmount()); + Object tag = map.remove("tags"); + if (tag != null) { + map.put("nbt", tag); + } + return map; + } + + private static void sectionToMap(Section section, Map outPut) { + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + HashMap map = new HashMap<>(); + outPut.put(entry.getKey(), map); + sectionToMap(inner, map); + } else { + outPut.put(entry.getKey(), entry.getValue()); + } + } + } + + @SuppressWarnings("UnstableApiUsage") + public static void sectionToComponentEditor(Section section, List itemEditors) { + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + String component = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Section inner) { + Map innerMap = new HashMap<>(); + sectionToMap(inner, innerMap); + TagMap tagMap = TagMap.of(innerMap); + itemEditors.add(((item, context) -> { + item.setComponent(component, tagMap.apply(context)); + })); + } else if (value instanceof List list) { + Object first = list.get(0); + if (first instanceof Map) { + ArrayList output = new ArrayList<>(); + for (Object o : list) { + Map innerMap = (Map) o; + TagMap tagMap = TagMap.of(innerMap); + output.add(tagMap); + } + itemEditors.add(((item, context) -> { + List> maps = output.stream().map(unparsed -> unparsed.apply(context)).toList(); + item.setComponent(component, maps); + })); + } else if (first instanceof String str) { + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case INT -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List integers = values.stream().map(unparsed -> (int) unparsed.evaluate(context)).toList(); + item.setComponent(component, integers); + })); + } + case BYTE -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List bytes = values.stream().map(unparsed -> (byte) unparsed.evaluate(context)).toList(); + item.setComponent(component, bytes); + })); + } + case LONG -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List longs = values.stream().map(unparsed -> (long) unparsed.evaluate(context)).toList(); + item.setComponent(component, longs); + })); + } + case FLOAT -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List floats = values.stream().map(unparsed -> (float) unparsed.evaluate(context)).toList(); + item.setComponent(component, floats); + })); + } + case DOUBLE -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List doubles = values.stream().map(unparsed -> (double) unparsed.evaluate(context)).toList(); + item.setComponent(component, doubles); + })); + } + case STRING -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(TextValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List texts = values.stream().map(unparsed -> unparsed.render(context)).toList(); + item.setComponent(component, texts); + })); + } + } + + } else { + itemEditors.add(((item, context) -> { + item.setComponent(component, list); + })); + } + } else if (value instanceof String str) { + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case INT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (int) mathValue.evaluate(context)); + })); + } + case BYTE -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (byte) mathValue.evaluate(context)); + })); + } + case FLOAT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (float) mathValue.evaluate(context)); + })); + } + case LONG -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (long) mathValue.evaluate(context)); + })); + } + case SHORT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (short) mathValue.evaluate(context)); + })); + } + case DOUBLE -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, (double) mathValue.evaluate(context)); + })); + } + case STRING -> { + TextValue textValue = TextValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.setComponent(component, textValue.render(context)); + })); + } + case INTARRAY -> { + String[] split = splitValue(str); + int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); + itemEditors.add(((item, context) -> { + item.setComponent(component, array); + })); + } + case BYTEARRAY -> { + String[] split = splitValue(str); + byte[] bytes = new byte[split.length]; + for (int i = 0; i < split.length; i++){ + bytes[i] = Byte.parseByte(split[i]); + } + itemEditors.add(((item, context) -> { + item.setComponent(component, bytes); + })); + } + } + } else { + itemEditors.add(((item, context) -> { + item.setComponent(component, value); + })); + } + } + } + + // ugly codes, remaining improvements + public static void sectionToTagEditor(Section section, List itemEditors, String... route) { + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + Object value = entry.getValue(); + String key = entry.getKey(); + String[] currentRoute = ArrayUtils.appendElementToArray(route, key); + if (value instanceof Section inner) { + sectionToTagEditor(inner, itemEditors, currentRoute); + } else if (value instanceof List list) { + Object first = list.get(0); + if (first instanceof Map) { + List maps = new ArrayList<>(); + for (Object o : list) { + Map map = (Map) o; + maps.add(TagMap.of(map)); + } + itemEditors.add(((item, context) -> { + List> parsed = maps.stream().map(render -> render.apply(context)).toList(); + item.set(parsed, (Object[]) currentRoute); + })); + } else { + if (first instanceof String str) { + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case INT -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List integers = values.stream().map(unparsed -> (int) unparsed.evaluate(context)).toList(); + item.set(integers, (Object[]) currentRoute); + })); + } + case BYTE -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List bytes = values.stream().map(unparsed -> (byte) unparsed.evaluate(context)).toList(); + item.set(bytes, (Object[]) currentRoute); + })); + } + case LONG -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List longs = values.stream().map(unparsed -> (long) unparsed.evaluate(context)).toList(); + item.set(longs, (Object[]) currentRoute); + })); + } + case FLOAT -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List floats = values.stream().map(unparsed -> (float) unparsed.evaluate(context)).toList(); + item.set(floats, (Object[]) currentRoute); + })); + } + case DOUBLE -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(MathValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List doubles = values.stream().map(unparsed -> (double) unparsed.evaluate(context)).toList(); + item.set(doubles, (Object[]) currentRoute); + })); + } + case STRING -> { + List> values = new ArrayList<>(); + for (Object o : list) { + values.add(TextValue.auto(toTypeAndData((String) o).right())); + } + itemEditors.add(((item, context) -> { + List texts = values.stream().map(unparsed -> unparsed.render(context)).toList(); + item.set(texts, (Object[]) currentRoute); + })); + } + } + } else { + itemEditors.add(((item, context) -> { + item.set(list, (Object[]) currentRoute); + })); + } + } + } else if (value instanceof String str) { + Pair pair = toTypeAndData(str); + switch (pair.left()) { + case INT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((int) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case BYTE -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((byte) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case LONG -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((long) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case SHORT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((short) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case DOUBLE -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((double) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case FLOAT -> { + MathValue mathValue = MathValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set((float) mathValue.evaluate(context), (Object[]) currentRoute); + })); + } + case STRING -> { + TextValue textValue = TextValue.auto(pair.right()); + itemEditors.add(((item, context) -> { + item.set(textValue.render(context), (Object[]) currentRoute); + })); + } + case INTARRAY -> { + String[] split = splitValue(str); + int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); + itemEditors.add(((item, context) -> { + item.set(array, (Object[]) currentRoute); + })); + } + case BYTEARRAY -> { + String[] split = splitValue(str); + byte[] bytes = new byte[split.length]; + for (int i = 0; i < split.length; i++){ + bytes[i] = Byte.parseByte(split[i]); + } + itemEditors.add(((item, context) -> { + item.set(bytes, (Object[]) currentRoute); + })); + } + } + } else { + itemEditors.add(((item, context) -> { + item.set(value, (Object[]) currentRoute); + })); + } + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/LocationUtils.java b/core/src/main/java/net/momirealms/customfishing/bukkit/util/LocationUtils.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customfishing/util/LocationUtils.java rename to core/src/main/java/net/momirealms/customfishing/bukkit/util/LocationUtils.java index d4616720..10c9b0cf 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/util/LocationUtils.java +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/util/LocationUtils.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customfishing.util; +package net.momirealms.customfishing.bukkit.util; import org.bukkit.Bukkit; import org.bukkit.Location; diff --git a/core/src/main/java/net/momirealms/customfishing/bukkit/util/PlayerUtils.java b/core/src/main/java/net/momirealms/customfishing/bukkit/util/PlayerUtils.java new file mode 100644 index 00000000..248bc47a --- /dev/null +++ b/core/src/main/java/net/momirealms/customfishing/bukkit/util/PlayerUtils.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.bukkit.util; + +import net.momirealms.customfishing.common.util.RandomUtils; +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +public class PlayerUtils { + + public static void dropItem(@NotNull Player player, @NotNull ItemStack itemStack, boolean retainOwnership, boolean noPickUpDelay, boolean throwRandomly) { + requireNonNull(player, "player"); + requireNonNull(itemStack, "itemStack"); + Location location = player.getLocation().clone(); + Item item = player.getWorld().dropItem(player.getEyeLocation().clone().subtract(new Vector(0,0.3,0)), itemStack); + item.setPickupDelay(noPickUpDelay ? 0 : 40); + if (retainOwnership) { + item.setThrower(player.getUniqueId()); + } + if (throwRandomly) { + double d1 = RandomUtils.generateRandomDouble(0,1) * 0.5f; + double d2 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2); + item.setVelocity(new Vector(-Math.sin(d2) * d1, 0.2f, Math.cos(d2) * d1)); + } else { + double d1 = Math.sin(location.getPitch() * (Math.PI/180)); + double d2 = RandomUtils.generateRandomDouble(0, 0.02); + double d3 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2); + Vector vector = location.getDirection().multiply(0.3).setY(-d1 * 0.3 + 0.1 + (RandomUtils.generateRandomDouble(0,1) - RandomUtils.generateRandomDouble(0,1)) * 0.1); + vector.add(new Vector(Math.cos(d3) * d2, 0, Math.sin(d3) * d2)); + item.setVelocity(vector); + } + } + + public static int putItemsToInventory(Inventory inventory, ItemStack itemStack, int amount) { + ItemMeta meta = itemStack.getItemMeta(); + int maxStackSize = itemStack.getMaxStackSize(); + for (ItemStack other : inventory.getStorageContents()) { + if (other != null) { + if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { + if (other.getAmount() < maxStackSize) { + int delta = maxStackSize - other.getAmount(); + if (amount > delta) { + other.setAmount(maxStackSize); + amount -= delta; + } else { + other.setAmount(amount + other.getAmount()); + return 0; + } + } + } + } + } + + if (amount > 0) { + for (ItemStack other : inventory.getStorageContents()) { + if (other == null) { + if (amount > maxStackSize) { + amount -= maxStackSize; + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + inventory.addItem(cloned); + } else { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(amount); + inventory.addItem(cloned); + return 0; + } + } + } + } + + return amount; + } + + public static int giveItem(Player player, ItemStack itemStack, int amount) { + PlayerInventory inventory = player.getInventory(); + ItemMeta meta = itemStack.getItemMeta(); + int maxStackSize = itemStack.getMaxStackSize(); + if (amount > maxStackSize * 100) { + amount = maxStackSize * 100; + } + int actualAmount = amount; + for (ItemStack other : inventory.getStorageContents()) { + if (other != null) { + if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { + if (other.getAmount() < maxStackSize) { + int delta = maxStackSize - other.getAmount(); + if (amount > delta) { + other.setAmount(maxStackSize); + amount -= delta; + } else { + other.setAmount(amount + other.getAmount()); + return actualAmount; + } + } + } + } + } + if (amount > 0) { + for (ItemStack other : inventory.getStorageContents()) { + if (other == null) { + if (amount > maxStackSize) { + amount -= maxStackSize; + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + inventory.addItem(cloned); + } else { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(amount); + inventory.addItem(cloned); + return actualAmount; + } + } + } + } + + if (amount > 0) { + for (int i = 0; i < amount / maxStackSize; i++) { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + player.getWorld().dropItem(player.getLocation(), cloned); + } + int left = amount % maxStackSize; + if (left != 0) { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(left); + player.getWorld().dropItem(player.getLocation(), cloned); + } + } + + return actualAmount; + } +} diff --git a/core/src/main/resources/commands.yml b/core/src/main/resources/commands.yml new file mode 100644 index 00000000..63e514cb --- /dev/null +++ b/core/src/main/resources/commands.yml @@ -0,0 +1,186 @@ +# +# Don't change this +# +config-version: "${config_version}" + +# +# For safety reasons, editing this file requires a restart to apply +# + +# A command to reload the plugin +# Usage: [COMMAND] +reload: + enable: true + permission: customfishing.command.reload + usage: + - /customfishing reload + - /cfishing reload + +# A command designed for players to sell fish +# Usage: [COMMAND] +sellfish: + enable: true + permission: customfishing.sellfish + usage: + - /sellfish + +# A command designed for players to open the fishing bag +# Usage: [COMMAND] +fishingbag: + enable: true + permission: fishingbag.user + usage: + - /fishingbag + +# A command to get items +# Usage: [COMMAND] [id] +get_item: + enable: true + permission: customfishing.command.getitem + usage: + - /customfishing items get + - /cfishing items get + +# A command to give items +# Usage: [COMMAND] [id] +give_item: + enable: true + permission: customfishing.command.giveitem + usage: + - /customfishing items give + - /cfishing items give + +# A command to import items +# Usage: [COMMAND] [type] [id] +import_item: + enable: true + permission: customfishing.command.importitem + usage: + - /customfishing items import + - /cfishing items import + +# A command to stop the competition +# Usage: [COMMAND] +stop_competition: + enable: true + permission: customfishing.command.competition + usage: + - /customfishing competition stop + - /cfishing competition stop + +# A command to end the competition +# Usage: [COMMAND] [type] [id] +end_competition: + enable: true + permission: customfishing.command.competition + usage: + - /customfishing competition end + - /cfishing competition end + +# A command to start competitions +# Usage: [COMMAND] [id] +start_competition: + enable: true + permission: customfishing.command.competition + usage: + - /customfishing competition start + - /cfishing competition start + +# A command to open market for players +# Usage: [COMMAND] [player] +open_market: + enable: true + permission: customfishing.command.open.market + usage: + - /customfishing open market + - /cfishing open market + +# A command to open bag for players +# Usage: [COMMAND] [player] +open_bag: + enable: true + permission: customfishing.command.open.bag + usage: + - /customfishing open bag + - /cfishing open bag + +# A command to edit bag contents +# Usage: [COMMAND] [player] +edit_online_bag: + enable: true + permission: customfishing.command.edit.bag + usage: + - /customfishing fishingbag edit-online + - /cfishing fishingbag edit-online + +# A command to edit bag contents +# Usage: [COMMAND] [uuid] +edit_offline_bag: + enable: true + permission: customfishing.command.edit.bag + usage: + - /customfishing fishingbag edit-offline + - /cfishing fishingbag edit-offline + +# A command to unlock those locked data +# Usage: [COMMAND] [uuid] +data_unlock: + enable: true + permission: customfishing.command.data + usage: + - /customfishing data unlock + - /cfishing data unlock + +# A command to export the data +# Usage: [COMMAND] +data_export: + enable: true + permission: customfishing.command.data + usage: + - /customfishing data export + - /cfishing data export + +# A command to import the data +# Usage: [COMMAND] [file] +data_import: + enable: true + permission: customfishing.command.data + usage: + - /customfishing data import + - /cfishing data import + +# A command to set a player's fishing statistics +# Usage: [COMMAND] [player] [id] [type] [value] +statistics_set: + enable: true + permission: customfishing.command.statistics + usage: + - /customfishing statistics set + - /cfishing statistics set + +# A command to reset a player's fishing statistics +# Usage: [COMMAND] [player] +statistics_reset: + enable: true + permission: customfishing.command.statistics + usage: + - /customfishing statistics reset + - /cfishing statistics reset + +# A command to query a player's fishing statistics +# Usage: [COMMAND] [player] [type] +statistics_query: + enable: true + permission: customfishing.command.statistics + usage: + - /customfishing statistics query + - /cfishing statistics query + +# A command to manually add a player's fishing statistics +# Usage: [COMMAND] [player] [id] [type] [value] +statistics_add: + enable: true + permission: customfishing.command.statistics + usage: + - /customfishing statistics add + - /cfishing statistics add \ No newline at end of file diff --git a/plugin/src/main/resources/config.yml b/core/src/main/resources/config.yml similarity index 54% rename from plugin/src/main/resources/config.yml rename to core/src/main/resources/config.yml index 580fcb50..427fa637 100644 --- a/plugin/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -1,20 +1,13 @@ -# Developer: @Xiao-MoMi -# Wiki: https://mo-mi.gitbook.io/xiaomomi-plugins/ -config-version: '32' +# Don"t change this +config-version: '${config_version}' # Debug debug: false - # BStats metrics: true - # Check updates update-checker: true -# Language -# https://github.com/Xiao-MoMi/Custom-Fishing/tree/main/plugin/src/main/resources/messages -lang: en - # Mechanic settings mechanics: # Specifies the conditions required for the plugin mechanics to work. @@ -25,6 +18,15 @@ mechanics: type: '!world' value: - blacklist_world + # If you want to let some players skip games, you can set requirements that used to skip games + # We used `impossible` requirement here, so players should play the game if there exists + skip-game-requirements: + impossible_requirement: + type: 'impossible' + # Requirements for enabling auto-fishing + auto-fishing-requirements: + impossible_requirement: + type: 'impossible' # Configures global effects. This is useful if you want to give all the players certain effects based on certain conditions global-effects: @@ -55,7 +57,7 @@ mechanics: actions: actionbar_action: type: actionbar - value: '<#FFD700>[New Record] <#FFFFF0>You caught a(n) {nick} which is <#FFA500>{size}cm long!' + value: '<#FFD700>[New Record] <#FFFFF0>You caught a(n) {nick} which is <#FFA500>{size_formatted}cm long!' sound_action: type: sound value: @@ -85,7 +87,7 @@ mechanics: actions: actionbar_action: type: actionbar - value: 'You caught a(n) {nick} which is <#F5F5F5>{size}cm long! <#C0C0C0>(Best record: {record}cm)' + value: 'You caught a(n) {nick} which is <#F5F5F5>{size_formatted}cm long! <#C0C0C0>(Best record: {record_formatted}cm)' title_action: type: random-title value: @@ -130,7 +132,7 @@ mechanics: value: duration: 35 position: other - item: util:lava_effect + item: lava_effect priority_2: conditions: lava-fishing: false @@ -140,7 +142,7 @@ mechanics: value: duration: 35 position: other - item: util:water_effect + item: water_effect # Global properties which would help you reduce duplicated lines global-loot-property: @@ -151,16 +153,18 @@ mechanics: # Fishing bag is where players can store their baits, utils, hooks and rods (Loot optional) fishing-bag: - # Enable enable: true # Fishing bag container title bag-title: '{player}''s Fishing Bag' # Other whitelist-items whitelist-items: - fishing_rod - - # Can fishing bag store fishing loots? + # Decide the items that can be stored in bag can-store-loot: false + can-store-rod: true + can-store-bait: true + can-store-hook: true + can-store-util: true # Requirements for automatically collecting collect-requirements: permission: fishingbag.collectloot @@ -195,31 +199,184 @@ mechanics: message_action: type: message value: "<#EEE8AA>[Fishing Bag] Your fishing bag has been full." - - # Fishing wait time + market: + # Market GUI title + title: 'Fish Market' + # Whether to enable limitations + limitation: + enable: true + earnings: '10000' # You can use expressions here + # Market menu layout + layout: + - 'AAAAAAAAA' + - 'AIIIIIIIA' + - 'AIIIIIIIA' + - 'AIIIIIIIA' + - 'AAAABAAAA' + # Price formula (For CustomFishing loots) + price-formula: '{base} + {bonus} * {size}' + # Allow player to sell fish in bundles + allow-bundle: true + # Allow player to sell fish in shulker boxes + allow-shulker-box: true + # Item price (For vanilla items & other plugin items that have CustomModelData) + item-price: + # Vanilla Items + COD: 10 + PUFFERFISH: 10 + SALMON: 10 + TROPICAL_FISH: 10 + # PAPER (CustomModelData: 999) + PAPER:999: 5 + # Slots to put items in + item-slot: + symbol: 'I' + allow-items-with-no-price: true + # This is an icon that allows players to sell all the fish from their inventory and fishingbag + # You can enable it by putting the symbol into layout + sell-all-icons: + symbol: 'S' + # Should the fish in fishing bag be sold + fishingbag: true + allow-icon: + material: IRON_BLOCK + display: + name: '<#00CED1>Ship the fish' + lore: + - 'You will get {money_formatted} coins from the fish in inventory and bag' + action: + sound_action: + type: sound + value: + key: 'minecraft:block.amethyst_block.place' + source: 'player' + volume: 1 + pitch: 1 + message_action: + type: message + value: 'You earned {money_formatted} coins from the fish! You can get {rest_formatted} more coins from market today' + command_action: + type: command + value: 'money give {player} {money}' + # Requires Vault and any economy plugin + # money_action: + # type: give-money + # value: '{money}' + deny-icon: + material: REDSTONE_BLOCK + display: + name: 'Denied trade' + lore: + - 'Nothing to sell!' + action: + sound_action: + type: sound + value: + key: 'minecraft:entity.villager.no' + source: 'player' + volume: 1 + pitch: 1 + limit-icon: + material: REDSTONE_BLOCK + display: + name: 'Denied trade' + lore: + - 'The worth of items exceeds the money that can be earned for the rest of today!' + action: + sound_action: + type: sound + value: + key: 'minecraft:block.anvil.land' + source: 'player' + volume: 1 + pitch: 1 + # Sell icon + sell-icons: + symbol: 'B' + allow-icon: + material: IRON_BLOCK + display: + name: '<#00CED1>Ship the fish' + lore: + - 'You will get {money_formatted} coins from the fish' + action: + sound_action: + type: sound + value: + key: 'minecraft:block.amethyst_block.place' + source: 'player' + volume: 1 + pitch: 1 + message_action: + type: message + value: 'You earned {money_formatted} coins from the fish! You can get {rest_formatted} more coins from market today' + command_action: + type: command + value: 'money give {player} {money}' + # Requires Vault and any economy plugin + # money_action: + # type: give-money + # value: '{money}' + deny-icon: + material: REDSTONE_BLOCK + display: + name: 'Denied trade' + lore: + - 'Nothing to sell!' + action: + sound_action: + type: sound + value: + key: 'minecraft:entity.villager.no' + source: 'player' + volume: 1 + pitch: 1 + limit-icon: + material: REDSTONE_BLOCK + display: + name: 'Denied trade' + lore: + - 'The worth of items exceeds the money that can be earned for the rest of today!' + action: + sound_action: + type: sound + value: + key: 'minecraft:block.anvil.land' + source: 'player' + volume: 1 + pitch: 1 + # Decorative icons + decorative-icons: + glass-pane: + symbol: 'A' + material: BLACK_STAINED_GLASS_PANE + display: + name: ' ' # This section would take effect if you set "override-vanilla" to true # That also means vanilla mechanics for example lure enchantment - # would no longer take effect, so you have to configurate its effect - # in enchantment effects. + # would no longer take effect, so you have to configure its effect in CustomFishing fishing-wait-time: # override vanilla mechanic override-vanilla: false # ticks min-wait-time: 100 max-wait-time: 600 - # Lava fishing settings # To modify vanilla fishing time, you should edit paper-world-defaults.yml where there's a section called fishing-time-range lava-fishing: + enable: true + # ticks + min-wait-time: 100 + max-wait-time: 600 + void-fishing: + enable: true # ticks min-wait-time: 100 max-wait-time: 600 - # Size settings size: # Some effects would increase/decrease size so the option decides whether they could ignore the limit restricted-size-range: true - # Competition settings competition: # Use redis for cross server data synchronization @@ -228,48 +385,38 @@ mechanics: server-group: default # Increase this value would allow you to use more placeholders like {4_player} {5_score} in sacrifice of some performance placeholder-limit: 3 - - # If a player could get multiple loots from fishing, should the loots spawn at the same time or have delays for each (tick) + # If a player could get multiple loots from fishing, should the loots spawn at the same time or have delay for each (measured in ticks) multiple-loot-spawn-delay: 4 + # Totem settings + totem: + # Is it allowed for different types of totems to take effect at the same time + allow-multiple-type: true + # Is it allowed for totems of the same type to take effect cumulatively + allow-same-type: false + # Enable fake bait casting animation + bait-animation: true # Other settings other-settings: # It's recommended to use MiniMessage format. If you insist on using legacy color code "&", enable the support below. # Disable this would improve performance legacy-color-code-support: true - - # Thread pool settings - thread-pool-settings: - # The size of the core Thread pool, that is, the size of the Thread pool when there is no task to execute - # Increase the size of corePoolSize when you are running a large server with many players fishing at the same time - corePoolSize: 10 - # The maximum number of threads allowed to be created in the Thread pool. The current number of threads in the Thread pool will not exceed this value - maximumPoolSize: 10 - # If a thread is idle for more than this attribute value, it will exit due to timeout - keepAliveTime: 30 - - # Event priority: MONITOR HIGHEST HIGH NORMAL LOW LOWEST + # Fishing event priority: MONITOR HIGHEST HIGH NORMAL LOW LOWEST event-priority: NORMAL - # Save the data from cache to file periodically to minimize the data loss if server crashes # -1 to disable data-saving-interval: 600 - # Log the consumption of time on data saving log-data-saving: true - # Lock player's data if a player is playing on a server that connected to database # If you can ensure low database link latency and fast processing, you can consider disabling this option to improve performance lock-data: true - # Requires PlaceholderAPI to work placeholder-register: - '{record}': '%fishingstats_size-record_{loot}%' # Requires server expansion '{date}': '%server_time_yyyy-MM-dd-HH:mm:ss%' # Requires player expansion '{yaw}': '%player_yaw%' - # CustomFishing supports using items/blocks from other plugins # If items share the same id, they would inherit the effects # Check the wiki for examples @@ -278,13 +425,11 @@ other-settings: - vanilla block-detection-order: - vanilla - # Custom durability format custom-durability-format: - '' - 'Durability: {dur} / {max}' - - # Offset characters' unicodes + # Offset characters # Never edit this unless you know what you are doing offset-characters: font: customfishing:offset_chars diff --git a/plugin/src/main/resources/contents/bait/default.yml b/core/src/main/resources/contents/bait/default.yml similarity index 100% rename from plugin/src/main/resources/contents/bait/default.yml rename to core/src/main/resources/contents/bait/default.yml diff --git a/plugin/src/main/resources/contents/block/default.yml b/core/src/main/resources/contents/block/default.yml similarity index 100% rename from plugin/src/main/resources/contents/block/default.yml rename to core/src/main/resources/contents/block/default.yml diff --git a/plugin/src/main/resources/contents/category/default.yml b/core/src/main/resources/contents/category/default.yml similarity index 100% rename from plugin/src/main/resources/contents/category/default.yml rename to core/src/main/resources/contents/category/default.yml diff --git a/plugin/src/main/resources/contents/competition/default.yml b/core/src/main/resources/contents/competition/default.yml similarity index 100% rename from plugin/src/main/resources/contents/competition/default.yml rename to core/src/main/resources/contents/competition/default.yml diff --git a/plugin/src/main/resources/contents/enchant/default.yml b/core/src/main/resources/contents/enchant/default.yml similarity index 51% rename from plugin/src/main/resources/contents/enchant/default.yml rename to core/src/main/resources/contents/enchant/default.yml index d41bf12e..50136281 100644 --- a/plugin/src/main/resources/contents/enchant/default.yml +++ b/core/src/main/resources/contents/enchant/default.yml @@ -29,4 +29,43 @@ minecraft:luck_of_the_sea:3: type: group-mod value: - silver_star:+6 - - golden_star:+3 \ No newline at end of file + - golden_star:+3 +minecraft:lure:1: + requirements: {} + effects: + effect_1: + type: conditional + conditions: + "||": + in-lava: true + in-void: true + effects: + effect_1: + type: wait-time + value: -80 +minecraft:lure:2: + requirements: {} + effects: + effect_1: + type: conditional + conditions: + "||": + in-lava: true + in-void: true + effects: + effect_1: + type: wait-time + value: -160 +minecraft:lure:3: + requirements: {} + effects: + effect_1: + type: conditional + conditions: + "||": + in-lava: true + in-void: true + effects: + effect_1: + type: wait-time + value: -240 \ No newline at end of file diff --git a/plugin/src/main/resources/contents/entity/default.yml b/core/src/main/resources/contents/entity/default.yml similarity index 100% rename from plugin/src/main/resources/contents/entity/default.yml rename to core/src/main/resources/contents/entity/default.yml diff --git a/plugin/src/main/resources/contents/hook/default.yml b/core/src/main/resources/contents/hook/default.yml similarity index 100% rename from plugin/src/main/resources/contents/hook/default.yml rename to core/src/main/resources/contents/hook/default.yml diff --git a/plugin/src/main/resources/contents/item/default.yml b/core/src/main/resources/contents/item/default.yml similarity index 83% rename from plugin/src/main/resources/contents/item/default.yml rename to core/src/main/resources/contents/item/default.yml index dde3b933..932c585e 100644 --- a/plugin/src/main/resources/contents/item/default.yml +++ b/core/src/main/resources/contents/item/default.yml @@ -12,6 +12,12 @@ vanilla: disable-stat: true group: - river + events: + success: + action_mending: + type: mending + value: 1~7 + chance: 1.0 # Some rubbish stick: tag: false @@ -129,106 +135,95 @@ rainbow_fish: price: base: 100 # Enchantments -sharpness_book: +enchantment_book: tag: false material: enchanted_book group: enchantments nick: "" show-in-fishfinder: false disable-stat: true - random-stored-enchantments: - lv5: - enchant: minecraft:sharpness - level: 5 - chance: 0.1 - lv4: - enchant: minecraft:sharpness - level: 4 - chance: 0.2 - lv3: - enchant: minecraft:sharpness - level: 3 - chance: 0.4 - lv2: - enchant: minecraft:sharpness - level: 2 - chance: 0.7 - lv1: - enchant: minecraft:sharpness - level: 1 - chance: 1 -efficiency_book: - tag: false - material: enchanted_book - group: enchantments - show-in-fishfinder: false - nick: "" - disable-stat: true - random-stored-enchantments: - lv5: - enchant: minecraft:efficiency - level: 5 - chance: 0.1 - lv4: - enchant: minecraft:efficiency - level: 4 - chance: 0.2 - lv3: - enchant: minecraft:efficiency - level: 3 - chance: 0.4 - lv2: - enchant: minecraft:efficiency - level: 2 - chance: 0.7 - lv1: - enchant: minecraft:efficiency - level: 1 - chance: 1 -unbreaking_book: - tag: false - material: enchanted_book - group: enchantments - show-in-fishfinder: false - nick: "" - disable-stat: true - random-stored-enchantments: - lv3: - enchant: minecraft:unbreaking - level: 3 - chance: 0.2 - lv2: - enchant: minecraft:unbreaking - level: 2 - chance: 0.5 - lv1: - enchant: minecraft:unbreaking - level: 1 - chance: 1 -protection_book: - tag: false - material: enchanted_book - group: enchantments - show-in-fishfinder: false - nick: "" - disable-stat: true - random-stored-enchantments: - lv4: - enchant: minecraft:protection - level: 4 - chance: 0.1 - lv3: - enchant: minecraft:protection - level: 3 - chance: 0.2 - lv2: - enchant: minecraft:protection - level: 2 - chance: 0.5 - lv1: - enchant: minecraft:protection - level: 1 - chance: 1 + stored-enchantment-pool: + # max enchantments on the item + amount: + 1: 6 + 2: 3 + 3: 1 + pool: + 'minecraft:unbreaking:1': 6 + 'minecraft:unbreaking:2': 3 + 'minecraft:unbreaking:3': 1 + 'minecraft:sharpness:1': 6 + 'minecraft:sharpness:2': 3 + 'minecraft:sharpness:3': 2 + 'minecraft:sharpness:4': 1 + 'minecraft:efficiency:1': 12 + 'minecraft:efficiency:2': 6 + 'minecraft:efficiency:3': 2 + 'minecraft:efficiency:4': 1 + 'minecraft:protection:1': 10 + 'minecraft:protection:2': 6 + 'minecraft:protection:3': 2 + 'minecraft:feather_falling:1': 4 + 'minecraft:feather_falling:2': 3 + 'minecraft:feather_falling:3': 2 + 'minecraft:blast_protection:1': 10 + 'minecraft:blast_protection:2': 6 + 'minecraft:blast_protection:3': 2 + 'minecraft:fire_protection:1': 10 + 'minecraft:fire_protection:2': 6 + 'minecraft:fire_protection:3': 2 + 'minecraft:projectile_protection:1': 10 + 'minecraft:projectile_protection:2': 6 + 'minecraft:projectile_protection:3': 2 + 'minecraft:binding_curse:1': 2 + 'minecraft:vanishing_curse:1': 2 + 'minecraft:depth_strider:1': 8 + 'minecraft:depth_strider:2': 4 + 'minecraft:depth_strider:3': 2 + 'minecraft:fortune:1': 4 + 'minecraft:fortune:2': 3 + 'minecraft:fortune:3': 2 + 'minecraft:flame:1': 12 + 'minecraft:loyalty:1': 8 + 'minecraft:loyalty:2': 5 + 'minecraft:lure:1': 9 + 'minecraft:lure:2': 3 + 'minecraft:lure:3': 1 + 'minecraft:luck_of_the_sea:1': 8 + 'minecraft:luck_of_the_sea:2': 2 + 'minecraft:luck_of_the_sea:3': 1 + 'minecraft:channeling:1': 7 + 'minecraft:frost_walker:1': 7 + 'minecraft:mending:1': 10 + 'minecraft:power:1': 12 + 'minecraft:power:2': 6 + 'minecraft:power:3': 3 + 'minecraft:power:4': 1 + 'minecraft:knockback:1': 8 + 'minecraft:knockback:2': 3 + 'minecraft:silk_touch:1': 12 + 'minecraft:smite:1': 12 + 'minecraft:smite:2': 6 + 'minecraft:smite:3': 3 + 'minecraft:smite:4': 1 + 'minecraft:bane_of_arthropods:1': 12 + 'minecraft:bane_of_arthropods:2': 6 + 'minecraft:bane_of_arthropods:3': 3 + 'minecraft:bane_of_arthropods:4': 1 + 'minecraft:multishot:1': 7 + 'minecraft:punch:1': 8 + 'minecraft:punch:2': 4 + 'minecraft:thorns:1': 8 + 'minecraft:thorns:2': 4 + 'minecraft:looting:1': 12 + 'minecraft:looting:2': 6 + 'minecraft:looting:3': 2 + 'minecraft:soul_speed:1': 8 + 'minecraft:soul_speed:2': 4 + 'minecraft:fire_aspect:1': 10 + 'minecraft:fire_aspect:2': 5 + 'minecraft:quick_charge:1': 9 + 'minecraft:infinity:1': 7 # Fish tuna_fish: material: cod @@ -236,7 +231,7 @@ tuna_fish: name: Tuna Fish lore: - Tuna is a kind of healthy food. - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50001 group: ocean events: @@ -257,7 +252,7 @@ tuna_fish_silver_star: Star) lore: - Tuna is a kind of healthy food. - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50002 group: - silver_star @@ -280,7 +275,7 @@ tuna_fish_golden_star: Star) lore: - Tuna is a kind of healthy food. - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50003 group: - golden_star @@ -304,7 +299,7 @@ pike_fish: - of salt and fresh water. It can - survive in seawater, brackish fresh - water and inland freshwater lakes - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50004 group: ocean events: @@ -328,7 +323,7 @@ pike_fish_silver_star: - of salt and fresh water. It can - survive in seawater, brackish fresh - water and inland freshwater lakes - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50005 group: - silver_star @@ -354,7 +349,7 @@ pike_fish_golden_star: - of salt and fresh water. It can - survive in seawater, brackish fresh - water and inland freshwater lakes - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50006 group: - golden_star @@ -377,7 +372,7 @@ gold_fish: - Goldfish is one of most famous ornamental - fishes in the world. It originated in China - and has a history of more than 1700 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' price: base: 70 bonus: 2.6 @@ -400,7 +395,7 @@ gold_fish_silver_star: - Goldfish is one of most famous ornamental - fishes in the world. It originated in China - and has a history of more than 1700 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' price: base: 80 bonus: 3 @@ -425,7 +420,7 @@ gold_fish_golden_star: - Goldfish is one of most famous ornamental - fishes in the world. It originated in China - and has a history of more than 1700 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' price: base: 100 bonus: 3.4 @@ -447,7 +442,7 @@ perch_fish: lore: - Living in various habitats and - foraging at dusk and early morning - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50010 group: river events: @@ -469,7 +464,7 @@ perch_fish_silver_star: lore: - Living in various habitats and - foraging at dusk and early morning - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50011 group: - silver_star @@ -493,7 +488,7 @@ perch_fish_golden_star: lore: - Living in various habitats and - foraging at dusk and early morning - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50012 group: - golden_star @@ -515,7 +510,7 @@ mullet_fish: lore: - Used in traditional Chinese medicine - to treat spleen and stomach weakness - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50013 group: river events: @@ -537,7 +532,7 @@ mullet_fish_silver_star: lore: - Used in traditional Chinese medicine - to treat spleen and stomach weakness - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50014 group: - silver_star @@ -561,7 +556,7 @@ mullet_fish_golden_star: lore: - Used in traditional Chinese medicine - to treat spleen and stomach weakness - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50015 group: - golden_star @@ -583,7 +578,7 @@ sardine_fish: lore: - Sardine fish is rich in DHA which improves memory - Therefore, sardine are also called "smart food" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50016 group: ocean events: @@ -604,7 +599,7 @@ sardine_fish_silver_star: lore: - Sardine fish is rich in DHA which improves memory - Therefore, sardine are also called "smart food" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50017 group: - silver_star @@ -627,7 +622,7 @@ sardine_fish_golden_star: lore: - Sardine fish is rich in DHA which improves memory - Therefore, sardine are also called "smart food" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50018 group: - golden_star @@ -648,7 +643,7 @@ carp_fish: name: Carp Fish lore: - One of the most common edible fish - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50019 group: river events: @@ -668,7 +663,7 @@ carp_fish_silver_star: name: Carp Fish <#F5F5F5>(Silver Star) lore: - One of the most common edible fish - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50020 group: - silver_star @@ -690,7 +685,7 @@ carp_fish_golden_star: name: Carp Fish <#FFD700>(Golden Star) lore: - One of the most common edible fish - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50021 group: - golden_star @@ -712,7 +707,7 @@ cat_fish: lore: - Catfish is a fierce carnivorous fish with - sharp jaw teeth, short intestine and stomach - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50022 group: river events: @@ -733,7 +728,7 @@ cat_fish_silver_star: lore: - Catfish is a fierce carnivorous fish with - sharp jaw teeth, short intestine and stomach - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50023 group: - silver_star @@ -756,7 +751,7 @@ cat_fish_golden_star: lore: - Catfish is a fierce carnivorous fish with - sharp jaw teeth, short intestine and stomach - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50024 group: - golden_star @@ -778,7 +773,7 @@ octopus: lore: - Octopus is crazy about all kinds of utensils - People often use pots to catch octopus - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50025 group: ocean events: @@ -799,7 +794,7 @@ octopus_silver_star: lore: - Octopus is crazy about all kinds of utensils - People often use pots to catch octopus - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50026 group: - silver_star @@ -822,7 +817,7 @@ octopus_golden_star: lore: - Octopus is crazy about all kinds of utensils - People often use pots to catch octopus - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50027 group: - golden_star @@ -843,7 +838,7 @@ sunfish: name: <#F5DEB3>Sunfish lore: - It only has one huge head - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50028 group: ocean events: @@ -863,7 +858,7 @@ sunfish_silver_star: name: <#F5DEB3>Sunfish <#F5F5F5>(Silver Star) lore: - It only has one huge head - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50029 group: - silver_star @@ -885,7 +880,7 @@ sunfish_golden_star: name: <#F5DEB3>Sunfish <#FFD700>(Golden Star) lore: - It only has one huge head - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50030 group: - golden_star @@ -907,7 +902,7 @@ red_snapper_fish: lore: - They usually have a large family of ten or twenty - with a male as the "head of the family" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50031 group: ocean events: @@ -929,7 +924,7 @@ red_snapper_fish_silver_star: lore: - They usually have a large family of ten or twenty - with a male as the "head of the family" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50032 group: - silver_star @@ -953,7 +948,7 @@ red_snapper_fish_golden_star: lore: - They usually have a large family of ten or twenty - with a male as the "head of the family" - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50033 group: - golden_star @@ -975,7 +970,7 @@ salmon_void_fish: lore: - A fish from the hell - It's looking at you... - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50034 group: lava events: @@ -996,7 +991,7 @@ salmon_void_fish_silver_star: lore: - A fish from the hell - It's looking at you... - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50035 group: - silver_star @@ -1019,7 +1014,7 @@ salmon_void_fish_golden_star: lore: - A fish from the hell - It's looking at you... - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50036 group: - golden_star @@ -1041,7 +1036,7 @@ woodskip_fish: lore: - A very sensitive fish that can only - live in pools deep in the forest - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50037 group: river events: @@ -1062,7 +1057,7 @@ woodskip_fish_silver_star: lore: - A very sensitive fish that can only - live in pools deep in the forest - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50038 group: - silver_star @@ -1085,7 +1080,7 @@ woodskip_fish_golden_star: lore: - A very sensitive fish that can only - live in pools deep in the forest - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50039 group: - golden_star @@ -1107,7 +1102,7 @@ sturgeon_fish: lore: - An ancient bottom-feeder with a dwindling - population. Females can live up to 150 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50040 group: river events: @@ -1128,7 +1123,7 @@ sturgeon_fish_silver_star: lore: - An ancient bottom-feeder with a dwindling - population. Females can live up to 150 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50041 group: - silver_star @@ -1151,7 +1146,7 @@ sturgeon_fish_golden_star: lore: - An ancient bottom-feeder with a dwindling - population. Females can live up to 150 years - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50042 group: - golden_star @@ -1172,7 +1167,7 @@ blue_jellyfish: name: <#87CEFA>Jellyfish lore: - Looks like a blue umbrella - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50043 group: ocean events: @@ -1192,7 +1187,7 @@ blue_jellyfish_silver_star: name: <#87CEFA>Jellyfish lore: - Looks like a blue umbrella - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50044 group: - silver_star @@ -1214,7 +1209,7 @@ blue_jellyfish_golden_star: name: <#87CEFA>Jellyfish lore: - Looks like a blue umbrella - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50045 group: - golden_star @@ -1235,7 +1230,7 @@ pink_jellyfish: name: <#FFC0CB>Jellyfish lore: - Seems to be sweet - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50046 group: ocean events: @@ -1255,7 +1250,7 @@ pink_jellyfish_silver_star: name: <#FFC0CB>Jellyfish lore: - Seems to be sweet - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50047 group: - silver_star @@ -1277,7 +1272,7 @@ pink_jellyfish_golden_star: name: <#FFC0CB>Jellyfish lore: - Seems to be sweet - - 'size: {size}cm' + - 'size: {size_formatted}cm' custom-model-data: 50048 group: - golden_star diff --git a/plugin/src/main/resources/contents/minigame/default.yml b/core/src/main/resources/contents/minigame/default.yml similarity index 99% rename from plugin/src/main/resources/contents/minigame/default.yml rename to core/src/main/resources/contents/minigame/default.yml index 9ccb9d75..5d507b45 100644 --- a/plugin/src/main/resources/contents/minigame/default.yml +++ b/core/src/main/resources/contents/minigame/default.yml @@ -1417,7 +1417,7 @@ tension_game_easy: game-type: tension difficulty: 20~35 time: 30 - title: '{tension}' + title: '{progress}' # Tip would show on the title to guide the player how to play tip: 'Press to start' subtitle: @@ -1454,7 +1454,7 @@ tension_game_normal: game-type: tension difficulty: 35~50 time: 30 - title: '{tension}' + title: '{progress}' # Tip would show on the title to guide the player how to play tip: 'Press to start' subtitle: @@ -1491,7 +1491,7 @@ tension_game_hard: game-type: tension difficulty: 50~65 time: 30 - title: '{tension}' + title: '{progress}' # Tip would show on the title to guide the player how to play tip: 'Press to start' subtitle: diff --git a/plugin/src/main/resources/contents/rod/default.yml b/core/src/main/resources/contents/rod/default.yml similarity index 95% rename from plugin/src/main/resources/contents/rod/default.yml rename to core/src/main/resources/contents/rod/default.yml index 27c9c8fa..bf0c4184 100644 --- a/plugin/src/main/resources/contents/rod/default.yml +++ b/core/src/main/resources/contents/rod/default.yml @@ -26,14 +26,14 @@ beginner_rod: - ' - novice angler''s best friend.' - '' - '<#FFD700>Effects:' - - ' - Increase the hook time' + - ' - Increase the waiting time' - ' - Reduces the challenge of fishing' custom-model-data: 50001 max-durability: 64 effects: effect_1: type: wait-time-multiplier - value: 1.8 + value: 1.5 effect_2: type: difficulty value: -8 @@ -117,8 +117,8 @@ bone_rod: - ' - regular rods.' - '' - '<#FFD700>Effects:' - - ' - Fishing in lava' - - ' - Sometimes skeleton would grab the hook' + - ' - Fish in lava' + - ' - Attract skeletons' custom-model-data: 50005 max-durability: 32 effects: @@ -140,7 +140,7 @@ magical_rod: - '' - '<#FFD700>Effects:' - ' - Get an enchantment book from fishing.' - - ' - Require a long time to get hooked.' + - ' - The waiting time is very long.' - '' - '<#CD5C5C>Requirements:' - ' - 1x book bait' @@ -150,7 +150,7 @@ magical_rod: requirements: requirement_1: type: level - value: 10 + value: 1 not-met-actions: action_1: type: message @@ -169,7 +169,7 @@ magical_rod: cast: action_level: type: level - value: -10 + value: -1 effects: effect_1: type: group-mod-ignore-conditions @@ -193,7 +193,7 @@ master_rod: - ' - time it takes for a fish to bite.' - '' - '<#FFD700>Effects:' - - ' - Reduce the hook time' + - ' - Reduce the waiting time' - ' - Increase the challenge of fishing' - ' - Higher chance of getting quality fish' custom-model-data: 50007 diff --git a/plugin/src/main/resources/contents/totem/default.yml b/core/src/main/resources/contents/totem/default.yml similarity index 100% rename from plugin/src/main/resources/contents/totem/default.yml rename to core/src/main/resources/contents/totem/default.yml diff --git a/plugin/src/main/resources/contents/util/default.yml b/core/src/main/resources/contents/util/default.yml similarity index 98% rename from plugin/src/main/resources/contents/util/default.yml rename to core/src/main/resources/contents/util/default.yml index a4f3aa5a..54e78067 100644 --- a/plugin/src/main/resources/contents/util/default.yml +++ b/core/src/main/resources/contents/util/default.yml @@ -38,8 +38,7 @@ fishfinder: actions: action_1: type: fish-finder - value: false - + value: water water_effect: material: PAPER custom-model-data: 49998 diff --git a/plugin/src/main/resources/database.yml b/core/src/main/resources/database.yml similarity index 97% rename from plugin/src/main/resources/database.yml rename to core/src/main/resources/database.yml index af8f3715..1c133b46 100644 --- a/plugin/src/main/resources/database.yml +++ b/core/src/main/resources/database.yml @@ -1,3 +1,5 @@ +config-version: '${config_version}' + # file: # JSON # YAML diff --git a/plugin/src/main/resources/game-conditions.yml b/core/src/main/resources/game-conditions.yml similarity index 99% rename from plugin/src/main/resources/game-conditions.yml rename to core/src/main/resources/game-conditions.yml index 4b976d85..4ce82c76 100644 --- a/plugin/src/main/resources/game-conditions.yml +++ b/core/src/main/resources/game-conditions.yml @@ -5,7 +5,7 @@ global-group: sub-groups: lava_fishing_game: conditions: - lava-fishing: true + in-lava: true list: - hold_game_easy:+15 - hold_game_normal:+5 @@ -28,7 +28,7 @@ global-group: - hold_game_hard:+7 water_fish_game: conditions: - lava-fishing: false + in-water: true list: [] sub-groups: rainbow_fish_game: diff --git a/plugin/src/main/resources/loot-conditions.yml b/core/src/main/resources/loot-conditions.yml similarity index 98% rename from plugin/src/main/resources/loot-conditions.yml rename to core/src/main/resources/loot-conditions.yml index 3935c6cf..8c4b9475 100644 --- a/plugin/src/main/resources/loot-conditions.yml +++ b/core/src/main/resources/loot-conditions.yml @@ -5,7 +5,7 @@ global-group: sub-groups: loots_in_water: conditions: - lava-fishing: false + in-water: true environment: - normal '!rod': @@ -13,6 +13,7 @@ global-group: list: - rubbish:+15 - seagrass:+5 + - vanilla:+60 sub-groups: # parent ocean group ocean_fish: @@ -79,7 +80,6 @@ global-group: - minecraft:deep_lukewarm_ocean - minecraft:warm_ocean list: - - vanilla:+30 - rainbow_fish:+5 - stick:+15 - gold_fish:+15 @@ -126,7 +126,7 @@ global-group: - 'skeleton:+30' loots_in_lava: conditions: - lava-fishing: true + in-lava: true list: [] sub-groups: world_loots: diff --git a/plugin/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml similarity index 83% rename from plugin/src/main/resources/plugin.yml rename to core/src/main/resources/plugin.yml index 7df57363..62a1e5b3 100644 --- a/plugin/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -1,11 +1,9 @@ name: CustomFishing -version: '${version}' -main: net.momirealms.customfishing.CustomFishingPluginImpl +version: '${project_version}' +main: net.momirealms.customfishing.bukkit.BukkitBootstrap api-version: 1.17 authors: [ XiaoMoMi ] folia-supported: true -depend: - - ProtocolLib softdepend: - Vault - PlaceholderAPI diff --git a/plugin/src/main/resources/schema/h2.sql b/core/src/main/resources/schema/h2.sql similarity index 100% rename from plugin/src/main/resources/schema/h2.sql rename to core/src/main/resources/schema/h2.sql diff --git a/plugin/src/main/resources/schema/mariadb.sql b/core/src/main/resources/schema/mariadb.sql similarity index 100% rename from plugin/src/main/resources/schema/mariadb.sql rename to core/src/main/resources/schema/mariadb.sql diff --git a/plugin/src/main/resources/schema/mysql.sql b/core/src/main/resources/schema/mysql.sql similarity index 100% rename from plugin/src/main/resources/schema/mysql.sql rename to core/src/main/resources/schema/mysql.sql diff --git a/plugin/src/main/resources/schema/sqlite.sql b/core/src/main/resources/schema/sqlite.sql similarity index 100% rename from plugin/src/main/resources/schema/sqlite.sql rename to core/src/main/resources/schema/sqlite.sql diff --git a/core/src/main/resources/translations/en.yml b/core/src/main/resources/translations/en.yml new file mode 100644 index 00000000..a64c0cc3 --- /dev/null +++ b/core/src/main/resources/translations/en.yml @@ -0,0 +1,90 @@ +# Don"t change this +config-version: "33" + +exception.invalid_syntax: "Invalid syntax. Correct syntax: " +exception.invalid_argument: "Invalid argument. Reason: " +exception.invalid_sender: " is not allowed to execute that command. Must be of type " +exception.unexpected: "An internal error occurred while attempting to perform this command" +exception.no_permission: "I'm sorry, but you do not have permission to perform this command" +exception.no_such_command: "Unknown command." +argument.entity.notfound.player: "" +argument.entity.notfound.entity: "" +argument.parse.failure.time: "'' is not a valid time format" +argument.parse.failure.material: "'' is not a valid material name" +argument.parse.failure.enchantment: "'' is not a valid enchantment" +argument.parse.failure.offlineplayer: "No player found for input ''" +argument.parse.failure.player: "No player found for input ''" +argument.parse.failure.world: "'' is not a valid Minecraft world" +argument.parse.failure.location.invalid_format: "'' is not a valid location. Required format is ' " +argument.parse.failure.location.mixed_local_absolute: "Cannot mix local and absolute coordinates. (either all coordinates use '^' or none do)" +argument.parse.failure.namespacedkey.namespace: "Invalid namespace ''. Must be [a-z0-9._-]" +argument.parse.failure.namespacedkey.key: "Invalid key ''. Must be [a-z0-9/._-]" +argument.parse.failure.namespacedkey.need_namespace: "Invalid input '', requires an explicit namespace" +argument.parse.failure.boolean: "Could not parse boolean from ''" +argument.parse.failure.number: "'' is not a valid number in the range to " +argument.parse.failure.char: "'' is not a valid character" +argument.parse.failure.string: "'' is not a valid string of type " +argument.parse.failure.uuid: "'' is not a valid UUID" +argument.parse.failure.enum: "'' is not one of the following: " +argument.parse.failure.regex: "'' does not match ''" +argument.parse.failure.flag.unknown: "Unknown flag ''" +argument.parse.failure.flag.duplicate_flag: "Duplicate flag ''" +argument.parse.failure.flag.no_flag_started: "No flag started. Don't know what to do with ''" +argument.parse.failure.flag.missing_argument: "Missing argument for ''" +argument.parse.failure.flag.no_permission: "You don't have permission to use ''" +argument.parse.failure.color: "'' is not a valid color" +argument.parse.failure.duration: "'' is not a duration format" +argument.parse.failure.aggregate.missing: "Missing component ''" +argument.parse.failure.aggregate.failure: "Invalid component '': " +argument.parse.failure.either: "Could not resolve or from ''" +argument.parse.failure.namedtextcolor: "'' is not a named text color" +command.reload.success: "Reloaded. Took ms." +command.item.failure.not_exist: "Item [] not exists." +command.item.give.success: "Successfully given x ." +command.item.get.success: "Successfully got x ." +command.item.import.failure.no_item: "You can't import air" +command.item.import.success: "Item [] has been saved to /plugins/CustomFishing/imported_items.yml" +command.fish_finder.possible_loots: "Possible loots here: " +command.fish_finder.no_loot: "No loot found here" +command.fish_finder.split_char: ", " +command.competition.failure.not_exist: "Competition does not exist." +command.competition.failure.no_competition: "There's no competition ongoing." +command.competition.stop.success: "Stopped the current competition." +command.competition.end.success: "Ended the current competition." +command.competition.start.success: "Started the competition." +command.bag.edit.failure.unsafe: "Cannot edit a player's fishing bag if they're active on another linked server." +command.bag.edit.failure.never_played: "The player hasn't joined the server before. Can't modify a nonexistent player's fishing bag." +command.bag.open.success: "Successfully opened the fishing bag for " +command.bag.open.failure.not_loaded: "Failed to open market for because data is not loaded" +command.data.failure.not_loaded: "Data hasn't been loaded. Please re-enter the server. If issues persist, reach out to the server admin." +command.market.open.success: "Successfully opened the market gui for " +command.market.open.failure.not_loaded: "Failed to open market for because data is not loaded" +command.data.unlock.success: "Successfully unlocked data for " +command.data.import.failure.not_exists: "That file doesn't exist" +command.data.import.failure.invalid_file: "That file is invalid" +command.data.import.failure.player_online: "Please kick all the online players before using this command" +command.data.import.start: "Importing..." +command.data.import.progress: "Progress: /" +command.data.import.success: "Successfully imported the data" +command.data.export.failure.player_online: "Please kick all the online players before using this command" +command.data.export.start: "Exporting..." +command.data.export.progress: "Progress: /" +command.data.export.success: "Successfully exported the data" +command.statistics.failure.not_loaded: "Data has not been loaded for that player" +command.statistics.failure.unsupported: "Unsupported operation" +command.statistics.modify.success: "Successfully modified the fishing statistics for " +command.statistics.reset.success: "Successfully reset the fishing statistics for " +command.statistics.query.size: "Max sizes: " +command.statistics.query.amount: "Amount of fish caught: " +competition.no_score: "No Score" +competition.no_player: "No Player" +competition.no_rank: "No Rank" +competition.goal.catch_amount: "Fish count caught" +competition.goal.max_size: "Largest fish caught" +competition.goal.min_size: "Smallest fish caught" +competition.goal.total_score: "Cumulative score of fish caught" +competition.goal.total_size: "Total length of fish caught" +format.day: "d" +format.hour: "h" +format.minute: "m" +format.second: "s" \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7b26f395..33df6cc7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,53 @@ +# Project settings +# Rule: [major update].[feature update].[bug fix] +project_version=2.2.0 +config_version=33 +project_group=net.momirealms + +# Dependency settings +paper_version=1.20.4 +jetbrains_annotations_version=24.0.0 +slf4j_version=2.0.13 +log4j_version=2.23.1 +gson_version=2.10.1 +asm_version=9.7 +asm_commons_version=9.7 +jar_relocator_version=1.7 +h2_driver_version=2.2.224 +sqlite_driver_version=3.46.0.0 +adventure_bundle_version=4.17.0 +adventure_platform_version=4.3.3 +sparrow_heart_version=0.30 +cloud_core_version=2.0.0-rc.2 +cloud_services_version=2.0.0-rc.2 +cloud_brigadier_version=2.0.0-beta.9 +cloud_bukkit_version=2.0.0-beta.9 +cloud_paper_version=2.0.0-beta.9 +cloud_minecraft_extras_version=2.0.0-beta.9 +boosted_yaml_version=1.3.6 +byte_buddy_version=1.14.14 +mojang_brigadier_version=1.0.18 +mongodb_driver_version=5.1.0 +mariadb_driver_version=3.3.3 +mysql_driver_version=8.4.0 +hikari_version=5.1.0 +commons_pool_version=2.12.0 +bstats_version=3.0.2 +geantyref_version=1.3.15 +caffeine_version=3.1.8 +rtag_version=6290733498 +jedis_version=5.1.2 +exp4j_version=0.4.8 +placeholder_api_version=2.11.6 +invui_version=1.32 +vault_version=1.7 +guava_version=33.2.0-jre +lz4_version=1.8.0 + +# Proxy settings systemProp.socks.proxyHost=127.0.0.1 systemProp.socks.proxyPort=7890 - systemProp.http.proxyHost=127.0.0.1 systemProp.http.proxyPort=7890 - systemProp.https.proxyHost=127.0.0.1 systemProp.https.proxyPort=7890 \ No newline at end of file diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts deleted file mode 100644 index 93a55d56..00000000 --- a/plugin/build.gradle.kts +++ /dev/null @@ -1,110 +0,0 @@ -dependencies { - // server - compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") - - // packet - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") - - // command - compileOnly("dev.jorel:commandapi-bukkit-core:9.4.1") - - // bStats - compileOnly("org.bstats:bstats-bukkit:3.0.2") - - // papi - compileOnly("me.clip:placeholderapi:2.11.5") - - // config - compileOnly("dev.dejvokep:boosted-yaml:1.3.4") - - // mythic - compileOnly("io.lumine:Mythic-Dist:5.3.5") - compileOnly("net.Indyuce:MMOItems-API:6.9.2-SNAPSHOT") - compileOnly("io.lumine:MythicLib-dist:1.6-SNAPSHOT") - compileOnly("net.Indyuce:MMOCore-API:1.12-SNAPSHOT") - - // Gson - compileOnly("com.google.code.gson:gson:2.10.1") - - // eco - compileOnly("com.willfp:eco:6.67.2") - compileOnly("com.willfp:EcoJobs:3.47.1") - compileOnly("com.willfp:EcoSkills:3.21.0") - compileOnly("com.willfp:libreforge:4.48.1") - - // database - compileOnly("org.xerial:sqlite-jdbc:3.45.3.0") - compileOnly("com.h2database:h2:2.2.224") - compileOnly("org.mongodb:mongodb-driver-sync:5.0.1") - compileOnly("com.zaxxer:HikariCP:5.0.1") - compileOnly("redis.clients:jedis:5.1.2") - - // others - compileOnly("com.github.LoneDev6:api-itemsadder:3.5.0c-r5") - compileOnly("io.th0rgal:oraxen:1.165.0") - compileOnly("pers.neige.neigeitems:NeigeItems:1.16.24") - compileOnly("com.github.Zrips:Jobs:4.17.2") - compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") - compileOnly("dev.aurelium:auraskills-api-bukkit:2.0.0-SNAPSHOT") - compileOnly("com.github.MilkBowl:VaultAPI:1.7") - compileOnly("org.betonquest:betonquest:2.0.0") - compileOnly("com.github.Xiao-MoMi:Custom-Crops:3.4.4.1") - compileOnly("org.apache.commons:commons-lang3:3.14.0") - - // local jars - compileOnly(files("libs/AdvancedEnchantments-api.jar")) - compileOnly(files("libs/BattlePass-4.0.6-api.jar")) - compileOnly(files("libs/RealisticSeasons-api.jar")) - compileOnly(files("libs/mcMMO-api.jar")) - compileOnly(files("libs/ClueScrolls-4.8.7-api.jar")) - compileOnly(files("libs/notquests-5.17.1.jar")) - compileOnly(files("libs/zaphkiel-2.0.24.jar")) - - // GUI - implementation("xyz.xenondevs.invui:invui:1.30") { - exclude("org.jetbrains", "annotations") - } - - // nbt - implementation("de.tr7zw:item-nbt-api:2.12.4") - - // api module - implementation(project(":api")) - - // sparrow heart - implementation("com.github.Xiao-MoMi:Sparrow-Heart:0.16") - - // adventure - implementation("net.kyori:adventure-api:4.17.0") - implementation("net.kyori:adventure-text-minimessage:4.17.0") - implementation("net.kyori:adventure-text-serializer-gson:4.17.0") { - exclude("com.google.code.gson", "gson") - } - implementation("net.kyori:adventure-platform-bukkit:4.3.2") -} - -tasks { - shadowJar { - exclude("org.jetbrains:annotations:*") - relocate ("org.apache.commons.pool2", "net.momirealms.customfishing.libraries.commonspool2") - relocate ("org.apache.commons.lang3", "net.momirealms.customfishing.libraries.lang3") - relocate ("com.mysql", "net.momirealms.customfishing.libraries.mysql") - relocate ("org.mariadb", "net.momirealms.customfishing.libraries.mariadb") - relocate ("com.zaxxer.hikari", "net.momirealms.customfishing.libraries.hikari") - relocate ("redis.clients.jedis", "net.momirealms.customfishing.libraries.jedis") - relocate ("com.mongodb", "net.momirealms.customfishing.libraries.mongodb") - relocate ("org.bson", "net.momirealms.customfishing.libraries.bson") - relocate ("net.objecthunter.exp4j", "net.momirealms.customfishing.libraries.exp4j") - relocate ("de.tr7zw.changeme", "net.momirealms.customfishing.libraries.changeme") - relocate ("net.kyori", "net.momirealms.customfishing.libraries") - relocate ("dev.jorel.commandapi", "net.momirealms.customfishing.libraries.commandapi") - relocate ("dev.dejvokep.boostedyaml", "net.momirealms.customfishing.libraries.boostedyaml") - relocate ("org.bstats", "net.momirealms.customfishing.libraries.bstats") - relocate ("net.momirealms.sparrow.heart", "net.momirealms.customfishing.libraries.heart") - relocate ("xyz.xenondevs", "net.momirealms.customfishing.libraries") - } -} - -tasks.withType { - options.encoding = "UTF-8" -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java b/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java deleted file mode 100644 index 5cb7bb1e..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolManager; -import com.comphenix.protocol.events.PacketContainer; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.event.CustomFishingReloadEvent; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.ReflectionUtils; -import net.momirealms.customfishing.command.CommandManagerImpl; -import net.momirealms.customfishing.compatibility.IntegrationManagerImpl; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.libraries.classpath.ReflectionClassPathAppender; -import net.momirealms.customfishing.libraries.dependencies.Dependency; -import net.momirealms.customfishing.libraries.dependencies.DependencyManager; -import net.momirealms.customfishing.libraries.dependencies.DependencyManagerImpl; -import net.momirealms.customfishing.mechanic.action.ActionManagerImpl; -import net.momirealms.customfishing.mechanic.bag.BagManagerImpl; -import net.momirealms.customfishing.mechanic.block.BlockManagerImpl; -import net.momirealms.customfishing.mechanic.competition.CompetitionManagerImpl; -import net.momirealms.customfishing.mechanic.effect.EffectManagerImpl; -import net.momirealms.customfishing.mechanic.entity.EntityManagerImpl; -import net.momirealms.customfishing.mechanic.fishing.FishingManagerImpl; -import net.momirealms.customfishing.mechanic.game.GameManagerImpl; -import net.momirealms.customfishing.mechanic.hook.HookManagerImpl; -import net.momirealms.customfishing.mechanic.item.ItemManagerImpl; -import net.momirealms.customfishing.mechanic.loot.LootManagerImpl; -import net.momirealms.customfishing.mechanic.market.MarketManagerImpl; -import net.momirealms.customfishing.mechanic.misc.ChatCatcherManager; -import net.momirealms.customfishing.mechanic.misc.CoolDownManager; -import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl; -import net.momirealms.customfishing.mechanic.statistic.StatisticsManagerImpl; -import net.momirealms.customfishing.mechanic.totem.TotemManagerImpl; -import net.momirealms.customfishing.scheduler.SchedulerImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.storage.StorageManagerImpl; -import net.momirealms.customfishing.util.NBTUtils; -import net.momirealms.customfishing.version.VersionManagerImpl; -import org.bstats.bukkit.Metrics; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class CustomFishingPluginImpl extends CustomFishingPlugin { - - private static ProtocolManager protocolManager; - private CoolDownManager coolDownManager; - private ChatCatcherManager chatCatcherManager; - private DependencyManager dependencyManager; - - public CustomFishingPluginImpl() { - super(); - } - - @Override - public void onLoad() { - this.versionManager = new VersionManagerImpl(this); - this.dependencyManager = new DependencyManagerImpl(this, new ReflectionClassPathAppender(this.getClassLoader())); - this.dependencyManager.loadDependencies(new ArrayList<>( - List.of( - Dependency.GSON, - Dependency.SLF4J_API, - Dependency.SLF4J_SIMPLE, - Dependency.BOOSTED_YAML, - Dependency.EXP4J, - Dependency.MYSQL_DRIVER, - Dependency.MARIADB_DRIVER, - Dependency.MONGODB_DRIVER_SYNC, - Dependency.MONGODB_DRIVER_CORE, - Dependency.MONGODB_DRIVER_BSON, - Dependency.JEDIS, - Dependency.COMMONS_POOL_2, - Dependency.COMMONS_LANG_3, - Dependency.H2_DRIVER, - Dependency.SQLITE_DRIVER, - Dependency.BSTATS_BASE, - Dependency.HIKARI, - Dependency.BSTATS_BUKKIT, - versionManager.isMojmap() ? Dependency.COMMAND_API_MOJMAP : Dependency.COMMAND_API - ) - )); - } - - @Override - public void onEnable() { - protocolManager = ProtocolLibrary.getProtocolManager(); - - NBTUtils.disableNBTAPILogs(); - ReflectionUtils.load(); - - this.actionManager = new ActionManagerImpl(this); - this.adventure = new AdventureHelper(this); - this.bagManager = new BagManagerImpl(this); - this.blockManager = new BlockManagerImpl(this); - this.commandManager = new CommandManagerImpl(this); - this.effectManager = new EffectManagerImpl(this); - this.fishingManager = new FishingManagerImpl(this); - this.gameManager = new GameManagerImpl(this); - this.itemManager = new ItemManagerImpl(this); - this.lootManager = new LootManagerImpl(this); - this.marketManager = new MarketManagerImpl(this); - this.entityManager = new EntityManagerImpl(this); - this.placeholderManager = new PlaceholderManagerImpl(this); - this.requirementManager = new RequirementManagerImpl(this); - this.scheduler = new SchedulerImpl(this); - this.storageManager = new StorageManagerImpl(this); - this.competitionManager = new CompetitionManagerImpl(this); - this.integrationManager = new IntegrationManagerImpl(this); - this.statisticsManager = new StatisticsManagerImpl(this); - this.coolDownManager = new CoolDownManager(this); - this.totemManager = new TotemManagerImpl(this); - this.hookManager = new HookManagerImpl(this); - this.chatCatcherManager = new ChatCatcherManager(this); - this.reload(); - super.initialized = true; - - if (CFConfig.metrics) new Metrics(this, 16648); - if (CFConfig.updateChecker) - this.versionManager.checkUpdate().thenAccept(result -> { - if (!result) this.getAdventure().sendConsoleMessage("[CustomFishing] You are using the latest version."); - else this.getAdventure().sendConsoleMessage("[CustomFishing] Update is available: https://polymart.org/resource/2723"); - }); - } - - @Override - public void onDisable() { - if (this.adventure != null) ((AdventureHelper) this.adventure).close(); - if (this.bagManager != null) ((BagManagerImpl) this.bagManager).disable(); - if (this.blockManager != null) ((BlockManagerImpl) this.blockManager).disable(); - if (this.effectManager != null) ((EffectManagerImpl) this.effectManager).disable(); - if (this.fishingManager != null) ((FishingManagerImpl) this.fishingManager).disable(); - if (this.gameManager != null) ((GameManagerImpl) this.gameManager).disable(); - if (this.itemManager != null) ((ItemManagerImpl) this.itemManager).disable(); - if (this.lootManager != null) ((LootManagerImpl) this.lootManager).disable(); - if (this.marketManager != null) ((MarketManagerImpl) this.marketManager).disable(); - if (this.entityManager != null) ((EntityManagerImpl) this.entityManager).disable(); - if (this.requirementManager != null) ((RequirementManagerImpl) this.requirementManager).disable(); - if (this.scheduler != null) ((SchedulerImpl) this.scheduler).shutdown(); - if (this.integrationManager != null) ((IntegrationManagerImpl) this.integrationManager).disable(); - if (this.competitionManager != null) ((CompetitionManagerImpl) this.competitionManager).disable(); - if (this.storageManager != null) ((StorageManagerImpl) this.storageManager).disable(); - if (this.placeholderManager != null) ((PlaceholderManagerImpl) this.placeholderManager).disable(); - if (this.statisticsManager != null) ((StatisticsManagerImpl) this.statisticsManager).disable(); - if (this.actionManager != null) ((ActionManagerImpl) this.actionManager).disable(); - if (this.totemManager != null) ((TotemManagerImpl) this.totemManager).disable(); - if (this.hookManager != null) ((HookManagerImpl) this.hookManager).disable(); - if (this.coolDownManager != null) this.coolDownManager.disable(); - if (this.chatCatcherManager != null) this.chatCatcherManager.disable(); - if (this.commandManager != null) this.commandManager.unload(); - HandlerList.unregisterAll(this); - } - - /** - * Reload the plugin - */ - @Override - public void reload() { - CFConfig.load(); - CFLocale.load(); - ((SchedulerImpl) this.scheduler).reload(); - ((RequirementManagerImpl) this.requirementManager).unload(); - ((RequirementManagerImpl) this.requirementManager).load(); - ((ActionManagerImpl) this.actionManager).unload(); - ((ActionManagerImpl) this.actionManager).load(); - ((GameManagerImpl) this.gameManager).unload(); - ((GameManagerImpl) this.gameManager).load(); - ((ItemManagerImpl) this.itemManager).unload(); - ((ItemManagerImpl) this.itemManager).load(); - ((LootManagerImpl) this.lootManager).unload(); - ((LootManagerImpl) this.lootManager).load(); - ((FishingManagerImpl) this.fishingManager).unload(); - ((FishingManagerImpl) this.fishingManager).load(); - ((TotemManagerImpl) this.totemManager).unload(); - ((TotemManagerImpl) this.totemManager).load(); - ((EffectManagerImpl) this.effectManager).unload(); - ((EffectManagerImpl) this.effectManager).load(); - ((MarketManagerImpl) this.marketManager).unload(); - ((MarketManagerImpl) this.marketManager).load(); - ((BagManagerImpl) this.bagManager).unload(); - ((BagManagerImpl) this.bagManager).load(); - ((BlockManagerImpl) this.blockManager).unload(); - ((BlockManagerImpl) this.blockManager).load(); - ((EntityManagerImpl) this.entityManager).unload(); - ((EntityManagerImpl) this.entityManager).load(); - ((CompetitionManagerImpl) this.competitionManager).unload(); - ((CompetitionManagerImpl) this.competitionManager).load(); - ((StorageManagerImpl) this.storageManager).reload(); - ((StatisticsManagerImpl) this.statisticsManager).unload(); - ((StatisticsManagerImpl) this.statisticsManager).load(); - ((PlaceholderManagerImpl) this.placeholderManager).unload(); - ((PlaceholderManagerImpl) this.placeholderManager).load(); - ((HookManagerImpl) this.hookManager).unload(); - ((HookManagerImpl) this.hookManager).load(); - this.commandManager.unload(); - this.commandManager.load(); - this.coolDownManager.unload(); - this.coolDownManager.load(); - this.chatCatcherManager.unload(); - this.chatCatcherManager.load(); - - CustomFishingReloadEvent event = new CustomFishingReloadEvent(this); - Bukkit.getPluginManager().callEvent(event); - } - - /** - * Retrieves a YAML configuration from a file within the plugin's data folder. - * - * @param file The name of the configuration file. - * @return A YamlConfiguration object representing the configuration. - */ - @Override - public YamlConfiguration getConfig(String file) { - File config = new File(this.getDataFolder(), file); - if (!config.exists()) this.saveResource(file, false); - return YamlConfiguration.loadConfiguration(config); - } - - /** - * Checks if a specified plugin is enabled on the Bukkit server. - * - * @param plugin The name of the plugin to check. - * @return True if the plugin is enabled, false otherwise. - */ - @Override - public boolean isHookedPluginEnabled(String plugin) { - return Bukkit.getPluginManager().isPluginEnabled(plugin); - } - - /** - * Outputs a debugging message if the debug mode is enabled. - * - * @param message The debugging message to be logged. - */ - @Override - public void debug(String message) { - if (!CFConfig.debug) return; - LogUtils.info(message); - } - - /** - * Gets the CoolDownManager instance associated with the plugin. - * - * @return The CoolDownManager instance. - */ - public CoolDownManager getCoolDownManager() { - return coolDownManager; - } - - /** - * Gets the ChatCatcherManager instance associated with the plugin. - * - * @return The ChatCatcherManager instance. - */ - public ChatCatcherManager getChatCatcherManager() { - return chatCatcherManager; - } - - public DependencyManager getDependencyManager() { - return dependencyManager; - } - - /** - * Retrieves the ProtocolManager instance used for managing packets. - * - * @return The ProtocolManager instance. - */ - @NotNull - public static ProtocolManager getProtocolManager() { - return protocolManager; - } - - public static void sendPacket(Player player, PacketContainer packet) { - protocolManager.sendServerPacket(player, packet); - } - - public static void sendPackets(Player player, PacketContainer... packets) { - List bundle = new ArrayList<>(Arrays.asList(packets)); - PacketContainer bundlePacket = new PacketContainer(PacketType.Play.Server.BUNDLE); - bundlePacket.getPacketBundles().write(0, bundle); - sendPacket(player, bundlePacket); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/adventure/AdventureHelper.java b/plugin/src/main/java/net/momirealms/customfishing/adventure/AdventureHelper.java deleted file mode 100644 index 9a0279bf..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/adventure/AdventureHelper.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.adventure; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.AdventureManager; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.ReflectionUtils; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; - -import java.lang.reflect.InvocationTargetException; - -public class AdventureHelper implements AdventureManager { - - private final BukkitAudiences adventure; - private static AdventureManager instance; - - public AdventureHelper(CustomFishingPlugin plugin) { - this.adventure = BukkitAudiences.create(plugin); - instance = this; - } - - public static AdventureManager getInstance() { - return instance; - } - - public void close() { - if (adventure != null) - adventure.close(); - } - - @Override - public Component getComponentFromMiniMessage(String text) { - if (text == null) { - return Component.empty(); - } - if (CFConfig.legacyColorSupport) { - return MiniMessage.miniMessage().deserialize(legacyToMiniMessage(text)); - } else { - return MiniMessage.miniMessage().deserialize(text); - } - } - - @Override - public void sendMessage(CommandSender sender, String s) { - if (s == null) return; - if (sender instanceof Player player) sendPlayerMessage(player, s); - else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(s); - } - - @Override - public void sendMessageWithPrefix(CommandSender sender, String s) { - if (s == null) return; - if (sender instanceof Player player) sendPlayerMessage(player, CFLocale.MSG_Prefix + s); - else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(CFLocale.MSG_Prefix + s); - } - - @Override - public void sendConsoleMessage(String s) { - if (s == null) return; - Audience au = adventure.sender(Bukkit.getConsoleSender()); - au.sendMessage(getComponentFromMiniMessage(s)); - } - - @Override - public void sendPlayerMessage(Player player, String s) { - if (s == null) return; - Audience au = adventure.player(player); - au.sendMessage(getComponentFromMiniMessage(s)); - } - - @Override - public void sendTitle(Player player, String title, String subtitle, int in, int duration, int out) { - sendTitle(player, getComponentFromMiniMessage(title), getComponentFromMiniMessage(subtitle), in, duration, out); - } - - @Override - public void sendTitle(Player player, Component title, Component subtitle, int in, int duration, int out) { - try { - PacketContainer titlePacket = new PacketContainer(PacketType.Play.Server.SET_TITLE_TEXT); - titlePacket.getModifier().write(0, getIChatComponent(componentToJson(title))); - PacketContainer subTitlePacket = new PacketContainer(PacketType.Play.Server.SET_SUBTITLE_TEXT); - subTitlePacket.getModifier().write(0, getIChatComponent(componentToJson(subtitle))); - PacketContainer timePacket = new PacketContainer(PacketType.Play.Server.SET_TITLES_ANIMATION); - timePacket.getIntegers().write(0, in); - timePacket.getIntegers().write(1, duration); - timePacket.getIntegers().write(2, out); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, titlePacket); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, subTitlePacket); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, timePacket); - } catch (InvocationTargetException | IllegalAccessException e) { - LogUtils.warn("Error occurred when sending title"); - } - } - - @Override - public void sendActionbar(Player player, String s) { - try { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.SET_ACTION_BAR_TEXT); - packet.getModifier().write(0, getIChatComponent(componentToJson(getComponentFromMiniMessage(s)))); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, packet); - } catch (InvocationTargetException | IllegalAccessException e) { - LogUtils.warn("Error occurred when sending actionbar"); - } - } - - @Override - public void sendSound(Player player, Sound.Source source, Key key, float volume, float pitch) { - Sound sound = Sound.sound(key, source, volume, pitch); - Audience au = adventure.player(player); - au.playSound(sound); - } - - @Override - public void sendSound(Player player, Sound sound) { - Audience au = adventure.player(player); - au.playSound(sound); - } - - @Override - public String legacyToMiniMessage(String legacy) { - StringBuilder stringBuilder = new StringBuilder(); - char[] chars = legacy.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (!isColorCode(chars[i])) { - stringBuilder.append(chars[i]); - continue; - } - if (i + 1 >= chars.length) { - stringBuilder.append(chars[i]); - continue; - } - switch (chars[i+1]) { - case '0' -> stringBuilder.append(""); - case '1' -> stringBuilder.append(""); - case '2' -> stringBuilder.append(""); - case '3' -> stringBuilder.append(""); - case '4' -> stringBuilder.append(""); - case '5' -> stringBuilder.append(""); - case '6' -> stringBuilder.append(""); - case '7' -> stringBuilder.append(""); - case '8' -> stringBuilder.append(""); - case '9' -> stringBuilder.append(""); - case 'a' -> stringBuilder.append(""); - case 'b' -> stringBuilder.append(""); - case 'c' -> stringBuilder.append(""); - case 'd' -> stringBuilder.append(""); - case 'e' -> stringBuilder.append(""); - case 'f' -> stringBuilder.append(""); - case 'r' -> stringBuilder.append(""); - case 'l' -> stringBuilder.append(""); - case 'm' -> stringBuilder.append(""); - case 'o' -> stringBuilder.append(""); - case 'n' -> stringBuilder.append(""); - case 'k' -> stringBuilder.append(""); - case 'x' -> { - if (i + 13 >= chars.length - || !isColorCode(chars[i+2]) - || !isColorCode(chars[i+4]) - || !isColorCode(chars[i+6]) - || !isColorCode(chars[i+8]) - || !isColorCode(chars[i+10]) - || !isColorCode(chars[i+12])) { - stringBuilder.append(chars[i]); - continue; - } - stringBuilder - .append("<#") - .append(chars[i+3]) - .append(chars[i+5]) - .append(chars[i+7]) - .append(chars[i+9]) - .append(chars[i+11]) - .append(chars[i+13]) - .append(">"); - i += 12; - } - default -> { - stringBuilder.append(chars[i]); - continue; - } - } - i++; - } - return stringBuilder.toString(); - } - - @Override - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean isColorCode(char c) { - return c == '§' || c == '&'; - } - - @Override - public String componentToLegacy(Component component) { - return LegacyComponentSerializer.legacySection().serialize(component); - } - - @Override - public String componentToJson(Component component) { - return GsonComponentSerializer.gson().serialize(component); - } - - @Override - public Object shadedComponentToOriginalComponent(Component component) { - Object cp; - try { - cp = ReflectionUtils.gsonDeserializeMethod.invoke(ReflectionUtils.gsonInstance, GsonComponentSerializer.gson().serialize(component)); - } catch (InvocationTargetException | IllegalAccessException e) { - e.printStackTrace(); - return null; - } - return cp; - } - - public Object getIChatComponent(String json) throws InvocationTargetException, IllegalAccessException { - return ReflectionUtils.iChatComponentMethod.invoke(null, json); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java deleted file mode 100644 index 7bbe1497..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/CommandManagerImpl.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command; - -import dev.jorel.commandapi.CommandAPI; -import dev.jorel.commandapi.CommandAPIBukkitConfig; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.EntitySelectorArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.arguments.UUIDArgument; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.CommandManager; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.command.sub.*; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; - -import java.util.Collection; -import java.util.UUID; - -public class CommandManagerImpl implements CommandManager { - - private final CustomFishingPlugin plugin; - - public CommandManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - } - - @Override - public void load() { - if (!CommandAPI.isLoaded()) - CommandAPI.onLoad(new CommandAPIBukkitConfig(plugin).silentLogs(true)); - new CommandAPICommand("customfishing") - .withAliases("cfishing") - .withPermission("customfishing.admin") - .withSubcommands( - getReloadCommand(), - getOpenCommand(), - getAboutCommand(), - GUIEditorCommand.INSTANCE.getEditorCommand(), - DataCommand.INSTANCE.getDataCommand(), - CompetitionCommand.INSTANCE.getCompetitionCommand(), - ItemCommand.INSTANCE.getItemCommand(), - DebugCommand.INSTANCE.getDebugCommand(), - StatisticsCommand.INSTANCE.getStatisticsCommand() - ) - .register(); - if (plugin.getMarketManager().isEnable()) { - new CommandAPICommand("sellfish") - .withPermission("customfishing.sellfish") - .executesPlayer((player, args) -> { - if (plugin.getMarketManager().isEnable()) - plugin.getMarketManager().openMarketGUI(player); - }) - .register(); - } - if (plugin.getBagManager().isEnabled()) { - FishingBagCommand.INSTANCE.getBagCommand().register(); - } - } - - @Override - public void unload() { - } - - private CommandAPICommand getReloadCommand() { - return new CommandAPICommand("reload") - .executes((sender, args) -> { - long time = System.currentTimeMillis(); - plugin.reload(); - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Reload.replace("{time}", String.valueOf(System.currentTimeMillis()-time))); - }); - } - - @SuppressWarnings("unchecked") - private CommandAPICommand getOpenCommand() { - CommandAPICommand command = new CommandAPICommand("open"); - if (plugin.getMarketManager().isEnable()) { - command.withSubcommands( - new CommandAPICommand("market") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .withOptionalArguments(new StringArgument("-s")) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - assert players != null; - boolean silence = args.getOrDefault("-s","").equals("-s"); - for (Player player : players) { - plugin.getMarketManager().openMarketGUI(player); - if (!silence) AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Market_GUI_Open.replace("{player}", player.getName())); - } - }), - new CommandAPICommand("market-uuid") - .withArguments(new UUIDArgument("uuid")) - .withOptionalArguments(new StringArgument("-s")) - .executes((sender, args) -> { - UUID uuid = (UUID) args.get("uuid"); - Player player = Bukkit.getPlayer(uuid); - boolean silence = args.getOrDefault("-s","").equals("-s"); - if (player == null) return; - plugin.getMarketManager().openMarketGUI(player); - if (!silence) AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Market_GUI_Open.replace("{player}", player.getName())); - }) - ); - } - if (plugin.getBagManager().isEnabled()) { - command.withSubcommands( - new CommandAPICommand("bag") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .withOptionalArguments(new StringArgument("-s")) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - assert players != null; - boolean silence = args.getOrDefault("-s","").equals("-s"); - for (Player player : players) { - Inventory inventory = plugin.getBagManager().getOnlineBagInventory(player.getUniqueId()); - if (inventory != null) { - player.openInventory(inventory); - if (!silence) AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Fishing_Bag_Open.replace("{player}", player.getName())); - } else { - LogUtils.warn("Player " + player.getName() + "'s bag data has not been loaded."); - } - } - }), - new CommandAPICommand("bag-uuid") - .withArguments(new UUIDArgument("uuid")) - .withOptionalArguments(new StringArgument("-s")) - .executes((sender, args) -> { - UUID uuid = (UUID) args.get("uuid"); - Player player = Bukkit.getPlayer(uuid); - boolean silence = args.getOrDefault("-s","").equals("-s"); - if (player == null) return; - Inventory inventory = plugin.getBagManager().getOnlineBagInventory(uuid); - if (inventory != null) { - player.openInventory(inventory); - if (!silence) AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Fishing_Bag_Open.replace("{player}", player.getName())); - } else { - LogUtils.warn("Player " + player.getName() + "'s bag data has not been loaded."); - } - }) - ); - } - return command; - } - - private CommandAPICommand getAboutCommand() { - return new CommandAPICommand("about").executes((sender, args) -> { - AdventureHelper.getInstance().sendMessage(sender, "<#00BFFF>\uD83C\uDFA3 CustomFishing - <#87CEEB>" + CustomFishingPlugin.getInstance().getVersionManager().getPluginVersion()); - AdventureHelper.getInstance().sendMessage(sender, "<#B0C4DE>A fishing plugin that provides innovative mechanics and powerful loot system"); - AdventureHelper.getInstance().sendMessage(sender, "<#DA70D6>\uD83E\uDDEA Author: <#FFC0CB>XiaoMoMi"); - AdventureHelper.getInstance().sendMessage(sender, "<#FF7F50>\uD83D\uDD25 Contributors: <#FFA07A>0ft3n, <#FFA07A>Peng_Lx, <#FFA07A>Masaki, <#FFA07A>g2213swo"); - AdventureHelper.getInstance().sendMessage(sender, "<#FFD700>⭐ Document <#A9A9A9>| <#FAFAD2>⛏ Github <#A9A9A9>| <#48D1CC>\uD83D\uDD14 Polymart"); - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java deleted file mode 100644 index 38bb4fac..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/CompetitionCommand.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.StringArgument; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.storage.method.database.nosql.RedisManager; - -import java.util.Set; - -public class CompetitionCommand { - - public static CompetitionCommand INSTANCE = new CompetitionCommand(); - - public CommandAPICommand getCompetitionCommand() { - return new CommandAPICommand("competition") - .withSubcommands( - getCompetitionStartCommand(), - getCompetitionEndCommand(), - getCompetitionStopCommand() - ); - } - - private CommandAPICommand getCompetitionStartCommand() { - Set allCompetitions = CustomFishingPlugin.get().getCompetitionManager().getAllCompetitionKeys(); - var command = new CommandAPICommand("start") - .withArguments( - new StringArgument("id") - .replaceSuggestions( - ArgumentSuggestions.strings(allCompetitions) - ) - ); - if (CFConfig.redisRanking) command.withOptionalArguments(new StringArgument("server-group")); - command.executes((sender, args) -> { - String id = (String) args.get(0); - assert id != null; - if (!allCompetitions.contains(id)) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Competition_Not_Exist.replace("{id}", id)); - return; - } - Object server = args.get("server-group"); - if (server != null) { - CustomFishingPlugin.get().getCompetitionManager().startCompetition(id, true, (String) server); - } else { - CustomFishingPlugin.get().getCompetitionManager().startCompetition(id, true, null); - } - }); - return command; - } - - private CommandAPICommand getCompetitionEndCommand() { - var command = new CommandAPICommand("end"); - if (CFConfig.redisRanking) command.withOptionalArguments(new StringArgument("server-group")); - command.executes((sender, args) -> { - Object server = args.get("server-group"); - if (server != null) { - RedisManager.getInstance().publishRedisMessage((String) server, "end"); - } else { - FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition(); - if (competition != null) { - CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> competition.end(true)); - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_End_Competition); - } else { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_No_Competition_Ongoing); - } - } - }); - return command; - } - - private CommandAPICommand getCompetitionStopCommand() { - var command = new CommandAPICommand("stop"); - if (CFConfig.redisRanking) command.withOptionalArguments(new StringArgument("server-group")); - command.executes((sender, args) -> { - Object server = args.get("server-group"); - if (server != null) { - RedisManager.getInstance().publishRedisMessage((String) server, "stop"); - } else { - FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition(); - if (competition != null) { - CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> { - competition.stop(true); - }); - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Stop_Competition); - } else { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_No_Competition_Ongoing); - } - } - }); - return command; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/DataCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/DataCommand.java deleted file mode 100644 index d2bfd6cc..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/DataCommand.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.StringArgument; -import dev.jorel.commandapi.arguments.UUIDArgument; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.DataStorageInterface; -import net.momirealms.customfishing.api.data.LegacyDataStorageInterface; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.storage.method.database.sql.MariaDBImpl; -import net.momirealms.customfishing.storage.method.database.sql.MySQLImpl; -import net.momirealms.customfishing.storage.method.file.YAMLImpl; -import net.momirealms.customfishing.util.CompletableFutures; -import org.bukkit.Bukkit; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -public class DataCommand { - - public static DataCommand INSTANCE = new DataCommand(); - - public CommandAPICommand getDataCommand() { - return new CommandAPICommand("data") - .withSubcommands( - getExportLegacyCommand(), - getExportCommand(), - getImportCommand(), - getUnlockCommand() - ); - } - - private CommandAPICommand getUnlockCommand() { - return new CommandAPICommand("unlock") - .withArguments(new UUIDArgument("uuid")) - .executes((sender, args) -> { - UUID uuid = (UUID) args.get("uuid"); - CustomFishingPlugin.get().getStorageManager().getDataSource().lockOrUnlockPlayerData(uuid, false); - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Successfully unlocked."); - }); - } - - @SuppressWarnings("DuplicatedCode") - private CommandAPICommand getExportLegacyCommand() { - return new CommandAPICommand("export-legacy") - .withArguments(new StringArgument("method") - .replaceSuggestions(ArgumentSuggestions.strings("MySQL", "MariaDB", "YAML"))) - .executes((sender, args) -> { - String arg = (String) args.get("method"); - if (arg == null) return; - CustomFishingPlugin plugin = CustomFishingPlugin.get(); - plugin.getScheduler().runTaskAsync(() -> { - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Starting export."); - - LegacyDataStorageInterface dataStorageInterface; - switch (arg) { - case "MySQL" -> dataStorageInterface = new MySQLImpl(plugin); - case "MariaDB" -> dataStorageInterface = new MariaDBImpl(plugin); - case "YAML" -> dataStorageInterface = new YAMLImpl(plugin); - default -> { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "No such legacy storage method."); - return; - } - } - - dataStorageInterface.initialize(); - Set uuids = dataStorageInterface.getUniqueUsers(true); - Set> futures = new HashSet<>(); - AtomicInteger userCount = new AtomicInteger(0); - Map out = Collections.synchronizedMap(new TreeMap<>()); - - for (UUID uuid : uuids) { - futures.add(dataStorageInterface.getLegacyPlayerData(uuid).thenAccept(it -> { - if (it.isPresent()) { - out.put(uuid, plugin.getStorageManager().toJson(it.get())); - userCount.incrementAndGet(); - } - })); - } - - CompletableFuture overallFuture = CompletableFutures.allOf(futures); - - while (true) { - try { - overallFuture.get(3, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - break; - } catch (TimeoutException e) { - LogUtils.info("Progress: " + userCount.get() + "/" + uuids.size()); - continue; - } - break; - } - - JsonObject outJson = new JsonObject(); - for (Map.Entry entry : out.entrySet()) { - outJson.addProperty(entry.getKey().toString(), entry.getValue()); - } - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); - String formattedDate = formatter.format(new Date()); - File outFile = new File(plugin.getDataFolder(), "exported-" + formattedDate + ".json.gz"); - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(outFile.toPath())), StandardCharsets.UTF_8))) { - new GsonBuilder().disableHtmlEscaping().create().toJson(outJson, writer); - } catch (IOException e) { - e.printStackTrace(); - } - - dataStorageInterface.disable(); - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Completed."); - }); - }); - } - - @SuppressWarnings("DuplicatedCode") - private CommandAPICommand getExportCommand() { - return new CommandAPICommand("export") - .executesConsole((sender, args) -> { - if (Bukkit.getOnlinePlayers().size() != 0) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Please kick all the players before exporting. Otherwise the cache will be inconsistent with data, resulting in the backup file not being up to date."); - return; - } - - CustomFishingPlugin plugin = CustomFishingPlugin.get(); - plugin.getScheduler().runTaskAsync(() -> { - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Starting export."); - DataStorageInterface dataStorageInterface = plugin.getStorageManager().getDataSource(); - - Set uuids = dataStorageInterface.getUniqueUsers(false); - Set> futures = new HashSet<>(); - AtomicInteger userCount = new AtomicInteger(0); - Map out = Collections.synchronizedMap(new TreeMap<>()); - - int amount = uuids.size(); - for (UUID uuid : uuids) { - futures.add(dataStorageInterface.getPlayerData(uuid, false).thenAccept(it -> { - if (it.isPresent()) { - out.put(uuid, plugin.getStorageManager().toJson(it.get())); - userCount.incrementAndGet(); - } - })); - } - - CompletableFuture overallFuture = CompletableFutures.allOf(futures); - - while (true) { - try { - overallFuture.get(3, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - break; - } catch (TimeoutException e) { - LogUtils.info("Progress: " + userCount.get() + "/" + amount); - continue; - } - break; - } - - JsonObject outJson = new JsonObject(); - for (Map.Entry entry : out.entrySet()) { - outJson.addProperty(entry.getKey().toString(), entry.getValue()); - } - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); - String formattedDate = formatter.format(new Date()); - File outFile = new File(plugin.getDataFolder(), "exported-" + formattedDate + ".json.gz"); - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(outFile.toPath())), StandardCharsets.UTF_8))) { - new GsonBuilder().disableHtmlEscaping().create().toJson(outJson, writer); - } catch (IOException e) { - e.printStackTrace(); - } - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Completed."); - }); - }); - } - - @SuppressWarnings("DuplicatedCode") - private CommandAPICommand getImportCommand() { - return new CommandAPICommand("import") - .withArguments(new StringArgument("file")) - .executesConsole((sender, args) -> { - if (Bukkit.getOnlinePlayers().size() != 0) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Please kick all the players before importing. Otherwise the cache will be inconsistent with data."); - return; - } - - String fileName = (String) args.get("file"); - if (fileName == null) return; - CustomFishingPlugin plugin = CustomFishingPlugin.get(); - - File file = new File(plugin.getDataFolder(), fileName); - if (!file.exists()) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "File not exists."); - return; - } - if (!file.getName().endsWith(".json.gz")) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Invalid file."); - return; - } - - plugin.getScheduler().runTaskAsync(() -> { - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Starting import."); - - JsonObject data; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(file.toPath())), StandardCharsets.UTF_8))) { - data = new GsonBuilder().disableHtmlEscaping().create().fromJson(reader, JsonObject.class); - } catch (IOException e) { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Error occurred when reading the backup file."); - e.printStackTrace(); - return; - } - - DataStorageInterface dataStorageInterface = plugin.getStorageManager().getDataSource(); - var entrySet = data.entrySet(); - int amount = entrySet.size(); - AtomicInteger userCount = new AtomicInteger(0); - Set> futures = new HashSet<>(); - - for (Map.Entry entry : entrySet) { - UUID uuid = UUID.fromString(entry.getKey()); - if (entry.getValue() instanceof JsonPrimitive primitive) { - PlayerData playerData = plugin.getStorageManager().fromJson(primitive.getAsString()); - futures.add(dataStorageInterface.updateOrInsertPlayerData(uuid, playerData, true).thenAccept(it -> userCount.incrementAndGet())); - } - } - - CompletableFuture overallFuture = CompletableFutures.allOf(futures); - - while (true) { - try { - overallFuture.get(3, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - break; - } catch (TimeoutException e) { - LogUtils.info("Progress: " + userCount.get() + "/" + amount); - continue; - } - break; - } - - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Completed."); - }); - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/DebugCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/DebugCommand.java deleted file mode 100644 index 29782ad2..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/DebugCommand.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import de.tr7zw.changeme.nbtapi.NBTItem; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.IStringTooltip; -import dev.jorel.commandapi.StringTooltip; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.BooleanArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.integration.SeasonInterface; -import net.momirealms.customfishing.api.manager.AdventureManager; -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import net.momirealms.customfishing.mechanic.fishing.FishingPreparationImpl; -import net.momirealms.customfishing.util.ConfigUtils; -import net.momirealms.customfishing.util.NBTUtils; -import net.momirealms.sparrow.heart.SparrowHeart; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.StringJoiner; - -public class DebugCommand { - - public static DebugCommand INSTANCE = new DebugCommand(); - - public CommandAPICommand getDebugCommand() { - return new CommandAPICommand("debug") - .withSubcommands( - getLootChanceCommand(), - getBiomeCommand(), - getSeasonCommand(), - getGroupCommand(), - getCategoryCommand(), - getNBTCommand(), - getLocationCommand() - ); - } - - public CommandAPICommand getBiomeCommand() { - return new CommandAPICommand("biome") - .executesPlayer((player, arg) -> { - AdventureHelper.getInstance().sendMessage(player, SparrowHeart.getInstance().getBiomeResourceLocation(player.getLocation())); - }); - } - - public CommandAPICommand getLocationCommand() { - return new CommandAPICommand("location") - .executesPlayer((player, arg) -> { - AdventureHelper.getInstance().sendMessage(player, player.getLocation().toString()); - }); - } - - public CommandAPICommand getNBTCommand() { - return new CommandAPICommand("nbt") - .executesPlayer((player, arg) -> { - ItemStack item = player.getInventory().getItemInMainHand(); - if (item.getType() == Material.AIR) - return; - ArrayList list = new ArrayList<>(); - ConfigUtils.mapToReadableStringList(NBTUtils.compoundToMap(new NBTItem(item)), list, 0, false); - for (String line : list) { - AdventureHelper.getInstance().sendMessage(player, line); - } - }); - } - - public CommandAPICommand getSeasonCommand() { - return new CommandAPICommand("season") - .executesPlayer((player, arg) -> { - SeasonInterface seasonInterface = CustomFishingPlugin.get().getIntegrationManager().getSeasonInterface(); - if (seasonInterface == null) { - AdventureHelper.getInstance().sendMessageWithPrefix(player, "NO SEASON PLUGIN"); - return; - } - AdventureHelper.getInstance().sendMessage(player, seasonInterface.getSeason(player.getLocation().getWorld())); - }); - } - - public CommandAPICommand getGroupCommand() { - return new CommandAPICommand("group") - .withArguments(new StringArgument("group")) - .executes((sender, arg) -> { - String group = (String) arg.get("group"); - StringJoiner stringJoiner = new StringJoiner(", "); - List groups = CustomFishingPlugin.get().getLootManager().getLootGroup(group); - if (groups != null) - for (String key : groups) { - stringJoiner.add(key); - } - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Group{" + group + "}[" + stringJoiner + "]"); - }); - } - - public CommandAPICommand getCategoryCommand() { - return new CommandAPICommand("category") - .withArguments(new StringArgument("category")) - .executes((sender, arg) -> { - String c = (String) arg.get("category"); - StringJoiner stringJoiner = new StringJoiner(", "); - List cs = CustomFishingPlugin.get().getStatisticsManager().getCategory(c); - if (cs != null) - for (String key : cs) { - stringJoiner.add(key); - } - AdventureHelper.getInstance().sendMessageWithPrefix(sender, "Category{" + c + "}[" + stringJoiner + "]"); - }); - } - - public CommandAPICommand getLootChanceCommand() { - return new CommandAPICommand("loot-chance") - .withArguments(new BooleanArgument("lava fishing").replaceSuggestions(ArgumentSuggestions.stringsWithTooltips(info -> - new IStringTooltip[] { - StringTooltip.ofString("true", "loots in lava"), - StringTooltip.ofString("false", "loots in water") - }))) - .executesPlayer((player, arg) -> { - if (player.getInventory().getItemInMainHand().getType() != Material.FISHING_ROD) { - AdventureHelper.getInstance().sendMessageWithPrefix(player, "Please hold a fishing rod before using this command."); - return; - } - FishingEffect initialEffect = CustomFishingPlugin.get().getEffectManager().getInitialEffect(); - FishingPreparation fishingPreparation = new FishingPreparationImpl(player, CustomFishingPlugin.get()); - boolean inLava = (boolean) arg.getOrDefault("lava fishing", false); - fishingPreparation.insertArg("{lava}", String.valueOf(inLava)); - fishingPreparation.mergeEffect(initialEffect); - EffectCarrier totemEffect = CustomFishingPlugin.get().getTotemManager().getTotemEffect(player.getLocation()); - if (totemEffect != null) - for (EffectModifier modifier : totemEffect.getEffectModifiers()) { - modifier.modify(initialEffect, fishingPreparation); - } - var map = CustomFishingPlugin.get().getLootManager().getPossibleLootKeysWithWeight(initialEffect, fishingPreparation); - List loots = new ArrayList<>(); - double sum = 0; - for (Map.Entry entry : map.entrySet()) { - double weight = entry.getValue(); - String loot = entry.getKey(); - if (weight <= 0) continue; - loots.add(new LootWithWeight(loot, weight)); - sum += weight; - } - LootWithWeight[] lootArray = loots.toArray(new LootWithWeight[0]); - quickSort(lootArray, 0,lootArray.length - 1); - AdventureManager adventureManager = AdventureHelper.getInstance(); - adventureManager.sendMessage(player, "---------- results ---------"); - for (LootWithWeight loot : lootArray) { - adventureManager.sendMessage(player, loot.key() + ": " + String.format("%.2f", loot.weight()*100/sum) + "% (" + String.format("%.2f", loot.weight()) + ")"); - } - adventureManager.sendMessage(player, "----------- end -----------"); - }); - } - - public record LootWithWeight(String key, double weight) { - } - - private static void quickSort(LootWithWeight[] loot, int low, int high) { - if (low < high) { - int pi = partition(loot, low, high); - quickSort(loot, low, pi - 1); - quickSort(loot, pi + 1, high); - } - } - - private static int partition(LootWithWeight[] loot, int low, int high) { - double pivot = loot[high].weight(); - int i = low - 1; - for (int j = low; j <= high - 1; j++) { - if (loot[j].weight() > pivot) { - i++; - swap(loot, i, j); - } - } - swap(loot, i + 1, high); - return i + 1; - } - - private static void swap(LootWithWeight[] loot, int i, int j) { - LootWithWeight temp = loot[i]; - loot[i] = loot[j]; - loot[j] = temp; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/FishingBagCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/FishingBagCommand.java deleted file mode 100644 index b147f3f5..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/FishingBagCommand.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.PlayerArgument; -import dev.jorel.commandapi.arguments.UUIDArgument; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.storage.user.OfflineUserImpl; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; - -import java.util.UUID; - -public class FishingBagCommand { - - public static FishingBagCommand INSTANCE = new FishingBagCommand(); - - public CommandAPICommand getBagCommand() { - return new CommandAPICommand("fishingbag") - .withPermission("fishingbag.user") - .withSubcommands(getEditOnlineCommand(), getEditOfflineCommand()) - .executesPlayer(((player, args) -> { - if (CustomFishingPlugin.get().getBagManager().isEnabled()) { - var inv = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(player.getUniqueId()); - if (inv != null) { - player.openInventory(inv); - } else { - AdventureHelper.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Data_Not_Loaded); - } - } - })); - } - - private CommandAPICommand getEditOnlineCommand() { - return new CommandAPICommand("edit-online") - .withPermission("fishingbag.admin") - .withArguments(new PlayerArgument("player")) - .executesPlayer(((player, args) -> { - Player player1 = (Player) args.get("player"); - UUID uuid = player1.getUniqueId(); - Inventory onlineInv = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(uuid); - if (onlineInv != null) { - player.openInventory(onlineInv); - } - })); - } - - private CommandAPICommand getEditOfflineCommand() { - return new CommandAPICommand("edit-offline") - .withPermission("fishingbag.admin") - .withArguments(new UUIDArgument("UUID")) - .executesPlayer(((player, args) -> { - UUID uuid = (UUID) args.get("UUID"); - Player online = Bukkit.getPlayer(uuid); - if (online != null) { - Inventory onlineInv = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(uuid); - if (onlineInv != null) { - player.openInventory(onlineInv); - return; - } - } - CustomFishingPlugin.get().getStorageManager().getOfflineUser(uuid, CFConfig.lockData).thenAccept(optional -> { - if (optional.isEmpty()) { - AdventureHelper.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Never_Played); - return; - } - OfflineUser offlineUser = optional.get(); - if (offlineUser == OfflineUserImpl.LOCKED_USER) { - AdventureHelper.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Unsafe_Modification); - return; - } - CustomFishingPlugin.get().getScheduler().runTaskSync(() -> { - CustomFishingPlugin.get().getBagManager().editOfflinePlayerBag(player, offlineUser); - }, player.getLocation()); - }); - })); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/GUIEditorCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/GUIEditorCommand.java deleted file mode 100644 index 874d9b6d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/GUIEditorCommand.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.momirealms.customfishing.command.sub; - -import dev.jorel.commandapi.CommandAPICommand; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.page.file.FileSelector; - -import java.io.File; - -public class GUIEditorCommand { - - public static GUIEditorCommand INSTANCE = new GUIEditorCommand(); - - public CommandAPICommand getEditorCommand() { - return new CommandAPICommand("browser") - .executesPlayer((player, arg) -> { - new FileSelector(player, new File(CustomFishingPlugin.get().getDataFolder(), "contents")); - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/ItemCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/ItemCommand.java deleted file mode 100644 index 15eba24f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/ItemCommand.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import de.tr7zw.changeme.nbtapi.NBTItem; -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.EntitySelectorArgument; -import dev.jorel.commandapi.arguments.IntegerArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.item.BuildableItem; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.util.ItemUtils; -import net.momirealms.customfishing.util.NBTUtils; -import org.bukkit.Material; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class ItemCommand { - - public static ItemCommand INSTANCE = new ItemCommand(); - - private final HashMap completionMap = new HashMap<>(); - - public CommandAPICommand getItemCommand() { - return new CommandAPICommand("items") - .withSubcommands( - getSubCommand("item"), - getSubCommand("util"), - getSubCommand("bait"), - getSubCommand("rod"), - getSubCommand("hook") - ); - } - - private CommandAPICommand getSubCommand(String namespace) { - completionMap.put(namespace, CustomFishingPlugin.get() - .getItemManager() - .getAllItemsKey() - .stream() - .filter(it -> it.namespace().equals(namespace)) - .map(Key::value) - .toList().toArray(new String[0])); - return new CommandAPICommand(namespace) - .withSubcommands( - getCommand(namespace), - giveCommand(namespace), - importCommand(namespace) - ); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private CommandAPICommand importCommand(String namespace) { - return new CommandAPICommand("import") - .withArguments(new StringArgument("key")) - .withOptionalArguments(new StringArgument("file")) - .executesPlayer((player, args) -> { - String key = (String) args.get("key"); - String fileName = args.getOrDefault("file","import") + ".yml"; - ItemStack itemStack = player.getInventory().getItemInMainHand(); - if (itemStack.getType() == Material.AIR) - return; - File file = new File(CustomFishingPlugin.get().getDataFolder(), - "contents" + File.separator + namespace + File.separator + fileName); - try { - if (!file.exists()) { - file.createNewFile(); - } - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - config.set(key + ".material", itemStack.getType().toString()); - config.set(key + ".amount", itemStack.getAmount()); - Map nbtMap = NBTUtils.compoundToMap(new NBTItem(itemStack)); - if (nbtMap.size() != 0) { - config.createSection(key + ".nbt", nbtMap); - } - try { - config.save(file); - AdventureHelper.getInstance().sendMessageWithPrefix(player, "Imported! Saved to " + file.getAbsolutePath()); - } catch (IOException e) { - e.printStackTrace(); - } - } catch (IOException e) { - LogUtils.warn("Failed to create imported file.", e); - } - }); - } - - private CommandAPICommand getCommand(String namespace) { - return new CommandAPICommand("get") - .withArguments(new StringArgument("id") - .replaceSuggestions(ArgumentSuggestions.strings( - info -> completionMap.get(namespace) - ))) - .withOptionalArguments(new IntegerArgument("amount", 1)) - .executesPlayer((player, args) -> { - String id = (String) args.get("id"); - assert id != null; - int amount = (int) args.getOrDefault("amount", 1); - ItemStack item = CustomFishingPlugin.get().getItemManager().build(player, namespace, id, new Condition(player).getArgs()); - if (item != null) { - int actual = ItemUtils.giveItem(player, item, amount); - AdventureHelper.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Get_Item.replace("{item}", id).replace("{amount}", String.valueOf(actual))); - } else { - AdventureHelper.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Item_Not_Exists); - } - }); - } - - @SuppressWarnings("unchecked") - private CommandAPICommand giveCommand(String namespace) { - return new CommandAPICommand("give") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .withArguments(new StringArgument("id") - .replaceSuggestions(ArgumentSuggestions.strings( - info -> completionMap.get(namespace) - ))) - .withOptionalArguments(new IntegerArgument("amount", 1)) - .withOptionalArguments(new StringArgument("-s")) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - String id = (String) args.get("id"); - boolean silence = args.getOrDefault("-s", "").equals("-s"); - int amount = (int) args.getOrDefault("amount", 1); - BuildableItem buildableItem = CustomFishingPlugin.get().getItemManager().getBuildableItem(namespace, id); - if (buildableItem != null) { - assert players != null; - for (Player player : players) { - ItemStack item = CustomFishingPlugin.get().getItemManager().build(player, namespace, id, new Condition(player).getArgs()); - int actual = ItemUtils.giveItem(player, item, amount); - if (!silence) AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Give_Item.replace("{item}", id).replace("{amount}", String.valueOf(actual)).replace("{player}", player.getName())); - } - } else { - AdventureHelper.getInstance().sendMessageWithPrefix(sender, CFLocale.MSG_Item_Not_Exists); - } - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/command/sub/StatisticsCommand.java b/plugin/src/main/java/net/momirealms/customfishing/command/sub/StatisticsCommand.java deleted file mode 100644 index f8d78dfb..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/command/sub/StatisticsCommand.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.command.sub; - -import dev.jorel.commandapi.CommandAPICommand; -import dev.jorel.commandapi.arguments.*; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; -import net.momirealms.customfishing.api.util.LogUtils; -import org.bukkit.entity.Player; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; - -public class StatisticsCommand { - - public static StatisticsCommand INSTANCE = new StatisticsCommand(); - - private Collection loots = new HashSet<>(); - - public CommandAPICommand getStatisticsCommand() { - loots = CustomFishingPlugin.get().getLootManager().getAllLoots().stream().filter(it -> !it.disableStats()).map(Loot::getID).toList(); - return new CommandAPICommand("statistics") - .withSubcommands( - getSetCommand(), - getResetCommand(), - getQueryCommand(), - getAddCommand() - ); - } - - @SuppressWarnings("unchecked") - private CommandAPICommand getSetCommand() { - return new CommandAPICommand("set") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .withArguments(new StringArgument("id").replaceSuggestions(ArgumentSuggestions.strings(loots))) - .withArguments(new IntegerArgument("amount", 0)) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - String id = (String) args.get("id"); - int amount = (int) args.getOrDefault("amount", 0); - assert players != null; - Loot loot = CustomFishingPlugin.get().getLootManager().getLoot(id); - for (Player player : players) { - Statistics statistics = CustomFishingPlugin.get().getStatisticsManager().getStatistics(player.getUniqueId()); - if (statistics != null) { - if (loot != null) - statistics.setData(id, amount); - else - throw new RuntimeException("Loot " + id + " doesn't exist."); - } else { - LogUtils.warn("Player " + player.getName() + "'s statistics data has not been loaded."); - } - } - }); - } - - @SuppressWarnings("unchecked") - private CommandAPICommand getResetCommand() { - return new CommandAPICommand("reset") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - assert players != null; - for (Player player : players) { - Statistics statistics = CustomFishingPlugin.get().getStatisticsManager().getStatistics(player.getUniqueId()); - if (statistics != null) { - statistics.reset(); - } else { - LogUtils.warn("Player " + player.getName() + "'s statistics data has not been loaded."); - } - } - }); - } - - @SuppressWarnings("unchecked") - private CommandAPICommand getAddCommand() { - return new CommandAPICommand("add") - .withArguments(new EntitySelectorArgument.ManyPlayers("player")) - .withArguments(new StringArgument("id").replaceSuggestions(ArgumentSuggestions.strings(loots))) - .withArguments(new IntegerArgument("amount", 0)) - .executes((sender, args) -> { - Collection players = (Collection) args.get("player"); - String id = (String) args.get("id"); - int amount = (int) args.getOrDefault("amount", 0); - assert players != null; - Loot loot = CustomFishingPlugin.get().getLootManager().getLoot(id); - for (Player player : players) { - Statistics statistics = CustomFishingPlugin.get().getStatisticsManager().getStatistics(player.getUniqueId()); - if (statistics != null) { - if (loot != null) - statistics.addLootAmount(loot, new Condition(player), amount); - else - throw new RuntimeException("Loot " + id + " doesn't exist."); - } else { - LogUtils.warn("Player " + player.getName() + "'s statistics data has not been loaded."); - } - } - }); - } - - private CommandAPICommand getQueryCommand() { - return new CommandAPICommand("query") - .withArguments(new PlayerArgument("player")) - .executes((sender, args) -> { - Player player = (Player) args.get("player"); - assert player != null; - Statistics statistics = CustomFishingPlugin.get().getStatisticsManager().getStatistics(player.getUniqueId()); - if (statistics != null) { - var adventure = AdventureHelper.getInstance(); - for (Map.Entry entry : statistics.getStatisticMap().entrySet()) { - adventure.sendMessage(sender, entry.getKey() + ": " + entry.getValue()); - } - } else { - throw new RuntimeException("Player " + player.getName() + "'s statistics data has not been loaded."); - } - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java deleted file mode 100644 index 1879c881..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.compatibility; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.integration.EnchantmentInterface; -import net.momirealms.customfishing.api.integration.LevelInterface; -import net.momirealms.customfishing.api.integration.SeasonInterface; -import net.momirealms.customfishing.api.manager.IntegrationManager; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.block.ItemsAdderBlockImpl; -import net.momirealms.customfishing.compatibility.enchant.AdvancedEnchantmentsImpl; -import net.momirealms.customfishing.compatibility.enchant.VanillaEnchantmentsImpl; -import net.momirealms.customfishing.compatibility.entity.ItemsAdderEntityImpl; -import net.momirealms.customfishing.compatibility.entity.MythicEntityImpl; -import net.momirealms.customfishing.compatibility.item.*; -import net.momirealms.customfishing.compatibility.level.*; -import net.momirealms.customfishing.compatibility.quest.BattlePassHook; -import net.momirealms.customfishing.compatibility.quest.BetonQuestHook; -import net.momirealms.customfishing.compatibility.quest.ClueScrollsHook; -import net.momirealms.customfishing.compatibility.season.CustomCropsSeasonImpl; -import net.momirealms.customfishing.compatibility.season.RealisticSeasonsImpl; -import org.bukkit.Bukkit; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -public class IntegrationManagerImpl implements IntegrationManager { - - private final CustomFishingPlugin plugin; - private final HashMap levelPluginMap; - private final HashMap enchantmentPluginMap; - private SeasonInterface seasonInterface; - - public IntegrationManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.levelPluginMap = new HashMap<>(); - this.enchantmentPluginMap = new HashMap<>(); - this.load(); - } - - public void disable() { - this.enchantmentPluginMap.clear(); - this.levelPluginMap.clear(); - } - - public void load() { - if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { - plugin.getItemManager().registerItemLibrary(new ItemsAdderItemImpl()); - plugin.getBlockManager().registerBlockLibrary(new ItemsAdderBlockImpl()); - plugin.getEntityManager().registerEntityLibrary(new ItemsAdderEntityImpl()); - hookMessage("ItemsAdder"); - } - if (Bukkit.getPluginManager().getPlugin("MMOItems") != null) { - plugin.getItemManager().registerItemLibrary(new MMOItemsItemImpl()); - hookMessage("MMOItems"); - } - if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) { - plugin.getItemManager().registerItemLibrary(new OraxenItemImpl()); - hookMessage("Oraxen"); - } - if (plugin.isHookedPluginEnabled("Zaphkiel")) { - plugin.getItemManager().registerItemLibrary(new ZaphkielItemImpl()); - hookMessage("Zaphkiel"); - } - if (plugin.isHookedPluginEnabled("NeigeItems")) { - plugin.getItemManager().registerItemLibrary(new NeigeItemsItemImpl()); - hookMessage("NeigeItems"); - } - if (Bukkit.getPluginManager().getPlugin("MythicMobs") != null) { - plugin.getItemManager().registerItemLibrary(new MythicMobsItemImpl()); - plugin.getEntityManager().registerEntityLibrary(new MythicEntityImpl()); - hookMessage("MythicMobs"); - } - if (plugin.isHookedPluginEnabled("EcoJobs")) { - registerLevelPlugin("EcoJobs", new EcoJobsImpl()); - hookMessage("EcoJobs"); - } - if (plugin.isHookedPluginEnabled("EcoSkills")) { - registerLevelPlugin("EcoSkills", new EcoSkillsImpl()); - hookMessage("EcoSkills"); - } - if (Bukkit.getPluginManager().getPlugin("Jobs") != null) { - registerLevelPlugin("JobsReborn", new JobsRebornImpl()); - hookMessage("JobsReborn"); - } - if (plugin.isHookedPluginEnabled("MMOCore")) { - registerLevelPlugin("MMOCore", new MMOCoreImpl()); - hookMessage("MMOCore"); - } - if (plugin.isHookedPluginEnabled("mcMMO")) { - try { - plugin.getItemManager().registerItemLibrary(new McMMOTreasureImpl()); - } catch (ClassNotFoundException | NoSuchMethodException e) { - LogUtils.warn("Failed to initialize mcMMO Treasure"); - } - registerLevelPlugin("mcMMO", new McMMOImpl()); - hookMessage("mcMMO"); - } - if (plugin.isHookedPluginEnabled("AureliumSkills")) { - registerLevelPlugin("AureliumSkills", new AureliumSkillsImpl()); - hookMessage("AureliumSkills"); - } - if (plugin.isHookedPluginEnabled("AuraSkills")) { - registerLevelPlugin("AuraSkills", new AuraSkillsImpl()); - hookMessage("AuraSkills"); - } - if (plugin.isHookedPluginEnabled("EcoEnchants")) { - this.enchantmentPluginMap.put("EcoEnchants", new VanillaEnchantmentsImpl()); - hookMessage("EcoEnchants"); - } else { - this.enchantmentPluginMap.put("vanilla", new VanillaEnchantmentsImpl()); - } - if (plugin.isHookedPluginEnabled("AdvancedEnchantments")) { - this.enchantmentPluginMap.put("AdvancedEnchantments", new AdvancedEnchantmentsImpl()); - hookMessage("AdvancedEnchantments"); - } - if (plugin.isHookedPluginEnabled("RealisticSeasons")) { - this.seasonInterface = new RealisticSeasonsImpl(); - } else if (plugin.isHookedPluginEnabled("CustomCrops")) { - this.seasonInterface = new CustomCropsSeasonImpl(); - } - if (plugin.isHookedPluginEnabled("Vault")) { - VaultHook.initialize(); - } - if (plugin.isHookedPluginEnabled("BattlePass")){ - BattlePassHook battlePassHook = new BattlePassHook(); - battlePassHook.register(); - hookMessage("BattlePass"); - } - if (plugin.isHookedPluginEnabled("ClueScrolls")) { - ClueScrollsHook clueScrollsHook = new ClueScrollsHook(); - clueScrollsHook.register(); - hookMessage("ClueScrolls"); - } - if (plugin.isHookedPluginEnabled("BetonQuest")) { - BetonQuestHook.register(); - hookMessage("BetonQuest"); - } -// if (plugin.isHookedPluginEnabled("NotQuests")) { -// NotQuestHook notQuestHook = new NotQuestHook(); -// notQuestHook.register(); -// hookMessage("NotQuests"); -// } - } - - /** - * Registers a level plugin with the specified name. - * - * @param plugin The name of the level plugin. - * @param level The implementation of the LevelInterface. - * @return true if the registration was successful, false if the plugin name is already registered. - */ - @Override - public boolean registerLevelPlugin(String plugin, LevelInterface level) { - if (levelPluginMap.containsKey(plugin)) return false; - levelPluginMap.put(plugin, level); - return true; - } - - /** - * Unregisters a level plugin with the specified name. - * - * @param plugin The name of the level plugin to unregister. - * @return true if the unregistration was successful, false if the plugin name is not found. - */ - @Override - public boolean unregisterLevelPlugin(String plugin) { - return levelPluginMap.remove(plugin) != null; - } - - /** - * Registers an enchantment provided by a plugin. - * - * @param plugin The name of the plugin providing the enchantment. - * @param enchantment The enchantment to register. - * @return true if the registration was successful, false if the enchantment name is already in use. - */ - @Override - public boolean registerEnchantment(String plugin, EnchantmentInterface enchantment) { - if (enchantmentPluginMap.containsKey(plugin)) return false; - enchantmentPluginMap.put(plugin, enchantment); - return true; - } - - /** - * Unregisters an enchantment provided by a plugin. - * - * @param plugin The name of the plugin providing the enchantment. - * @return true if the enchantment was successfully unregistered, false if the enchantment was not found. - */ - @Override - public boolean unregisterEnchantment(String plugin) { - return enchantmentPluginMap.remove(plugin) != null; - } - - private void hookMessage(String plugin) { - LogUtils.info( plugin + " hooked!"); - } - - /** - * Get the LevelInterface provided by a plugin. - * - * @param plugin The name of the plugin providing the LevelInterface. - * @return The LevelInterface provided by the specified plugin, or null if the plugin is not registered. - */ - @Override - @Nullable - public LevelInterface getLevelPlugin(String plugin) { - return levelPluginMap.get(plugin); - } - - /** - * Get an enchantment plugin by its plugin name. - * - * @param plugin The name of the enchantment plugin. - * @return The enchantment plugin interface, or null if not found. - */ - @Override - @Nullable - public EnchantmentInterface getEnchantmentPlugin(String plugin) { - return enchantmentPluginMap.get(plugin); - } - - /** - * Get a list of enchantment keys with level applied to the given ItemStack. - * - * @param itemStack The ItemStack to check for enchantments. - * @return A list of enchantment names applied to the ItemStack. - */ - @Override - public List getEnchantments(ItemStack itemStack) { - ArrayList list = new ArrayList<>(); - for (EnchantmentInterface enchantmentInterface : enchantmentPluginMap.values()) { - list.addAll(enchantmentInterface.getEnchants(itemStack)); - } - return list; - } - - /** - * Get the current season interface, if available. - * - * @return The current season interface, or null if not available. - */ - @Nullable - public SeasonInterface getSeasonInterface() { - return seasonInterface; - } - - /** - * Set the current season interface. - * - * @param season The season interface to set. - */ - @Override - public void setSeasonInterface(SeasonInterface season) { - this.seasonInterface = season; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/CustomFishingItemImpl.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/CustomFishingItemImpl.java deleted file mode 100644 index a754e006..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/item/CustomFishingItemImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.compatibility.item; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -public class CustomFishingItemImpl implements ItemLibrary { - - @Override - public String identification() { - return "CustomFishing"; - } - - @Override - public ItemStack buildItem(Player player, String id) { - String[] split = id.split(":", 2); - return CustomFishingPlugin.get().getItemManager().build(player, split[0], split[1]); - } - - @Override - public String getItemID(ItemStack itemStack) { - return CustomFishingPlugin.get().getItemManager().getCustomFishingItemID(itemStack); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/PlaceholderManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/PlaceholderManagerImpl.java deleted file mode 100644 index 79f5ef63..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/PlaceholderManagerImpl.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.compatibility.papi; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.PlaceholderManager; -import net.momirealms.customfishing.util.ConfigUtils; -import net.objecthunter.exp4j.ExpressionBuilder; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class PlaceholderManagerImpl implements PlaceholderManager { - - private static PlaceholderManagerImpl instance; - private final CustomFishingPlugin plugin; - private final boolean hasPapi; - private final Pattern pattern; - private final HashMap customPlaceholderMap; - private CompetitionPapi competitionPapi; - private StatisticsPapi statisticsPapi; - private CFPapi cfPapi; - - public PlaceholderManagerImpl(CustomFishingPlugin plugin) { - instance = this; - this.plugin = plugin; - this.hasPapi = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); - this.pattern = Pattern.compile("\\{[^{}]+}"); - this.customPlaceholderMap = new HashMap<>(); - if (this.hasPapi) { - competitionPapi = new CompetitionPapi(plugin); - statisticsPapi = new StatisticsPapi(plugin); - cfPapi = new CFPapi(plugin); - } - } - - public void load() { - if (competitionPapi != null) competitionPapi.load(); - if (statisticsPapi != null) statisticsPapi.load(); - if (cfPapi != null) cfPapi.load(); - loadCustomPlaceholders(); - } - - public void unload() { - if (competitionPapi != null) competitionPapi.unload(); - if (statisticsPapi != null) statisticsPapi.unload(); - if (cfPapi != null) cfPapi.unload(); - } - - public void disable() { - this.customPlaceholderMap.clear(); - } - - public void loadCustomPlaceholders() { - YamlConfiguration config = plugin.getConfig("config.yml"); - ConfigurationSection section = config.getConfigurationSection("other-settings.placeholder-register"); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - registerCustomPlaceholder(entry.getKey(), (String) entry.getValue()); - } - } - } - - @Override - public boolean registerCustomPlaceholder(String placeholder, String original) { - if (this.customPlaceholderMap.containsKey(placeholder)) { - return false; - } - this.customPlaceholderMap.put(placeholder, original); - return true; - } - - /** - * Set placeholders in a text string for a player. - * - * @param player The player for whom the placeholders should be set. - * @param text The text string containing placeholders. - * @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text. - */ - @Override - public String setPlaceholders(Player player, String text) { - return hasPapi ? ParseUtils.setPlaceholders(player, text) : text; - } - - /** - * Set placeholders in a text string for an offline player. - * - * @param player The offline player for whom the placeholders should be set. - * @param text The text string containing placeholders. - * @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text. - */ - @Override - public String setPlaceholders(OfflinePlayer player, String text) { - return hasPapi ? ParseUtils.setPlaceholders(player, text) : text; - } - - /** - * Detect and extract placeholders from a text string. - * - * @param text The text string to search for placeholders. - * @return A list of detected placeholders in the text. - */ - @Override - public List detectPlaceholders(String text) { - List placeholders = new ArrayList<>(); - Matcher matcher = pattern.matcher(text); - while (matcher.find()) placeholders.add(matcher.group()); - return placeholders; - } - - /** - * Get the value associated with a single placeholder. - * - * @param player The player for whom the placeholders are being resolved (nullable). - * @param placeholder The placeholder to look up. - * @param placeholders A map of placeholders to their corresponding values. - * @return The value associated with the placeholder, or the original placeholder if not found. - */ - @Override - public String getSingleValue(@Nullable Player player, String placeholder, Map placeholders) { - String result = null; - if (placeholders != null) - result = placeholders.get(placeholder); - if (result != null) - return result; - String custom = customPlaceholderMap.get(placeholder); - if (custom == null) - return placeholder; - return setPlaceholders(player, custom); - } - - /** - * Parse a text string by replacing placeholders with their corresponding values. - * - * @param player The offline player for whom the placeholders are being resolved (nullable). - * @param text The text string containing placeholders. - * @param placeholders A map of placeholders to their corresponding values. - * @return The text string with placeholders replaced by their values. - */ - @Override - public String parse(@Nullable OfflinePlayer player, String text, Map placeholders) { - var list = detectPlaceholders(text); - for (String papi : list) { - String replacer = null; - if (placeholders != null) { - replacer = placeholders.get(papi); - } - if (replacer == null) { - String custom = customPlaceholderMap.get(papi); - if (custom != null) { - replacer = setPlaceholders(player, parse(player, custom, placeholders)); - } - } - if (replacer != null) { - text = text.replace(papi, replacer); - } - } - return text; - } - - /** - * Parse a list of text strings by replacing placeholders with their corresponding values. - * - * @param player The player for whom the placeholders are being resolved (can be null for offline players). - * @param list The list of text strings containing placeholders. - * @param replacements A map of custom replacements for placeholders. - * @return The list of text strings with placeholders replaced by their values. - */ - @Override - public List parse(@Nullable OfflinePlayer player, List list, Map replacements) { - return list.stream() - .map(s -> parse(player, s, replacements)) - .collect(Collectors.toList()); - } - - - public static PlaceholderManagerImpl getInstance() { - return instance; - } - - public boolean hasPapi() { - return hasPapi; - } - - @Override - public double getExpressionValue(Player player, String formula, Map vars) { - return ConfigUtils.getExpressionValue(player, formula, vars); - } - - @Override - public double getExpressionValue(String formula) { - return new ExpressionBuilder(formula).build().evaluate(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/StatisticsPapi.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/StatisticsPapi.java deleted file mode 100644 index f313ad0e..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/papi/StatisticsPapi.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.compatibility.papi; - -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public class StatisticsPapi extends PlaceholderExpansion { - - private final CustomFishingPlugin plugin; - - public StatisticsPapi(CustomFishingPlugin plugin) { - this.plugin = plugin; - } - - public void load() { - super.register(); - } - - public void unload() { - super.unregister(); - } - - @Override - public @NotNull String getIdentifier() { - return "fishingstats"; - } - - @Override - public @NotNull String getAuthor() { - return "XiaoMoMi"; - } - - @Override - public @NotNull String getVersion() { - return "2.0"; - } - - @Override - public boolean persist() { - return true; - } - - @Override - public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { - OnlineUser onlineUser = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); - if (onlineUser == null) return "Data not loaded"; - Statistics statistics = onlineUser.getStatistics(); - String[] split = params.split("_", 2); - switch (split[0]) { - case "total" -> { - return String.valueOf(statistics.getTotalCatchAmount()); - } - case "hascaught" -> { - if (split.length == 1) return "Invalid format"; - return String.valueOf(statistics.getLootAmount(split[1]) != 0); - } - case "amount" -> { - if (split.length == 1) return "Invalid format"; - return String.valueOf(statistics.getLootAmount(split[1])); - } - case "size-record" -> { - return String.format("%.2f", statistics.getSizeRecord(split[1])); - } - case "category" -> { - if (split.length == 1) return "Invalid format"; - String[] categorySplit = split[1].split("_", 2); - if (categorySplit.length == 1) return "Invalid format"; - List category = plugin.getStatisticsManager().getCategory(categorySplit[1]); - if (category == null) return "Category Not Exists"; - if (categorySplit[0].equals("total")) { - int total = 0; - for (String loot : category) { - total += statistics.getLootAmount(loot); - } - return String.valueOf(total); - } else if (categorySplit[0].equals("progress")) { - int size = category.size(); - int unlocked = 0; - for (String loot : category) { - if (statistics.getLootAmount(loot) != 0) unlocked++; - } - double percent = ((double) unlocked * 100) / size; - String progress = String.format("%.1f", percent); - return progress.equals("100.0") ? "100" : progress; - } - } - } - - return "null"; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/NotQuestHook.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/NotQuestHook.java deleted file mode 100644 index aec0f0c1..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/NotQuestHook.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.compatibility.quest; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.event.FishingResultEvent; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.checkerframework.checker.nullness.qual.Nullable; -import rocks.gravili.notquests.paper.NotQuests; -import rocks.gravili.notquests.paper.structs.ActiveObjective; -import rocks.gravili.notquests.paper.structs.ActiveQuest; -import rocks.gravili.notquests.paper.structs.QuestPlayer; -import rocks.gravili.notquests.paper.structs.objectives.Objective; - -import java.util.Map; - -public class NotQuestHook implements Listener { - - private final NotQuests notQuestsInstance; - - public NotQuestHook() { - this.notQuestsInstance = NotQuests.getInstance(); - } - - @EventHandler - public void onFish(FishingResultEvent event) { - if (event.isCancelled() || event.getResult() == FishingResultEvent.Result.FAILURE) - return; - Loot loot = event.getLoot(); - Player player = event.getPlayer(); - final QuestPlayer questPlayer = notQuestsInstance.getQuestPlayerManager().getActiveQuestPlayer(player.getUniqueId()); - if (questPlayer != null) { - if (questPlayer.getActiveQuests().size() > 0) { - for (final ActiveQuest activeQuest : questPlayer.getActiveQuests()) { - for (final ActiveObjective activeObjective : activeQuest.getActiveObjectives()) { - if (activeObjective.getObjective() instanceof GroupObjective groupObjective) { - if (activeObjective.isUnlocked()) { - final String[] groups = loot.getLootGroup(); - if (groups != null) - for (String group : groups) { - if (group.equals(groupObjective.getGroupToFish())) { - activeObjective.addProgress(event.getAmount()); - } - } - } - } else if (activeObjective.getObjective() instanceof LootObjective lootObjective) { - if (activeObjective.isUnlocked()) { - if (lootObjective.getLootID().equals(loot.getID()) || lootObjective.getLootID().equals("any")) { - activeObjective.addProgress(event.getAmount()); - } - } - } - } - activeQuest.removeCompletedObjectives(true); - } - questPlayer.removeCompletedQuests(); - } - } - } - - public void register() { - Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.get()); - notQuestsInstance.getObjectiveManager().registerObjective("CustomFishingGroup", GroupObjective.class); - notQuestsInstance.getObjectiveManager().registerObjective("CustomFishingGroup", GroupObjective.class); - } - - public static class GroupObjective extends Objective { - - private String group; - - public GroupObjective(NotQuests main) { - super(main); - } - - @Override - protected String getTaskDescriptionInternal(QuestPlayer questPlayer, @Nullable ActiveObjective activeObjective) { - return main.getLanguageManager() - .getString( - "chat.objectives.taskDescription.customfishingGroup.base", - questPlayer, - activeObjective, - Map.of("%CUSTOMFISHINGGROUP%", getGroupToFish())); - } - - @Override - public void save(FileConfiguration fileConfiguration, String initialPath) { - fileConfiguration.set(initialPath + ".specifics.group", getGroupToFish()); - } - - @Override - public void load(FileConfiguration fileConfiguration, String initialPath) { - group = fileConfiguration.getString(initialPath + ".specifics.group"); - } - - @Override - public void onObjectiveUnlock(ActiveObjective activeObjective, boolean b) { - } - - @Override - public void onObjectiveCompleteOrLock(ActiveObjective activeObjective, boolean b, boolean b1) { - } - - public String getGroupToFish() { - return group; - } - } - - public static class LootObjective extends Objective { - - private String loot; - - public LootObjective(NotQuests main) { - super(main); - } - - @Override - protected String getTaskDescriptionInternal(QuestPlayer questPlayer, @Nullable ActiveObjective activeObjective) { - String toReturn; - if (!getLootID().isBlank() && !getLootID().equals("any")) { - toReturn = - main.getLanguageManager() - .getString( - "chat.objectives.taskDescription.customfishingLoot.base", - questPlayer, - activeObjective, - Map.of("%CUSTOMFISHINGLOOT%", getLootID())); - } else { - toReturn = - main.getLanguageManager() - .getString( - "chat.objectives.taskDescription.customfishingLoot.any", - questPlayer, - activeObjective); - } - return toReturn; - } - - @Override - public void save(FileConfiguration fileConfiguration, String initialPath) { - fileConfiguration.set(initialPath + ".specifics.id", getLootID()); - } - - @Override - public void load(FileConfiguration fileConfiguration, String initialPath) { - loot = fileConfiguration.getString(initialPath + ".specifics.id"); - } - - @Override - public void onObjectiveUnlock(ActiveObjective activeObjective, boolean b) { - } - - @Override - public void onObjectiveCompleteOrLock(ActiveObjective activeObjective, boolean b, boolean b1) { - } - - public String getLootID() { - return loot; - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/SectionPage.java b/plugin/src/main/java/net/momirealms/customfishing/gui/SectionPage.java deleted file mode 100644 index 9ea86ecf..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/SectionPage.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui; - -import org.bukkit.configuration.ConfigurationSection; - -public interface SectionPage extends YamlPage { - - ConfigurationSection getSection(); -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToFolderItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToFolderItem.java deleted file mode 100644 index 5b348c4b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToFolderItem.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.Icon; -import net.momirealms.customfishing.gui.page.file.FileSelector; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -import java.io.File; -import java.util.List; - -public class BackToFolderItem extends AbstractItem implements Icon { - - private final File file; - - public BackToFolderItem(File file) { - this.file = file; - } - - @Override - public ItemProvider getItemProvider() { - if (file != null && (file.getPath().startsWith("plugins\\CustomFishing\\contents") || file.getPath().startsWith("plugins/CustomFishing/contents"))) { - return new ItemBuilder(Material.ORANGE_STAINED_GLASS_PANE) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_BACK_TO_PARENT_FOLDER - ))) - .setLore(List.of(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "<#FFA500>-> " + file.getName() - )))); - } else { - return new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (file != null && (file.getPath().startsWith("plugins\\CustomFishing\\contents") || file.getPath().startsWith("plugins/CustomFishing/contents"))) - new FileSelector(player, file); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToPageItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToPageItem.java deleted file mode 100644 index f5fb2cca..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/BackToPageItem.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.ParentPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class BackToPageItem extends AbstractItem { - - private final ParentPage parentPage; - - public BackToPageItem(ParentPage parentPage) { - this.parentPage = parentPage; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ORANGE_STAINED_GLASS_PANE) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_BACK_TO_PARENT_PAGE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - parentPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/NextPageItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/NextPageItem.java deleted file mode 100644 index 6da7372d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/NextPageItem.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.Icon; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.controlitem.PageItem; - -public class NextPageItem extends PageItem implements Icon { - - public NextPageItem() { - super(true); - } - - @Override - public ItemProvider getItemProvider(PagedGui gui) { - ItemBuilder builder = new ItemBuilder(Material.GREEN_STAINED_GLASS_PANE); - builder.setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEXT_PAGE - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - gui.hasNextPage() - ? CFLocale.GUI_GOTO_NEXT_PAGE - .replace("{0}", String.valueOf(gui.getCurrentPage() + 2)) - .replace("{1}", String.valueOf(gui.getPageAmount())) - : CFLocale.GUI_CANNOT_GOTO_NEXT_PAGE - ))); - return builder; - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/PreviousPageItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/PreviousPageItem.java deleted file mode 100644 index 8ea9fc89..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/PreviousPageItem.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.Icon; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.controlitem.PageItem; - -public class PreviousPageItem extends PageItem implements Icon { - - public PreviousPageItem() { - super(false); - } - - @Override - public ItemProvider getItemProvider(PagedGui gui) { - ItemBuilder builder = new ItemBuilder(Material.RED_STAINED_GLASS_PANE); - builder.setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_PREVIOUS_PAGE - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - gui.hasPreviousPage() - ? CFLocale.GUI_GOTO_PREVIOUS_PAGE - .replace("{0}", String.valueOf(gui.getCurrentPage())) - .replace("{1}", String.valueOf(gui.getPageAmount())) - : CFLocale.GUI_CANNOT_GOTO_PREVIOUS_PAGE - ))); - return builder; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollDownItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollDownItem.java deleted file mode 100644 index 0426248c..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollDownItem.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.Icon; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import xyz.xenondevs.invui.gui.ScrollGui; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.controlitem.ScrollItem; - -public class ScrollDownItem extends ScrollItem implements Icon { - - public ScrollDownItem() { - super(1); - } - - @Override - public ItemProvider getItemProvider(ScrollGui gui) { - ItemBuilder builder = new ItemBuilder(Material.GREEN_STAINED_GLASS_PANE); - builder.setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SCROLL_DOWN - ))); - if (!gui.canScroll(1)) - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CANNOT_SCROLL_DOWN - ))); - return builder; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollUpItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollUpItem.java deleted file mode 100644 index c040c611..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/ScrollUpItem.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.Icon; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import xyz.xenondevs.invui.gui.ScrollGui; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.controlitem.ScrollItem; - -public class ScrollUpItem extends ScrollItem implements Icon { - - public ScrollUpItem() { - super(-1); - } - - @Override - public ItemProvider getItemProvider(ScrollGui gui) { - ItemBuilder builder = new ItemBuilder(Material.RED_STAINED_GLASS_PANE); - builder.setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SCROLL_UP - ))); - if (!gui.canScroll(-1)) - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CANNOT_SCROLL_UP - ))); - return builder; - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/AmountItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/AmountItem.java deleted file mode 100644 index f47c40da..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/AmountItem.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.AmountEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class AmountItem extends AbstractItem { - - private final SectionPage itemPage; - - public AmountItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.IRON_NUGGET) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_AMOUNT - ))) - .setAmount(itemPage.getSection().getInt("amount", 1)); - if (itemPage.getSection().contains("amount")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getInt("amount") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new AmountEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("amount", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/CMDItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/CMDItem.java deleted file mode 100644 index ae3cec8b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/CMDItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.CustomModelDataEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class CMDItem extends AbstractItem { - - private final SectionPage itemPage; - - public CMDItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.GLOW_INK_SAC) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_MODEL_DATA - ))); - if (itemPage.getSection().contains("custom-model-data")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getInt("custom-model-data") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new CustomModelDataEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("custom-model-data", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DisplayNameItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DisplayNameItem.java deleted file mode 100644 index 3fe0e95b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DisplayNameItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.DisplayNameEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class DisplayNameItem extends AbstractItem { - - private final SectionPage itemPage; - - public DisplayNameItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.NAME_TAG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_DISPLAY_NAME - ))); - if (itemPage.getSection().contains("display.name")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getString("display.name") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new DisplayNameEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("display.name", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DurabilityItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DurabilityItem.java deleted file mode 100644 index 7341393d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/DurabilityItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.DurabilityEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class DurabilityItem extends AbstractItem { - - private final SectionPage itemPage; - - public DurabilityItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.DIAMOND_CHESTPLATE) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_DURABILITY - ))); - if (itemPage.getSection().contains("max-durability")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getInt("max-durability") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new DurabilityEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("max-durability", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/EnchantmentItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/EnchantmentItem.java deleted file mode 100644 index fe73a540..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/EnchantmentItem.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.EnchantmentEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -import java.util.Map; - -public class EnchantmentItem extends AbstractItem { - - private final SectionPage itemPage; - - public EnchantmentItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.IRON_HOE) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_ENCHANTMENT - ))) - .addEnchantment(Enchantment.ARROW_FIRE,1,true) - .addItemFlags(ItemFlag.HIDE_ENCHANTS); - if (itemPage.getSection().contains("enchantments")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - for (Map.Entry entry : itemPage.getSection().getConfigurationSection("enchantments").getValues(false).entrySet()) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + entry.getKey() + ":" + entry.getValue() - ))); - } - itemBuilder.addLoreLines("").addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new EnchantmentEditor(player, itemPage, false); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("enchantments", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/Head64Item.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/Head64Item.java deleted file mode 100644 index ca213160..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/Head64Item.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -import java.util.ArrayList; - -public class Head64Item extends AbstractItem { - - private final SectionPage itemPage; - - public Head64Item(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.PLAYER_HEAD) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_HEAD64 - ))); - if (itemPage.getSection().contains("head64")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - String head64 = itemPage.getSection().getString("head64", ""); - ArrayList list = new ArrayList<>(); - for (int i = 0; i < head64.length(); i += 16) { - if (i + 16 > head64.length()) { - list.add(head64.substring(i)); - } else { - list.add(head64.substring(i, i + 16)); - } - } - for (String line : list) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - ""+ line - ))); - } - itemBuilder.addLoreLines("").addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - player.closeInventory(); - AdventureHelper.getInstance().sendMessageWithPrefix(player, "Input the head64 value in chat"); - ((CustomFishingPluginImpl) CustomFishingPlugin.get()).getChatCatcherManager().catchMessage(player, "head64", itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("head64", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/ItemFlagItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/ItemFlagItem.java deleted file mode 100644 index 90f70925..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/ItemFlagItem.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.ItemFlagEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class ItemFlagItem extends AbstractItem { - - private final SectionPage itemPage; - - public ItemFlagItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.CYAN_BANNER) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_FLAG - ))); - if (itemPage.getSection().contains("item-flags")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - for (String lore : itemPage.getSection().getStringList("item-flags")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + lore - ))); - } - itemBuilder.addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new ItemFlagEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("item-flags", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/LoreItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/LoreItem.java deleted file mode 100644 index f5525a7d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/LoreItem.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.LoreEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class LoreItem extends AbstractItem { - - private final SectionPage itemPage; - - public LoreItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.BIRCH_SIGN) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_LORE - ))); - if (itemPage.getSection().contains("display.lore")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - for (String lore : itemPage.getSection().getStringList("display.lore")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + lore - ))); - } - itemBuilder.addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new LoreEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("display.lore", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/MaterialItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/MaterialItem.java deleted file mode 100644 index ac638535..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/MaterialItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.MaterialEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class MaterialItem extends AbstractItem { - - private final SectionPage itemPage; - - public MaterialItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.COD) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_MATERIAL - ))); - if (itemPage.getSection().contains("material")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getString("material") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new MaterialEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("material", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/NBTItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/NBTItem.java deleted file mode 100644 index df3b1ece..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/NBTItem.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.NBTEditor; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class NBTItem extends AbstractItem { - - private final SectionPage itemPage; - - public NBTItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.COMMAND_BLOCK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_NBT - ))); - var section = itemPage.getSection().getConfigurationSection("nbt"); - if (section != null) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - for (String line : ConfigUtils.getReadableSection(section.getValues(false))) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - line - ))); - } - itemBuilder.addLoreLines("").addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new NBTEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("nbt", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PreventGrabItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PreventGrabItem.java deleted file mode 100644 index 436e2031..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PreventGrabItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class PreventGrabItem extends AbstractItem { - - private final SectionPage itemPage; - - public PreventGrabItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.DRAGON_EGG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PREVENT_GRAB - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("prevent-grabbing", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("prevent-grabbing", !itemPage.getSection().getBoolean("prevent-grabbing", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PriceItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PriceItem.java deleted file mode 100644 index bfcd5080..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/PriceItem.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.PriceEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class PriceItem extends AbstractItem { - - private final SectionPage itemPage; - - public PriceItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.GOLD_INGOT) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PRICE - ))); - if (itemPage.getSection().contains("price")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PRICE_BASE + itemPage.getSection().getDouble("price.base") - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PRICE_BONUS + itemPage.getSection().getDouble("price.bonus") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new PriceEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("price", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/RandomDurabilityItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/RandomDurabilityItem.java deleted file mode 100644 index e94841c6..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/RandomDurabilityItem.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class RandomDurabilityItem extends AbstractItem { - - private final SectionPage itemPage; - - public RandomDurabilityItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.LEATHER_BOOTS) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_RANDOM_DURABILITY - ))) - .setDamage(15); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("random-durability", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("random-durability", !itemPage.getSection().getBoolean("random-durability", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/SizeItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/SizeItem.java deleted file mode 100644 index 36a461a2..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/SizeItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.SizeEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class SizeItem extends AbstractItem { - - private final SectionPage itemPage; - - public SizeItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.PUFFERFISH) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_SIZE - ))); - if (itemPage.getSection().contains("size")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getString("size") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new SizeEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("size", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StackableItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StackableItem.java deleted file mode 100644 index b5f3c504..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StackableItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class StackableItem extends AbstractItem { - - private final SectionPage itemPage; - - public StackableItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.CHEST_MINECART) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_STACKABLE - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("stackable", true) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("stackable", !itemPage.getSection().getBoolean("stackable", true)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StoredEnchantmentItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StoredEnchantmentItem.java deleted file mode 100644 index c55c5900..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/StoredEnchantmentItem.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.EnchantmentEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -import java.util.Map; - -public class StoredEnchantmentItem extends AbstractItem { - - private final SectionPage itemPage; - - public StoredEnchantmentItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.ENCHANTED_BOOK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_STORED_ENCHANTMENT - ))) - .addEnchantment(Enchantment.ARROW_FIRE,1,true) - .addItemFlags(ItemFlag.HIDE_ENCHANTS); - if (itemPage.getSection().contains("stored-enchantments")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE - ))); - for (Map.Entry entry : itemPage.getSection().getConfigurationSection("stored-enchantments").getValues(false).entrySet()) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + entry.getKey() + ":" + entry.getValue() - ))); - } - itemBuilder.addLoreLines("").addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new EnchantmentEditor(player, itemPage, true); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("stored-enchantments", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/TagItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/TagItem.java deleted file mode 100644 index 694870d0..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/TagItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class TagItem extends AbstractItem { - - private final SectionPage itemPage; - - public TagItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.TOTEM_OF_UNDYING) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_TAG - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("tag", true) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("tag", !itemPage.getSection().getBoolean("tag", true)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/UnbreakableItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/UnbreakableItem.java deleted file mode 100644 index 30e3ee31..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/item/UnbreakableItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class UnbreakableItem extends AbstractItem { - - private final SectionPage itemPage; - - public UnbreakableItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.BEDROCK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_UNBREAKABLE - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("unbreakable", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("unbreakable", !itemPage.getSection().getBoolean("unbreakable", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableGameItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableGameItem.java deleted file mode 100644 index d314f5da..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableGameItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class DisableGameItem extends AbstractItem { - - private final SectionPage itemPage; - - public DisableGameItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.LEAD) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_DISABLE_GAME - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("disable-game", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("disable-game", !itemPage.getSection().getBoolean("disable-game", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableStatsItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableStatsItem.java deleted file mode 100644 index 5527128b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/DisableStatsItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class DisableStatsItem extends AbstractItem { - - private final SectionPage itemPage; - - public DisableStatsItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.WRITTEN_BOOK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_DISABLE_STATS - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("disable-stat", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("disable-game", !itemPage.getSection().getBoolean("disable-stat", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/InstantGameItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/InstantGameItem.java deleted file mode 100644 index 5109dd1e..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/InstantGameItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class InstantGameItem extends AbstractItem { - - private final SectionPage itemPage; - - public InstantGameItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.FISHING_ROD) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_INSTANT_GAME - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("instant-game", false) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("instant-game", !itemPage.getSection().getBoolean("instant-game", false)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/NickItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/NickItem.java deleted file mode 100644 index f208f219..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/NickItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.NickEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class NickItem extends AbstractItem { - - private final SectionPage itemPage; - - public NickItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.WRITABLE_BOOK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_NICK - ))); - if (itemPage.getSection().contains("nick")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getString("nick") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new NickEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("nick", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ScoreItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ScoreItem.java deleted file mode 100644 index c953d39c..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ScoreItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.page.property.ScoreEditor; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class ScoreItem extends AbstractItem { - - private final SectionPage itemPage; - - public ScoreItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.NETHER_STAR) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_SCORE - ))); - if (itemPage.getSection().contains("score")) { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getDouble("score") - ))) - .addLoreLines(""); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_RESET - ))); - } else { - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))); - } - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - new ScoreEditor(player, itemPage); - } else if (clickType.isRightClick()) { - itemPage.getSection().set("score", null); - itemPage.save(); - itemPage.reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ShowInFinderItem.java b/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ShowInFinderItem.java deleted file mode 100644 index 9154e529..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/icon/property/loot/ShowInFinderItem.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.icon.property.loot; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; - -public class ShowInFinderItem extends AbstractItem { - - private final SectionPage itemPage; - - public ShowInFinderItem(SectionPage itemPage) { - this.itemPage = itemPage; - } - - @Override - public ItemProvider getItemProvider() { - ItemBuilder itemBuilder = new ItemBuilder(Material.COMPASS) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LOOT_SHOW_IN_FINDER - ))); - itemBuilder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CURRENT_VALUE + itemPage.getSection().getBoolean("show-in-fishfinder", true) - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_TO_TOGGLE - ))); - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - itemPage.getSection().set("show-in-fishfinder", !itemPage.getSection().getBoolean("show-in-fishfinder", true)); - itemPage.save(); - itemPage.reOpen(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java deleted file mode 100644 index 31953891..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/file/FileSelector.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.file; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.gui.icon.BackToFolderItem; -import net.momirealms.customfishing.gui.icon.ScrollDownItem; -import net.momirealms.customfishing.gui.icon.ScrollUpItem; -import net.momirealms.customfishing.gui.page.item.ItemSelector; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.ScrollGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.window.Window; - -import java.io.File; -import java.util.ArrayDeque; -import java.util.Deque; - -public class FileSelector { - - public FileSelector(Player player, File folder) { - File[] files = folder.listFiles(); - Deque items = new ArrayDeque<>(); - if (files != null) { - for (File file : files) { - if (file.isFile() && file.getName().endsWith(".yml")) { - items.addLast(new FileItem(file)); - } else if (file.isDirectory()) { - String path = file.getPath().replace("/", "\\"); - String[] split = path.split("\\\\"); - String type = split[3]; - switch (type) { - case "item", "rod", "bait", "util", "hook" -> items.addFirst(new FolderItem(file)); - } - } - } - } - - Gui gui = ScrollGui.items() - .setStructure( - "x x x x x x x x u", - "x x x x x x x x #", - "x x x x x x x x b", - "x x x x x x x x #", - "x x x x x x x x d" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('#', new BackGroundItem()) - .addIngredient('u', new ScrollUpItem()) - .addIngredient('d', new ScrollDownItem()) - .addIngredient('b', new BackToFolderItem(folder.getParentFile())) - .setContent(items.stream().toList()) - .build(); - - Window window = Window.single() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SELECT_FILE - ))) - .setGui(gui) - .build(); - -// gui.playAnimation(new SequentialAnimation(1, true), slotElement -> { -// if (slotElement instanceof SlotElement.ItemSlotElement itemSlotElement) { -// return !(itemSlotElement.getItem() instanceof Icon); -// } -// return true; -// }); - - window.open(); - } - - public static class FileItem extends AbstractItem { - - private final File file; - - public FileItem(File file) { - this.file = file; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.PAPER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "<#FDF5E6>" + file.getName() - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - String path = file.getPath().replace("/", "\\"); - String[] split = path.split("\\\\"); - String type = split[3]; - switch (type) { - case "item", "rod", "bait", "util", "hook" -> { - new ItemSelector(player, file, type); - } - } - } - } - - public static class FolderItem extends AbstractItem { - - private final File file; - - public FolderItem(File file) { - this.file = file; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.BOOK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "<#D2B48C>" + file.getName() - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - new FileSelector(player, file); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/AbstractSectionEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/AbstractSectionEditor.java deleted file mode 100644 index 3121eff8..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/AbstractSectionEditor.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.gui.icon.BackToPageItem; -import net.momirealms.customfishing.gui.icon.NextPageItem; -import net.momirealms.customfishing.gui.icon.PreviousPageItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.List; - -public abstract class AbstractSectionEditor implements SectionPage { - - protected final Player player; - protected final ItemSelector itemSelector; - protected final ConfigurationSection section; - protected final String key; - - public AbstractSectionEditor(Player player, ItemSelector itemSelector, ConfigurationSection section, String key) { - this.player = player; - this.itemSelector = itemSelector; - this.section = section; - this.key = key; - this.reOpen(); - } - - @Override - public ConfigurationSection getSection() { - return section; - } - - @Override - public void reOpen() { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - Gui upperGui = Gui.normal() - .setStructure( - "# a #" - ) - .addIngredient('a', new RefreshExample()) - .addIngredient('#', border) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # a # c # b # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('#', new BackGroundItem()) - .addIngredient('a', new PreviousPageItem()) - .addIngredient('b', new NextPageItem()) - .addIngredient('c', new BackToPageItem(itemSelector)) - .setContent(getItemList()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_EDIT_KEY.replace("{0}", key)) - )) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - @Override - public void save() { - itemSelector.save(); - } - - public class RefreshExample extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(CustomFishingPlugin.get().getItemManager().getItemBuilder(section, "bait", key).build(player)); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - notifyWindows(); - } - } - - public abstract List getItemList(); -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/BaitEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/BaitEditor.java deleted file mode 100644 index 57295a35..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/BaitEditor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import net.momirealms.customfishing.gui.icon.property.item.*; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import xyz.xenondevs.invui.item.Item; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("DuplicatedCode") -public class BaitEditor extends AbstractSectionEditor { - - public BaitEditor(Player player, String key, ItemSelector itemSelector, ConfigurationSection section) { - super(player, itemSelector, section, key); - } - - @Override - public List getItemList() { - ArrayList items = new ArrayList<>(); - items.add(new MaterialItem(this)); - items.add(new DisplayNameItem(this)); - items.add(new LoreItem(this)); - items.add(new CMDItem(this)); - items.add(new TagItem(this)); - items.add(new UnbreakableItem(this)); - items.add(new DurabilityItem(this)); - items.add(new RandomDurabilityItem(this)); - items.add(new StackableItem(this)); - items.add(new ItemFlagItem(this)); - items.add(new Head64Item(this)); - items.add(new NBTItem(this)); - items.add(new EnchantmentItem(this)); - items.add(new StoredEnchantmentItem(this)); - return items; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/HookEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/HookEditor.java deleted file mode 100644 index 1ac8e4a6..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/HookEditor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import net.momirealms.customfishing.gui.icon.property.item.*; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import xyz.xenondevs.invui.item.Item; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("DuplicatedCode") -public class HookEditor extends AbstractSectionEditor { - - public HookEditor(Player player, String key, ItemSelector itemSelector, ConfigurationSection section) { - super(player, itemSelector, section, key); - } - - @Override - public List getItemList() { - ArrayList items = new ArrayList<>(); - items.add(new MaterialItem(this)); - items.add(new DisplayNameItem(this)); - items.add(new LoreItem(this)); - items.add(new CMDItem(this)); - items.add(new TagItem(this)); - items.add(new UnbreakableItem(this)); - items.add(new DurabilityItem(this)); - items.add(new RandomDurabilityItem(this)); - items.add(new StackableItem(this)); - items.add(new ItemFlagItem(this)); - items.add(new Head64Item(this)); - items.add(new NBTItem(this)); - items.add(new EnchantmentItem(this)); - items.add(new StoredEnchantmentItem(this)); - return items; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/ItemSelector.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/ItemSelector.java deleted file mode 100644 index 4500c195..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/ItemSelector.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.gui.YamlPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.gui.icon.BackToFolderItem; -import net.momirealms.customfishing.gui.icon.NextPageItem; -import net.momirealms.customfishing.gui.icon.PreviousPageItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class ItemSelector implements YamlPage { - - private final String SEARCH; - private final Player player; - private final YamlConfiguration yaml; - private String prefix; - private final File file; - private long coolDown; - private final String type; - - public ItemSelector(Player player, File file, String type) { - this.yaml = YamlConfiguration.loadConfiguration(file); - this.player = player; - this.file = file; - this.type = type; - this.SEARCH = CFLocale.GUI_SEARCH; - this.prefix = SEARCH; - this.reOpenWithFilter(SEARCH); - } - - @Override - public void reOpen() { - reOpenWithFilter(prefix); - } - - public void reOpenWithFilter(String filter) { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - Gui upperGui = Gui.normal() - .setStructure("a # #") - .addIngredient('a', new SimpleItem(new ItemBuilder(Material.NAME_TAG).setDisplayName(filter))) - .addIngredient('#', border) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # a # c # b # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('#', new BackGroundItem()) - .addIngredient('a', new PreviousPageItem()) - .addIngredient('b', new NextPageItem()) - .addIngredient('c', new BackToFolderItem(file.getParentFile())) - .setContent(getItemList()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SELECT_ITEM - ))) - .addRenameHandler(s -> { - long current = System.currentTimeMillis(); - if (current - coolDown < 100) return; - if (s.equals(filter)) return; - prefix = s; - coolDown = current; - reOpenWithFilter(s); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void reOpenWithNewKey() { - String tempKey = CFLocale.GUI_TEMP_NEW_KEY; - prefix = tempKey; - var confirmIcon = new ConfirmIcon(); - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new SimpleItem(new ItemBuilder(Material.NAME_TAG).setDisplayName(tempKey))) - .addIngredient('b', confirmIcon) - .addIngredient('#', border) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # a # c # b # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('#', new BackGroundItem()) - .addIngredient('a', new PreviousPageItem()) - .addIngredient('b', new NextPageItem()) - .addIngredient('c', new BackToFolderItem(file.getParentFile())) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_SET_NEW_KEY) - )) - .addRenameHandler(s -> { - long current = System.currentTimeMillis(); - if (current - coolDown < 100) return; - if (s.equals(tempKey)) return; - prefix = s; - coolDown = current; - confirmIcon.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public List getItemList() { - List itemList = new ArrayList<>(); - for (Map.Entry entry : this.yaml.getValues(false).entrySet()) { - String key = entry.getKey(); - if (entry.getValue() instanceof ConfigurationSection section) { - if (!prefix.equals(SEARCH) && !entry.getKey().startsWith(prefix)) continue; - String material = section.getString("material"); - if (material != null) { - ItemStack build = CustomFishingPlugin.get().getItemManager().getItemBuilder(section, type, key).build(player); - NBTItem nbtItem = new NBTItem(build); - nbtItem.removeKey("display"); - ItemBuilder itemBuilder = new ItemBuilder(nbtItem.getItem()); - itemList.add(new ItemInList(key, itemBuilder, this)); - continue; - } - } - itemList.add(new ItemInList(key, new ItemBuilder(Material.STRUCTURE_VOID), this)); - } - itemList.add(new AddKey()); - return itemList; - } - - public void removeKey(String key) { - yaml.set(key, null); - } - - public void openEditor(String key) { - switch (type) { - case "item" -> new SectionEditor(player, key, this, yaml.getConfigurationSection(key)); - case "rod" -> new RodEditor(player, key, this, yaml.getConfigurationSection(key)); - case "bait" -> new BaitEditor(player, key, this, yaml.getConfigurationSection(key)); - case "hook" -> new HookEditor(player, key, this, yaml.getConfigurationSection(key)); - } - } - - @Override - public void save() { - try { - yaml.save(file); - } catch (IOException e) { - LogUtils.warn("Failed to save file", e); - } - } - - public static class ItemInList extends AbstractItem { - - private final String key; - private final ItemBuilder itemBuilder; - private final ItemSelector itemSelector; - - public ItemInList(String key, ItemBuilder itemBuilder, ItemSelector itemSelector) { - this.key = key; - this.itemBuilder = itemBuilder.setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - key - ))).addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - this.itemSelector = itemSelector; - } - - @Override - public ItemProvider getItemProvider() { - return itemBuilder; - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - this.itemSelector.openEditor(key); - } else if (clickType.isRightClick()) { - this.itemSelector.removeKey(key); - this.itemSelector.save(); - this.itemSelector.reOpenWithFilter(itemSelector.prefix); - } - } - } - - public class AddKey extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ANVIL).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ADD_NEW_KEY - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - reOpenWithNewKey(); - } - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (prefix != null && !yaml.contains(prefix) && prefix.matches("^[a-zA-Z0-9_]+$")) { - var builder = new ItemBuilder(Material.NAME_TAG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - prefix - ))); - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_CANCEL - ))); - return builder; - } else { - return new ItemBuilder(Material.BARRIER) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DUPE_INVALID_KEY - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - if (prefix != null && !yaml.contains(prefix) && prefix.matches("^[a-zA-Z0-9_]+$")) { - yaml.createSection(prefix); - save(); - } else { - return; - } - } - prefix = SEARCH; - reOpenWithFilter(SEARCH); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/RodEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/RodEditor.java deleted file mode 100644 index b6d35262..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/RodEditor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import net.momirealms.customfishing.gui.icon.property.item.*; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import xyz.xenondevs.invui.item.Item; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("DuplicatedCode") -public class RodEditor extends AbstractSectionEditor { - - public RodEditor(Player player, String key, ItemSelector itemSelector, ConfigurationSection section) { - super(player, itemSelector, section, key); - } - - @Override - public List getItemList() { - ArrayList items = new ArrayList<>(); - items.add(new MaterialItem(this)); - items.add(new DisplayNameItem(this)); - items.add(new LoreItem(this)); - items.add(new CMDItem(this)); - items.add(new TagItem(this)); - items.add(new UnbreakableItem(this)); - items.add(new DurabilityItem(this)); - items.add(new RandomDurabilityItem(this)); - items.add(new ItemFlagItem(this)); - items.add(new NBTItem(this)); - items.add(new EnchantmentItem(this)); - return items; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/SectionEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/SectionEditor.java deleted file mode 100644 index 1b314394..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/item/SectionEditor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.item; - -import net.momirealms.customfishing.gui.icon.property.item.*; -import net.momirealms.customfishing.gui.icon.property.loot.*; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import xyz.xenondevs.invui.item.Item; - -import java.util.ArrayList; -import java.util.List; - -public class SectionEditor extends AbstractSectionEditor { - - public SectionEditor(Player player, String key, ItemSelector itemSelector, ConfigurationSection section) { - super(player, itemSelector, section, key); - } - - @Override - public List getItemList() { - ArrayList items = new ArrayList<>(); - items.add(new MaterialItem(this)); - items.add(new NickItem(this)); - items.add(new DisplayNameItem(this)); - items.add(new LoreItem(this)); - items.add(new CMDItem(this)); - items.add(new AmountItem(this)); - items.add(new TagItem(this)); - items.add(new UnbreakableItem(this)); - items.add(new DurabilityItem(this)); - items.add(new RandomDurabilityItem(this)); - items.add(new StackableItem(this)); - items.add(new PreventGrabItem(this)); - items.add(new PriceItem(this)); - items.add(new ShowInFinderItem(this)); - items.add(new DisableStatsItem(this)); - items.add(new DisableGameItem(this)); - items.add(new InstantGameItem(this)); - items.add(new ScoreItem(this)); - items.add(new SizeItem(this)); - items.add(new ItemFlagItem(this)); - items.add(new Head64Item(this)); - items.add(new NBTItem(this)); - items.add(new EnchantmentItem(this)); - items.add(new StoredEnchantmentItem(this)); - return items; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/AmountEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/AmountEditor.java deleted file mode 100644 index 6eca4aa1..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/AmountEditor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class AmountEditor { - - private final SectionPage parentPage; - private String amount; - private final ConfigurationSection section; - - public AmountEditor(Player player, SectionPage parentPage) { - this.parentPage = parentPage; - this.section = parentPage.getSection(); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.IRON_NUGGET).setDisplayName(String.valueOf(section.getInt("amount", 1)))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_AMOUNT) - )) - .addRenameHandler(s -> { - amount = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (amount == null || amount.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - int m = Integer.parseInt(amount); - if (m >= 1) { - return new ItemBuilder(Material.IRON_NUGGET) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))) - .setAmount(m); - } else { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (amount == null || amount.isEmpty()) { - section.set("amount", null); - } else { - try { - int value = Integer.parseInt(amount); - if (value >= 1) { - section.set("amount", value); - } else { - return; - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/CustomModelDataEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/CustomModelDataEditor.java deleted file mode 100644 index fcefd2e2..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/CustomModelDataEditor.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class CustomModelDataEditor { - - private final Player player; - private final SectionPage parentPage; - private String cmd; - private final ConfigurationSection section; - private final String material; - - public CustomModelDataEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.material = section.getString("material"); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure( - "a # b" - ) - .addIngredient('a', new ItemBuilder(CustomFishingPlugin.get() - .getItemManager() - .getItemStackAppearance(player, material) - ) - .setCustomModelData(section.getInt("custom-model-data", 0)) - .setDisplayName(String.valueOf(section.getInt("custom-model-data", 0)))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_MODEL_DATA) - )) - .addRenameHandler(s -> { - cmd = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (cmd == null || cmd.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - int value = Integer.parseInt(cmd); - if (value >= 0) { - return new ItemBuilder( - CustomFishingPlugin.get() - .getItemManager() - .getItemStackAppearance(player, material) - ) - .setCustomModelData(value) - .setDisplayName(CFLocale.GUI_NEW_VALUE + value) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } else { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (cmd == null || cmd.isEmpty()) { - section.set("custom-model-data", null); - } else { - try { - int value = Integer.parseInt(cmd); - if (value >= 0) { - section.set("custom-model-data", value); - } else { - return; - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DisplayNameEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DisplayNameEditor.java deleted file mode 100644 index 5b8ce025..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DisplayNameEditor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class DisplayNameEditor { - - private final SectionPage parentPage; - private String name; - private final ConfigurationSection section; - - public DisplayNameEditor(Player player, SectionPage parentPage) { - this.parentPage = parentPage; - this.section = parentPage.getSection(); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.NAME_TAG).setDisplayName(section.getString("display.name", CFLocale.GUI_NEW_DISPLAY_NAME))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_DISPLAY_NAME) - )) - .addRenameHandler(s -> { - name = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (name == null || name.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - return new ItemBuilder(Material.NAME_TAG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + name - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (name == null || name.isEmpty()) { - section.set("display.name", null); - } else { - section.set("display.name", name); - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DurabilityEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DurabilityEditor.java deleted file mode 100644 index 99ebfbc0..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/DurabilityEditor.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class DurabilityEditor { - - private final SectionPage parentPage; - private String dur; - private final ConfigurationSection section; - - public DurabilityEditor(Player player, SectionPage parentPage) { - this.parentPage = parentPage; - this.section = parentPage.getSection(); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.NETHERITE_PICKAXE).setDisplayName(String.valueOf(section.getInt("max-durability", 64)))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_CUSTOM_DURABILITY) - )) - .addRenameHandler(s -> { - dur = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (dur == null || dur.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - int m = Integer.parseInt(dur); - if (m >= 1) { - return new ItemBuilder(Material.NETHERITE_PICKAXE) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE + dur - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))) - .setDamage(Math.max(0, Material.NETHERITE_PICKAXE.getMaxDurability() - m)); - } else { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (dur == null || dur.isEmpty()) { - section.set("max-durability", null); - } else { - try { - int value = Integer.parseInt(dur); - if (value >= 1) { - section.set("max-durability", value); - } else { - return; - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/EnchantmentEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/EnchantmentEditor.java deleted file mode 100644 index cc954eac..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/EnchantmentEditor.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class EnchantmentEditor { - - private final Player player; - private final SectionPage parentPage; - private final ArrayList enchantments; - private final ConfigurationSection section; - private int index; - private final boolean store; - - public EnchantmentEditor(Player player, SectionPage parentPage, boolean store) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.store = store; - this.index = 0; - this.enchantments = new ArrayList<>(); - this.enchantments.add(CFLocale.GUI_SELECT_ONE_ENCHANTMENT); - ConfigurationSection eSection = section.getConfigurationSection(store ? "stored-enchantments" : "enchantments"); - if (eSection != null) - for (Map.Entry entry : eSection.getValues(false).entrySet()) { - this.enchantments.add(entry.getKey() + ":" + entry.getValue()); - } - reOpen(0); - } - - public void reOpen(int idx) { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.NAME_TAG).setDisplayName(enchantments.get(idx))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(store ? CFLocale.GUI_TITLE_STORED_ENCHANTMENT : CFLocale.GUI_TITLE_ENCHANTMENT) - )) - .addRenameHandler(s -> { - if (index == 0) return; - enchantments.set(index, s); - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public List getContents() { - ArrayList items = new ArrayList<>(); - int i = 1; - List subList = enchantments.subList(1, enchantments.size()); - for (String lore : subList) { - items.add(new EnchantmentElement(lore, i++)); - } - items.add(new AddEnchantment()); - return items; - } - - public class AddEnchantment extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ANVIL).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ADD_NEW_ENCHANTMENT - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - enchantments.add("namespace:enchantment:level"); - index = enchantments.size() - 1; - reOpen(index); - } - } - - public class EnchantmentElement extends AbstractItem { - - private final String line; - private final int idx; - - public EnchantmentElement(String line, int idx) { - this.line = line; - this.idx = idx; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ENCHANTED_BOOK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - line - ))).addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType == ClickType.LEFT) { - index = idx; - reOpen(idx); - } else if (clickType == ClickType.RIGHT) { - enchantments.remove(idx); - index = Math.min(index, enchantments.size() - 1); - reOpen(index); - } - } - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - List subList = enchantments.subList(1, enchantments.size()); - if (subList.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - var builder = new ItemBuilder(Material.NAME_TAG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - for (String enchantment : subList) { - String[] split = enchantment.split(":"); - if (split.length != 3) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ILLEGAL_FORMAT - ))); - } - try { - Integer.parseInt(split[2]); - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + enchantment - ))); - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ILLEGAL_FORMAT - ))); - } - } - return builder; - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - List subList = enchantments.subList(1, enchantments.size()); - for (String line : subList) { - String[] split = line.split(":"); - if (split.length != 3) { - return; - } - try { - Integer.parseInt(split[2]); - } catch (NumberFormatException e) { - return; - } - } - section.set(store ? "stored-enchantments" : "enchantments", null); - for (String line : subList) { - String[] split = line.split(":"); - section.set((store ? "stored-enchantments" : "enchantments") + "." + split[0] + ":" + split[1], Integer.parseInt(split[2])); - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ItemFlagEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ItemFlagEditor.java deleted file mode 100644 index 5f6d1e7d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ItemFlagEditor.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemFlag; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.ArrayList; -import java.util.List; - -public class ItemFlagEditor { - - private final Player player; - private final SectionPage parentPage; - private final List flags; - private final ConfigurationSection section; - - public ItemFlagEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.flags = section.getStringList("item-flags"); - reOpen(); - } - - public void reOpen() { - Gui upperGui = Gui.normal() - .setStructure( - "# a #" - ) - .addIngredient('a', new ItemBuilder(CustomFishingPlugin.get().getItemManager().getItemBuilder(section, "item", "id").build(player))) - .addIngredient('#', new SimpleItem(new ItemBuilder(Material.AIR))) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_ITEM_FLAG) - )) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public List getContents() { - ArrayList items = new ArrayList<>(); - for (ItemFlag itemFlag : ItemFlag.values()) { - items.add(new ItemFlagToggleItem(itemFlag.name())); - } - return items; - } - - public class ItemFlagToggleItem extends AbstractItem { - - private final String flag; - - public ItemFlagToggleItem(String flag) { - this.flag = flag; - } - - @Override - public ItemProvider getItemProvider() { - if (flags.contains(flag)) { - return new ItemBuilder(Material.GREEN_BANNER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + flag - ))); - } else { - return new ItemBuilder(Material.RED_BANNER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + flag - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (flags.contains(flag)) { - flags.remove(flag); - } else { - flags.add(flag); - } - if (flags.size() != 0) { - section.set("item-flags", flags); - } else { - section.set("item-flags", null); - } - parentPage.save(); - reOpen(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/LoreEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/LoreEditor.java deleted file mode 100644 index dd9aa32f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/LoreEditor.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.ArrayList; -import java.util.List; - -public class LoreEditor { - - private final Player player; - private final SectionPage parentPage; - private final ArrayList lore; - private final ConfigurationSection section; - private int index; - - public LoreEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.index = 0; - this.lore = new ArrayList<>(section.getStringList("display.lore")); - this.lore.add(0, CFLocale.GUI_SELECT_ONE_LORE); - reOpen(0); - } - - public void reOpen(int idx) { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.NAME_TAG).setDisplayName(lore.get(idx))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_LORE) - )) - .addRenameHandler(s -> { - if (index == 0) return; - lore.set(index, s); - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public List getContents() { - ArrayList items = new ArrayList<>(); - int i = 1; - List subList = lore.subList(1, lore.size()); - for (String lore : subList) { - items.add(new LoreElement(lore, i++)); - } - items.add(new AddLore()); - return items; - } - - public class AddLore extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ANVIL).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ADD_NEW_LORE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - lore.add("Text"); - index = lore.size() - 1; - reOpen(index); - } - } - - public class LoreElement extends AbstractItem { - - private final String line; - private final int idx; - - public LoreElement(String line, int idx) { - this.line = line; - this.idx = idx; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.PAPER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - line - ))).addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType == ClickType.LEFT) { - index = idx; - reOpen(idx); - } else if (clickType == ClickType.RIGHT) { - lore.remove(idx); - index = Math.min(index, lore.size() - 1); - reOpen(index); - } - } - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - List subList = lore.subList(1, lore.size()); - if (subList.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - var builder = new ItemBuilder(Material.NAME_TAG) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - for (String lore : subList) { - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + lore - ))); - } - return builder; - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - List subList = lore.subList(1, lore.size()); - if (lore.isEmpty()) { - section.set("display.lore", null); - } else { - section.set("display.lore", subList); - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/MaterialEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/MaterialEditor.java deleted file mode 100644 index f5dfdf87..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/MaterialEditor.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.mechanic.item.ItemManagerImpl; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.ArrayList; -import java.util.List; - -public class MaterialEditor { - - private final Player player; - private final SectionPage parentPage; - private String material; - private final ConfigurationSection section; - - public MaterialEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.material = section.getString("material"); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - var itemBuilder = new ItemBuilder(CustomFishingPlugin.get() - .getItemManager() - .getItemStackAppearance(player, material) - ) - .setDisplayName(section.getString("material", "")); - - if (section.contains("custom-model-data")) - itemBuilder.setCustomModelData(section.getInt("custom-model-data", 0)); - - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', itemBuilder) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getCompatibilityItemList()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_MATERIAL) - )) - .addRenameHandler(s -> { - material = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public List getCompatibilityItemList() { - ArrayList items = new ArrayList<>(); - for (String lib : ((ItemManagerImpl) CustomFishingPlugin.get().getItemManager()).getItemLibraries()) { - switch (lib) { - case "MMOItems" -> items.add(new SimpleItem(new ItemBuilder(Material.BELL).setDisplayName(lib + ":TYPE:ID"))); - case "ItemsAdder" -> items.add(new SimpleItem(new ItemBuilder(Material.BELL).setDisplayName(lib + ":namespace:id"))); - case "vanilla", "CustomFishing" -> {} - default -> items.add(new SimpleItem(new ItemBuilder(Material.BELL).setDisplayName(lib + ":ID"))); - } - } - return items; - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (material == null || material.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - var builder = new ItemBuilder( - CustomFishingPlugin.get() - .getItemManager() - .getItemStackAppearance(player, material) - ).setDisplayName(CFLocale.GUI_NEW_VALUE + material) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - if (section.contains("custom-model-data")) - builder.setCustomModelData(section.getInt("custom-model-data")); - return builder; - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (material == null || material.isEmpty()) { - section.set("material", null); - } else if (CustomFishingPlugin.get().getItemManager().getItemStackAppearance(player, material).getType() == Material.BARRIER) { - return; - } else { - section.set("material", material); - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NBTEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NBTEditor.java deleted file mode 100644 index 9f044ca5..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NBTEditor.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.util.ConfigUtils; -import net.momirealms.customfishing.util.NBTUtils; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.gui.structure.Markers; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -import java.util.*; - -@SuppressWarnings("DuplicatedCode") -public class NBTEditor { - - private final Player player; - private final SectionPage parentPage; - private ConfigurationSection nbtSection; - private ConfigurationSection currentSection; - private String value; - private String currentNode; - - public NBTEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.nbtSection = parentPage.getSection().getConfigurationSection("nbt"); - if (this.nbtSection == null) - this.nbtSection = parentPage.getSection().createSection("nbt"); - this.currentSection = nbtSection; - this.currentNode = ""; - reOpenMain(); - } - - public List getNBTContents() { - Deque deque = new ArrayDeque<>(); - for (Map.Entry entry : currentSection.getValues(false).entrySet()) { - String path = Objects.equals(currentNode, "") ? entry.getKey() : currentNode + "." + entry.getKey(); - if (entry.getValue() instanceof List list) { - deque.addLast(new InvListIcon(path)); - } else if (entry.getValue() instanceof String str) { - deque.addLast(new InvValueIcon(path, str)); - } else if (entry.getValue() instanceof ConfigurationSection inner) { - deque.addFirst(new InvCompoundIcon(path, inner)); - } else if (entry.getValue() instanceof Map map) { - deque.addLast(new InvMapIcon(path)); - } - } - deque.addLast(new NewCompoundIcon()); - deque.addLast(new NewListIcon()); - deque.addLast(new NewValueIcon()); - if (currentSection.getParent() != null && !currentSection.getName().equals("nbt")) { - deque.addLast(new BackToParentIcon()); - } - return new ArrayList<>(deque); - } - - public void reOpenMain() { - Gui upperGui = Gui.normal() - .setStructure("b b c") - .addIngredient('b', new ItemStack(Material.AIR)) - .addIngredient('c', new SaveIcon()) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getNBTContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_NBT_EDIT_TITLE))) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void reOpenAddCompound() { - var confirm = new ConfirmCompoundItem(); - Gui upperGui = Gui.normal() - .setStructure("a b c") - .addIngredient('a', new ItemBuilder(Material.COMMAND_BLOCK_MINECART).setDisplayName("")) - .addIngredient('b', new ItemStack(Material.AIR)) - .addIngredient('c', confirm) - .build(); - - value = ""; - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getNBTContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_NBT_COMPOUND) - )) - .addRenameHandler(s -> { - value = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void reOpenAddList() { - var confirm = new ConfirmListItem(); - Gui upperGui = Gui.normal() - .setStructure("a b c") - .addIngredient('a', new ItemBuilder(Material.CHAIN_COMMAND_BLOCK).setDisplayName("")) - .addIngredient('b', new ItemStack(Material.AIR)) - .addIngredient('c', confirm) - .build(); - - value = ""; - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getNBTContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_NBT_LIST) - )) - .addRenameHandler(s -> { - value = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void reOpenAddValue() { - var confirm =new ConfirmValueItem(); - Gui upperGui = Gui.normal() - .setStructure("a b c") - .addIngredient('a', new ItemBuilder(Material.COMMAND_BLOCK).setDisplayName("")) - .addIngredient('b', new ItemStack(Material.AIR)) - .addIngredient('c', confirm) - .build(); - - value = ""; - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getNBTContents()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_TITLE_NBT_KEY))) - .addRenameHandler(s -> { - value = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void reOpenSetValue(String key, String type) { - var save = new SaveValueIcon(key); - Gui upperGui = Gui.normal() - .setStructure("a b c") - .addIngredient('a', new ItemBuilder(Material.COMMAND_BLOCK).setDisplayName(type == null ? "" : "(" + type + ") ")) - .addIngredient('b', new ItemStack(Material.AIR)) - .addIngredient('c', save) - .build(); - - value = ""; - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', Markers.CONTENT_LIST_SLOT_HORIZONTAL) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .setContent(getTypeContents(key)) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_NBT_SET_VALUE_TITLE) - )) - .addRenameHandler(s -> { - value = s; - save.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public void removeByNode(String node) { - nbtSection.set(node, null); - parentPage.save(); - } - - public List getTypeContents(String key) { - ArrayList list = new ArrayList<>(); - for (Map.Entry entry : Map.of( - "String","some text", - "Byte","1", - "Short","123", - "Int","123456", - "Long","123456789", - "Double", "1.2345", - "Float", "1.23", - "Boolean", "true", - "IntArray", "[111,222,333,444]", - "ByteArray","[1,2,3,4]" - ).entrySet()) { - list.add(new TypeItem(key, entry.getKey(), entry.getValue())); - } - return list; - } - - public class TypeItem extends AbstractItem { - - private final String type; - private final String tip; - private final String key; - - public TypeItem(String key, String type, String tip) { - this.type = type; - this.tip = tip; - this.key = key; - } - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.BELL).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "(" + type + ") " + tip - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - reOpenSetValue(key, type); - } - } - - public class ConfirmCompoundItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (value == null || value.equals("") || value.contains(".") || currentSection.contains(value)) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_INVALID_KEY - ))); - } - - return new ItemBuilder(Material.COMMAND_BLOCK_MINECART).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE + value - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_CANCEL - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - if (value == null || value.equals("") || value.contains(".")) { - return; - } - if (currentSection.contains(value)) { - return; - } - currentSection.createSection(value); - parentPage.save(); - } - reOpenMain(); - } - } - - public class ConfirmListItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (value == null || value.equals("") || value.contains(".") || currentSection.contains(value)) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_INVALID_KEY - ))); - } - return new ItemBuilder(Material.CHAIN_COMMAND_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE + value - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_CANCEL - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - if (value == null || value.equals("") || value.contains(".")) { - return; - } - if (currentSection.contains(value)) { - return; - } - currentSection.set(value, new ArrayList<>()); - parentPage.save(); - } - reOpenMain(); - } - } - - public class ConfirmValueItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (value == null || value.equals("") || value.contains(".") || currentSection.contains(value)) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_INVALID_KEY - ))); - } - - return new ItemBuilder(Material.COMMAND_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE + value - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_CANCEL - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - if (value == null || value.equals("") || value.contains(".")) { - return; - } - if (currentSection.contains(value)) { - return; - } - reOpenSetValue(value, null); - } else if (clickType.isRightClick()) { - reOpenMain(); - } - } - } - - public class NewCompoundIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.OAK_SIGN).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_ADD_COMPOUND - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - reOpenAddCompound(); - } - } - - public class NewListIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.SPRUCE_SIGN).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_ADD_LIST - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - reOpenAddList(); - } - } - - public class NewValueIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.ACACIA_SIGN).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_ADD_VALUE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - reOpenAddValue(); - } - } - - public class InvCompoundIcon extends AbstractItem { - - private final String node; - private final ConfigurationSection compound; - - public InvCompoundIcon(String node, ConfigurationSection compound) { - this.compound = compound; - this.node = node; - } - - @Override - public ItemProvider getItemProvider() { - String[] splits = node.split("\\."); - return new ItemBuilder(Material.COMMAND_BLOCK_MINECART).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "Compound: " + splits[splits.length -1] - ))).addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isLeftClick()) { - currentSection = compound; - currentNode = node; - } else if (clickType.isRightClick()) { - removeByNode(node); - } - reOpenMain(); - } - } - - public class InvValueIcon extends AbstractItem { - - private final String node; - private final String value; - - public InvValueIcon(String node, String value) { - this.node = node; - this.value = value; - } - - @Override - public ItemProvider getItemProvider() { - String[] splits = node.split("\\."); - return new ItemBuilder(Material.REPEATING_COMMAND_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - splits[splits.length -1] + ": " + value - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_LEFT_CLICK_EDIT - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - String[] split = node.split("\\."); - if (clickType.isLeftClick()) { - reOpenSetValue(split[split.length-1], NBTUtils.getTypeAndData(value)[0]); - } else if (clickType.isRightClick()) { - removeByNode(node); - reOpenMain(); - } - } - } - - public class InvListIcon extends AbstractItem { - - private final String node; - - public InvListIcon(String node) { - this.node = node; - } - - @Override - public ItemProvider getItemProvider() { - String[] splits = node.split("\\."); - return new ItemBuilder(Material.CHAIN_COMMAND_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "List: " + splits[splits.length -1] - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + CFLocale.GUI_LEFT_CLICK_EDIT + "" - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isRightClick()) { - removeByNode(node); - reOpenMain(); - } - } - } - - public class InvMapIcon extends AbstractItem { - - private final String node; - - public InvMapIcon(String node) { - this.node = node; - } - - @Override - public ItemProvider getItemProvider() { - String[] splits = node.split("\\."); - return new ItemBuilder(Material.COMMAND_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "Map: " + splits[splits.length -1] - ))) - .addLoreLines("") - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + CFLocale.GUI_LEFT_CLICK_EDIT + "" - ))).addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_DELETE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType.isRightClick()) { - removeByNode(node); - reOpenMain(); - } - } - } - - public class SaveValueIcon extends AbstractItem { - - private final String key; - - public SaveValueIcon(String key) { - this.key = key; - } - - @Override - public ItemProvider getItemProvider() { - try { - NBTUtils.getTypeAndData(value); - return new ItemBuilder(Material.COMMAND_BLOCK) - .setDisplayName(value) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_RIGHT_CLICK_CANCEL - ))); - } catch (IllegalArgumentException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ILLEGAL_FORMAT - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (clickType == ClickType.LEFT) { - try { - NBTUtils.getTypeAndData(value); - currentSection.set(key, value); - parentPage.save(); - reOpenMain(); - } catch (IllegalArgumentException e) { - reOpenMain(); - } - } else if (clickType == ClickType.RIGHT) { - reOpenMain(); - } - } - } - - public class SaveIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (nbtSection.getValues(false).size() > 0) { - var builder = new ItemBuilder(Material.ACACIA_SIGN).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_PREVIEW - ))); - for (String line : ConfigUtils.getReadableSection(nbtSection.getValues(false))) { - builder.addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - line - ))); - } - return builder; - } else { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (nbtSection.getValues(false).size() == 0) { - Objects.requireNonNull(nbtSection.getParent()).set("nbt", null); - } - parentPage.save(); - parentPage.reOpen(); - } - } - - public class BackToParentIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.MINECART).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NBT_BACK_TO_COMPOUND - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - currentSection = currentSection.getParent(); - currentNode = currentNode.lastIndexOf(".") == -1 ? "" : currentNode.substring(0, currentNode.lastIndexOf(".")); - reOpenMain(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NickEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NickEditor.java deleted file mode 100644 index 77ece67f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/NickEditor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class NickEditor { - - private final SectionPage parentPage; - private String nick; - private final ConfigurationSection section; - - public NickEditor(Player player, SectionPage parentPage) { - this.parentPage = parentPage; - this.section = parentPage.getSection(); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.WRITABLE_BOOK).setDisplayName(section.getString("nick", CFLocale.GUI_NICK_NEW))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_NICK_TITLE) - )) - .addRenameHandler(s -> { - nick = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (nick == null || nick.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - return new ItemBuilder(Material.WRITABLE_BOOK) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + nick - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (nick == null || nick.isEmpty()) { - section.set("nick", null); - } else { - section.set("nick", nick); - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/PriceEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/PriceEditor.java deleted file mode 100644 index aa866830..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/PriceEditor.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class PriceEditor { - - private final Player player; - private final SectionPage parentPage; - private final String[] price; - private int index; - private final ConfigurationSection section; - - public PriceEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.index = 0; - this.price = new String[]{section.getString("price.base","0"), section.getString("price.bonus","0")}; - reOpen(); - } - - public void reOpen() { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.GOLD_INGOT).setDisplayName(price[index])) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "a b x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .addIngredient('a', new BaseItem()) - .addIngredient('b', new BonusItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_PRICE_TITLE) - )) - .addRenameHandler(s -> { - if (s == null || s.equals("")) { - price[index] = "0"; - return; - } - price[index] = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class BaseItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.GOLD_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_PRICE_BASE - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - index = 0; - reOpen(); - } - } - - public class BonusItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.GOLD_NUGGET).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_PRICE_BONUS - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - index = 1; - reOpen(); - } - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (price[0].equals("0") && price[1].equals("0")) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - Double.parseDouble(price[0]); - Double.parseDouble(price[1]); - return new ItemBuilder(Material.GOLD_INGOT) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PRICE_BASE + price[0] - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_ITEM_PRICE_BONUS + price[1] - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (price[0].equals("0") && price[1].equals("0")) { - section.set("price", null); - } else { - try { - double base = Double.parseDouble(price[0]); - double bonus = Double.parseDouble(price[1]); - if (base != 0) { - section.set("price.base", base); - } - if (bonus != 0) { - section.set("price.bonus", bonus); - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ScoreEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ScoreEditor.java deleted file mode 100644 index c524d29f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/ScoreEditor.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class ScoreEditor { - - private final SectionPage parentPage; - private String score; - private final ConfigurationSection section; - - public ScoreEditor(Player player, SectionPage parentPage) { - this.parentPage = parentPage; - this.section = parentPage.getSection(); - - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.NETHER_STAR).setDisplayName(String.valueOf(section.getDouble("score", 0)))) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "x x x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_SCORE_TITLE) - )) - .addRenameHandler(s -> { - score = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (score == null || score.isEmpty()) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - Double.parseDouble(score); - return new ItemBuilder(Material.NETHER_STAR) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE + score - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (score == null || score.isEmpty()) { - section.set("score", null); - } else { - try { - double value = Double.parseDouble(score); - if (value >= 0) { - section.set("score", value); - } else { - return; - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/SizeEditor.java b/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/SizeEditor.java deleted file mode 100644 index 8ee058b8..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/gui/page/property/SizeEditor.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.gui.page.property; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.adventure.component.ShadedAdventureComponentWrapper; -import net.momirealms.customfishing.gui.SectionPage; -import net.momirealms.customfishing.gui.icon.BackGroundItem; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.gui.Gui; -import xyz.xenondevs.invui.gui.PagedGui; -import xyz.xenondevs.invui.item.Item; -import xyz.xenondevs.invui.item.ItemProvider; -import xyz.xenondevs.invui.item.builder.ItemBuilder; -import xyz.xenondevs.invui.item.impl.AbstractItem; -import xyz.xenondevs.invui.item.impl.SimpleItem; -import xyz.xenondevs.invui.window.AnvilWindow; - -public class SizeEditor { - - private final Player player; - private final SectionPage parentPage; - private final String[] size; - private int index; - private final ConfigurationSection section; - - public SizeEditor(Player player, SectionPage parentPage) { - this.player = player; - this.parentPage = parentPage; - this.section = parentPage.getSection(); - this.index = 0; - this.size = section.contains("size") ? section.getString("size").split("~") : new String[]{"0","0"}; - reOpen(); - } - - public void reOpen() { - Item border = new SimpleItem(new ItemBuilder(Material.AIR)); - var confirm = new ConfirmIcon(); - Gui upperGui = Gui.normal() - .setStructure("a # b") - .addIngredient('a', new ItemBuilder(Material.PUFFERFISH).setDisplayName(size[index])) - .addIngredient('#', border) - .addIngredient('b', confirm) - .build(); - - var gui = PagedGui.items() - .setStructure( - "a b x x x x x x x", - "x x x x x x x x x", - "x x x x x x x x x", - "# # # # c # # # #" - ) - .addIngredient('x', new ItemStack(Material.AIR)) - .addIngredient('c', parentPage.getBackItem()) - .addIngredient('#', new BackGroundItem()) - .addIngredient('a', new MinItem()) - .addIngredient('b', new MaxItem()) - .build(); - - var window = AnvilWindow.split() - .setViewer(player) - .setTitle(new ShadedAdventureComponentWrapper( - AdventureHelper.getInstance().getComponentFromMiniMessage(CFLocale.GUI_SIZE_TITLE) - )) - .addRenameHandler(s -> { - if (s == null || s.equals("")) { - size[index] = "0"; - return; - } - size[index] = s; - confirm.notifyWindows(); - }) - .setUpperGui(upperGui) - .setLowerGui(gui) - .build(); - - window.open(); - } - - public class MinItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.IRON_INGOT).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SIZE_MIN - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - index = 0; - reOpen(); - } - } - - public class MaxItem extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - return new ItemBuilder(Material.IRON_BLOCK).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SIZE_MAX - ))); - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - index = 1; - reOpen(); - } - } - - public class ConfirmIcon extends AbstractItem { - - @Override - public ItemProvider getItemProvider() { - if (size[0].equals("0") && size[1].equals("0")) { - return new ItemBuilder(Material.STRUCTURE_VOID).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_DELETE_PROPERTY - ))); - } else { - try { - double min = Double.parseDouble(size[0]); - double max = Double.parseDouble(size[1]); - - if (min <= max) { - return new ItemBuilder(Material.PUFFERFISH) - .setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_NEW_VALUE - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - " - " + size[0] + "~" + size[1] - ))) - .addLoreLines(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_CLICK_CONFIRM - ))); - } else { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_SIZE_MAX_NO_LESS - ))); - } - } catch (NumberFormatException e) { - return new ItemBuilder(Material.BARRIER).setDisplayName(new ShadedAdventureComponentWrapper(AdventureHelper.getInstance().getComponentFromMiniMessage( - CFLocale.GUI_INVALID_NUMBER - ))); - } - } - } - - @Override - public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) { - if (size[0].equals("0") && size[1].equals("0")) { - section.set("size", null); - } else { - try { - double min = Double.parseDouble(size[0]); - double max = Double.parseDouble(size[1]); - if (min <= max) { - section.set("size", min + "~" + max); - } - } catch (NumberFormatException e) { - return; - } - } - parentPage.reOpen(); - parentPage.save(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java deleted file mode 100644 index d8321eec..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/dependencies/Dependency.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.momirealms.customfishing.libraries.dependencies; - -import com.google.common.collect.ImmutableList; -import net.momirealms.customfishing.libraries.dependencies.relocation.Relocation; -import org.bukkit.Bukkit; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Locale; - -/** - * The dependencies used by CustomFishing. - */ -public enum Dependency { - - ASM( - "org.ow2.asm", - "asm", - "9.7", - "asm" - ), - ASM_COMMONS( - "org.ow2.asm", - "asm-commons", - "9.7", - "asm-commons" - ), - JAR_RELOCATOR( - "me.lucko", - "jar-relocator", - "1.7", - "jar-relocator" - ), - COMMAND_API( - "dev{}jorel", - "commandapi-bukkit-shade", - "9.4.1", - "commandapi-bukkit", - Relocation.of("commandapi", "dev{}jorel{}commandapi") - ), - COMMAND_API_MOJMAP( - "dev{}jorel", - "commandapi-bukkit-shade-mojang-mapped", - "9.4.1", - "commandapi-bukkit-shade-mojang-mapped", - Relocation.of("commandapi", "dev{}jorel{}commandapi") - ), - MARIADB_DRIVER( - "org{}mariadb{}jdbc", - "mariadb-java-client", - "3.3.3", - "mariadb-java-client", - Relocation.of("mariadb", "org{}mariadb") - ), - BOOSTED_YAML( - "dev{}dejvokep", - "boosted-yaml", - "1.3.4", - "boosted-yaml", - Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") - ), - EXP4J( - "net{}objecthunter", - "exp4j", - "0.4.8", - "exp4j", - Relocation.of("exp4j", "net{}objecthunter{}exp4j") - ), - MYSQL_DRIVER( - "com{}mysql", - "mysql-connector-j", - "8.4.0", - "mysql-connector-j", - Relocation.of("mysql", "com{}mysql") - ), - H2_DRIVER( - "com.h2database", - "h2", - "2.2.224", - "h2database" - ), - SQLITE_DRIVER( - "org.xerial", - "sqlite-jdbc", - "3.45.3.0", - "sqlite-jdbc" - ), - HIKARI( - "com{}zaxxer", - "HikariCP", - "5.1.0", - "HikariCP", - Relocation.of("hikari", "com{}zaxxer{}hikari") - ), - SLF4J_SIMPLE( - "org.slf4j", - "slf4j-simple", - "2.0.12", - "slf4j-simple" - ), - SLF4J_API( - "org.slf4j", - "slf4j-api", - "2.0.12", - "slf4j-api" - ), - MONGODB_DRIVER_CORE( - "org{}mongodb", - "mongodb-driver-core", - "5.1.0", - "mongodb-driver-core", - Relocation.of("mongodb", "com{}mongodb"), - Relocation.of("bson", "org{}bson") - ), - MONGODB_DRIVER_SYNC( - "org{}mongodb", - "mongodb-driver-sync", - "5.1.0", - "mongodb-driver-sync", - Relocation.of("mongodb", "com{}mongodb"), - Relocation.of("bson", "org{}bson") - ), - MONGODB_DRIVER_BSON( - "org{}mongodb", - "bson", - "5.1.0", - "mongodb-bson", - Relocation.of("mongodb", "com{}mongodb"), - Relocation.of("bson", "org{}bson") - ), - JEDIS( - "redis{}clients", - "jedis", - "5.1.2", - "jedis", - Relocation.of("jedis", "redis{}clients{}jedis"), - Relocation.of("commonspool2", "org{}apache{}commons{}pool2") - ), - BSTATS_BASE( - "org{}bstats", - "bstats-base", - "3.0.2", - "bstats-base", - Relocation.of("bstats", "org{}bstats") - ), - BSTATS_BUKKIT( - "org{}bstats", - "bstats-bukkit", - "3.0.2", - "bstats-bukkit", - Relocation.of("bstats", "org{}bstats") - ), - COMMONS_POOL_2( - "org{}apache{}commons", - "commons-pool2", - "2.12.0", - "commons-pool2", - Relocation.of("commonspool2", "org{}apache{}commons{}pool2") - ), - GSON( - "com.google.code.gson", - "gson", - "2.10.1", - "gson" - ), - COMMONS_LANG_3( - "org{}apache{}commons", - "commons-lang3", - "3.14.0", - "commons-lang3", - Relocation.of("lang3", "org{}apache{}commons{}lang3") - ); - - private final String mavenRepoPath; - private final String version; - private final List relocations; - private final String artifact; - - private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; - - Dependency(String groupId, String artifactId, String version, String artifact) { - this(groupId, artifactId, version, artifact, new Relocation[0]); - } - - Dependency(String groupId, String artifactId, String version, String artifact, Relocation... relocations) { - this.mavenRepoPath = String.format(MAVEN_FORMAT, - rewriteEscaping(groupId).replace(".", "/"), - rewriteEscaping(artifactId), - version, - rewriteEscaping(artifactId), - version - ); - this.version = version; - this.relocations = ImmutableList.copyOf(relocations); - this.artifact = artifact; - } - - private static String rewriteEscaping(String s) { - return s.replace("{}", "."); - } - - public String getFileName(String classifier) { - String name = artifact.toLowerCase(Locale.ROOT).replace('_', '-'); - String extra = classifier == null || classifier.isEmpty() - ? "" - : "-" + classifier; - - return name + "-" + this.version + extra + ".jar"; - } - - String getMavenRepoPath() { - return this.mavenRepoPath; - } - - public List getRelocations() { - return this.relocations; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java b/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java deleted file mode 100644 index e61b6c76..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/libraries/loader/JarInJarClassLoader.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.momirealms.customfishing.libraries.loader; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; - -/** - * Classloader that can load a jar from within another jar file. - * - *

The "loader" jar contains the loading code & public API classes, - * and is class-loaded by the platform.

- * - *

The inner "plugin" jar contains the plugin itself, and is class-loaded - * by the loading code & this classloader.

- */ -public class JarInJarClassLoader extends URLClassLoader { - static { - ClassLoader.registerAsParallelCapable(); - } - - /** - * Creates a new jar-in-jar class loader. - * - * @param loaderClassLoader the loader plugin's classloader (setup and created by the platform) - * @param jarResourcePath the path to the jar-in-jar resource within the loader jar - * @throws LoadingException if something unexpectedly bad happens - */ - public JarInJarClassLoader(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { - super(new URL[]{extractJar(loaderClassLoader, jarResourcePath)}, loaderClassLoader); - } - - public void addJarToClasspath(URL url) { - addURL(url); - } - - public void deleteJarResource() { - URL[] urls = getURLs(); - if (urls.length == 0) { - return; - } - - try { - Path path = Paths.get(urls[0].toURI()); - Files.deleteIfExists(path); - } catch (Exception e) { - // ignore - } - } - - /** - * Creates a new plugin instance. - * - * @param bootstrapClass the name of the bootstrap plugin class - * @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap - * plugin constructor - * @param loaderPlugin the loader plugin instance - * @param the type of the loader plugin - * @return the instantiated bootstrap plugin - */ - public LoaderBootstrap instantiatePlugin(String bootstrapClass, Class loaderPluginType, T loaderPlugin) throws LoadingException { - Class plugin; - try { - plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to load bootstrap class", e); - } - - Constructor constructor; - try { - constructor = plugin.getConstructor(loaderPluginType); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to get bootstrap constructor", e); - } - - try { - return constructor.newInstance(loaderPlugin); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to create bootstrap plugin instance", e); - } - } - - /** - * Extracts the "jar-in-jar" from the loader plugin into a temporary file, - * then returns a URL that can be used by the {@link JarInJarClassLoader}. - * - * @param loaderClassLoader the classloader for the "host" loader plugin - * @param jarResourcePath the inner jar resource path - * @return a URL to the extracted file - */ - private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { - // get the jar-in-jar resource - URL jarInJar = loaderClassLoader.getResource(jarResourcePath); - if (jarInJar == null) { - throw new LoadingException("Could not locate jar-in-jar"); - } - - // create a temporary file - // on posix systems by default this is only read/writable by the process owner - Path path; - try { - path = Files.createTempFile("customfishing-jarinjar", ".jar.tmp"); - } catch (IOException e) { - throw new LoadingException("Unable to create a temporary file", e); - } - - // mark that the file should be deleted on exit - path.toFile().deleteOnExit(); - - // copy the jar-in-jar to the temporary file path - try (InputStream in = jarInJar.openStream()) { - Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new LoadingException("Unable to copy jar-in-jar to temporary path", e); - } - - try { - return path.toUri().toURL(); - } catch (MalformedURLException e) { - throw new LoadingException("Unable to get URL from path", e); - } - } - -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java deleted file mode 100644 index 84082f06..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/ActionManagerImpl.java +++ /dev/null @@ -1,1054 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.action; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.manager.ActionManager; -import net.momirealms.customfishing.api.manager.LootManager; -import net.momirealms.customfishing.api.mechanic.GlobalSettings; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionExpansion; -import net.momirealms.customfishing.api.mechanic.action.ActionFactory; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.VaultHook; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.setting.CFLocale; -import net.momirealms.customfishing.util.*; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; -import org.bukkit.entity.ExperienceOrb; -import org.bukkit.entity.Player; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class ActionManagerImpl implements ActionManager { - - private final CustomFishingPlugin plugin; - private final HashMap actionFactoryMap; - private final String EXPANSION_FOLDER = "expansions/action"; - - public ActionManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.actionFactoryMap = new HashMap<>(); - this.registerInbuiltActions(); - } - - // Method to register various built-in actions during initialization. - private void registerInbuiltActions() { - this.registerMessageAction(); - this.registerCommandAction(); - this.registerMendingAction(); - this.registerExpAction(); - this.registerChainAction(); - this.registerPotionAction(); - this.registerSoundAction(); - this.registerPluginExpAction(); - this.registerTitleAction(); - this.registerActionBarAction(); - this.registerCloseInvAction(); - this.registerDelayedAction(); - this.registerConditionalAction(); - this.registerPriorityAction(); - this.registerLevelAction(); - this.registerHologramAction(); - this.registerFakeItemAction(); - this.registerFishFindAction(); - this.registerFoodAction(); - this.registerItemAmountAction(); - this.registerItemDurabilityAction(); - this.registerGiveItemAction(); - this.registerMoneyAction(); - this.registerTimerAction(); - } - - // Method to load expansions and global event actions. - public void load() { - this.loadExpansions(); - this.loadGlobalEventActions(); - } - - public void unload() { - GlobalSettings.unload(); - } - - public void disable() { - unload(); - this.actionFactoryMap.clear(); - } - - // Method to load global event actions from the plugin's configuration file. - private void loadGlobalEventActions() { - YamlConfiguration config = plugin.getConfig("config.yml"); - GlobalSettings.loadEvents(config.getConfigurationSection("mechanics.global-events")); - } - - /** - * Registers an ActionFactory for a specific action type. - * This method allows you to associate an ActionFactory with a custom action type. - * - * @param type The custom action type to register. - * @param actionFactory The ActionFactory responsible for creating actions of the specified type. - * @return True if the registration was successful (the action type was not already registered), false otherwise. - */ - @Override - public boolean registerAction(String type, ActionFactory actionFactory) { - if (this.actionFactoryMap.containsKey(type)) return false; - this.actionFactoryMap.put(type, actionFactory); - return true; - } - - /** - * Unregisters an ActionFactory for a specific action type. - * This method allows you to remove the association between an action type and its ActionFactory. - * - * @param type The custom action type to unregister. - * @return True if the action type was successfully unregistered, false if it was not found. - */ - @Override - public boolean unregisterAction(String type) { - return this.actionFactoryMap.remove(type) != null; - } - - /** - * Retrieves an Action object based on the configuration provided in a ConfigurationSection. - * This method reads the type of action from the section, obtains the corresponding ActionFactory, - * and builds an Action object using the specified values and chance. - * - * @param section The ConfigurationSection containing the action configuration. - * @return An Action object created based on the configuration, or an EmptyAction instance if the action type is invalid. - */ - @Override - public Action getAction(ConfigurationSection section) { - ActionFactory factory = getActionFactory(section.getString("type")); - if (factory == null) { - LogUtils.warn("Action type: " + section.getString("type") + " doesn't exist."); - // to prevent NPE - return EmptyAction.instance; - } - return factory.build( - section.get("value"), - section.getDouble("chance", 1d) - ); - } - - /** - * Retrieves a mapping of ActionTriggers to arrays of Actions from a ConfigurationSection. - * This method iterates through the provided ConfigurationSection to extract action triggers - * and their associated arrays of Actions. - * - * @param section The ConfigurationSection containing action mappings. - * @return A HashMap where keys are ActionTriggers and values are arrays of Action objects. - */ - @Override - @NotNull - public HashMap getActionMap(ConfigurationSection section) { - // Create an empty HashMap to store the action mappings - HashMap actionMap = new HashMap<>(); - - // If the provided ConfigurationSection is null, return the empty actionMap - if (section == null) return actionMap; - - // Iterate through all key-value pairs in the ConfigurationSection - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - // Convert the key to an ActionTrigger enum (assuming it's in uppercase English) - // and map it to an array of Actions obtained from the inner section - try { - actionMap.put( - ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)), - getActions(innerSection) - ); - } catch (IllegalArgumentException e) { - LogUtils.warn("Event: " + entry.getKey() + " doesn't exist!"); - } - } - } - return actionMap; - } - - /** - * Retrieves an array of Action objects from a ConfigurationSection. - * This method iterates through the provided ConfigurationSection to extract Action configurations - * and build an array of Action objects. - * - * @param section The ConfigurationSection containing action configurations. - * @return An array of Action objects created based on the configurations in the section. - */ - @NotNull - @Override - public Action[] getActions(ConfigurationSection section) { - // Create an ArrayList to store the Actions - ArrayList actionList = new ArrayList<>(); - if (section == null) return actionList.toArray(new Action[0]); - - // Iterate through all key-value pairs in the ConfigurationSection - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - Action action = getAction(innerSection); - if (action != null) - actionList.add(action); - } - } - return actionList.toArray(new Action[0]); - } - - /** - * Retrieves an ActionFactory associated with a specific action type. - * - * @param type The action type for which to retrieve the ActionFactory. - * @return The ActionFactory associated with the specified action type, or null if not found. - */ - @Nullable - @Override - public ActionFactory getActionFactory(String type) { - return actionFactoryMap.get(type); - } - - /** - * Loads custom ActionExpansions from JAR files located in the expansion directory. - * This method scans the expansion folder for JAR files, loads classes that extend ActionExpansion, - * and registers them with the appropriate action type and ActionFactory. - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, ActionExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterAction(expansion.getActionType()); - registerAction(expansion.getActionType(), expansion.getActionFactory()); - LogUtils.info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() ); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } - - /** - * Retrieves a mapping of success times to corresponding arrays of actions from a ConfigurationSection. - * - * @param section The ConfigurationSection containing success times actions. - * @return A HashMap where success times associated with actions. - */ - @Override - public HashMap getTimesActionMap(ConfigurationSection section) { - HashMap actionMap = new HashMap<>(); - if (section == null) return actionMap; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actionMap.put(Integer.parseInt(entry.getKey()), plugin.getActionManager().getActions(innerSection)); - } - } - return actionMap; - } - - private void registerMessageAction() { - registerAction("message", (args, chance) -> { - ArrayList msg = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - List replaced = PlaceholderManagerImpl.getInstance().parse( - condition.getPlayer(), - msg, - condition.getArgs() - ); - for (String text : replaced) { - AdventureHelper.getInstance().sendPlayerMessage(condition.getPlayer(), text); - } - }; - }); - registerAction("broadcast", (args, chance) -> { - ArrayList msg = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - List replaced = PlaceholderManagerImpl.getInstance().parse( - condition.getPlayer(), - msg, - condition.getArgs() - ); - for (Player player : Bukkit.getOnlinePlayers()) { - for (String text : replaced) { - AdventureHelper.getInstance().sendPlayerMessage(player, text); - } - } - }; - }); - registerAction("message-nearby", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List msg = section.getStringList("message"); - int range = section.getInt("range"); - return condition -> { - if (Math.random() > chance) return; - Player owner = condition.getPlayer(); - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : condition.getLocation().getWorld().getNearbyEntities(condition.getLocation(), range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), condition.getLocation()); - if (distance <= range) { - condition.insertArg("{near}", player.getName()); - List replaced = PlaceholderManagerImpl.getInstance().parse( - owner, - msg, - condition.getArgs() - ); - for (String text : replaced) { - AdventureHelper.getInstance().sendPlayerMessage((Player) player, text); - } - condition.delArg("{near}"); - } - } - }, condition.getLocation()); - }; - } else { - LogUtils.warn("Illegal value format found at action: message-nearby"); - return EmptyAction.instance; - } - }); - registerAction("random-message", (args, chance) -> { - ArrayList msg = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - String random = msg.get(ThreadLocalRandom.current().nextInt(msg.size())); - random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs()); - AdventureHelper.getInstance().sendPlayerMessage(condition.getPlayer(), random); - }; - }); - } - - private void registerCommandAction() { - registerAction("command", (args, chance) -> { - ArrayList cmd = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - List replaced = PlaceholderManagerImpl.getInstance().parse( - condition.getPlayer(), - cmd, - condition.getArgs() - ); - plugin.getScheduler().runTaskSync(() -> { - for (String text : replaced) { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text); - } - }, condition.getLocation()); - }; - }); - registerAction("random-command", (args, chance) -> { - ArrayList cmd = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - String random = cmd.get(ThreadLocalRandom.current().nextInt(cmd.size())); - random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs()); - String finalRandom = random; - plugin.getScheduler().runTaskSync(() -> { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom); - }, condition.getLocation()); - }; - }); - registerAction("command-nearby", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List cmd = section.getStringList("command"); - int range = section.getInt("range"); - return condition -> { - if (Math.random() > chance) return; - Player owner = condition.getPlayer(); - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : condition.getLocation().getWorld().getNearbyEntities(condition.getLocation(), range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), condition.getLocation()); - if (distance <= range) { - condition.insertArg("{near}", player.getName()); - List replaced = PlaceholderManagerImpl.getInstance().parse( - owner, - cmd, - condition.getArgs() - ); - for (String text : replaced) { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text); - } - condition.delArg("{near}"); - } - } - }, condition.getLocation()); - }; - } else { - LogUtils.warn("Illegal value format found at action: command-nearby"); - return EmptyAction.instance; - } - }); - } - - private void registerCloseInvAction() { - registerAction("close-inv", (args, chance) -> condition -> { - if (Math.random() > chance) return; - condition.getPlayer().closeInventory(); - }); - } - - private void registerActionBarAction() { - registerAction("actionbar", (args, chance) -> { - String text = (String) args; - return condition -> { - if (Math.random() > chance) return; - String parsed = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), text, condition.getArgs()); - AdventureHelper.getInstance().sendActionbar(condition.getPlayer(), parsed); - }; - }); - registerAction("random-actionbar", (args, chance) -> { - ArrayList texts = ConfigUtils.stringListArgs(args); - return condition -> { - if (Math.random() > chance) return; - String random = texts.get(ThreadLocalRandom.current().nextInt(texts.size())); - random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs()); - AdventureHelper.getInstance().sendActionbar(condition.getPlayer(), random); - }; - }); - registerAction("actionbar-nearby", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String actionbar = section.getString("actionbar"); - int range = section.getInt("range"); - return condition -> { - if (Math.random() > chance) return; - Player owner = condition.getPlayer(); - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : condition.getLocation().getWorld().getNearbyEntities(condition.getLocation(), range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), condition.getLocation()); - if (distance <= range) { - condition.insertArg("{near}", player.getName()); - String replaced = PlaceholderManagerImpl.getInstance().parse( - owner, - actionbar, - condition.getArgs() - ); - AdventureHelper.getInstance().sendActionbar((Player) player, replaced); - condition.delArg("{near}"); - } - } - }, condition.getLocation() - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: command-nearby"); - return EmptyAction.instance; - } - }); - } - - private void registerMendingAction() { - registerAction("mending", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - if (CustomFishingPlugin.get().getVersionManager().isSpigot()) { - condition.getPlayer().getLocation().getWorld().spawn(condition.getPlayer().getLocation(), ExperienceOrb.class, e -> e.setExperience((int) value.get(condition.getPlayer(), condition.getArgs()))); - } else { - condition.getPlayer().giveExp((int) value.get(condition.getPlayer(), condition.getArgs()), true); - AdventureHelper.getInstance().sendSound(condition.getPlayer(), Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1, 1); - } - }; - }); - } - - private void registerFoodAction() { - registerAction("food", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - player.setFoodLevel((int) (player.getFoodLevel() + value.get(player, condition.getArgs()))); - }; - }); - registerAction("saturation", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - player.setSaturation((float) (player.getSaturation() + value.get(player, condition.getArgs()))); - }; - }); - } - - private void registerExpAction() { - registerAction("exp", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - condition.getPlayer().giveExp((int) value.get(condition.getPlayer(), condition.getArgs())); - AdventureHelper.getInstance().sendSound(condition.getPlayer(), Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1, 1); - }; - }); - } - - private void registerHologramAction() { - registerAction("hologram", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String text = section.getString("text", ""); - int duration = section.getInt("duration", 20); - boolean position = section.getString("position", "other").equals("other"); - double x = section.getDouble("x"); - double y = section.getDouble("y"); - double z = section.getDouble("z"); - int range = section.getInt("range", 16); - return condition -> { - if (Math.random() > chance) return; - Player owner = condition.getPlayer(); - Location location = position ? condition.getLocation() : owner.getLocation(); - if (range > 0) { - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), location); - if (distance <= range) { - ArmorStandUtils.sendHologram( - (Player) player, - location.clone().add(x, y, z), - AdventureHelper.getInstance().getComponentFromMiniMessage( - PlaceholderManagerImpl.getInstance().parse(owner, text, condition.getArgs()) - ), - duration - ); - } - } - }, location - ); - } else { - ArmorStandUtils.sendHologram( - owner, - location.clone().add(x, y, z), - AdventureHelper.getInstance().getComponentFromMiniMessage( - PlaceholderManagerImpl.getInstance().parse(owner, text, condition.getArgs()) - ), - duration - ); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: hologram"); - return EmptyAction.instance; - } - }); - } - - private void registerItemAmountAction() { - registerAction("item-amount", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - boolean mainOrOff = section.getString("hand", "main").equalsIgnoreCase("main"); - int amount = section.getInt("amount", 1); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - ItemStack itemStack = mainOrOff ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand(); - itemStack.setAmount(Math.max(0, itemStack.getAmount() + amount)); - }; - } else { - LogUtils.warn("Illegal value format found at action: item-amount"); - return EmptyAction.instance; - } - }); - } - - private void registerItemDurabilityAction() { - registerAction("durability", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - EquipmentSlot slot = EquipmentSlot.valueOf(section.getString("slot", "hand").toUpperCase(Locale.ENGLISH)); - int amount = section.getInt("amount", 1); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - ItemStack itemStack = player.getInventory().getItem(slot); - if (amount > 0) { - ItemUtils.increaseDurability(itemStack, amount, true); - } else { - ItemUtils.decreaseDurability(condition.getPlayer(), itemStack, -amount, true); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: durability"); - return EmptyAction.instance; - } - }); - } - - private void registerGiveItemAction() { - registerAction("give-item", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String id = section.getString("item"); - int amount = section.getInt("amount", 1); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - ItemUtils.giveItem(player, Objects.requireNonNull(CustomFishingPlugin.get().getItemManager().buildAnyPluginItemByID(player, id)), amount); - }; - } else { - LogUtils.warn("Illegal value format found at action: give-item"); - return EmptyAction.instance; - } - }); - } - - private void registerFakeItemAction() { - registerAction("fake-item", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String[] itemSplit = section.getString("item", "").split(":", 2); - int duration = section.getInt("duration", 20); - boolean position = !section.getString("position", "player").equals("player"); - String x = ConfigUtils.getString(section.get("x", "0")); - String y = ConfigUtils.getString(section.get("y", "0")); - String z = ConfigUtils.getString(section.get("z", "0")); - String yaw = ConfigUtils.getString(section.get("yaw", "0")); - int range = section.getInt("range", 0); - boolean opposite = section.getBoolean("opposite-yaw", false); - return condition -> { - if (Math.random() > chance) return; - Player owner = condition.getPlayer(); - Location location = position ? condition.getLocation() : owner.getLocation(); - location = location.clone().add( - plugin.getPlaceholderManager().getExpressionValue(owner, x, condition.getArgs()), - plugin.getPlaceholderManager().getExpressionValue(owner, y, condition.getArgs()), - plugin.getPlaceholderManager().getExpressionValue(owner, z, condition.getArgs()) - ); - Location finalLocation = location; - ItemStack itemStack = plugin.getItemManager().build( - owner, itemSplit[0], - plugin.getPlaceholderManager().parse(owner, itemSplit[1], condition.getArgs()), - condition.getArgs() - ); - if (range > 0) { - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : finalLocation.getWorld().getNearbyEntities(finalLocation, range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), finalLocation); - if (distance <= range) { - Location locationTemp = finalLocation.clone(); - if (opposite) locationTemp.setYaw(-player.getLocation().getYaw()); - else locationTemp.setYaw((float) plugin.getPlaceholderManager().getExpressionValue((Player) player, yaw, condition.getArgs())); - ArmorStandUtils.sendFakeItem( - condition.getPlayer(), - locationTemp, - itemStack, - duration - ); - } - } - }, condition.getLocation() - ); - } else { - if (opposite) finalLocation.setYaw(-owner.getLocation().getYaw()); - else finalLocation.setYaw((float) plugin.getPlaceholderManager().getExpressionValue(owner, yaw, condition.getArgs())); - ArmorStandUtils.sendFakeItem( - condition.getPlayer(), - finalLocation, - itemStack, - duration - ); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: fake-item"); - return EmptyAction.instance; - } - }); - } - - private void registerChainAction() { - registerAction("chain", (args, chance) -> { - List actions = new ArrayList<>(); - if (args instanceof ConfigurationSection section) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - return condition -> { - if (Math.random() > chance) return; - for (Action action : actions) { - action.trigger(condition); - } - }; - }); - } - - private void registerMoneyAction() { - registerAction("give-money", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - VaultHook.getEconomy().depositPlayer(condition.getPlayer(), value.get(condition.getPlayer(), condition.getArgs())); - }; - }); - registerAction("take-money", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - VaultHook.getEconomy().withdrawPlayer(condition.getPlayer(), value.get(condition.getPlayer(), condition.getArgs())); - }; - }); - } - - private void registerDelayedAction() { - registerAction("delay", (args, chance) -> { - List actions = new ArrayList<>(); - int delay; - boolean async; - if (args instanceof ConfigurationSection section) { - delay = section.getInt("delay", 1); - async = section.getBoolean("async", false); - ConfigurationSection actionSection = section.getConfigurationSection("actions"); - if (actionSection != null) { - for (Map.Entry entry : actionSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - } else { - delay = 1; - async = false; - } - return condition -> { - if (Math.random() > chance) return; - if (async) { - plugin.getScheduler().runTaskSyncLater(() -> { - for (Action action : actions) { - action.trigger(condition); - } - }, condition.getLocation(), delay * 50L, TimeUnit.MILLISECONDS); - } else { - plugin.getScheduler().runTaskSyncLater(() -> { - for (Action action : actions) { - action.trigger(condition); - } - }, condition.getLocation(), delay * 50L, TimeUnit.MILLISECONDS); - } - }; - }); - } - - private void registerTimerAction() { - registerAction("timer", (args, chance) -> { - List actions = new ArrayList<>(); - int delay; - int duration; - int period; - boolean async; - if (args instanceof ConfigurationSection section) { - delay = section.getInt("delay", 2); - duration = section.getInt("duration", 20); - period = section.getInt("period", 2); - async = section.getBoolean("async", false); - ConfigurationSection actionSection = section.getConfigurationSection("actions"); - if (actionSection != null) { - for (Map.Entry entry : actionSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - } else { - delay = 1; - async = false; - duration = 20; - period = 1; - } - return condition -> { - if (Math.random() > chance) return; - CancellableTask cancellableTask; - if (async) { - cancellableTask = plugin.getScheduler().runTaskAsyncTimer(() -> { - for (Action action : actions) { - action.trigger(condition); - } - }, delay * 50L, period * 50L, TimeUnit.MILLISECONDS); - } else { - cancellableTask = plugin.getScheduler().runTaskSyncTimer(() -> { - for (Action action : actions) { - action.trigger(condition); - } - }, condition.getLocation(), delay, period); - } - plugin.getScheduler().runTaskSyncLater(cancellableTask::cancel, condition.getLocation(), duration); - }; - }); - } - - private void registerTitleAction() { - registerAction("title", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String title = section.getString("title"); - String subtitle = section.getString("subtitle"); - int fadeIn = section.getInt("fade-in", 20); - int stay = section.getInt("stay", 30); - int fadeOut = section.getInt("fade-out", 10); - return condition -> { - if (Math.random() > chance) return; - AdventureHelper.getInstance().sendTitle( - condition.getPlayer(), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), title, condition.getArgs()), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), subtitle, condition.getArgs()), - fadeIn, - stay, - fadeOut - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: title"); - return EmptyAction.instance; - } - }); - registerAction("title-nearby", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String title = section.getString("title"); - String subtitle = section.getString("subtitle"); - int fadeIn = section.getInt("fade-in", 20); - int stay = section.getInt("stay", 30); - int fadeOut = section.getInt("fade-out", 10); - int range = section.getInt("range", 0); - return condition -> { - if (Math.random() > chance) return; - plugin.getScheduler().runTaskSync(() -> { - for (Entity player : condition.getLocation().getWorld().getNearbyEntities(condition.getLocation(), range, range, range, entity -> entity instanceof Player)) { - double distance = LocationUtils.getDistance(player.getLocation(), condition.getLocation()); - if (distance <= range) { - condition.insertArg("{near}", player.getName()); - AdventureHelper.getInstance().sendTitle( - condition.getPlayer(), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), title, condition.getArgs()), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), subtitle, condition.getArgs()), - fadeIn, - stay, - fadeOut - ); - condition.delArg("{near}"); - } - } - }, condition.getLocation() - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: title-nearby"); - return EmptyAction.instance; - } - }); - registerAction("random-title", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List titles = section.getStringList("titles"); - if (titles.size() == 0) titles.add(""); - List subtitles = section.getStringList("subtitles"); - if (subtitles.size() == 0) subtitles.add(""); - int fadeIn = section.getInt("fade-in", 20); - int stay = section.getInt("stay", 30); - int fadeOut = section.getInt("fade-out", 10); - return condition -> { - if (Math.random() > chance) return; - AdventureHelper.getInstance().sendTitle( - condition.getPlayer(), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), titles.get(ThreadLocalRandom.current().nextInt(titles.size())), condition.getArgs()), - PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), subtitles.get(ThreadLocalRandom.current().nextInt(subtitles.size())), condition.getArgs()), - fadeIn, - stay, - fadeOut - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: random-title"); - return EmptyAction.instance; - } - }); - } - - private void registerPotionAction() { - registerAction("potion-effect", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - PotionEffect potionEffect = new PotionEffect( - Objects.requireNonNull(PotionEffectType.getByName(section.getString("type", "BLINDNESS").toUpperCase(Locale.ENGLISH))), - section.getInt("duration", 20), - section.getInt("amplifier", 0) - ); - return condition -> { - if (Math.random() > chance) return; - condition.getPlayer().addPotionEffect(potionEffect); - }; - } else { - LogUtils.warn("Illegal value format found at action: potion-effect"); - return EmptyAction.instance; - } - }); - } - - private void registerLevelAction() { - registerAction("level", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return condition -> { - if (Math.random() > chance) return; - Player player = condition.getPlayer(); - player.setLevel((int) Math.max(0, player.getLevel() + value.get(condition.getPlayer(), condition.getArgs()))); - }; - }); - } - - @SuppressWarnings("all") - private void registerSoundAction() { - registerAction("sound", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Sound sound = Sound.sound( - Key.key(section.getString("key")), - Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)), - (float) section.getDouble("volume", 1), - (float) section.getDouble("pitch", 1) - ); - return condition -> { - if (Math.random() > chance) return; - AdventureHelper.getInstance().sendSound(condition.getPlayer(), sound); - }; - } else { - LogUtils.warn("Illegal value format found at action: sound"); - return EmptyAction.instance; - } - }); - } - - private void registerConditionalAction() { - registerAction("conditional", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Action[] actions = getActions(section.getConfigurationSection("actions")); - Requirement[] requirements = plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions"), true); - return condition -> { - if (Math.random() > chance) return; - if (requirements != null) - for (Requirement requirement : requirements) { - if (!requirement.isConditionMet(condition)) { - return; - } - } - for (Action action : actions) { - action.trigger(condition); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: conditional"); - return EmptyAction.instance; - } - }); - } - - private void registerPriorityAction() { - registerAction("priority", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List> conditionActionPairList = new ArrayList<>(); - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - Action[] actions = getActions(inner.getConfigurationSection("actions")); - Requirement[] requirements = plugin.getRequirementManager().getRequirements(inner.getConfigurationSection("conditions"), false); - conditionActionPairList.add(Pair.of(requirements, actions)); - } - } - return condition -> { - if (Math.random() > chance) return; - outer: - for (Pair pair : conditionActionPairList) { - if (pair.left() != null) - for (Requirement requirement : pair.left()) { - if (!requirement.isConditionMet(condition)) { - continue outer; - } - } - if (pair.right() != null) - for (Action action : pair.right()) { - action.trigger(condition); - } - return; - } - }; - } else { - LogUtils.warn("Illegal value format found at action: priority"); - return EmptyAction.instance; - } - }); - } - - private void registerPluginExpAction() { - registerAction("plugin-exp", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String pluginName = section.getString("plugin"); - var value = ConfigUtils.getValue(section.get("exp")); - String target = section.getString("target"); - return condition -> { - if (Math.random() > chance) return; - Optional.ofNullable(plugin.getIntegrationManager().getLevelPlugin(pluginName)).ifPresentOrElse(it -> { - it.addXp(condition.getPlayer(), target, value.get(condition.getPlayer(), condition.getArgs())); - }, () -> LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.")); - }; - } else { - LogUtils.warn("Illegal value format found at action: plugin-exp"); - return EmptyAction.instance; - } - }); - } - - private void registerFishFindAction() { - registerAction("fish-finder", (args, chance) -> { - boolean arg = (boolean) args; - return condition -> { - if (Math.random() > chance) return; - condition.insertArg("{lava}", String.valueOf(arg)); - LootManager lootManager = plugin.getLootManager(); - List loots = plugin.getLootManager().getPossibleLootKeys(condition).stream().map(lootManager::getLoot).filter(Objects::nonNull).filter(Loot::showInFinder).map(Loot::getNick).toList(); - StringJoiner stringJoiner = new StringJoiner(CFLocale.MSG_Split_Char); - for (String loot : loots) { - stringJoiner.add(loot); - } - condition.delArg("{lava}"); - AdventureHelper.getInstance().sendMessageWithPrefix(condition.getPlayer(), CFLocale.MSG_Possible_Loots + stringJoiner); - }; - }); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/EmptyAction.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/EmptyAction.java deleted file mode 100644 index 27011f2b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/action/EmptyAction.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.momirealms.customfishing.mechanic.action; - -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.condition.Condition; - -/** - * An implementation of the Action interface that represents an empty action with no behavior. - * This class serves as a default action to prevent NPE. - */ -public class EmptyAction implements Action { - - public static EmptyAction instance = new EmptyAction(); - - @Override - public void trigger(Condition condition) { - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/bag/BagManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/bag/BagManagerImpl.java deleted file mode 100644 index fbec08ec..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/bag/BagManagerImpl.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.bag; - -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.manager.BagManager; -import net.momirealms.customfishing.api.manager.EffectManager; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import net.momirealms.customfishing.api.util.InventoryUtils; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.setting.CFConfig; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryAction; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class BagManagerImpl implements BagManager, Listener { - - private final CustomFishingPlugin plugin; - private final HashMap tempEditMap; - private Action[] collectLootActions; - private Action[] bagFullActions; - private boolean bagStoreLoots; - private String bagTitle; - private List bagWhiteListItems; - private Requirement[] collectRequirements; - - public BagManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - this.tempEditMap = new HashMap<>(); - } - - @Override - public boolean isEnabled() { - return CFConfig.enableFishingBag; - } - - public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); - YamlConfiguration config = plugin.getConfig("config.yml"); - ConfigurationSection bagSection = config.getConfigurationSection("mechanics.fishing-bag"); - if (bagSection != null) { - bagTitle = bagSection.getString("bag-title"); - bagStoreLoots = bagSection.getBoolean("can-store-loot", false); - bagWhiteListItems = bagSection.getStringList("whitelist-items").stream().map(it -> Material.valueOf(it.toUpperCase(Locale.ENGLISH))).toList(); - if (bagStoreLoots) { - collectLootActions = plugin.getActionManager().getActions(bagSection.getConfigurationSection("collect-actions")); - bagFullActions = plugin.getActionManager().getActions(bagSection.getConfigurationSection("full-actions")); - collectRequirements = plugin.getRequirementManager().getRequirements(bagSection.getConfigurationSection("collect-requirements"), false); - } - } - } - - public void unload() { - HandlerList.unregisterAll(this); - } - - public void disable() { - unload(); - plugin.getStorageManager().getDataSource().updateManyPlayersData(tempEditMap.values(), true); - } - - /** - * Retrieves the online bag inventory associated with a player's UUID. - * - * @param uuid The UUID of the player for whom the bag inventory is retrieved. - * @return The online bag inventory if the player is online, or null if not found. - */ - @Nullable - @Override - public Inventory getOnlineBagInventory(UUID uuid) { - var onlinePlayer = plugin.getStorageManager().getOnlineUser(uuid); - if (onlinePlayer == null) { - return null; - } - Player player = onlinePlayer.getPlayer(); - int rows = getBagInventoryRows(player); - Inventory bag = onlinePlayer.getHolder().getInventory(); - if (bag.getSize() != rows * 9) { - Inventory newBag = InventoryUtils.createInventory(onlinePlayer.getHolder(), rows * 9, AdventureHelper.getInstance().getComponentFromMiniMessage(PlaceholderManagerImpl.getInstance().parse(player, bagTitle, Map.of("{player}", player.getName())))); - onlinePlayer.getHolder().setInventory(newBag); - assert newBag != null; - ItemStack[] newContents = new ItemStack[rows * 9]; - ItemStack[] oldContents = bag.getContents(); - for (int i = 0; i < rows * 9 && i < oldContents.length; i++) { - newContents[i] = oldContents[i]; - } - newBag.setContents(newContents); - } - return onlinePlayer.getHolder().getInventory(); - } - - @Override - public int getBagInventoryRows(Player player) { - int size = 1; - for (int i = 6; i > 1; i--) { - if (player.hasPermission("fishingbag.rows." + i)) { - size = i; - break; - } - } - return size; - } - - /** - * Initiates the process of editing the bag inventory of an offline player by an admin. - * - * @param admin The admin player performing the edit. - * @param userData The OfflineUser data of the player whose bag is being edited. - */ - @Override - public void editOfflinePlayerBag(Player admin, OfflineUser userData) { - this.tempEditMap.put(admin.getUniqueId(), userData); - admin.openInventory(userData.getHolder().getInventory()); - } - - /** - * Handles the InventoryCloseEvent to save changes made to an offline player's bag inventory when it's closed. - * - * @param event The InventoryCloseEvent triggered when the inventory is closed. - */ - @EventHandler - public void onInvClose(InventoryCloseEvent event) { - if (!(event.getInventory().getHolder() instanceof FishingBagHolder)) - return; - final Player viewer = (Player) event.getPlayer(); - OfflineUser offlineUser = tempEditMap.remove(viewer.getUniqueId()); - if (offlineUser == null) - return; - plugin.getStorageManager().saveUserData(offlineUser, true); - } - - /** - * Handles InventoryClickEvent to prevent certain actions on the Fishing Bag inventory. - * This method cancels the event if specific conditions are met to restrict certain item interactions. - * - * @param event The InventoryClickEvent triggered when an item is clicked in an inventory. - */ - @EventHandler - public void onInvClick(InventoryClickEvent event) { - if (event.isCancelled()) - return; - if (!(event.getInventory().getHolder() instanceof FishingBagHolder)) - return; - Inventory clicked = event.getClickedInventory(); - ItemStack clickedItem = event.getCurrentItem(); - if (clicked != event.getWhoClicked().getInventory()) { - if (event.getAction() != InventoryAction.HOTBAR_SWAP && event.getAction() != InventoryAction.HOTBAR_MOVE_AND_READD) { - return; - } - clickedItem = event.getWhoClicked().getInventory().getItem(event.getHotbarButton()); - } - if (clickedItem == null || clickedItem.getType() == Material.AIR) - return; - if (bagWhiteListItems.contains(clickedItem.getType())) - return; - String id = plugin.getItemManager().getAnyPluginItemID(clickedItem); - EffectManager effectManager = plugin.getEffectManager(); - if (effectManager.hasEffectCarrier("rod", id) - || effectManager.hasEffectCarrier("bait", id) - || effectManager.hasEffectCarrier("util", id) - || effectManager.hasEffectCarrier("hook", id) - ) { - return; - } - if (bagStoreLoots && plugin.getLootManager().getLoot(id) != null) - return; - event.setCancelled(true); - } - - /** - * Event handler for the PlayerQuitEvent. - * This method is triggered when a player quits the server. - * It checks if the player was in the process of editing an offline player's bag inventory, - * and if so, saves the offline player's data if necessary. - * - * @param event The PlayerQuitEvent triggered when a player quits. - */ - @EventHandler - public void onQuit(PlayerQuitEvent event) { - OfflineUser offlineUser = tempEditMap.remove(event.getPlayer().getUniqueId()); - if (offlineUser == null) - return; - plugin.getStorageManager().saveUserData(offlineUser, true); - } - - @Override - public Action[] getCollectLootActions() { - return collectLootActions; - } - - @Override - public Action[] getBagFullActions() { - return bagFullActions; - } - - @Override - public boolean doesBagStoreLoots() { - return bagStoreLoots; - } - - @Override - public String getBagTitle() { - return bagTitle; - } - - @Override - public List getBagWhiteListItems() { - return bagWhiteListItems; - } - - @Override - public Requirement[] getCollectRequirements() { - return collectRequirements; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/block/BlockManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/block/BlockManagerImpl.java deleted file mode 100644 index 66514fc0..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/block/BlockManagerImpl.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.block; - -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.common.Tuple; -import net.momirealms.customfishing.api.manager.BlockManager; -import net.momirealms.customfishing.api.mechanic.block.*; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.block.VanillaBlockImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.*; -import org.bukkit.block.*; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Directional; -import org.bukkit.block.data.Rotatable; -import org.bukkit.block.data.type.Campfire; -import org.bukkit.block.data.type.Farmland; -import org.bukkit.block.data.type.NoteBlock; -import org.bukkit.block.data.type.TurtleEgg; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Ageable; -import org.bukkit.entity.FallingBlock; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class BlockManagerImpl implements BlockManager, Listener { - - private final CustomFishingPlugin plugin; - private final HashMap blockLibraryMap; - private BlockLibrary[] blockDetectionArray; - private final HashMap blockConfigMap; - private final HashMap dataBuilderMap; - private final HashMap stateBuilderMap; - - public BlockManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - this.blockLibraryMap = new HashMap<>(); - this.blockConfigMap = new HashMap<>(); - this.dataBuilderMap = new HashMap<>(); - this.stateBuilderMap = new HashMap<>(); - this.registerInbuiltProperties(); - this.registerBlockLibrary(new VanillaBlockImpl()); - } - - public void load() { - this.loadConfig(); - Bukkit.getPluginManager().registerEvents(this, plugin); - this.resetBlockDetectionOrder(); - } - - public void unload() { - HandlerList.unregisterAll(this); - HashMap tempMap = new HashMap<>(this.blockConfigMap); - this.blockConfigMap.clear(); - for (Map.Entry entry : tempMap.entrySet()) { - if (entry.getValue().isPersist()) { - tempMap.put(entry.getKey(), entry.getValue()); - } - } - } - - public void disable() { - this.blockLibraryMap.clear(); - } - - private void resetBlockDetectionOrder() { - ArrayList list = new ArrayList<>(); - for (String plugin : CFConfig.blockDetectOrder) { - BlockLibrary library = blockLibraryMap.get(plugin); - if (library != null) { - list.add(library); - } - } - this.blockDetectionArray = list.toArray(new BlockLibrary[0]); - } - - /** - * Event handler for the EntityChangeBlockEvent. - * This method is triggered when an entity changes a block, typically when a block falls or lands. - */ - @EventHandler - public void onBlockLands(EntityChangeBlockEvent event) { - if (event.isCancelled()) - return; - - // Retrieve a custom string value stored in the entity's persistent data container. - String temp = event.getEntity().getPersistentDataContainer().get( - Objects.requireNonNull(NamespacedKey.fromString("block", CustomFishingPlugin.get())), - PersistentDataType.STRING - ); - - // If the custom string value is not present, return without further action. - if (temp == null) return; - - // "BLOCK;PLAYER" - String[] split = temp.split(";"); - - // If no BlockConfig is found for the specified key, return without further action. - BlockConfig blockConfig = blockConfigMap.get(split[0]); - if (blockConfig == null) return; - - // If the player is not online or not found, remove the entity and set the block to air - Player player = Bukkit.getPlayer(split[1]); - if (player == null) { - event.getEntity().remove(); - event.getBlock().setType(Material.AIR); - return; - } - Location location = event.getBlock().getLocation(); - - // Apply block state modifiers from the BlockConfig to the block 1 tick later. - plugin.getScheduler().runTaskSyncLater(() -> { - BlockState state = location.getBlock().getState(); - for (BlockStateModifier modifier : blockConfig.getStateModifierList()) { - modifier.apply(player, state); - } - }, location, 50, TimeUnit.MILLISECONDS); - } - - /** - * Registers a BlockLibrary instance. - * This method associates a BlockLibrary with its unique identification and adds it to the registry. - * - * @param blockLibrary The BlockLibrary instance to register. - * @return True if the registration was successful (the identification is not already registered), false otherwise. - */ - @Override - public boolean registerBlockLibrary(BlockLibrary blockLibrary) { - if (this.blockLibraryMap.containsKey(blockLibrary.identification())) return false; - this.blockLibraryMap.put(blockLibrary.identification(), blockLibrary); - this.resetBlockDetectionOrder(); - return true; - } - - /** - * Unregisters a BlockLibrary instance by its identification. - * This method removes a BlockLibrary from the registry based on its unique identification. - * - * @param identification The unique identification of the BlockLibrary to unregister. - * @return True if the BlockLibrary was successfully unregistered, false if it was not found. - */ - @Override - public boolean unregisterBlockLibrary(String identification) { - boolean success = blockLibraryMap.remove(identification) != null; - if (success) - this.resetBlockDetectionOrder(); - return success; - } - - /** - * Registers a BlockDataModifierBuilder for a specific type. - * This method associates a BlockDataModifierBuilder with its type and adds it to the registry. - * - * @param type The type of the BlockDataModifierBuilder to register. - * @param builder The BlockDataModifierBuilder instance to register. - * @return True if the registration was successful (the type is not already registered), false otherwise. - */ - @Override - public boolean registerBlockDataModifierBuilder(String type, BlockDataModifierBuilder builder) { - if (dataBuilderMap.containsKey(type)) return false; - dataBuilderMap.put(type, builder); - return true; - } - - /** - * Registers a BlockStateModifierBuilder for a specific type. - * This method associates a BlockStateModifierBuilder with its type and adds it to the registry. - * - * @param type The type of the BlockStateModifierBuilder to register. - * @param builder The BlockStateModifierBuilder instance to register. - * @return True if the registration was successful (the type is not already registered), false otherwise. - */ - @Override - public boolean registerBlockStateModifierBuilder(String type, BlockStateModifierBuilder builder) { - if (stateBuilderMap.containsKey(type)) return false; - stateBuilderMap.put(type, builder); - return true; - } - - /** - * Unregisters a BlockDataModifierBuilder with the specified type. - * - * @param type The type of the BlockDataModifierBuilder to unregister. - * @return True if the BlockDataModifierBuilder was successfully unregistered, false otherwise. - */ - @Override - public boolean unregisterBlockDataModifierBuilder(String type) { - return dataBuilderMap.remove(type) != null; - } - - /** - * Unregisters a BlockStateModifierBuilder with the specified type. - * - * @param type The type of the BlockStateModifierBuilder to unregister. - * @return True if the BlockStateModifierBuilder was successfully unregistered, false otherwise. - */ - @Override - public boolean unregisterBlockStateModifierBuilder(String type) { - return stateBuilderMap.remove(type) != null; - } - - private void registerInbuiltProperties() { - this.registerDirectional(); - this.registerStorage(); - this.registerRotatable(); - this.registerTurtleEggs(); - this.registerMoisture(); - this.registerNoteBlock(); - this.registerCampfire(); - this.registerAge(); - } - - /** - * Loads configuration files from the plugin's data folder and processes them. - * Configuration files are organized by type (e.g., "block"). - */ - @SuppressWarnings("DuplicatedCode") - private void loadConfig() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("block")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile); - } - } - } - } - } - - /** - * Loads configuration data from a single YAML file and processes it to create BlockConfig instances. - * - * @param file The YAML file to load and process. - */ - private void loadSingleFile(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - - // Check if the "block" is null and log a warning if so. - String blockID = section.getString("block"); - if (blockID == null) { - LogUtils.warn("Block can't be null. File:" + file.getAbsolutePath() + "; Section:" + section.getCurrentPath()); - continue; - } - List dataModifiers = new ArrayList<>(); - List stateModifiers = new ArrayList<>(); - - // If a "properties" section exists, process its entries. - ConfigurationSection property = section.getConfigurationSection("properties"); - if (property != null) { - for (Map.Entry innerEntry : property.getValues(false).entrySet()) { - BlockDataModifierBuilder dataBuilder = dataBuilderMap.get(innerEntry.getKey()); - if (dataBuilder != null) { - dataModifiers.add(dataBuilder.build(innerEntry.getValue())); - continue; - } - BlockStateModifierBuilder stateBuilder = stateBuilderMap.get(innerEntry.getKey()); - if (stateBuilder != null) { - stateModifiers.add(stateBuilder.build(innerEntry.getValue())); - } - } - } - - // Create a BlockConfig instance with the processed data and add it to the blockConfigMap. - BlockConfig blockConfig = new BlockConfig.Builder() - .blockID(blockID) - .persist(false) - .horizontalVector(section.getDouble("velocity.horizontal", 1.1)) - .verticalVector(section.getDouble("velocity.vertical", 1.2)) - .dataModifiers(dataModifiers) - .stateModifiers(stateModifiers) - .build(); - blockConfigMap.put(entry.getKey(), blockConfig); - } - } - } - - /** - * Summons a falling block at a specified location based on the provided loot. - * This method spawns a falling block at the given hookLocation with specific properties determined by the loot. - * - * @param player The player who triggered the action. - * @param hookLocation The location where the hook is positioned. - * @param playerLocation The location of the player. - * @param loot The loot to be associated with the summoned block. - */ - @Override - public void summonBlock(Player player, Location hookLocation, Location playerLocation, Loot loot) { - BlockConfig config = blockConfigMap.get(loot.getID()); - if (config == null) { - LogUtils.warn("Block: " + loot.getID() + " doesn't exist."); - return; - } - String blockID = config.getBlockID(); - BlockData blockData; - if (blockID.contains(":")) { - String[] split = blockID.split(":", 2); - String lib = split[0]; - String id = split[1]; - blockData = blockLibraryMap.get(lib).getBlockData(player, id, config.getDataModifier()); - } else { - blockData = blockLibraryMap.get("vanilla").getBlockData(player, blockID, config.getDataModifier()); - } - FallingBlock fallingBlock = hookLocation.getWorld().spawnFallingBlock(hookLocation, blockData); - fallingBlock.getPersistentDataContainer().set( - Objects.requireNonNull(NamespacedKey.fromString("block", CustomFishingPlugin.get())), - PersistentDataType.STRING, - loot.getID() + ";" + player.getName() - ); - Vector vector = playerLocation.subtract(hookLocation).toVector().multiply((config.getHorizontalVector()) - 1); - vector = vector.setY((vector.getY() + 0.2) * config.getVerticalVector()); - fallingBlock.setVelocity(vector); - } - - /** - * Retrieves the block ID associated with a given Block instance using block detection order. - * This method iterates through the configured block detection order to find the block's ID - * by checking different BlockLibrary instances in the specified order. - * - * @param block The Block instance for which to retrieve the block ID. - * @return The block ID - */ - @Override - @NotNull - public String getAnyPluginBlockID(Block block) { - for (BlockLibrary blockLibrary : blockDetectionArray) { - String id = blockLibrary.getBlockID(block); - if (id != null) { - return id; - } - } - // Should not reach this because vanilla library would always work - return "AIR"; - } - - private void registerDirectional() { - this.registerBlockDataModifierBuilder("directional-4", (args) -> (player, blockData) -> { - boolean arg = (boolean) args; - if (arg && blockData instanceof Directional directional) { - directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 4)]); - } - }); - this.registerBlockDataModifierBuilder("directional-6", (args) -> (player, blockData) -> { - boolean arg = (boolean) args; - if (arg && blockData instanceof Directional directional) { - directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 6)]); - } - }); - } - - private void registerMoisture() { - this.registerBlockDataModifierBuilder("moisture", (args) -> { - int arg = (int) args; - return (player, blockData) -> { - if (blockData instanceof Farmland farmland) { - farmland.setMoisture(arg); - } - }; - }); - } - - private void registerCampfire() { - this.registerBlockDataModifierBuilder("campfire", (args) -> { - boolean arg = (boolean) args; - return (player, blockData) -> { - if (blockData instanceof Campfire campfire) { - campfire.setSignalFire(arg); - } - }; - }); - } - - private void registerRotatable() { - this.registerBlockDataModifierBuilder("rotatable", (args) -> { - boolean arg = (boolean) args; - return (player, blockData) -> { - if (arg && blockData instanceof Rotatable rotatable) { - rotatable.setRotation(BlockFace.values()[ThreadLocalRandom.current().nextInt(BlockFace.values().length)]); - } - }; - }); - } - - private void registerNoteBlock() { - this.registerBlockDataModifierBuilder("noteblock", (args) -> { - if (args instanceof ConfigurationSection section) { - var instrument = Instrument.valueOf(section.getString("instrument")); - var note = new Note(section.getInt("note")); - return (player, blockData) -> { - if (blockData instanceof NoteBlock noteBlock) { - noteBlock.setNote(note); - noteBlock.setInstrument(instrument); - } - }; - } else { - LogUtils.warn("Invalid property format found at block noteblock."); - return null; - } - }); - } - - private void registerAge() { - this.registerBlockDataModifierBuilder("age", (args) -> { - int arg = (int) args; - return (player, blockData) -> { - if (blockData instanceof Ageable ageable) { - ageable.setAge(arg); - } - }; - }); - } - - private void registerTurtleEggs() { - this.registerBlockDataModifierBuilder("turtle-eggs", (args) -> { - int arg = (int) args; - return (player, blockData) -> { - if (blockData instanceof TurtleEgg egg) { - egg.setEggs(arg); - } - }; - }); - } - - private void registerStorage() { - this.registerBlockStateModifierBuilder("storage", (args) -> { - if (args instanceof ConfigurationSection section) { - ArrayList>> tempChanceList = new ArrayList<>(); - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - String item = inner.getString("item"); - Pair amountPair = ConfigUtils.splitStringIntegerArgs(inner.getString("amount","1~1"), "~"); - double chance = inner.getDouble("chance", 1); - tempChanceList.add(Tuple.of(chance, item, amountPair)); - } - } - return (player, blockState) -> { - if (blockState instanceof Chest chest) { - setInventoryItems(tempChanceList, player, chest.getInventory()); - return; - } - if (blockState instanceof Barrel barrel) { - setInventoryItems(tempChanceList, player, barrel.getInventory()); - return; - } - if (blockState instanceof ShulkerBox shulkerBox) { - setInventoryItems(tempChanceList, player, shulkerBox.getInventory()); - return; - } - }; - } else { - LogUtils.warn("Invalid property format found at block storage."); - return null; - } - }); - } - - /** - * Sets items in the BLOCK's inventory based on chance and configuration. - * - * @param tempChanceList A list of tuples containing chance, item ID, and quantity range for each item. - * @param player The inventory items are being set. - * @param inventory The inventory where the items will be placed. - */ - private void setInventoryItems( - ArrayList>> tempChanceList, - Player player, - Inventory inventory - ) { - LinkedList unused = new LinkedList<>(); - for (int i = 0; i < 27; i++) { - unused.add(i); - } - Collections.shuffle(unused); - for (Tuple> tuple : tempChanceList) { - ItemStack itemStack = plugin.getItemManager().buildAnyPluginItemByID(player, tuple.getMid()); - itemStack.setAmount(ThreadLocalRandom.current().nextInt(tuple.getRight().left(), tuple.getRight().right() + 1)); - if (tuple.getLeft() > Math.random()) { - inventory.setItem(unused.pop(), itemStack); - } - } - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java deleted file mode 100644 index 17ac9644..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/Competition.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.competition; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.event.CompetitionEvent; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig; -import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal; -import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import net.momirealms.customfishing.api.mechanic.competition.Ranking; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.mechanic.competition.actionbar.ActionBarManager; -import net.momirealms.customfishing.mechanic.competition.bossbar.BossBarManager; -import net.momirealms.customfishing.mechanic.competition.ranking.LocalRankingImpl; -import net.momirealms.customfishing.mechanic.competition.ranking.RedisRankingImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class Competition implements FishingCompetition { - - private final CompetitionConfig config; - private CancellableTask competitionTimerTask; - private final CompetitionGoal goal; - private final ConcurrentHashMap publicPlaceholders; - private final Ranking ranking; - private float progress; - private long remainingTime; - private long startTime; - private BossBarManager bossBarManager; - private ActionBarManager actionBarManager; - - public Competition(CompetitionConfig config) { - this.config = config; - this.goal = config.getGoal() == CompetitionGoal.RANDOM ? CompetitionGoal.getRandom() : config.getGoal(); - if (CFConfig.redisRanking) this.ranking = new RedisRankingImpl(); - else this.ranking = new LocalRankingImpl(); - this.publicPlaceholders = new ConcurrentHashMap<>(); - this.publicPlaceholders.put("{goal}", CustomFishingPlugin.get().getCompetitionManager().getCompetitionGoalLocale(goal)); - } - - /** - * Starts the fishing competition, initializing its settings and actions. - * This method sets the initial progress, remaining time, start time, and updates public placeholders. - * It also arranges timer tasks for competition timing and initializes boss bar and action bar managers if configured. - * Additionally, it triggers the start actions defined in the competition's configuration. - */ - @Override - public void start() { - this.progress = 1; - this.remainingTime = config.getDurationInSeconds(); - this.startTime = Instant.now().getEpochSecond(); - - this.arrangeTimerTask(); - if (config.getBossBarConfig() != null) { - this.bossBarManager = new BossBarManager(config.getBossBarConfig(), this); - this.bossBarManager.load(); - } - if (config.getActionBarConfig() != null) { - this.actionBarManager = new ActionBarManager(config.getActionBarConfig(), this); - this.actionBarManager.load(); - } - - Action[] actions = config.getStartActions(); - if (actions != null) { - Condition condition = new Condition(null, null, this.publicPlaceholders); - for (Action action : actions) { - action.trigger(condition); - } - } - - this.ranking.clear(); - this.updatePublicPlaceholders(); - - CompetitionEvent competitionStartEvent = new CompetitionEvent(CompetitionEvent.State.START, this); - Bukkit.getPluginManager().callEvent(competitionStartEvent); - } - - /** - * Arranges the timer task for the fishing competition. - * This method schedules a recurring task that updates the competition's remaining time and public placeholders. - * If the remaining time reaches zero, the competition is ended. - */ - private void arrangeTimerTask() { - this.competitionTimerTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { - if (decreaseTime()) { - end(true); - return; - } - updatePublicPlaceholders(); - }, 1, 1, TimeUnit.SECONDS); - } - - /** - * Update public placeholders for the fishing competition. - * This method updates placeholders representing player rankings, remaining time, and score in public messages. - * Placeholders for player rankings include {1_player}, {1_score}, {2_player}, {2_score}, and so on. - * The placeholders for time include {hour}, {minute}, {second}, and {seconds}. - */ - private void updatePublicPlaceholders() { - for (int i = 1; i < CFConfig.placeholderLimit + 1; i++) { - int finalI = i; - Optional.ofNullable(ranking.getPlayerAt(i)).ifPresentOrElse(player -> { - publicPlaceholders.put("{" + finalI + "_player}", player); - publicPlaceholders.put("{" + finalI + "_score}", String.format("%.2f", ranking.getScoreAt(finalI))); - }, () -> { - publicPlaceholders.put("{" + finalI + "_player}", CFLocale.MSG_No_Player); - publicPlaceholders.put("{" + finalI + "_score}", CFLocale.MSG_No_Score); - }); - } - publicPlaceholders.put("{hour}", remainingTime < 3600 ? "" : (remainingTime / 3600) + CFLocale.FORMAT_Hour); - publicPlaceholders.put("{minute}", remainingTime < 60 ? "" : (remainingTime % 3600) / 60 + CFLocale.FORMAT_Minute); - publicPlaceholders.put("{second}", remainingTime == 0 ? "" : remainingTime % 60 + CFLocale.FORMAT_Second); - publicPlaceholders.put("{seconds}", String.valueOf(remainingTime)); - } - - /** - * Stop the fishing competition. - * This method cancels the competition timer task, unloads boss bars and action bars, clears the ranking, - * and sets the remaining time to zero. - */ - @Override - public void stop(boolean triggerEvent) { - if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel(); - if (this.bossBarManager != null) this.bossBarManager.unload(); - if (this.actionBarManager != null) this.actionBarManager.unload(); - this.ranking.clear(); - this.remainingTime = 0; - - if (triggerEvent) { - CompetitionEvent competitionEvent = new CompetitionEvent(CompetitionEvent.State.STOP, this); - Bukkit.getPluginManager().callEvent(competitionEvent); - } - } - - /** - * End the fishing competition. - * This method marks the competition as ended, cancels sub-tasks such as timers and bar management, - * gives prizes to top participants and participation rewards, performs end actions, and clears the ranking. - */ - @Override - public void end(boolean triggerEvent) { - // mark it as ended - this.remainingTime = 0; - - // cancel some sub tasks - if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel(); - if (this.bossBarManager != null) this.bossBarManager.unload(); - if (this.actionBarManager != null) this.actionBarManager.unload(); - - // give prizes - HashMap rewardsMap = config.getRewards(); - if (ranking.getSize() != 0 && rewardsMap != null) { - Iterator> iterator = ranking.getIterator(); - int i = 1; - while (iterator.hasNext()) { - Pair competitionPlayer = iterator.next(); - this.publicPlaceholders.put("{" + i + "_player}", competitionPlayer.left()); - this.publicPlaceholders.put("{" + i + "_score}", String.format("%.2f", competitionPlayer.right())); - if (i < rewardsMap.size()) { - Player player = Bukkit.getPlayer(competitionPlayer.left()); - if (player != null) - for (Action action : rewardsMap.get(String.valueOf(i))) - action.trigger(new Condition(player, this.publicPlaceholders)); - } else { - Action[] actions = rewardsMap.get("participation"); - if (actions != null) { - Player player = Bukkit.getPlayer(competitionPlayer.left()); { - if (player != null) - for (Action action : actions) - action.trigger(new Condition(player, this.publicPlaceholders)); - } - } - } - i++; - } - } - - // do end actions - Action[] actions = config.getEndActions(); - if (actions != null) { - Condition condition = new Condition(null, null, new HashMap<>(publicPlaceholders)); - for (Action action : actions) { - action.trigger(condition); - } - } - - // call event - if (triggerEvent) { - CompetitionEvent competitionEndEvent = new CompetitionEvent(CompetitionEvent.State.END, this); - Bukkit.getPluginManager().callEvent(competitionEndEvent); - } - - // 1 seconds delay for other servers to read the redis data - CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(this.ranking::clear, 1, TimeUnit.SECONDS); - } - - /** - * Check if the fishing competition is ongoing. - * - * @return {@code true} if the competition is still ongoing, {@code false} if it has ended. - */ - @Override - public boolean isOnGoing() { - return remainingTime > 0; - } - - /** - * Decreases the remaining time for the fishing competition and updates the progress. - * - * @return {@code true} if the remaining time becomes zero or less, indicating the competition has ended. - */ - private boolean decreaseTime() { - long current = Instant.now().getEpochSecond(); - int duration = config.getDurationInSeconds(); - remainingTime = duration - (current - startTime); - progress = (float) remainingTime / duration; - return remainingTime <= 0; - } - - /** - * Refreshes the data for a player in the fishing competition, including updating their score and triggering - * actions if it's their first time joining the competition. - * - * @param player The player whose data needs to be refreshed. - * @param score The player's current score in the competition. - */ - @Override - public void refreshData(Player player, double score) { - // if player join for the first time, trigger join actions - if (!hasPlayerJoined(player)) { - Action[] actions = config.getJoinActions(); - if (actions != null) { - Condition condition = new Condition(player); - for (Action action : actions) { - action.trigger(condition); - } - } - } - - // show competition info - if (this.bossBarManager != null) this.bossBarManager.showBossBarTo(player); - if (this.actionBarManager != null) this.actionBarManager.showActionBarTo(player); - - // refresh data - switch (this.goal) { - case CATCH_AMOUNT -> ranking.refreshData(player.getName(), 1); - case TOTAL_SIZE, TOTAL_SCORE -> ranking.refreshData(player.getName(), score); - case MAX_SIZE -> { - if (score > ranking.getPlayerScore(player.getName())) { - ranking.setData(player.getName(), score); - } - } - } - } - - /** - * Checks if a player has joined the fishing competition based on their name. - * - * @param player The player to check for participation. - * @return {@code true} if the player has joined the competition; {@code false} otherwise. - */ - @Override - public boolean hasPlayerJoined(OfflinePlayer player) { - return ranking.getPlayerRank(player.getName()) != -1; - } - - /** - * Gets the progress of the fishing competition as a float value (0~1). - * - * @return The progress of the fishing competition as a float. - */ - @Override - public float getProgress() { - return progress; - } - - /** - * Gets the remaining time in seconds for the fishing competition. - * - * @return The remaining time in seconds. - */ - @Override - public long getRemainingTime() { - return remainingTime; - } - - /** - * Gets the start time of the fishing competition. - * - * @return The start time of the fishing competition. - */ - @Override - public long getStartTime() { - return startTime; - } - - /** - * Gets the configuration of the fishing competition. - * - * @return The configuration of the fishing competition. - */ - @NotNull - @Override - public CompetitionConfig getConfig() { - return config; - } - - /** - * Gets the goal of the fishing competition. - * - * @return The goal of the fishing competition. - */ - @NotNull - @Override - public CompetitionGoal getGoal() { - return goal; - } - - /** - * Gets the ranking data for the fishing competition. - * - * @return The ranking data for the fishing competition. - */ - @NotNull - @Override - public Ranking getRanking() { - return ranking; - } - - /** - * Gets the cached placeholders for the fishing competition. - * - * @return A ConcurrentHashMap containing cached placeholders. - */ - @NotNull - @Override - public Map getCachedPlaceholders() { - return publicPlaceholders; - } - - /** - * Gets a specific cached placeholder value by its key. - * - * @param papi The key of the cached placeholder. - * @return The cached placeholder value as a string, or null if not found. - */ - @Override - public String getCachedPlaceholder(String papi) { - return publicPlaceholders.get(papi); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarSender.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarSender.java deleted file mode 100644 index 84124c45..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/actionbar/ActionBarSender.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.competition.actionbar; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.competition.ActionBarConfig; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.mechanic.competition.Competition; -import net.momirealms.customfishing.mechanic.misc.DynamicText; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.concurrent.TimeUnit; - -/** - * Manages and updates ActionBar messages for a specific player in a competition context. - */ -public class ActionBarSender { - - private final Player player; - private int refreshTimer; - private int switchTimer; - private int counter; - private final DynamicText[] texts; - private CancellableTask senderTask; - private final ActionBarConfig config; - private boolean isShown; - private final Competition competition; - private final HashMap privatePlaceholders; - - /** - * Creates a new ActionBarSender instance for a player. - * - * @param player The player to manage ActionBar messages for. - * @param config The configuration for ActionBar messages. - * @param competition The competition associated with this ActionBarSender. - */ - public ActionBarSender(Player player, ActionBarConfig config, Competition competition) { - this.player = player; - this.config = config; - this.isShown = false; - this.competition = competition; - this.privatePlaceholders = new HashMap<>(); - this.privatePlaceholders.put("{player}", player.getName()); - this.updatePrivatePlaceholders(); - - String[] str = config.getTexts(); - texts = new DynamicText[str.length]; - for (int i = 0; i < str.length; i++) { - texts[i] = new DynamicText(player, str[i]); - texts[i].update(privatePlaceholders); - } - } - - /** - * Updates private placeholders used in ActionBar messages. - */ - @SuppressWarnings("DuplicatedCode") - private void updatePrivatePlaceholders() { - this.privatePlaceholders.put("{score}", String.format("%.2f", competition.getRanking().getPlayerScore(player.getName()))); - int rank = competition.getRanking().getPlayerRank(player.getName()); - this.privatePlaceholders.put("{rank}", rank != -1 ? String.valueOf(rank) : CFLocale.MSG_No_Rank); - this.privatePlaceholders.putAll(competition.getCachedPlaceholders()); - } - - /** - * Shows the ActionBar message to the player. - */ - public void show() { - this.isShown = true; - senderTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { - switchTimer++; - if (switchTimer > config.getSwitchInterval()) { - switchTimer = 0; - counter++; - } - if (refreshTimer < config.getRefreshRate()){ - refreshTimer++; - } else { - refreshTimer = 0; - DynamicText text = texts[counter % (texts.length)]; - updatePrivatePlaceholders(); - text.update(privatePlaceholders); - AdventureHelper.getInstance().sendActionbar( - player, - text.getLatestValue() - ); - } - }, 50, 50, TimeUnit.MILLISECONDS); - } - - /** - * Hides the ActionBar message from the player. - */ - public void hide() { - if (senderTask != null && !senderTask.isCancelled()) - senderTask.cancel(); - this.isShown = false; - } - - /** - * Checks if the ActionBar message is currently visible to the player. - * - * @return True if the ActionBar message is visible, false otherwise. - */ - public boolean isVisible() { - return this.isShown; - } - - /** - * Gets the ActionBar configuration. - * - * @return The ActionBar configuration. - */ - public ActionBarConfig getConfig() { - return config; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarSender.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarSender.java deleted file mode 100644 index 262ceda3..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/competition/bossbar/BossBarSender.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.competition.bossbar; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.InternalStructure; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.WrappedChatComponent; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.competition.BossBarConfig; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.util.ReflectionUtils; -import net.momirealms.customfishing.mechanic.competition.Competition; -import net.momirealms.customfishing.mechanic.misc.DynamicText; -import net.momirealms.customfishing.setting.CFLocale; -import org.bukkit.boss.BarColor; -import org.bukkit.entity.Player; - -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * Manages and updates boss bars for a specific player in a competition context. - */ -public class BossBarSender { - - private final Player player; - private int refreshTimer; - private int switchTimer; - private int counter; - private final DynamicText[] texts; - private CancellableTask senderTask; - private final UUID uuid; - private final BossBarConfig config; - private boolean isShown; - private final Competition competition; - private final HashMap privatePlaceholders; - - /** - * Creates a new BossBarSender instance for a player. - * - * @param player The player to manage the boss bar for. - * @param config The configuration for the boss bar. - * @param competition The competition associated with this boss bar. - */ - public BossBarSender(Player player, BossBarConfig config, Competition competition) { - this.player = player; - this.uuid = UUID.randomUUID(); - this.config = config; - this.isShown = false; - this.competition = competition; - this.privatePlaceholders = new HashMap<>(); - this.privatePlaceholders.put("{player}", player.getName()); - this.updatePrivatePlaceholders(); - - String[] str = config.getTexts(); - texts = new DynamicText[str.length]; - for (int i = 0; i < str.length; i++) { - texts[i] = new DynamicText(player, str[i]); - texts[i].update(privatePlaceholders); - } - } - - /** - * Updates private placeholders used in boss bar messages. - */ - @SuppressWarnings("DuplicatedCode") - private void updatePrivatePlaceholders() { - this.privatePlaceholders.put("{score}", String.format("%.2f", competition.getRanking().getPlayerScore(player.getName()))); - int rank = competition.getRanking().getPlayerRank(player.getName()); - this.privatePlaceholders.put("{rank}", rank != -1 ? String.valueOf(rank) : CFLocale.MSG_No_Rank); - this.privatePlaceholders.putAll(competition.getCachedPlaceholders()); - } - - /** - * Shows the boss bar to the player. - */ - public void show() { - this.isShown = true; - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getCreatePacket()); - senderTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> { - switchTimer++; - if (switchTimer > config.getSwitchInterval()) { - switchTimer = 0; - counter++; - } - if (refreshTimer < config.getRefreshRate()){ - refreshTimer++; - } else { - refreshTimer = 0; - DynamicText text = texts[counter % (texts.length)]; - updatePrivatePlaceholders(); - if (text.update(privatePlaceholders)) { - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getUpdatePacket(text)); - } - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getProgressPacket()); - } - }, 50, 50, TimeUnit.MILLISECONDS); - } - - /** - * Checks if the boss bar is currently visible to the player. - * - * @return True if the boss bar is visible, false otherwise. - */ - public boolean isVisible() { - return this.isShown; - } - - /** - * Gets the boss bar configuration. - * - * @return The boss bar configuration. - */ - public BossBarConfig getConfig() { - return config; - } - - /** - * Hides the boss bar from the player. - */ - public void hide() { - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getRemovePacket()); - if (senderTask != null && !senderTask.isCancelled()) senderTask.cancel(); - this.isShown = false; - } - - private PacketContainer getUpdatePacket(DynamicText text) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS); - packet.getModifier().write(0, uuid); - try { - Object chatComponent = ReflectionUtils.iChatComponentMethod.invoke(null, - GsonComponentSerializer.gson().serialize( - AdventureHelper.getInstance().getComponentFromMiniMessage( - text.getLatestValue() - ))); - Object updatePacket = ReflectionUtils.updateConstructor.newInstance(chatComponent); - packet.getModifier().write(1, updatePacket); - } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { - throw new RuntimeException(e); - } - return packet; - } - - private PacketContainer getProgressPacket() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS); - packet.getModifier().write(0, uuid); - try { - Object updatePacket = ReflectionUtils.progressConstructor.newInstance(competition.getProgress()); - packet.getModifier().write(1, updatePacket); - } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { - throw new RuntimeException(e); - } - return packet; - } - - private PacketContainer getCreatePacket() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS); - packet.getModifier().write(0, uuid); - InternalStructure internalStructure = packet.getStructures().read(1); - internalStructure.getChatComponents().write(0, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(MiniMessage.miniMessage().deserialize(texts[0].getLatestValue())))); - internalStructure.getFloat().write(0, competition.getProgress()); - internalStructure.getEnumModifier(BarColor.class, 2).write(0, config.getColor()); - internalStructure.getEnumModifier(BossBarConfig.Overlay.class, 3).write(0, config.getOverlay()); - internalStructure.getModifier().write(4, false); - internalStructure.getModifier().write(5, false); - internalStructure.getModifier().write(6, false); - return packet; - } - - private PacketContainer getRemovePacket() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS); - packet.getModifier().write(0, uuid); - packet.getModifier().write(1, ReflectionUtils.removeBossBarPacket); - return packet; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/effect/EffectManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/effect/EffectManagerImpl.java deleted file mode 100644 index fc0f978b..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/effect/EffectManagerImpl.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.effect; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.manager.EffectManager; -import net.momirealms.customfishing.api.mechanic.GlobalSettings; -import net.momirealms.customfishing.api.mechanic.effect.BaseEffect; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.mechanic.misc.value.PlainValue; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -public class EffectManagerImpl implements EffectManager { - - private final CustomFishingPlugin plugin; - - private final HashMap effectMap; - - public EffectManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.effectMap = new HashMap<>(); - } - - public void disable() { - this.effectMap.clear(); - } - - /** - * Registers an EffectCarrier with a unique Key. - * - * @param key The unique Key associated with the EffectCarrier. - * @param effect The EffectCarrier to be registered. - * @return True if the registration was successful, false if the Key already exists. - */ - @Override - public boolean registerEffectCarrier(Key key, EffectCarrier effect) { - if (effectMap.containsKey(key)) return false; - this.effectMap.put(key, effect); - return true; - } - - /** - * Unregisters an EffectCarrier associated with the specified Key. - * - * @param key The unique Key of the EffectCarrier to unregister. - * @return True if the EffectCarrier was successfully unregistered, false if the Key does not exist. - */ - @Override - public boolean unregisterEffectCarrier(Key key) { - return this.effectMap.remove(key) != null; - } - - /** - * Checks if an EffectCarrier with the specified namespace and id exists. - * - * @param namespace The namespace of the EffectCarrier. - * @param id The unique identifier of the EffectCarrier. - * @return True if an EffectCarrier with the given namespace and id exists, false otherwise. - */ - @Override - public boolean hasEffectCarrier(String namespace, String id) { - return effectMap.containsKey(Key.of(namespace, id)); - } - - /** - * Retrieves an EffectCarrier with the specified namespace and id. - * - * @param namespace The namespace of the EffectCarrier. - * @param id The unique identifier of the EffectCarrier. - * @return The EffectCarrier with the given namespace and id, or null if it doesn't exist. - */ - @Nullable - @Override - public EffectCarrier getEffectCarrier(String namespace, String id) { - return effectMap.get(Key.of(namespace, id)); - } - - public void load() { - this.loadFiles(); - this.loadGlobalEffects(); - } - - /** - * Loads EffectCarrier configurations from YAML files in different content folders. - * EffectCarrier configurations are organized by type (rod, bait, enchant, util, totem, hook) in separate folders. - * Each YAML file within these folders is processed to populate the effectMap. - */ - @SuppressWarnings("DuplicatedCode") - private void loadFiles() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("rod", "bait", "enchant", "util", "totem", "hook")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile, type); - } - } - } - } - } - - /** - * Loads EffectCarrier configurations from a YAML file and populates the effectMap. - * - * @param file The YAML file to load configurations from. - * @param namespace The namespace to use when creating keys for EffectCarriers. - */ - private void loadSingleFile(File file, String namespace) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : yaml.getValues(false).entrySet()) { - String value = entry.getKey(); - if (entry.getValue() instanceof ConfigurationSection section) { - Key key = Key.of(namespace, value); - EffectCarrier item = getEffectCarrierFromSection(key, section); - if (item != null) - effectMap.put(key, item); - } - } - } - - /** - * Parses a ConfigurationSection to create an EffectCarrier based on the specified key and configuration. - * - * @param key The key that uniquely identifies the EffectCarrier. - * @param section The ConfigurationSection containing the EffectCarrier configuration. - * @return An EffectCarrier instance based on the key and configuration, or null if the section is null. - */ - @Override - @Nullable - public EffectCarrier getEffectCarrierFromSection(Key key, ConfigurationSection section) { - if (section == null) return null; - return new EffectCarrier.Builder() - .key(key) - .requirements(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("requirements"), true)) - .effect(getEffectModifiers(section.getConfigurationSection("effects"))) - .actionMap(plugin.getActionManager().getActionMap(section.getConfigurationSection("events"))) - .build(); - } - - public void unload() { - HashMap temp = new HashMap<>(effectMap); - effectMap.clear(); - for (Map.Entry entry : temp.entrySet()) { - if (entry.getValue().isPersist()) { - effectMap.put(entry.getKey(), entry.getValue()); - } - } - } - - /** - * Retrieves the initial FishingEffect that represents no special effects. - * - * @return The initial FishingEffect. - */ - @NotNull - @Override - public FishingEffect getInitialEffect() { - return new FishingEffect(); - } - - /** - * Retrieves a list of modifiers based on specified loot groups. - * - * @param modList A list of strings containing group modifiers in the format "group:modifier". - * @return A list of pairs where each pair represents a loot item and its associated modifier. - */ - private List> getGroupModifiers(List modList) { - List> result = new ArrayList<>(); - for (String group : modList) { - String[] split = group.split(":",2); - String key = split[0]; - List members = plugin.getLootManager().getLootGroup(key); - if (members == null) { - LogUtils.warn("Group " + key + " doesn't contain any loot. The effect would not take effect."); - return result; - } - for (String loot : members) { - result.add(Pair.of(loot, ConfigUtils.getModifier(split[1]))); - } - } - return result; - } - - /** - * Parses a ConfigurationSection to retrieve an array of EffectModifiers. - * - * @param section The ConfigurationSection to parse. - * @return An array of EffectModifiers based on the values found in the section. - */ - @NotNull - @Override - public EffectModifier[] getEffectModifiers(ConfigurationSection section) { - if (section == null) return new EffectModifier[0]; - ArrayList modifiers = new ArrayList<>(); - for (Map.Entry entry: section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - EffectModifier effectModifier = getEffectModifier(inner); - if (effectModifier != null) - modifiers.add(effectModifier); - } - } - return modifiers.toArray(new EffectModifier[0]); - } - - @Override - public BaseEffect getBaseEffect(ConfigurationSection section) { - if (section == null) return new BaseEffect( - new PlainValue(0), new PlainValue(1d), - new PlainValue(0), new PlainValue(1d), - new PlainValue(0), new PlainValue(1d) - ); - Value waitTime = section.contains("wait-time") ? ConfigUtils.getValue(section.get("wait-time")) : new PlainValue(0); - Value difficulty = section.contains("difficulty") ? ConfigUtils.getValue(section.get("difficulty")) : new PlainValue(0); - Value gameTime = section.contains("game-time") ? ConfigUtils.getValue(section.get("game-time")) : new PlainValue(0); - Value waitTimeMultiplier = section.contains("wait-time-multiplier") ? ConfigUtils.getValue(section.get("wait-time-multiplier")) : new PlainValue(1); - Value difficultyMultiplier = section.contains("difficulty-multiplier") ? ConfigUtils.getValue(section.get("difficulty-multiplier")) : new PlainValue(1); - Value gameTimeMultiplier = section.contains("game-time-multiplier") ? ConfigUtils.getValue(section.get("game-time-multiplier")) : new PlainValue(1); - return new BaseEffect( - waitTime, waitTimeMultiplier, - difficulty, difficultyMultiplier, - gameTime, gameTimeMultiplier - ); - } - - private void loadGlobalEffects() { - YamlConfiguration config = plugin.getConfig("config.yml"); - ConfigurationSection section = config.getConfigurationSection("mechanics.global-effects"); - GlobalSettings.setEffects(getEffectModifiers(section)); - } - - /** - * Parses a ConfigurationSection to create an EffectModifier based on the specified type and configuration. - * - * @param section The ConfigurationSection containing the effect modifier configuration. - * @return An EffectModifier instance based on the type and configuration. - */ - @Override - @Nullable - public EffectModifier getEffectModifier(ConfigurationSection section) { - String type = section.getString("type"); - if (type == null) return null; - switch (type) { - case "weight-mod" -> { - var modList = ConfigUtils.getModifiers(section.getStringList("value")); - return ((effect, condition) -> { - effect.addWeightModifier(modList); - }); - } - case "weight-mod-ignore-conditions" -> { - var modList = ConfigUtils.getModifiers(section.getStringList("value")); - return ((effect, condition) -> { - effect.addWeightModifierIgnored(modList); - }); - } - case "group-mod" -> { - var modList = getGroupModifiers(section.getStringList("value")); - return ((effect, condition) -> { - effect.addWeightModifier(modList); - }); - } - case "group-mod-ignore-conditions" -> { - var modList = getGroupModifiers(section.getStringList("value")); - return ((effect, condition) -> { - effect.addWeightModifierIgnored(modList); - }); - } - case "wait-time" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setWaitTime(effect.getWaitTime() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "hook-time", "wait-time-multiplier" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setWaitTimeMultiplier(effect.getWaitTimeMultiplier() + value.get(condition.getPlayer(), condition.getArgs()) - 1); - }); - } - case "difficulty" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setDifficulty(effect.getDifficulty() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "difficulty-multiplier", "difficulty-bonus" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setDifficultyMultiplier(effect.getDifficultyMultiplier() + value.get(condition.getPlayer(), condition.getArgs()) - 1); - }); - } - case "multiple-loot" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setMultipleLootChance(effect.getMultipleLootChance() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "score" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setScore(effect.getScore() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "score-bonus", "score-multiplier" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setScoreMultiplier(effect.getScoreMultiplier() + value.get(condition.getPlayer(), condition.getArgs()) - 1); - }); - } - case "size" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setSize(effect.getSize() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "size-bonus", "size-multiplier" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setSizeMultiplier(effect.getSizeMultiplier() + value.get(condition.getPlayer(), condition.getArgs()) - 1); - }); - } - case "game-time" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setGameTime(effect.getGameTime() + value.get(condition.getPlayer(), condition.getArgs())); - }); - } - case "game-time-bonus", "game-time-multiplier" -> { - var value = ConfigUtils.getValue(section.get("value")); - return ((effect, condition) -> { - effect.setGameTimeMultiplier(effect.getGameTimeMultiplier() + value.get(condition.getPlayer(), condition.getArgs()) - 1); - }); - } - case "lava-fishing" -> { - return ((effect, condition) -> effect.setLavaFishing(true)); - } - case "conditional" -> { - Requirement[] requirements = plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions"), true); - EffectModifier[] modifiers = getEffectModifiers(section.getConfigurationSection("effects")); - return ((effect, condition) -> { - for (Requirement requirement : requirements) - if (!requirement.isConditionMet(condition)) - return; - for (EffectModifier modifier : modifiers) { - modifier.modify(effect, condition); - } - }); - } - default -> { - LogUtils.warn("Effect " + type + " doesn't exist."); - return null; - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/entity/EntityManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/entity/EntityManagerImpl.java deleted file mode 100644 index d1c7f108..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/entity/EntityManagerImpl.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.entity; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.EntityManager; -import net.momirealms.customfishing.api.mechanic.entity.EntityConfig; -import net.momirealms.customfishing.api.mechanic.entity.EntityLibrary; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.entity.VanillaEntityImpl; -import org.bukkit.Location; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; -import org.bukkit.util.Vector; - -import java.io.File; -import java.util.*; - -public class EntityManagerImpl implements EntityManager { - - private final CustomFishingPlugin plugin; - private final HashMap entityLibraryMap; - private final HashMap entityConfigMap; - - public EntityManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.entityLibraryMap = new HashMap<>(); - this.entityConfigMap = new HashMap<>(); - this.registerEntityLibrary(new VanillaEntityImpl()); - } - - public void load() { - this.loadConfig(); - } - - public void unload() { - HashMap tempMap = new HashMap<>(this.entityConfigMap); - this.entityConfigMap.clear(); - for (Map.Entry entry : tempMap.entrySet()) { - if (entry.getValue().isPersist()) { - tempMap.put(entry.getKey(), entry.getValue()); - } - } - } - - /** - * Registers an entity library for use in the plugin. - * - * @param entityLibrary The entity library to register. - * @return {@code true} if the entity library was successfully registered, {@code false} if it already exists. - */ - @Override - public boolean registerEntityLibrary(EntityLibrary entityLibrary) { - if (entityLibraryMap.containsKey(entityLibrary.identification())) return false; - else entityLibraryMap.put(entityLibrary.identification(), entityLibrary); - return true; - } - - /** - * Unregisters an entity library by its identification key. - * - * @param identification The identification key of the entity library to unregister. - * @return {@code true} if the entity library was successfully unregistered, {@code false} if it does not exist. - */ - @Override - public boolean unregisterEntityLibrary(String identification) { - return entityLibraryMap.remove(identification) != null; - } - - /** - * Load configuration files for entity properties. - */ - @SuppressWarnings("DuplicatedCode") - private void loadConfig() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("entity")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile); - } - } - } - } - } - - /** - * Load a single entity configuration file. - * - * @param file The YAML file to load. - */ - private void loadSingleFile(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - String entityID = section.getString("entity"); - if (entityID == null) { - LogUtils.warn("Entity can't be null. File:" + file.getAbsolutePath() + "; Section:" + section.getCurrentPath()); - continue; - } - HashMap propertyMap = new HashMap<>(); - ConfigurationSection property = section.getConfigurationSection("properties"); - if (property != null) { - propertyMap.putAll(property.getValues(false)); - } - EntityConfig entityConfig = new EntityConfig.Builder() - .entityID(entityID) - .persist(false) - .horizontalVector(section.getDouble("velocity.horizontal", 1.1)) - .verticalVector(section.getDouble("velocity.vertical", 1.2)) - .propertyMap(propertyMap) - .build(); - entityConfigMap.put(entry.getKey(), entityConfig); - } - } - } - - public void disable() { - unload(); - this.entityConfigMap.clear(); - this.entityLibraryMap.clear(); - } - - /** - * Summons an entity based on the given loot configuration to a specified location. - * - * @param hookLocation The location where the entity will be summoned, typically where the fishing hook is. - * @param playerLocation The location of the player who triggered the entity summoning. - * @param loot The loot configuration that defines the entity to be summoned. - */ - @Override - public void summonEntity(Location hookLocation, Location playerLocation, Loot loot) { - EntityConfig config = entityConfigMap.get(loot.getID()); - if (config == null) { - LogUtils.warn("Entity: " + loot.getID() + " doesn't exist."); - return; - } - String entityID = config.getEntityID(); - Entity entity; - if (entityID.contains(":")) { - String[] split = entityID.split(":", 2); - String identification = split[0]; - String id = split[1]; - EntityLibrary library = entityLibraryMap.get(identification); - entity = library.spawn(hookLocation, id, config.getPropertyMap()); - } else { - entity = entityLibraryMap.get("vanilla").spawn(hookLocation, entityID, config.getPropertyMap()); - } - Vector vector = playerLocation.subtract(hookLocation).toVector().multiply((config.getHorizontalVector()) - 1); - vector = vector.setY((vector.getY() + 0.2) * config.getVerticalVector()); - entity.setVelocity(vector); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/BaitAnimationTask.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/BaitAnimationTask.java deleted file mode 100644 index 8e7e6381..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/BaitAnimationTask.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.fishing; - -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.util.FakeItemUtils; -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -/** - * A task responsible for animating bait when it's attached to a fishing hook. - */ -public class BaitAnimationTask implements Runnable { - - private final CancellableTask cancellableTask; - private final int entityID; - private final Player player; - private final FishHook fishHook; - - /** - * Constructs a new BaitAnimationTask. - * - * @param plugin The CustomFishingPlugin instance. - * @param player The player who cast the fishing rod. - * @param fishHook The FishHook entity. - * @param baitItem The bait ItemStack. - */ - public BaitAnimationTask(CustomFishingPlugin plugin, Player player, FishHook fishHook, ItemStack baitItem) { - this.player = player; - this.fishHook = fishHook; - entityID = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - if (plugin.getVersionManager().isVersionNewerThan1_19_R3()) { - CustomFishingPluginImpl.sendPackets(player, FakeItemUtils.getSpawnPacket(entityID, fishHook.getLocation()), FakeItemUtils.getMetaPacket(entityID, baitItem)); - } else { - CustomFishingPluginImpl.sendPacket(player, FakeItemUtils.getSpawnPacket(entityID, fishHook.getLocation())); - CustomFishingPluginImpl.sendPacket(player, FakeItemUtils.getMetaPacket(entityID, baitItem)); - } - this.cancellableTask = plugin.getScheduler().runTaskAsyncTimer(this, 50, 50, TimeUnit.MILLISECONDS); - } - - @Override - public void run() { - if ( fishHook == null - || fishHook.isOnGround() - || fishHook.isInLava() - || fishHook.isInWater() - || !fishHook.isValid() - ) { - cancelAnimation(); - } else { - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getVelocityPacket(entityID, fishHook.getVelocity())); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getTpPacket(entityID, fishHook.getLocation())); - } - } - - /** - * Cancels the bait animation and cleans up resources. - */ - private void cancelAnimation() { - cancellableTask.cancel(); - CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getDestroyPacket(entityID)); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java deleted file mode 100644 index a760a84c..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java +++ /dev/null @@ -1,883 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.fishing; - -import com.destroystokyo.paper.event.player.PlayerJumpEvent; -import de.tr7zw.changeme.nbtapi.NBTCompound; -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.event.FishingResultEvent; -import net.momirealms.customfishing.api.event.LavaFishingEvent; -import net.momirealms.customfishing.api.event.RodCastEvent; -import net.momirealms.customfishing.api.manager.FishingManager; -import net.momirealms.customfishing.api.manager.RequirementManager; -import net.momirealms.customfishing.api.mechanic.GlobalSettings; -import net.momirealms.customfishing.api.mechanic.TempFishingState; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import net.momirealms.customfishing.api.mechanic.game.BasicGameConfig; -import net.momirealms.customfishing.api.mechanic.game.GameInstance; -import net.momirealms.customfishing.api.mechanic.game.GameSettings; -import net.momirealms.customfishing.api.mechanic.game.GamingPlayer; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.loot.LootType; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.WeightUtils; -import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.util.ItemUtils; -import org.bukkit.*; -import org.bukkit.entity.*; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.*; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.persistence.PersistentDataType; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class FishingManagerImpl implements Listener, FishingManager { - - private final CustomFishingPluginImpl plugin; - private final ConcurrentHashMap hookCacheMap; - private final ConcurrentHashMap hookCheckMap; - private final ConcurrentHashMap tempFishingStateMap; - private final ConcurrentHashMap gamingPlayerMap; - private final ConcurrentHashMap> vanillaLootMap; - - public FishingManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - this.hookCacheMap = new ConcurrentHashMap<>(); - this.tempFishingStateMap = new ConcurrentHashMap<>(); - this.gamingPlayerMap = new ConcurrentHashMap<>(); - this.hookCheckMap = new ConcurrentHashMap<>(); - this.vanillaLootMap = new ConcurrentHashMap<>(); - } - - public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); - } - - public void unload() { - HandlerList.unregisterAll(this); - for (FishHook hook : hookCacheMap.values()) { - hook.remove(); - } - for (HookCheckTimerTask task : hookCheckMap.values()) { - task.destroy(); - } - for (GamingPlayer gamingPlayer : gamingPlayerMap.values()) { - gamingPlayer.cancel(); - } - this.hookCacheMap.clear(); - this.tempFishingStateMap.clear(); - this.gamingPlayerMap.clear(); - this.hookCheckMap.clear(); - } - - public void disable() { - unload(); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onFishMONITOR(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.MONITOR) return; - this.selectState(event); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onFishHIGHEST(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.HIGHEST) return; - this.selectState(event); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onFishHIGH(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.HIGH) return; - this.selectState(event); - } - - @EventHandler(priority = EventPriority.NORMAL) - public void onFishNORMAL(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.NORMAL) return; - this.selectState(event); - } - - @EventHandler(priority = EventPriority.LOW) - public void onFishLOW(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.LOW) return; - this.selectState(event); - } - - @EventHandler(priority = EventPriority.LOWEST) - public void onFishLOWEST(PlayerFishEvent event) { - if (CFConfig.eventPriority != EventPriority.LOWEST) return; - this.selectState(event); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - final Player player = event.getPlayer(); - final UUID uuid = player.getUniqueId(); - this.removeHook(uuid); - this.removeTempFishingState(player); - this.removeHookCheckTask(player); - this.vanillaLootMap.remove(uuid); - GamingPlayer gamingPlayer = gamingPlayerMap.remove(player.getUniqueId()); - if (gamingPlayer != null) { - gamingPlayer.cancel(); - } - } - - /** - * Known bug: This is a Minecraft packet limitation - * When you fish, both left click air and right click air - * are triggered. And you can't cancel the left click event. - */ - @EventHandler (ignoreCancelled = false) - public void onLeftClick(PlayerInteractEvent event) { - if (event.getAction() != Action.LEFT_CLICK_AIR) - return; - if (event.getMaterial() != Material.FISHING_ROD) - return; - if (event.getHand() != EquipmentSlot.HAND) - return; - GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId()); - if (gamingPlayer != null) { - if (gamingPlayer.onLeftClick()) - event.setCancelled(true); - } - } - - @EventHandler (ignoreCancelled = true) - public void onSwapHand(PlayerSwapHandItemsEvent event) { - GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId()); - if (gamingPlayer != null) { - if (gamingPlayer.onSwapHand()) - event.setCancelled(true); - } - } - - @EventHandler - public void onJump(PlayerJumpEvent event) { - if (event.isCancelled()) return; - GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId()); - if (gamingPlayer != null) { - if (gamingPlayer.onJump()) - event.setCancelled(true); - } - } - - @EventHandler - public void onSneak(PlayerToggleSneakEvent event) { - if (event.isCancelled()) return; - if (!event.isSneaking()) return; - GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId()); - if (gamingPlayer != null) { - if (gamingPlayer.onSneak()) - event.setCancelled(true); - } - } - - @EventHandler - public void onChat(AsyncPlayerChatEvent event) { - if (event.isCancelled()) return; - GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId()); - if (gamingPlayer != null) { - if (gamingPlayer.onChat(event.getMessage())) - event.setCancelled(true); - } - } - - /** - * Removes a fishing hook entity associated with a given UUID. - * - * @param uuid The UUID of the fishing hook entity to be removed. - * @return {@code true} if the fishing hook was successfully removed, {@code false} otherwise. - */ - @Override - public boolean removeHook(UUID uuid) { - FishHook hook = hookCacheMap.remove(uuid); - if (hook != null && hook.isValid()) { - plugin.getScheduler().runTaskSync(hook::remove, hook.getLocation()); - return true; - } else { - return false; - } - } - - /** - * Retrieves a FishHook object associated with the provided player's UUID - * - * @param uuid The UUID of the player - * @return fishhook entity, null if not exists - */ - @Override - @Nullable - public FishHook getHook(UUID uuid) { - FishHook fishHook = hookCacheMap.get(uuid); - if (fishHook != null) { - if (!fishHook.isValid()) { - hookCacheMap.remove(uuid); - return null; - } else { - return fishHook; - } - } - return null; - } - - /** - * Selects the appropriate fishing state based on the provided PlayerFishEvent and triggers the corresponding action. - * - * @param event The PlayerFishEvent that represents the fishing action. - */ - public void selectState(PlayerFishEvent event) { - if (event.isCancelled()) return; - switch (event.getState()) { - case FISHING -> onCastRod(event); - case REEL_IN -> onReelIn(event); - case CAUGHT_ENTITY -> onCaughtEntity(event); - case CAUGHT_FISH -> onCaughtFish(event); - case BITE -> onBite(event); - case IN_GROUND -> onInGround(event); - } - } - - /** - * Handle the event when the fishing hook lands on the ground. - * - * @param event The PlayerFishEvent that occurred. - */ - private void onInGround(PlayerFishEvent event) { - final Player player = event.getPlayer(); - FishHook hook = event.getHook(); - if (player.getGameMode() != GameMode.CREATIVE) { - ItemStack itemStack = player.getInventory().getItemInMainHand(); - if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand(); - if (itemStack.getType() == Material.FISHING_ROD) { - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound compound = nbtItem.getCompound("CustomFishing"); - if (compound != null && compound.hasTag("max_dur")) { - event.setCancelled(true); - hook.remove(); - ItemUtils.decreaseDurability(player, itemStack, 2, true); - } - } - } - } - - /** - * Handle the event when a player casts a fishing rod. - * - * @param event The PlayerFishEvent that occurred. - */ - public void onCastRod(PlayerFishEvent event) { - var player = event.getPlayer(); - var fishingPreparation = new FishingPreparationImpl(player, plugin); - if (!fishingPreparation.canFish()) { - event.setCancelled(true); - return; - } - // Check mechanic requirements - if (!RequirementManager.isRequirementMet( - fishingPreparation, RequirementManagerImpl.mechanicRequirements - )) { - this.removeTempFishingState(player); - return; - } - FishingEffect initialEffect = plugin.getEffectManager().getInitialEffect(); - // Merge totem effects - EffectCarrier totemEffect = plugin.getTotemManager().getTotemEffect(player.getLocation()); - if (totemEffect != null) - for (EffectModifier modifier : totemEffect.getEffectModifiers()) { - modifier.modify(initialEffect, fishingPreparation); - } - - // Call custom event - RodCastEvent rodCastEvent = new RodCastEvent(event, fishingPreparation, initialEffect); - Bukkit.getPluginManager().callEvent(rodCastEvent); - if (rodCastEvent.isCancelled()) { - return; - } - - // Store fishhook entity and apply the effects - final FishHook fishHook = event.getHook(); - this.hookCacheMap.put(player.getUniqueId(), fishHook); - - // Reduce amount & Send animation - var baitItem = fishingPreparation.getBaitItemStack(); - if (baitItem != null) { - ItemStack cloned = baitItem.clone(); - cloned.setAmount(1); - new BaitAnimationTask(plugin, player, fishHook, cloned); - baitItem.setAmount(baitItem.getAmount() - 1); - } - - // Arrange hook check task - this.hookCheckMap.put(player.getUniqueId(), new HookCheckTimerTask(this, fishHook, fishingPreparation, initialEffect)); - // trigger actions - fishingPreparation.triggerActions(ActionTrigger.CAST); - } - - /** - * Handle the event when a player catches an entity. - * - * @param event The PlayerFishEvent that occurred. - */ - private void onCaughtEntity(PlayerFishEvent event) { - final Player player = event.getPlayer(); - final UUID uuid = player.getUniqueId(); - - Entity entity = event.getCaught(); - if ((entity instanceof ArmorStand armorStand) - && armorStand.getPersistentDataContainer().get( - Objects.requireNonNull(NamespacedKey.fromString("lavafishing", plugin)), - PersistentDataType.STRING - ) != null) { - // The hook is hooked into the temp entity - // This might be called both not in game and in game - LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(player, LavaFishingEvent.State.REEL_IN, event.getHook()); - Bukkit.getPluginManager().callEvent(lavaFishingEvent); - if (lavaFishingEvent.isCancelled()) { - event.setCancelled(true); - return; - } - - GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid); - if (gamingPlayer != null) { - // in game - if (gamingPlayer.onRightClick()) - event.setCancelled(true); - } else { - // not in game - HookCheckTimerTask task = hookCheckMap.get(uuid); - if (task != null) - task.destroy(); - else - // should not reach this but in case - entity.remove(); - } - return; - } - - if (player.getGameMode() != GameMode.CREATIVE) { - ItemStack itemStack = player.getInventory().getItemInMainHand(); - if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand(); - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound nbtCompound = nbtItem.getCompound("CustomFishing"); - if (nbtCompound != null && nbtCompound.hasTag("max_dur")) { - event.getHook().remove(); - event.setCancelled(true); - ItemUtils.decreaseDurability(player, itemStack, 5, true); - } - } - } - - /** - * Handle the event when a player catches a fish. - * - * @param event The PlayerFishEvent that occurred. - */ - private void onCaughtFish(PlayerFishEvent event) { - final Player player = event.getPlayer(); - final UUID uuid = player.getUniqueId(); - if (!(event.getCaught() instanceof Item item)) - return; - - // If player is playing the game - GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid); - if (gamingPlayer != null) { - if (gamingPlayer.onRightClick()) - event.setCancelled(true); - return; - } - - // If player is not playing the game - var temp = this.getTempFishingState(uuid); - if (temp != null) { - var loot = temp.getLoot(); - if (loot.getID().equals("vanilla")) { - // put vanilla loot in map - this.vanillaLootMap.put(uuid, Pair.of(item.getItemStack(), event.getExpToDrop())); - } - var fishingPreparation = temp.getPreparation(); - loot.triggerActions(ActionTrigger.HOOK, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.HOOK); - if (!loot.disableGame()) { - // start the game if the loot has a game - if (startFishingGame(player, fishingPreparation, temp.getEffect())) { - event.setCancelled(true); - } - } else { - // remove temp state if fishing game not exists - this.removeTempFishingState(player); - var hook = event.getHook(); - // If the game is disabled, then do success actions - success(temp, hook); - // Cancel the event because loots can be multiple and unique - event.setCancelled(true); - hook.remove(); - } - return; - } - } - - /** - * Handle the event when a player receives a bite on their fishing hook. - * - * @param event The PlayerFishEvent that occurred. - */ - private void onBite(PlayerFishEvent event) { - final Player player = event.getPlayer(); - final UUID uuid = player.getUniqueId(); - - // If player is already in game - // then ignore the event - GamingPlayer gamingPlayer = getGamingPlayer(uuid); - if (gamingPlayer != null) { - return; - } - - // If the loot's game is instant - TempFishingState temp = getTempFishingState(uuid); - if (temp != null) { - var loot = temp.getLoot(); - var fishingPreparation = temp.getPreparation(); - fishingPreparation.setLocation(event.getHook().getLocation()); - - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.BITE, fishingPreparation); - loot.triggerActions(ActionTrigger.BITE, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.BITE); - - if (loot.instanceGame() && !loot.disableGame()) { - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.HOOK, fishingPreparation); - loot.triggerActions(ActionTrigger.HOOK, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.HOOK); - startFishingGame(player, fishingPreparation, temp.getEffect()); - } - } - } - - /** - * Handle the event when a player reels in their fishing line. - * - * @param event The PlayerFishEvent that occurred. - */ - private void onReelIn(PlayerFishEvent event) { - final Player player = event.getPlayer(); - final UUID uuid = player.getUniqueId(); - - // If player is in game - GamingPlayer gamingPlayer = getGamingPlayer(uuid); - if (gamingPlayer != null) { - if (gamingPlayer.onRightClick()) - event.setCancelled(true); - return; - } - - // If player is lava fishing - HookCheckTimerTask hookTask = hookCheckMap.get(uuid); - if (hookTask != null && hookTask.isFishHooked()) { - LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(player, LavaFishingEvent.State.CAUGHT_FISH, event.getHook()); - Bukkit.getPluginManager().callEvent(lavaFishingEvent); - if (lavaFishingEvent.isCancelled()) { - event.setCancelled(true); - return; - } - - var temp = getTempFishingState(uuid); - if (temp != null ) { - Loot loot = temp.getLoot(); - var fishingPreparation = temp.getPreparation(); - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.HOOK, fishingPreparation); - loot.triggerActions(ActionTrigger.HOOK, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.HOOK); - if (!loot.disableGame()) { - event.setCancelled(true); - startFishingGame(player, fishingPreparation, temp.getEffect()); - } else { - success(temp, event.getHook()); - } - } - return; - } - } - - /** - * Removes the temporary fishing state associated with a player. - * - * @param player The player whose temporary fishing state should be removed. - */ - @Override - public TempFishingState removeTempFishingState(Player player) { - return this.tempFishingStateMap.remove(player.getUniqueId()); - } - - /** - * Processes the game result for a gaming player - * - * @param gamingPlayer The gaming player whose game result should be processed. - */ - @Override - public void processGameResult(GamingPlayer gamingPlayer) { - final Player player = gamingPlayer.getPlayer(); - final UUID uuid = player.getUniqueId(); - FishHook fishHook = hookCacheMap.remove(uuid); - if (fishHook == null) { - LogUtils.warn("Unexpected situation: Can't get player's fish hook when processing game results."); - return; - } - TempFishingState tempFishingState = removeTempFishingState(player); - if (tempFishingState == null) { - LogUtils.warn("Unexpected situation: Can't get player's fishing state when processing game results."); - return; - } - Effect bonus = gamingPlayer.getEffectReward(); - if (bonus != null) - tempFishingState.getEffect().merge(bonus); - - gamingPlayer.cancel(); - gamingPlayerMap.remove(uuid); - plugin.getScheduler().runTaskSync(() -> { - - if (gamingPlayer.isSuccessful()) { - success(tempFishingState, fishHook); - } else { - fail(tempFishingState, fishHook); - } - - fishHook.remove(); - - }, fishHook.getLocation()); - } - - public void fail(TempFishingState state, FishHook hook) { - var loot = state.getLoot(); - var fishingPreparation = state.getPreparation(); - - if (loot.getID().equals("vanilla")) { - Pair pair = this.vanillaLootMap.remove(fishingPreparation.getPlayer().getUniqueId()); - if (pair != null) { - fishingPreparation.insertArg("{nick}", ""); - fishingPreparation.insertArg("{loot}", pair.left().getType().toString()); - } - } - - // call event - FishingResultEvent fishingResultEvent = new FishingResultEvent( - fishingPreparation.getPlayer(), - FishingResultEvent.Result.FAILURE, - hook, - loot, - fishingPreparation.getArgs() - ); - Bukkit.getPluginManager().callEvent(fishingResultEvent); - if (fishingResultEvent.isCancelled()) { - return; - } - - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.FAILURE, fishingPreparation); - loot.triggerActions(ActionTrigger.FAILURE, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.FAILURE); - - if (state.getPreparation().getPlayer().getGameMode() != GameMode.CREATIVE) { - ItemUtils.decreaseHookDurability(fishingPreparation.getRodItemStack(), 1, true); - } - } - - /** - * Handle the success of a fishing attempt, including spawning loot, calling events, and executing success actions. - * - * @param state The temporary fishing state containing information about the loot and effect. - * @param hook The FishHook entity associated with the fishing attempt. - */ - public void success(TempFishingState state, FishHook hook) { - var loot = state.getLoot(); - var effect = state.getEffect(); - var fishingPreparation = state.getPreparation(); - var player = fishingPreparation.getPlayer(); - fishingPreparation.insertArg("{size-multiplier}", String.valueOf(effect.getSizeMultiplier())); - fishingPreparation.insertArg("{size-fixed}", String.valueOf(effect.getSize())); - int amount; - if (loot.getType() == LootType.ITEM) { - amount = (int) effect.getMultipleLootChance(); - amount += Math.random() < (effect.getMultipleLootChance() - amount) ? 2 : 1; - } else { - amount = 1; - } - fishingPreparation.insertArg("{amount}", String.valueOf(amount)); - - // call event - FishingResultEvent fishingResultEvent = new FishingResultEvent( - player, - FishingResultEvent.Result.SUCCESS, - hook, - loot, - fishingPreparation.getArgs() - ); - Bukkit.getPluginManager().callEvent(fishingResultEvent); - if (fishingResultEvent.isCancelled()) { - return; - } - - switch (loot.getType()) { - case ITEM -> { - // build the items for multiple times instead of using setAmount() to make sure that each item is unique - if (loot.getID().equals("vanilla")) { - Pair pair = vanillaLootMap.remove(player.getUniqueId()); - if (pair != null) { - fishingPreparation.insertArg("{nick}", ""); - for (int i = 0; i < amount; i++) { - plugin.getScheduler().runTaskSyncLater(() -> { - plugin.getItemManager().dropItem(player, hook.getLocation(), player.getLocation(), pair.left().clone(), fishingPreparation); - doSuccessActions(loot, effect, fishingPreparation, player); - if (pair.right() > 0) { - player.giveExp(pair.right(), true); - AdventureHelper.getInstance().sendSound(player, Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1, 1); - } - }, hook.getLocation(), (long) CFConfig.multipleLootSpawnDelay * i); - } - } - } else { - for (int i = 0; i < amount; i++) { - plugin.getScheduler().runTaskSyncLater(() -> { - ItemStack item = plugin.getItemManager().build(player, "item", loot.getID(), fishingPreparation.getArgs()); - if (item == null) { - LogUtils.warn(String.format("Item %s not exists", loot.getID())); - return; - } - plugin.getItemManager().dropItem(player, hook.getLocation(), player.getLocation(), item, fishingPreparation); - doSuccessActions(loot, effect, fishingPreparation, player); - }, hook.getLocation(), (long) CFConfig.multipleLootSpawnDelay * i); - } - } - } - case ENTITY -> { - plugin.getEntityManager().summonEntity(hook.getLocation(), player.getLocation(), loot); - doSuccessActions(loot, effect, fishingPreparation, player); - } - case BLOCK -> { - plugin.getBlockManager().summonBlock(player, hook.getLocation(), player.getLocation(), loot); - doSuccessActions(loot, effect, fishingPreparation, player); - } - } - - if (player.getGameMode() != GameMode.CREATIVE) { - ItemStack rod = state.getPreparation().getRodItemStack(); - ItemUtils.decreaseHookDurability(rod, 1, false); - ItemUtils.decreaseDurability(player, rod, 1, true); - } - } - - /** - * Execute success-related actions after a successful fishing attempt, including updating competition data, triggering events and actions, and updating player statistics. - * - * @param loot The loot that was successfully caught. - * @param effect The effect applied during fishing. - * @param fishingPreparation The fishing preparation containing preparation data. - * @param player The player who successfully caught the loot. - */ - private void doSuccessActions(Loot loot, Effect effect, FishingPreparation fishingPreparation, Player player) { - FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); - if (competition != null && RequirementManager.isRequirementMet(fishingPreparation, competition.getConfig().getRequirements())) { - String scoreStr = fishingPreparation.getArg("{CUSTOM_SCORE}"); - if (scoreStr != null) { - competition.refreshData(player, Double.parseDouble(scoreStr)); - } else { - double score = 0; - switch (competition.getGoal()) { - case CATCH_AMOUNT -> { - score = 1; - competition.refreshData(player, score); - } - case MAX_SIZE, TOTAL_SIZE -> { - String size = fishingPreparation.getArg("{SIZE}"); - if (size != null) { - score = Double.parseDouble(size); - competition.refreshData(player, score); - } else { - score = 0; - } - } - case TOTAL_SCORE -> { - score = loot.getScore(); - if (score > 0) { - score = score * effect.getScoreMultiplier() + effect.getScore(); - competition.refreshData(player, score); - } else { - score = 0; - } - } - } - fishingPreparation.insertArg("{score}", String.format("%.2f", score)); - fishingPreparation.insertArg("{SCORE}", String.valueOf(score)); - } - } - - // events and actions - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.SUCCESS, fishingPreparation); - loot.triggerActions(ActionTrigger.SUCCESS, fishingPreparation); - fishingPreparation.triggerActions(ActionTrigger.SUCCESS); - - player.setStatistic( - Statistic.FISH_CAUGHT, - player.getStatistic(Statistic.FISH_CAUGHT) + 1 - ); - - if (!loot.disableStats()) - Optional.ofNullable( - plugin.getStatisticsManager() - .getStatistics(player.getUniqueId()) - ).ifPresent(it -> { - it.addLootAmount(loot, fishingPreparation, 1); - String size = fishingPreparation.getArg("{SIZE}"); - if (size != null) - if (it.setSizeIfHigher(loot.getStatisticKey().getSizeKey(), Float.parseFloat(size))) { - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.NEW_SIZE_RECORD, fishingPreparation); - loot.triggerActions(ActionTrigger.NEW_SIZE_RECORD, fishingPreparation); - } - }); - } - - /** - * Starts a fishing game for the specified player with the given condition and effect. - * - * @param player The player starting the fishing game. - * @param condition The condition used to determine the game. - * @param effect The effect applied to the game. - */ - @Override - public boolean startFishingGame(Player player, Condition condition, Effect effect) { - Map gameWithWeight = plugin.getGameManager().getGameWithWeight(condition); - String random = WeightUtils.getRandom(gameWithWeight); - Pair gamePair = plugin.getGameManager().getGameInstance(random); - if (random == null) { - LogUtils.warn("No game is available for player:" + player.getName() + " location:" + condition.getLocation()); - return false; - } - if (gamePair == null) { - LogUtils.warn(String.format("Game %s doesn't exist.", random)); - return false; - } - plugin.debug("Game: " + random); - return startFishingGame(player, Objects.requireNonNull(gamePair.left().getGameSetting(effect)), gamePair.right()); - } - - /** - * Starts a fishing game for the specified player with the given settings and game instance. - * - * @param player The player starting the fishing game. - * @param settings The game settings for the fishing game. - * @param gameInstance The instance of the fishing game to start. - */ - @Override - public boolean startFishingGame(Player player, GameSettings settings, GameInstance gameInstance) { - plugin.debug("Difficulty:" + settings.getDifficulty()); - plugin.debug("Time:" + settings.getTime()); - FishHook hook = getHook(player.getUniqueId()); - if (hook != null) { - this.gamingPlayerMap.put(player.getUniqueId(), gameInstance.start(player, hook, settings)); - return true; - } else { - LogUtils.warn("It seems that player " + player.getName() + " is not fishing. Fishing game failed to start."); - return false; - } - } - - /** - * Checks if a player with the given UUID has cast their fishing hook. - * - * @param uuid The UUID of the player to check. - * @return {@code true} if the player has cast their fishing hook, {@code false} otherwise. - */ - @Override - public boolean hasPlayerCastHook(UUID uuid) { - FishHook fishHook = hookCacheMap.get(uuid); - if (fishHook == null) return false; - if (!fishHook.isValid()) { - hookCacheMap.remove(uuid); - return false; - } - return true; - } - - /** - * Sets the temporary fishing state for a player. - * - * @param player The player for whom to set the temporary fishing state. - * @param tempFishingState The temporary fishing state to set for the player. - */ - @Override - public void setTempFishingState(Player player, TempFishingState tempFishingState) { - tempFishingStateMap.put(player.getUniqueId(), tempFishingState); - } - - public void removeHookCheckTask(Player player) { - hookCheckMap.remove(player.getUniqueId()); - } - - /** - * Gets the {@link GamingPlayer} object associated with the given UUID. - * - * @param uuid The UUID of the player. - * @return The {@link GamingPlayer} object if found, or {@code null} if not found. - */ - @Override - @Nullable - public GamingPlayer getGamingPlayer(UUID uuid) { - return gamingPlayerMap.get(uuid); - } - - /** - * Gets the {@link TempFishingState} object associated with the given UUID. - * - * @param uuid The UUID of the player. - * @return The {@link TempFishingState} object if found, or {@code null} if not found. - */ - @Override - @Nullable - public TempFishingState getTempFishingState(UUID uuid) { - return tempFishingStateMap.get(uuid); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingPreparationImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingPreparationImpl.java deleted file mode 100644 index 76a54c58..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingPreparationImpl.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.fishing; - -import de.tr7zw.changeme.nbtapi.NBTCompound; -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.GlobalSettings; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.effect.EffectModifier; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -public class FishingPreparationImpl extends FishingPreparation { - - private boolean hasBait = false; - private boolean hasHook = false; - private @Nullable ItemStack baitItemStack; - private final @NotNull ItemStack rodItemStack; - private final List effects; - private boolean canFish = true; - - public FishingPreparationImpl(Player player, CustomFishingPlugin plugin) { - super(player); - - PlayerInventory playerInventory = player.getInventory(); - ItemStack mainHandItem = playerInventory.getItemInMainHand(); - ItemStack offHandItem = playerInventory.getItemInOffHand(); - - this.effects = new ArrayList<>(); - boolean rodOnMainHand = mainHandItem.getType() == Material.FISHING_ROD; - this.rodItemStack = rodOnMainHand ? mainHandItem : offHandItem; - String rodItemID = plugin.getItemManager().getAnyPluginItemID(this.rodItemStack); - EffectCarrier rodEffect = plugin.getEffectManager().getEffectCarrier("rod", rodItemID); - if (rodEffect != null) effects.add(rodEffect); - super.insertArg("{rod}", rodItemID); - - NBTItem nbtItem = new NBTItem(rodItemStack); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("hook_id")) { - String hookID = cfCompound.getString("hook_id"); - super.insertArg("{hook}", hookID); - this.hasHook = true; - EffectCarrier carrier = plugin.getEffectManager().getEffectCarrier("hook", hookID); - if (carrier != null) { - this.effects.add(carrier); - } - } - - String baitItemID = plugin.getItemManager().getAnyPluginItemID(rodOnMainHand ? offHandItem : mainHandItem); - EffectCarrier baitEffect = plugin.getEffectManager().getEffectCarrier("bait", baitItemID); - - if (baitEffect != null) { - this.baitItemStack = rodOnMainHand ? offHandItem : mainHandItem; - this.effects.add(baitEffect); - this.hasBait = true; - super.insertArg("{bait}", baitItemID); - } - - if (plugin.getBagManager().isEnabled()) { - Inventory fishingBag = plugin.getBagManager().getOnlineBagInventory(player.getUniqueId()); - HashSet uniqueUtils = new HashSet<>(4); - if (fishingBag != null) { - this.insertArg("{in-bag}", "true"); - for (int i = 0; i < fishingBag.getSize(); i++) { - ItemStack itemInBag = fishingBag.getItem(i); - String bagItemID = plugin.getItemManager().getAnyPluginItemID(itemInBag); - if (!hasBait) { - EffectCarrier effect = plugin.getEffectManager().getEffectCarrier("bait", bagItemID); - if (effect != null) { - this.hasBait = true; - this.baitItemStack = itemInBag; - this.effects.add(effect); - super.insertArg("{bait}", bagItemID); - continue; - } - } - EffectCarrier utilEffect = plugin.getEffectManager().getEffectCarrier("util", bagItemID); - if (utilEffect != null && !uniqueUtils.contains(bagItemID)) { - effects.add(utilEffect); - uniqueUtils.add(bagItemID); - } - } - this.delArg("{in-bag}"); - } - } - - for (String enchant : plugin.getIntegrationManager().getEnchantments(rodItemStack)) { - EffectCarrier enchantEffect = plugin.getEffectManager().getEffectCarrier("enchant", enchant); - if (enchantEffect != null) { - this.effects.add(enchantEffect); - } - } - - for (EffectCarrier effectCarrier : effects) { - if (!effectCarrier.isConditionMet(this)) { - this.canFish = false; - return; - } - } - } - - /** - * Retrieves the ItemStack representing the fishing rod. - * - * @return The ItemStack representing the fishing rod. - */ - @NotNull - public ItemStack getRodItemStack() { - return rodItemStack; - } - - /** - * Retrieves the ItemStack representing the bait (if available). - * - * @return The ItemStack representing the bait, or null if no bait is set. - */ - @Nullable - public ItemStack getBaitItemStack() { - return baitItemStack; - } - - /** - * Checks if player meet the requirements for fishing gears - * - * @return True if can fish, false otherwise. - */ - public boolean canFish() { - return this.canFish; - } - - /** - * Merges a FishingEffect into this fishing rod, applying effect modifiers. - * - * @param effect The FishingEffect to merge into this rod. - */ - public void mergeEffect(FishingEffect effect) { - for (EffectModifier modifier : GlobalSettings.getEffectModifiers()) { - modifier.modify(effect, this); - } - for (EffectCarrier effectCarrier : effects) { - for (EffectModifier modifier : effectCarrier.getEffectModifiers()) { - modifier.modify(effect, this); - } - } - } - - /** - * Triggers actions associated with a specific action trigger. - * - * @param actionTrigger The action trigger that initiates the actions. - */ - public void triggerActions(ActionTrigger actionTrigger) { - GlobalSettings.triggerRodActions(actionTrigger, this); - if (hasBait) GlobalSettings.triggerBaitActions(actionTrigger, this); - if (hasHook) GlobalSettings.triggerHookActions(actionTrigger, this); - for (EffectCarrier effectCarrier : effects) { - Action[] actions = effectCarrier.getActions(actionTrigger); - if (actions != null) - for (Action action : actions) { - action.trigger(this); - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/HookCheckTimerTask.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/HookCheckTimerTask.java deleted file mode 100644 index 09fffc25..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/HookCheckTimerTask.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.fishing; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.event.FishHookLandEvent; -import net.momirealms.customfishing.api.event.LavaFishingEvent; -import net.momirealms.customfishing.api.mechanic.TempFishingState; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.effect.FishingEffect; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.setting.CFConfig; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; -import org.bukkit.entity.FishHook; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.Vector; - -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -/** - * A task responsible for checking the state of a fishing hook and handling lava fishing mechanics. - */ -public class HookCheckTimerTask implements Runnable { - - private final FishingManagerImpl manager; - private final CancellableTask hookMovementTask; - private LavaEffectTask lavaFishingTask; - private final FishHook fishHook; - private final FishingPreparation fishingPreparation; - private final FishingEffect initialEffect; - private Effect tempEffect; - private final int lureLevel; - private boolean firstTime; - private boolean fishHooked; - private boolean reserve; - private int jumpTimer; - private Entity hookedEntity; - private Loot loot; - private boolean inWater; - - /** - * Constructs a new HookCheckTimerTask. - * - * @param manager The FishingManagerImpl instance. - * @param fishHook The FishHook entity being checked. - * @param fishingPreparation The FishingPreparation instance. - * @param initialEffect The initial fishing effect. - */ - public HookCheckTimerTask( - FishingManagerImpl manager, - FishHook fishHook, - FishingPreparation fishingPreparation, - FishingEffect initialEffect - ) { - this.inWater = false; - this.manager = manager; - this.fishHook = fishHook; - this.initialEffect = initialEffect; - this.fishingPreparation = fishingPreparation; - this.hookMovementTask = CustomFishingPlugin.get().getScheduler().runTaskSyncTimer(this, fishHook.getLocation(), 1, 1); - this.lureLevel = fishingPreparation.getRodItemStack().getEnchantmentLevel(Enchantment.LURE); - this.firstTime = true; - this.tempEffect = new FishingEffect(); - } - - @Override - public void run() { - if ( - !this.fishHook.isValid() - //|| (fishHook.getHookedEntity() != null && fishHook.getHookedEntity().getType() != EntityType.ARMOR_STAND) - ) { - // This task would be cancelled when hook is removed - this.destroy(); - return; - } - if (this.fishHook.isOnGround()) { - this.inWater = false; - return; - } - if (this.fishHook.getLocation().getBlock().getType() == Material.LAVA) { - this.inWater = false; - // if player can fish in lava - if (firstTime) { - this.firstTime = false; - - this.fishingPreparation.setLocation(this.fishHook.getLocation()); - this.fishingPreparation.mergeEffect(this.initialEffect); - if (!initialEffect.canLavaFishing()) { - this.destroy(); - return; - } - - FishHookLandEvent event = new FishHookLandEvent(this.fishingPreparation.getPlayer(), FishHookLandEvent.Target.LAVA, this.fishHook, true, this.initialEffect); - Bukkit.getPluginManager().callEvent(event); - - this.fishingPreparation.insertArg("{lava}", "true"); - this.fishingPreparation.triggerActions(ActionTrigger.LAND); - } - - // simulate fishing mechanic - if (this.fishHooked) { - this.jumpTimer++; - if (this.jumpTimer < 4) - return; - this.jumpTimer = 0; - this.fishHook.setVelocity(new Vector(0,0.24,0)); - return; - } - - if (!this.reserve) { - // jump - if (this.jumpTimer < 5) { - this.jumpTimer++; - this.fishHook.setVelocity(new Vector(0,0.2 - this.jumpTimer * 0.02,0)); - return; - } - - this.reserve = true; - - this.setNextLoot(); - if (this.loot != null) { - this.tempEffect = this.loot.getBaseEffect().build(fishingPreparation.getPlayer(), fishingPreparation.getArgs()); - this.tempEffect.merge(this.initialEffect); - this.setTempState(); - this.startLavaFishingMechanic(); - } else { - this.tempEffect = new FishingEffect(); - this.tempEffect.merge(this.initialEffect); - this.manager.removeTempFishingState(fishingPreparation.getPlayer()); - CustomFishingPlugin.get().debug("No loot available for " + fishingPreparation.getPlayer().getName() + " at " + fishingPreparation.getLocation()); - } - - this.makeHookStatic(this.fishHook.getLocation()); - } - return; - } - if (!this.inWater && this.fishHook.isInWater()) { - this.inWater = true; - - this.fishingPreparation.setLocation(this.fishHook.getLocation()); - this.fishingPreparation.insertArg("{lava}", "false"); - this.fishingPreparation.insertArg("{open-water}", String.valueOf(this.fishHook.isInOpenWater())); - - if (this.firstTime) { - this.firstTime = false; - this.fishingPreparation.mergeEffect(this.initialEffect); - - FishHookLandEvent event = new FishHookLandEvent(this.fishingPreparation.getPlayer(), FishHookLandEvent.Target.WATER, this.fishHook, false, this.initialEffect); - Bukkit.getPluginManager().callEvent(event); - - this.fishingPreparation.triggerActions(ActionTrigger.LAND); - - } else { - FishHookLandEvent event = new FishHookLandEvent(this.fishingPreparation.getPlayer(), FishHookLandEvent.Target.WATER, this.fishHook, true, this.initialEffect); - Bukkit.getPluginManager().callEvent(event); - } - - this.setNextLoot(); - if (this.loot == null) { - // prevent players from getting vanilla loots - this.fishHook.setWaitTime(Integer.MAX_VALUE); - this.tempEffect = new FishingEffect(); - this.tempEffect.merge(this.initialEffect); - this.manager.removeTempFishingState(fishingPreparation.getPlayer()); - CustomFishingPlugin.get().debug("No loot available for " + fishingPreparation.getPlayer().getName() + " at " + fishingPreparation.getLocation()); - } else { - this.tempEffect = this.loot.getBaseEffect().build(fishingPreparation.getPlayer(), fishingPreparation.getArgs()); - this.tempEffect.merge(this.initialEffect); - this.setWaitTime(); - this.setTempState(); - } - - return; - } - } - - /** - * Destroys the task and associated entities. - */ - public void destroy() { - this.cancelSubTask(); - this.removeTempEntity(); - this.hookMovementTask.cancel(); - this.manager.removeHookCheckTask(fishingPreparation.getPlayer()); - } - - /** - * Cancels the lava fishing subtask if it's active. - */ - public void cancelSubTask() { - if (lavaFishingTask != null && !lavaFishingTask.isCancelled()) { - lavaFishingTask.cancel(); - lavaFishingTask = null; - } - } - - private void setNextLoot() { - Loot nextLoot = CustomFishingPlugin.get().getLootManager().getNextLoot(initialEffect, fishingPreparation); - if (nextLoot == null) { - this.loot = null; - return; - } - this.loot = nextLoot; - } - - /** - * Sets temporary state and prepares for the next loot. - */ - private void setTempState() { - fishingPreparation.insertArg("{nick}", loot.getNick()); - fishingPreparation.insertArg("{loot}", loot.getID()); - if (!loot.disableStats()) { - fishingPreparation.insertArg("{statistics_size}", loot.getStatisticKey().getSizeKey()); - fishingPreparation.insertArg("{statistics_amount}", loot.getStatisticKey().getAmountKey()); - } - manager.setTempFishingState(fishingPreparation.getPlayer(), new TempFishingState( - tempEffect, - fishingPreparation, - loot - )); - } - - /** - * Removes the temporary hooked entity. - */ - public void removeTempEntity() { - if (hookedEntity != null && !hookedEntity.isDead()) - hookedEntity.remove(); - } - - /** - * Starts the lava fishing mechanic. - */ - private void startLavaFishingMechanic() { - // get random time - int random; - if (CFConfig.overrideVanilla) { - random = ThreadLocalRandom.current().nextInt(CFConfig.lavaMinTime, CFConfig.lavaMaxTime); - random *= tempEffect.getWaitTimeMultiplier(); - random += tempEffect.getWaitTime(); - random = Math.max(1, random); - } else { - random = ThreadLocalRandom.current().nextInt(CFConfig.lavaMinTime, CFConfig.lavaMaxTime); - random -= lureLevel * 100; - random = Math.max(CFConfig.lavaMinTime, random); - random *= tempEffect.getWaitTimeMultiplier(); - random += tempEffect.getWaitTime(); - random = Math.max(1, random); - } - - // lava effect task (Three seconds in advance) - this.lavaFishingTask = new LavaEffectTask( - this, - fishHook.getLocation(), - random - 3 * 20 - ); - } - - /** - * Handles the hook state of the fish hook. - */ - public void getHooked() { - LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(fishingPreparation.getPlayer(), LavaFishingEvent.State.BITE, fishHook); - Bukkit.getPluginManager().callEvent(lavaFishingEvent); - if (lavaFishingEvent.isCancelled()) { - this.startLavaFishingMechanic(); - return; - } - - this.loot.triggerActions(ActionTrigger.BITE, fishingPreparation); - this.fishingPreparation.triggerActions(ActionTrigger.BITE); - - this.fishHooked = true; - this.removeTempEntity(); - - AdventureHelper.getInstance().sendSound( - fishingPreparation.getPlayer(), - Sound.Source.NEUTRAL, - Key.key("minecraft:block.pointed_dripstone.drip_lava_into_cauldron"), - 1, - 1 - ); - - CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> { - fishHooked = false; - reserve = false; - }, (2 * 20) * 50L, TimeUnit.MILLISECONDS); - } - - private void makeHookStatic(Location armorLoc) { - armorLoc.setY(armorLoc.getBlockY() + 0.2); - if (hookedEntity != null && !hookedEntity.isDead()) - hookedEntity.remove(); - hookedEntity = armorLoc.getWorld().spawn(armorLoc, ArmorStand.class); - setTempEntity((ArmorStand) hookedEntity); - fishHook.setHookedEntity(hookedEntity); - } - - private void setTempEntity(ArmorStand entity) { - entity.setInvisible(true); - entity.setCollidable(false); - entity.setInvulnerable(true); - entity.setVisible(false); - entity.setCustomNameVisible(false); - entity.setSmall(true); - entity.setGravity(false); - entity.getPersistentDataContainer().set( - Objects.requireNonNull(NamespacedKey.fromString("lavafishing", CustomFishingPlugin.get())), - PersistentDataType.STRING, - "temp" - ); - } - - /** - * Checks if the fish hook is currently hooked. - * - * @return True if the fish hook is hooked, false otherwise. - */ - public boolean isFishHooked() { - return fishHooked; - } - - private void setWaitTime() { - if (CFConfig.overrideVanilla) { - double initialTime = ThreadLocalRandom.current().nextInt(CFConfig.waterMaxTime - CFConfig.waterMinTime + 1) + CFConfig.waterMinTime; - fishHook.setWaitTime(Math.max(1, (int) (initialTime * tempEffect.getWaitTimeMultiplier() + tempEffect.getWaitTime()))); - } else { - int maxWait = Math.max(2, (int) (fishHook.getMaxWaitTime() * tempEffect.getWaitTimeMultiplier() + tempEffect.getWaitTime())); - int minWait = Math.min(Math.max(1, (int) (fishHook.getMinWaitTime() * tempEffect.getWaitTimeMultiplier() + tempEffect.getWaitTime())), fishHook.getMaxWaitTime()); - fishHook.setWaitTime(ThreadLocalRandom.current().nextInt(minWait, maxWait + 1)); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/LavaEffectTask.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/LavaEffectTask.java deleted file mode 100644 index 22a06065..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/LavaEffectTask.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.fishing; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Location; -import org.bukkit.Particle; - -import java.util.concurrent.TimeUnit; - -/** - * A task responsible for creating a lava effect animation between two points. - */ -public class LavaEffectTask implements Runnable { - - private final Location startLoc; - private final Location endLoc; - private final Location controlLoc; - private int timer; - private final CancellableTask lavaTask; - private final HookCheckTimerTask hookCheckTimerTask; - - /** - * Constructs a new LavaEffectTask. - * - * @param hookCheckTimerTask The HookCheckTimerTask instance. - * @param location The starting location for the lava effect. - * @param delay The delay before starting the task. - */ - public LavaEffectTask(HookCheckTimerTask hookCheckTimerTask, Location location, int delay) { - this.hookCheckTimerTask = hookCheckTimerTask; - this.startLoc = location.clone().add(0,0.3,0); - this.endLoc = this.startLoc.clone().add((Math.random() * 16 - 8), startLoc.getY(), (Math.random() * 16 - 8)); - this.controlLoc = new Location( - startLoc.getWorld(), - (startLoc.getX() + endLoc.getX())/2 + Math.random() * 12 - 6, - startLoc.getY(), - (startLoc.getZ() + endLoc.getZ())/2 + Math.random() * 12 - 6 - ); - this.lavaTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, delay * 50L, 50, TimeUnit.MILLISECONDS); - } - - @Override - public void run() { - timer++; - if (timer > 60) { - lavaTask.cancel(); - CustomFishingPlugin.get().getScheduler().runTaskSync(hookCheckTimerTask::getHooked, startLoc); - } else { - double t = (double) timer / 60; - Location particleLoc = endLoc.clone().multiply(Math.pow((1 - t), 2)).add(controlLoc.clone().multiply(2 * t * (1 - t))).add(startLoc.clone().multiply(Math.pow(t, 2))); - particleLoc.setY(startLoc.getY()); - startLoc.getWorld().spawnParticle(Particle.FLAME, particleLoc,1,0,0,0,0); - } - } - - /** - * Cancels the lava effect task. - */ - public void cancel() { - if (lavaTask != null && !lavaTask.isCancelled()) - lavaTask.cancel(); - } - - /** - * Checks if the lava effect task is cancelled. - * - * @return True if the task is cancelled, false otherwise. - */ - public boolean isCancelled() { - return lavaTask.isCancelled(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/game/GameManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/game/GameManagerImpl.java deleted file mode 100644 index 50037e9d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/game/GameManagerImpl.java +++ /dev/null @@ -1,1209 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.game; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.manager.GameManager; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.game.*; -import net.momirealms.customfishing.api.util.FontUtils; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.OffsetUtils; -import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl; -import net.momirealms.customfishing.util.ClassUtils; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("DuplicatedCode") -public class GameManagerImpl implements GameManager { - - private final CustomFishingPlugin plugin; - private final HashMap gameCreatorMap; - private final HashMap> gameInstanceMap; - private final String EXPANSION_FOLDER = "expansions/minigame"; - - public GameManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.gameCreatorMap = new HashMap<>(); - this.gameInstanceMap = new HashMap<>(); - this.registerInbuiltGames(); - } - - private void registerInbuiltGames() { - this.registerHoldGame(); - this.registerHoldV2Game(); - this.registerTensionGame(); - this.registerClickGame(); - this.registerDanceGame(); - this.registerAccurateClickGame(); - this.registerAccurateClickV2Game(); - this.registerAccurateClickV3Game(); - } - - public void load() { - this.loadExpansions(); - this.loadGamesFromPluginFolder(); - } - - public void unload() { - this.gameInstanceMap.clear(); - } - - public void disable() { - unload(); - this.gameCreatorMap.clear(); - } - - /** - * Registers a new game type with the specified type identifier. - * - * @param type The type identifier for the game. - * @param gameFactory The {@link GameFactory} that creates instances of the game. - * @return {@code true} if the registration was successful, {@code false} if the type identifier is already registered. - */ - @Override - public boolean registerGameType(String type, GameFactory gameFactory) { - if (gameCreatorMap.containsKey(type)) - return false; - else - gameCreatorMap.put(type, gameFactory); - return true; - } - - /** - * Unregisters a game type with the specified type identifier. - * - * @param type The type identifier of the game to unregister. - * @return {@code true} if the game type was successfully unregistered, {@code false} if the type identifier was not found. - */ - @Override - public boolean unregisterGameType(String type) { - return gameCreatorMap.remove(type) != null; - } - - /** - * Retrieves the game factory associated with the specified game type. - * - * @param type The type identifier of the game. - * @return The {@code GameFactory} for the specified game type, or {@code null} if not found. - */ - @Override - @Nullable - public GameFactory getGameFactory(String type) { - return gameCreatorMap.get(type); - } - - /** - * Retrieves a game instance and its basic configuration associated with the specified key. - * - * @param key The key identifying the game instance. - * @return An {@code Optional} containing a {@code Pair} of the basic game configuration and the game instance - * if found, or an empty {@code Optional} if not found. - */ - @Override - public Pair getGameInstance(String key) { - return gameInstanceMap.get(key); - } - - /** - * Retrieves a map of game names and their associated weights based on the specified conditions. - * - * @param condition The condition to evaluate game weights. - * @return A {@code HashMap} containing game names as keys and their associated weights as values. - */ - @Override - public HashMap getGameWithWeight(Condition condition) { - return ((RequirementManagerImpl) plugin.getRequirementManager()).getGameWithWeight(condition); - } - - /** - * Loads minigames from the plugin folder. - * This method searches for minigame configuration files in the plugin's data folder and loads them. - */ - public void loadGamesFromPluginFolder() { - Deque fileDeque = new ArrayDeque<>(); - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + "minigame"); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + "minigame" + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - loadSingleFile(subFile); - } - } - } - } - - /** - * Loads a minigame configuration from a YAML file. - * This method parses the YAML file and extracts minigame configurations to be used in the plugin. - * - * @param file The YAML file to load. - */ - private void loadSingleFile(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - GameFactory creator = this.getGameFactory(section.getString("game-type")); - if (creator == null) { - LogUtils.warn("Game type:" + section.getString("game-type") + " doesn't exist."); - continue; - } - - BasicGameConfig.Builder basicGameBuilder = new BasicGameConfig.Builder(); - Object time = section.get("time", 15); - if (time instanceof String str) { - String[] split = str.split("~"); - basicGameBuilder.time(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } else if (time instanceof Integer integer) { - basicGameBuilder.time(integer); - } - Object difficulty = section.get("difficulty", "20~80"); - if (difficulty instanceof String str) { - String[] split = str.split("~"); - basicGameBuilder.difficulty(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } else if (difficulty instanceof Integer integer) { - basicGameBuilder.difficulty(integer); - } - gameInstanceMap.put(entry.getKey(), Pair.of(basicGameBuilder.build(), creator.setArgs(section))); - } - } - } - - private void registerAccurateClickGame() { - this.registerGameType("accurate_click", (section -> { - - Set chances = Objects.requireNonNull(section.getConfigurationSection("success-rate-sections")).getKeys(false); - var widthPerSection = section.getInt("arguments.width-per-section", 16); - var successRate = new double[chances.size()]; - for(int i = 0; i < chances.size(); i++) - successRate[i] = section.getDouble("success-rate-sections." + (i + 1)); - var totalWidth = chances.size() * widthPerSection - 1; - var pointerOffset = section.getInt("arguments.pointer-offset"); - var pointerWidth = section.getInt("arguments.pointer-width"); - var title = ConfigUtils.stringListArgs(section.get("title")); - var font = section.getString("subtitle.font"); - var barImage = section.getString("subtitle.bar"); - var pointerImage = section.getString("subtitle.pointer"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private int progress = -1; - private boolean face = true; - private final String sendTitle = title.get(ThreadLocalRandom.current().nextInt(title.size())); - - @Override - public void arrangeTask() { - var period = ((double) 10*(200-settings.getDifficulty()))/((double) (1+4*settings.getDifficulty())); - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer( - this, - 50, - (long) period, - TimeUnit.MILLISECONDS - ); - } - - @Override - public void onTick() { - if (face) progress++; - else progress--; - if (progress > totalWidth) { - face = !face; - progress = 2 * totalWidth - progress; - } else if (progress < 0) { - face = !face; - progress = -progress; - } - showUI(); - } - - public void showUI() { - String bar = FontUtils.surroundWithFont(barImage, font) - + OffsetUtils.getOffsetChars(pointerOffset + progress) - + FontUtils.surroundWithFont(pointerImage, font) - + OffsetUtils.getOffsetChars(totalWidth - progress - pointerWidth); - AdventureHelper.getInstance().sendTitle(player, sendTitle, bar,0,10,0); - } - - @Override - public boolean isSuccessful() { - if (isTimeOut) return false; - int last = progress / widthPerSection; - return (Math.random() < successRate[last]); - } - }; - })); - } - - private void registerHoldGame() { - this.registerGameType("hold", (section -> { - - var timeRequirements = section.getIntegerList("hold-time-requirements").stream().mapToInt(Integer::intValue).toArray(); - var judgementAreaImage = section.getString("subtitle.judgment-area"); - var pointerImage = section.getString("subtitle.pointer"); - var barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); - var judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); - var judgementAreaWidth = section.getInt("arguments.judgment-area-width"); - var pointerIconWidth = section.getInt("arguments.pointer-icon-width"); - var punishment = section.getDouble("arguments.punishment"); - var progress = section.getStringList("progress").toArray(new String[0]); - var waterResistance = section.getDouble("arguments.water-resistance", 0.15); - var pullingStrength = section.getDouble("arguments.pulling-strength", 0.45); - var looseningLoss = section.getDouble("arguments.loosening-strength-loss", 0.3); - - var title = section.getString("title","{progress}"); - var font = section.getString("subtitle.font"); - var barImage = section.getString("subtitle.bar"); - var tip = section.getString("tip"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - private double hold_time; - private double judgement_position; - private double fish_position; - private double judgement_velocity; - private double fish_velocity; - private int timer; - private final int time_requirement = timeRequirements[ThreadLocalRandom.current().nextInt(timeRequirements.length)] * 1000; - private boolean played; - - @Override - public void arrangeTask() { - this.judgement_position = (double) (barEffectiveWidth - judgementAreaWidth) / 2; - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer( - this, - 50, - 33, - TimeUnit.MILLISECONDS - ); - } - - @Override - public void onTick() { - if (player.isSneaking()) addV(); - else reduceV(); - if (timer < 40 - (settings.getDifficulty() / 10)) { - timer++; - } else { - timer = 0; - if (Math.random() > ((double) 25 / (settings.getDifficulty() + 100))) { - burst(); - } - } - judgement_position += judgement_velocity; - fish_position += fish_velocity; - fraction(); - calibrate(); - if (fish_position >= judgement_position && fish_position + pointerIconWidth <= judgement_position + judgementAreaWidth) { - hold_time += 33; - } else { - hold_time -= punishment * 33; - } - if (hold_time >= time_requirement) { - setGameResult(true); - endGame(); - return; - } - hold_time = Math.max(0, Math.min(hold_time, time_requirement)); - showUI(); - } - - private void burst() { - if (Math.random() < (judgement_position / barEffectiveWidth)) { - judgement_velocity = -1 - 0.8 * Math.random() * ((double) settings.getDifficulty() / 15); - } else { - judgement_velocity = 1 + 0.8 * Math.random() * ((double) settings.getDifficulty() / 15); - } - } - - private void fraction() { - if (judgement_velocity > 0) { - judgement_velocity -= waterResistance; - if (judgement_velocity < 0) judgement_velocity = 0; - } else { - judgement_velocity += waterResistance; - if (judgement_velocity > 0) judgement_velocity = 0; - } - } - - private void reduceV() { - fish_velocity -= looseningLoss; - } - - private void addV() { - played = true; - fish_velocity += pullingStrength; - } - - private void calibrate() { - if (fish_position < 0) { - fish_position = 0; - fish_velocity = 0; - } - if (fish_position + pointerIconWidth > barEffectiveWidth) { - fish_position = barEffectiveWidth - pointerIconWidth; - fish_velocity = 0; - } - if (judgement_position < 0) { - judgement_position = 0; - judgement_velocity = 0; - } - if (judgement_position + judgementAreaWidth > barEffectiveWidth) { - judgement_position = barEffectiveWidth - judgementAreaWidth; - judgement_velocity = 0; - } - } - - public void showUI() { - String bar = FontUtils.surroundWithFont(barImage, font) - + OffsetUtils.getOffsetChars((int) (judgementAreaOffset + judgement_position)) - + FontUtils.surroundWithFont(judgementAreaImage, font) - + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - judgement_position - judgementAreaWidth)) - + OffsetUtils.getOffsetChars((int) (-barEffectiveWidth - 1 + fish_position)) - + FontUtils.surroundWithFont(pointerImage, font) - + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - fish_position - pointerIconWidth + 1)); - AdventureHelper.getInstance().sendTitle( - player, - tip != null && !played ? tip : title.replace("{progress}", progress[(int) ((hold_time / time_requirement) * progress.length)]), - bar, - 0, - 10, - 0 - ); - } - }; - })); - } - - private void registerTensionGame() { - this.registerGameType("tension", (section -> { - - var fishIconWidth = section.getInt("arguments.fish-icon-width"); - var fishImage = section.getString("subtitle.fish"); - var tension = section.getStringList("tension").toArray(new String[0]); - var strugglingFishImage = section.getStringList("subtitle.struggling-fish").toArray(new String[0]); - var barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); - var fishOffset = section.getInt("arguments.fish-offset"); - var fishStartPosition = section.getInt("arguments.fish-start-position"); - var successPosition = section.getInt("arguments.success-position"); - var ultimateTension = section.getDouble("arguments.ultimate-tension", 50); - var normalIncrease = section.getDouble("arguments.normal-pull-tension-increase", 1); - var strugglingIncrease = section.getDouble("arguments.struggling-tension-increase", 2); - var tensionLoss = section.getDouble("arguments.loosening-tension-loss", 2); - - var title = section.getString("title","{progress}"); - var font = section.getString("subtitle.font"); - var barImage = section.getString("subtitle.bar"); - var tip = section.getString("tip"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private int fish_position = fishStartPosition; - private double strain; - private int struggling_time; - private boolean played; - - @Override - public void arrangeTask() { - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, 50, 40, TimeUnit.MILLISECONDS); - } - - @Override - public void onTick() { - if (struggling_time <= 0) { - if (Math.random() < ((double) settings.getDifficulty() / 4000)) { - struggling_time = (int) (10 + Math.random() * (settings.getDifficulty() / 4)); - } - } else { - struggling_time--; - } - if (player.isSneaking()) pull(); - else loosen(); - if (fish_position < successPosition - fishIconWidth - 1) { - setGameResult(true); - endGame(); - return; - } - if (fish_position + fishIconWidth > barEffectiveWidth || strain >= ultimateTension) { - setGameResult(false); - endGame(); - return; - } - showUI(); - } - - public void pull() { - played = true; - if (struggling_time > 0) { - strain += (strugglingIncrease + ((double) settings.getDifficulty() / 50)); - fish_position -= 1; - } else { - strain += normalIncrease; - fish_position -= 2; - } - } - - public void loosen() { - fish_position++; - strain -= tensionLoss; - } - - public void showUI() { - String bar = FontUtils.surroundWithFont(barImage, font) - + OffsetUtils.getOffsetChars(fishOffset + fish_position) - + FontUtils.surroundWithFont((struggling_time > 0 ? strugglingFishImage[struggling_time % strugglingFishImage.length] : fishImage), font) - + OffsetUtils.getOffsetChars(barEffectiveWidth - fish_position - fishIconWidth); - strain = Math.max(0, Math.min(strain, ultimateTension)); - AdventureHelper.getInstance().sendTitle( - player, - tip != null && !played ? tip : title.replace("{tension}", tension[(int) ((strain / ultimateTension) * tension.length)]), - bar, - 0, - 10, - 0 - ); - } - }; - })); - } - - private void registerDanceGame() { - this.registerGameType("dance", (section -> { - - var subtitle = section.getString("subtitle", "Dance to win. Time left {time}s"); - var leftNot = section.getString("title.left-button"); - var leftCorrect = section.getString("title.left-button-correct"); - var leftWrong = section.getString("title.left-button-wrong"); - var leftCurrent = section.getString("title.left-button-current"); - var rightNot = section.getString("title.right-button"); - var rightCorrect = section.getString("title.right-button-correct"); - var rightWrong = section.getString("title.right-button-wrong"); - var rightCurrent = section.getString("title.right-button-current"); - - var upNot = section.getString("title.up-button"); - var upCorrect = section.getString("title.up-button-correct"); - var upWrong = section.getString("title.up-button-wrong"); - var upCurrent = section.getString("title.up-button-current"); - var downNot = section.getString("title.down-button"); - var downCorrect = section.getString("title.down-button-correct"); - var downWrong = section.getString("title.down-button-wrong"); - var downCurrent = section.getString("title.down-button-current"); - - var maxShown = section.getInt("title.display-amount", 7); - var tip = section.getString("tip"); - var easy = section.getBoolean("easy", false); - - var correctSound = section.getString("sound.correct", "minecraft:block.amethyst_block.hit"); - var wrongSound = section.getString("sound.wrong", "minecraft:block.anvil.land"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private int clickedTimes; - private int requiredTimes; - private boolean preventFirst = true; - // 0 = left / 1 = right / 2 = up / 3 = down - private int[] order; - boolean fail = false; - - @Override - public void arrangeTask() { - requiredTimes = settings.getDifficulty() / 4; - order = new int[requiredTimes]; - for (int i = 0; i < requiredTimes; i++) { - order[i] = ThreadLocalRandom.current().nextInt(0, easy ? 2 : 4); - } - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, 50, 50, TimeUnit.MILLISECONDS); - } - - @Override - public void onTick() { - showUI(); - if (tip != null) { - AdventureHelper.getInstance().sendActionbar(player, tip); - } - } - - @Override - public boolean onRightClick() { - preventFirst = true; - if (order[clickedTimes] != 1) { - setGameResult(false); - fail = true; - showUI(); - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(wrongSound), - 1, - 1 - ); - endGame(); - return true; - } - - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(correctSound), - 1, - 1 - ); - clickedTimes++; - if (clickedTimes >= requiredTimes) { - setGameResult(true); - showUI(); - endGame(); - } - return true; - } - - @Override - public boolean onJump() { - if (order[clickedTimes] != 2) { - setGameResult(false); - fail = true; - showUI(); - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(wrongSound), - 1, - 1 - ); - endGame(); - return false; - } - - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(correctSound), - 1, - 1 - ); - clickedTimes++; - if (clickedTimes >= requiredTimes) { - setGameResult(true); - showUI(); - endGame(); - } - return false; - } - - @Override - public boolean onSneak() { - if (order[clickedTimes] != 3) { - setGameResult(false); - fail = true; - showUI(); - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(wrongSound), - 1, - 1 - ); - endGame(); - return false; - } - - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(correctSound), - 1, - 1 - ); - clickedTimes++; - if (clickedTimes >= requiredTimes) { - setGameResult(true); - showUI(); - endGame(); - } - return false; - } - - @Override - public boolean onLeftClick() { - if (preventFirst) { - preventFirst = false; - return false; - } - - if (order[clickedTimes] != 0) { - setGameResult(false); - fail = true; - showUI(); - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(wrongSound), - 1, - 1 - ); - endGame(); - return true; - } - - AdventureHelper.getInstance().sendSound( - player, - Sound.Source.PLAYER, - Key.key(correctSound), - 1, - 1 - ); - clickedTimes++; - if (clickedTimes >= requiredTimes) { - setGameResult(true); - showUI(); - endGame(); - } - return false; - } - - public void showUI() { - try { - if (requiredTimes <= maxShown) { - StringBuilder sb = new StringBuilder(); - for (int x = 0; x < requiredTimes; x++) { - if (x < clickedTimes) { - switch (order[x]) { - case 0 -> sb.append(leftCorrect); - case 1 -> sb.append(rightCorrect); - case 2 -> sb.append(upCorrect); - case 3 -> sb.append(downCorrect); - } - } else if (clickedTimes == x) { - switch (order[x]) { - case 0 -> sb.append(fail ? leftWrong : leftCurrent); - case 1 -> sb.append(fail ? rightWrong : rightCurrent); - case 2 -> sb.append(fail ? upWrong : upCurrent); - case 3 -> sb.append(fail ? downWrong : downCurrent); - } - } else { - switch (order[x]) { - case 0 -> sb.append(leftNot); - case 1 -> sb.append(rightNot); - case 2 -> sb.append(upNot); - case 3 -> sb.append(downNot); - } - } - } - AdventureHelper.getInstance().sendTitle( - player, - sb.toString(), - subtitle.replace("{time}", String.format("%.1f", ((double) deadline - System.currentTimeMillis())/1000)), - 0, - 10, - 0 - ); - } else { - int half = (maxShown - 1) / 2; - int low = clickedTimes - half; - int high = clickedTimes + half; - if (low < 0) { - high += (-low); - low = 0; - } else if (high >= requiredTimes) { - low -= (high - requiredTimes + 1); - high = requiredTimes - 1; - } - StringBuilder sb = new StringBuilder(); - for (int x = low; x < high + 1; x++) { - if (x < clickedTimes) { - switch (order[x]) { - case 0 -> sb.append(leftCorrect); - case 1 -> sb.append(rightCorrect); - case 2 -> sb.append(upCorrect); - case 3 -> sb.append(downCorrect); - } - } else if (clickedTimes == x) { - switch (order[x]) { - case 0 -> sb.append(fail ? leftWrong : leftCurrent); - case 1 -> sb.append(fail ? rightWrong : rightCurrent); - case 2 -> sb.append(fail ? upWrong : upCurrent); - case 3 -> sb.append(fail ? downWrong : downCurrent); - } - } else { - switch (order[x]) { - case 0 -> sb.append(leftNot); - case 1 -> sb.append(rightNot); - case 2 -> sb.append(upNot); - case 3 -> sb.append(downNot); - } - } - } - AdventureHelper.getInstance().sendTitle( - player, - sb.toString(), - subtitle.replace("{time}", String.format("%.1f", ((double) deadline - System.currentTimeMillis())/1000)), - 0, - 10, - 0 - ); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - })); - } - - private void registerClickGame() { - this.registerGameType("click", (section -> { - - var title = section.getString("title","{click}"); - var subtitle = section.getString("subtitle", "Click {clicks} times to win. Time left {time}s"); - var left = section.getBoolean("left-click", true); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private int clickedTimes; - private final int requiredTimes = settings.getDifficulty(); - private boolean preventFirst = true; - - @Override - public void arrangeTask() { - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, 50, 50, TimeUnit.MILLISECONDS); - } - - @Override - public void onTick() { - showUI(); - } - - @Override - public boolean onRightClick() { - if (left) { - setGameResult(false); - endGame(); - return true; - } - clickedTimes++; - if (clickedTimes >= requiredTimes) { - showUI(); - setGameResult(true); - endGame(); - } - return true; - } - - @Override - public boolean onLeftClick() { - if (!left) { - return false; - } - if (preventFirst) { - preventFirst = false; - return false; - } - clickedTimes++; - if (clickedTimes >= requiredTimes) { - showUI(); - setGameResult(true); - endGame(); - } - return false; - } - - public void showUI() { - AdventureHelper.getInstance().sendTitle( - player, - title.replace("{click}", String.valueOf(clickedTimes)), - subtitle.replace("{clicks}", String.valueOf(requiredTimes)).replace("{time}", String.format("%.1f", ((double) deadline - System.currentTimeMillis())/1000)), - 0, - 10, - 0 - ); - } - }; - })); - } - - private void registerAccurateClickV2Game() { - - this.registerGameType("accurate_click_v2", (section -> { - - var barWidth = ConfigUtils.getIntegerPair(section.getString("title.total-width", "15~20")); - var barSuccess = ConfigUtils.getIntegerPair(section.getString("title.success-width","3~4")); - var barBody = section.getString("title.body",""); - var barPointer = section.getString("title.pointer", ""); - var barTarget = section.getString("title.target",""); - - var subtitle = section.getString("subtitle", "Reel in at the most critical moment"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private final int totalWidth = ThreadLocalRandom.current().nextInt(barWidth.right() - barWidth.left() + 1) + barWidth.left(); - private final int successWidth = ThreadLocalRandom.current().nextInt(barSuccess.right() - barSuccess.left() + 1) + barSuccess.left(); - private final int successPosition = ThreadLocalRandom.current().nextInt((totalWidth - successWidth + 1)) + 1; - private int currentIndex = 0; - private int timer = 0; - private boolean face = true; - - @Override - public void arrangeTask() { - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, 50, 50, TimeUnit.MILLISECONDS); - } - - @Override - public void onTick() { - timer++; - if (timer % (21 - settings.getDifficulty() / 5) == 0) { - movePointer(); - } - showUI(); - } - - private void movePointer() { - if (face) { - currentIndex++; - if (currentIndex >= totalWidth - 1) { - face = false; - } - } else { - currentIndex--; - if (currentIndex <= 0) { - face = true; - } - } - } - - public void showUI() { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 1; i <= totalWidth; i++) { - if (i == currentIndex + 1) { - stringBuilder.append(barPointer); - continue; - } - if (i >= successPosition && i <= successPosition + successWidth - 1) { - stringBuilder.append(barTarget); - continue; - } - stringBuilder.append(barBody); - } - - AdventureHelper.getInstance().sendTitle( - player, - stringBuilder.toString(), - subtitle, - 0, - 10, - 0 - ); - } - - @Override - public boolean isSuccessful() { - return currentIndex + 1 <= successPosition + successWidth - 1 && currentIndex + 1 >= successPosition; - } - }; - })); - } - - private void registerAccurateClickV3Game() { - - this.registerGameType("accurate_click_v3", (section -> { - - var font = section.getString("subtitle.font"); - var pointerImage = section.getString("subtitle.pointer"); - var barImage = section.getString("subtitle.bar"); - var judgementAreaImage = section.getString("subtitle.judgment-area"); - var titles = ConfigUtils.stringListArgs(section.get("title")); - - var barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); - var judgementAreaWidth = section.getInt("arguments.judgment-area-width"); - var judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); - var pointerIconWidth = section.getInt("arguments.pointer-icon-width"); - var pointerOffset = section.getInt("arguments.pointer-offset"); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - - private int progress = -1; - private boolean face = true; - private final int judgement_position = ThreadLocalRandom.current().nextInt(barEffectiveWidth - judgementAreaWidth + 1); - private final String title = titles.get(ThreadLocalRandom.current().nextInt(titles.size())); - - @Override - public void arrangeTask() { - var period = ((double) 10*(200-settings.getDifficulty()))/((double) (1+4*settings.getDifficulty())); - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer( - this, - 50, - (long) period, - TimeUnit.MILLISECONDS - ); - } - - @Override - public void onTick() { - if (face) { - progress++; - if (progress >= barEffectiveWidth - 1) { - face = false; - } - } else { - progress--; - if (progress <= 0) { - face = true; - } - } - showUI(); - } - - public void showUI() { - String bar = FontUtils.surroundWithFont(barImage, font) - + OffsetUtils.getOffsetChars(judgementAreaOffset + judgement_position) - + FontUtils.surroundWithFont(judgementAreaImage, font) - + OffsetUtils.getOffsetChars(barEffectiveWidth - judgement_position - judgementAreaWidth) - + OffsetUtils.getOffsetChars(progress + pointerOffset) - + FontUtils.surroundWithFont(pointerImage, font) - + OffsetUtils.getOffsetChars(barEffectiveWidth - progress - pointerIconWidth + 1); - AdventureHelper.getInstance().sendTitle( - player, - title, - bar, - 0, - 10, - 0 - ); - } - - @Override - public boolean isSuccessful() { - return progress < judgement_position + judgementAreaWidth && progress >= judgement_position; - } - }; - })); - } - - private void registerHoldV2Game() { - this.registerGameType("hold_v2", (section -> { - - var timeRequirements = section.getIntegerList("hold-time-requirements").stream().mapToInt(Integer::intValue).toArray(); - var judgementAreaImage = section.getString("subtitle.judgment-area"); - var pointerImage = section.getString("subtitle.pointer"); - var barEffectiveWidth = section.getInt("arguments.bar-effective-area-width"); - var judgementAreaOffset = section.getInt("arguments.judgment-area-offset"); - var judgementAreaWidth = section.getInt("arguments.judgment-area-width"); - var pointerIconWidth = section.getInt("arguments.pointer-icon-width"); - var punishment = section.getDouble("arguments.punishment"); - var progress = section.getStringList("progress").toArray(new String[0]); - var waterResistance = section.getDouble("arguments.water-resistance", 0.15); - var pullingStrength = section.getDouble("arguments.pulling-strength", 3); - var looseningLoss = section.getDouble("arguments.loosening-strength-loss", 0.5); - - var title = section.getString("title", "{progress}"); - var font = section.getString("subtitle.font"); - var barImage = section.getString("subtitle.bar"); - var tip = section.getString("tip"); - - var left = section.getBoolean("left-click", true); - - return (player, fishHook, settings) -> new AbstractGamingPlayer(player, fishHook, settings) { - private double hold_time; - private double judgement_position; - private double fish_position; - private double judgement_velocity; - private double fish_velocity; - private int timer; - private final int time_requirement = timeRequirements[ThreadLocalRandom.current().nextInt(timeRequirements.length)] * 1000; - private boolean played; - private boolean preventFirst = true; - - @Override - public void arrangeTask() { - this.judgement_position = (double) (barEffectiveWidth - judgementAreaWidth) / 2; - this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer( - this, - 50, - 33, - TimeUnit.MILLISECONDS - ); - } - - @Override - public void onTick() { - if (timer < 40 - (settings.getDifficulty() / 10)) { - timer++; - } else { - timer = 0; - if (Math.random() > ((double) 25 / (settings.getDifficulty() + 100))) { - burst(); - } - } - judgement_position += judgement_velocity; - fish_position += fish_velocity; - fraction(); - calibrate(); - if (fish_position >= judgement_position && fish_position + pointerIconWidth <= judgement_position + judgementAreaWidth) { - hold_time += 33; - } else { - hold_time -= punishment * 33; - } - if (hold_time >= time_requirement) { - setGameResult(true); - endGame(); - return; - } - hold_time = Math.max(0, Math.min(hold_time, time_requirement)); - showUI(); - } - - private void burst() { - if (Math.random() < (judgement_position / barEffectiveWidth)) { - judgement_velocity = -1 - 0.8 * Math.random() * ((double) settings.getDifficulty() / 15); - } else { - judgement_velocity = 1 + 0.8 * Math.random() * ((double) settings.getDifficulty() / 15); - } - } - - private void fraction() { - if (judgement_velocity > 0) { - judgement_velocity -= waterResistance; - if (judgement_velocity < 0) judgement_velocity = 0; - } else { - judgement_velocity += waterResistance; - if (judgement_velocity > 0) judgement_velocity = 0; - } - fish_velocity -= looseningLoss; - if (fish_velocity < -10 * looseningLoss) { - fish_velocity = -10 * looseningLoss; - } - } - - private void calibrate() { - if (fish_position < 0) { - fish_position = 0; - fish_velocity = 0; - } - if (fish_position + pointerIconWidth > barEffectiveWidth) { - fish_position = barEffectiveWidth - pointerIconWidth; - fish_velocity = 0; - } - if (judgement_position < 0) { - judgement_position = 0; - judgement_velocity = 0; - } - if (judgement_position + judgementAreaWidth > barEffectiveWidth) { - judgement_position = barEffectiveWidth - judgementAreaWidth; - judgement_velocity = 0; - } - } - - @Override - public boolean onRightClick() { - if (left) { - setGameResult(false); - endGame(); - return true; - } - played = true; - fish_velocity = pullingStrength; - return true; - } - - @Override - public boolean onLeftClick() { - if (preventFirst) { - preventFirst = false; - return false; - } - if (left) { - played = true; - fish_velocity = pullingStrength; - } - return false; - } - - public void showUI() { - String bar = FontUtils.surroundWithFont(barImage, font) - + OffsetUtils.getOffsetChars((int) (judgementAreaOffset + judgement_position)) - + FontUtils.surroundWithFont(judgementAreaImage, font) - + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - judgement_position - judgementAreaWidth)) - + OffsetUtils.getOffsetChars((int) (-barEffectiveWidth - 1 + fish_position)) - + FontUtils.surroundWithFont(pointerImage, font) - + OffsetUtils.getOffsetChars((int) (barEffectiveWidth - fish_position - pointerIconWidth + 1)); - AdventureHelper.getInstance().sendTitle( - player, - tip != null && !played ? tip : title.replace("{progress}", progress[(int) ((hold_time / time_requirement) * progress.length)]), - bar, - 0, - 10, - 0 - ); - } - }; - })); - } - - /** - * Loads minigame expansions from the expansion folder. - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, GameExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - GameExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterGameType(expansion.getGameType()); - registerGameType(expansion.getGameType(), expansion.getGameFactory()); - LogUtils.info("Loaded minigame expansion: " + expansion.getGameType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() ); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/hook/HookManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/hook/HookManagerImpl.java deleted file mode 100644 index 0c6a830a..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/hook/HookManagerImpl.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.hook; - -import de.tr7zw.changeme.nbtapi.NBTCompound; -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.manager.HookManager; -import net.momirealms.customfishing.api.manager.RequirementManager; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.hook.HookSetting; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.mechanic.item.ItemManagerImpl; -import net.momirealms.customfishing.util.ItemUtils; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -public class HookManagerImpl implements Listener, HookManager { - - private final CustomFishingPlugin plugin; - private final HashMap hookSettingMap; - - public HookManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.hookSettingMap = new HashMap<>(); - } - - public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); - loadConfig(); - } - - public void unload() { - HandlerList.unregisterAll(this); - hookSettingMap.clear(); - } - - public void disable() { - unload(); - } - - /** - * Loads configuration files for the specified types. - */ - @SuppressWarnings("DuplicatedCode") - private void loadConfig() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("hook")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile); - } - } - } - } - } - - /** - * Loads data from a single configuration file. - * - * @param file The configuration file to load. - */ - private void loadSingleFile(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - if (!section.contains("max-durability")) { - LogUtils.warn("Please set max-durability to hook: " + entry.getKey()); - continue; - } - var setting = new HookSetting.Builder(entry.getKey()) - .durability(section.getInt("max-durability", 16)) - .lore(section.getStringList("lore-on-rod").stream().map(it -> "" + it).toList()) - .build(); - hookSettingMap.put(entry.getKey(), setting); - } - } - } - - /** - * Get the hook setting by its ID. - * - * @param id The ID of the hook setting to retrieve. - * @return The hook setting with the given ID, or null if not found. - */ - @Nullable - @Override - public HookSetting getHookSetting(String id) { - return hookSettingMap.get(id); - } - - /** - * Decreases the durability of a fishing hook by a specified amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The amount by which to decrease the durability. - * @param updateLore Whether to update the lore of the fishing rod. - */ - @Override - public void decreaseHookDurability(ItemStack rod, int amount, boolean updateLore) { - ItemUtils.decreaseHookDurability(rod, amount, updateLore); - } - - /** - * Increases the durability of a fishing hook by a specified amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The amount by which to increase the durability. - * @param updateLore Whether to update the lore of the fishing rod. - */ - @Override - public void increaseHookDurability(ItemStack rod, int amount, boolean updateLore) { - ItemUtils.increaseHookDurability(rod, amount, updateLore); - } - - /** - * Sets the durability of a fishing hook to a specific amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The new durability value to set. - * @param updateLore Whether to update the lore of the fishing rod. - */ - @Override - public void setHookDurability(ItemStack rod, int amount, boolean updateLore) { - ItemUtils.setHookDurability(rod, amount, updateLore); - } - - /** - * Equips a fishing hook on a fishing rod. - * - * @param rod The fishing rod ItemStack. - * @param hook The fishing hook ItemStack. - * @return True if the hook was successfully equipped, false otherwise. - */ - @Override - public boolean equipHookOnRod(ItemStack rod, ItemStack hook) { - if (rod == null || hook == null || hook.getType() == Material.AIR || hook.getAmount() != 1) - return false; - if (rod.getType() != Material.FISHING_ROD) - return false; - - String hookID = plugin.getItemManager().getAnyPluginItemID(hook); - HookSetting setting = getHookSetting(hookID); - if (setting == null) - return false; - - var curDurability = ItemUtils.getCustomDurability(hook); - if (curDurability.left() == 0) - return false; - - NBTItem rodNBTItem = new NBTItem(rod); - NBTCompound cfCompound = rodNBTItem.getOrCreateCompound("CustomFishing"); - - cfCompound.setString("hook_id", hookID); - cfCompound.setItemStack("hook_item", hook); - cfCompound.setInteger("hook_dur", curDurability.right()); - - ItemUtils.updateNBTItemLore(rodNBTItem); - rod.setItemMeta(rodNBTItem.getItem().getItemMeta()); - return true; - } - - /** - * Removes the fishing hook from a fishing rod. - * - * @param rod The fishing rod ItemStack. - * @return The removed fishing hook ItemStack, or null if no hook was found. - */ - @Override - public ItemStack removeHookFromRod(ItemStack rod) { - if (rod == null || rod.getType() != Material.FISHING_ROD) - return null; - - NBTItem rodNBTItem = new NBTItem(rod); - NBTCompound cfCompound = rodNBTItem.getCompound("CustomFishing"); - if (cfCompound == null) - return null; - - ItemStack hook = cfCompound.getItemStack("hook_item"); - if (hook != null) { - cfCompound.removeKey("hook_item"); - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_dur"); - ItemUtils.updateNBTItemLore(rodNBTItem); - rod.setItemMeta(rodNBTItem.getItem().getItemMeta()); - } - - return hook; - } - - /** - * Handles the event when a player clicks on a fishing rod in their inventory. - * - * @param event The InventoryClickEvent to handle. - */ - @EventHandler - @SuppressWarnings("deprecation") - public void onDragDrop(InventoryClickEvent event) { - if (event.isCancelled()) - return; - final Player player = (Player) event.getWhoClicked(); - if (event.getClickedInventory() != player.getInventory()) - return; - ItemStack clicked = event.getCurrentItem(); - if (clicked == null || clicked.getType() != Material.FISHING_ROD) - return; - if (player.getGameMode() != GameMode.SURVIVAL) - return; - if (plugin.getFishingManager().hasPlayerCastHook(player.getUniqueId())) - return; - - ItemStack cursor = event.getCursor(); - if (cursor.getType() == Material.AIR) { - if (event.getClick() == ClickType.RIGHT) { - NBTItem nbtItem = new NBTItem(clicked); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound == null) - return; - if (cfCompound.hasTag("hook_id")) { - event.setCancelled(true); - ItemStack hook = cfCompound.getItemStack("hook_item"); - ItemUtils.setDurability(hook, cfCompound.getInteger("hook_dur"), true); - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_item"); - cfCompound.removeKey("hook_dur"); - event.setCursor(hook); - ItemUtils.updateNBTItemLore(nbtItem); - clicked.setItemMeta(nbtItem.getItem().getItemMeta()); - } - } - return; - } - - String hookID = plugin.getItemManager().getAnyPluginItemID(cursor); - HookSetting setting = getHookSetting(hookID); - if (setting == null) - return; - - var cursorDurability = ItemUtils.getCustomDurability(cursor); - if (cursorDurability.left() == 0) { - if (plugin.getItemManager().getBuildableItem("hook", hookID) instanceof ItemManagerImpl.CFBuilder cfBuilder) { - ItemStack itemStack = cfBuilder.build(player, new HashMap<>()); - var pair = ItemUtils.getCustomDurability(itemStack); - cursorDurability = pair; - NBTItem nbtItem = new NBTItem(cursor); - NBTCompound compound = nbtItem.getOrCreateCompound("CustomFishing"); - compound.setInteger("max_dur", pair.left()); - compound.setInteger("cur_dur", pair.right()); - compound.setString("type", "hook"); - compound.setString("id", hookID); - cursor.setItemMeta(nbtItem.getItem().getItemMeta()); - } else { - return; - } - } - - Condition condition = new Condition(player, new HashMap<>()); - condition.insertArg("{rod}", plugin.getItemManager().getAnyPluginItemID(clicked)); - EffectCarrier effectCarrier = plugin.getEffectManager().getEffectCarrier("hook", hookID); - if (effectCarrier != null) { - if (!RequirementManager.isRequirementMet(condition, effectCarrier.getRequirements())) { - return; - } - } - - event.setCancelled(true); - - NBTItem rodNBTItem = new NBTItem(clicked); - NBTCompound cfCompound = rodNBTItem.getOrCreateCompound("CustomFishing"); - String previousHookID = cfCompound.getString("hook_id"); - - ItemStack clonedHook = cursor.clone(); - clonedHook.setAmount(1); - cursor.setAmount(cursor.getAmount() - 1); - - if (previousHookID != null && !previousHookID.isEmpty()) { - int previousHookDurability = cfCompound.getInteger("hook_dur"); - ItemStack previousItemStack = cfCompound.getItemStack("hook_item"); - ItemUtils.setDurability(previousItemStack, previousHookDurability, true); - if (cursor.getAmount() == 0) { - event.setCursor(previousItemStack); - } else { - ItemUtils.giveItem(player, previousItemStack, 1); - } - } - - cfCompound.setString("hook_id", hookID); - cfCompound.setItemStack("hook_item", clonedHook); - cfCompound.setInteger("hook_dur", cursorDurability.right()); - - ItemUtils.updateNBTItemLore(rodNBTItem); - clicked.setItemMeta(rodNBTItem.getItem().getItemMeta()); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/item/ItemManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/item/ItemManagerImpl.java deleted file mode 100644 index 696e3a6f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/item/ItemManagerImpl.java +++ /dev/null @@ -1,1124 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.item; - -import de.tr7zw.changeme.nbtapi.*; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Key; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.common.Tuple; -import net.momirealms.customfishing.api.event.FishingBagPreCollectEvent; -import net.momirealms.customfishing.api.event.FishingLootPreSpawnEvent; -import net.momirealms.customfishing.api.event.FishingLootSpawnEvent; -import net.momirealms.customfishing.api.manager.ActionManager; -import net.momirealms.customfishing.api.manager.ItemManager; -import net.momirealms.customfishing.api.manager.RequirementManager; -import net.momirealms.customfishing.api.mechanic.GlobalSettings; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.item.BuildableItem; -import net.momirealms.customfishing.api.mechanic.item.ItemBuilder; -import net.momirealms.customfishing.api.mechanic.item.ItemLibrary; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.WeightUtils; -import net.momirealms.customfishing.compatibility.item.CustomFishingItemImpl; -import net.momirealms.customfishing.compatibility.item.VanillaItemImpl; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.util.ConfigUtils; -import net.momirealms.customfishing.util.ItemUtils; -import net.momirealms.customfishing.util.LocationUtils; -import net.momirealms.customfishing.util.NBTUtils; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.block.Skull; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.block.*; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.inventory.InventoryPickupItemEvent; -import org.bukkit.event.inventory.PrepareAnvilEvent; -import org.bukkit.event.player.PlayerAttemptPickupItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.event.player.PlayerItemMendEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class ItemManagerImpl implements ItemManager, Listener { - - private static ItemManager instance; - private final CustomFishingPlugin plugin; - private final HashMap buildableItemMap; - private final HashMap itemLibraryMap; - private ItemLibrary[] itemDetectionArray; - private NamespacedKey blockKey; - - public ItemManagerImpl(CustomFishingPlugin plugin) { - instance = this; - this.plugin = plugin; - this.itemLibraryMap = new LinkedHashMap<>(); - this.buildableItemMap = new HashMap<>(); - this.blockKey = NamespacedKey.fromString("block", plugin); - this.registerItemLibrary(new CustomFishingItemImpl()); - this.registerItemLibrary(new VanillaItemImpl()); - } - - public void load() { - this.loadItemsFromPluginFolder(); - Bukkit.getPluginManager().registerEvents(this, plugin); - this.resetItemDetectionOrder(); - } - - public void unload() { - HandlerList.unregisterAll(this); - HashMap tempMap = new HashMap<>(this.buildableItemMap); - this.buildableItemMap.clear(); - for (Map.Entry entry : tempMap.entrySet()) { - if (entry.getValue().persist()) { - tempMap.put(entry.getKey(), entry.getValue()); - } - } - } - - private void resetItemDetectionOrder() { - ArrayList list = new ArrayList<>(); - for (String plugin : CFConfig.itemDetectOrder) { - ItemLibrary library = itemLibraryMap.get(plugin); - if (library != null) { - list.add(library); - } - } - this.itemDetectionArray = list.toArray(new ItemLibrary[0]); - } - - public Collection getItemLibraries() { - return itemLibraryMap.keySet(); - } - - /** - * Get a set of all item keys in the CustomFishing plugin. - * - * @return A set of item keys. - */ - @Override - public Set getAllItemsKey() { - return buildableItemMap.keySet(); - } - - public void disable() { - HandlerList.unregisterAll(this); - this.buildableItemMap.clear(); - this.itemLibraryMap.clear(); - } - - /** - * Loads items from the plugin folder. - * This method scans various item types (item, bait, rod, util, hook) in the plugin's content folder and loads their configurations. - */ - @SuppressWarnings("DuplicatedCode") - public void loadItemsFromPluginFolder() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("item", "bait", "rod", "util", "hook")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile, type); - } - } - } - } - } - - /** - * Loads a single item configuration file. - * - * @param file The YAML configuration file to load. - * @param namespace The namespace of the item type (item, bait, rod, util, hook). - */ - private void loadSingleFile(File file, String namespace) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : yaml.getValues(false).entrySet()) { - String value = entry.getKey(); - if (entry.getValue() instanceof ConfigurationSection section) { - Key key = Key.of(namespace, value); - if (buildableItemMap.containsKey(key)) { - LogUtils.severe("Duplicated item key found: " + key + "."); - } else { - buildableItemMap.put(key, getItemBuilder(section, namespace, value)); - } - } - } - } - - /** - * Build an ItemStack with a specified namespace and value for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param namespace The namespace of the item. - * @param value The value of the item. - * @return The constructed ItemStack. - */ - @Override - public ItemStack build(Player player, String namespace, String value) { - return build(player, namespace, value, new HashMap<>()); - } - - /** - * Build an ItemStack with a specified namespace and value, replacing placeholders, - * for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param namespace The namespace of the item. - * @param value The value of the item. - * @param placeholders The placeholders to replace in the item's attributes. - * @return The constructed ItemStack, or null if the item doesn't exist. - */ - @Override - public ItemStack build(Player player, String namespace, String value, Map placeholders) { - BuildableItem buildableItem = buildableItemMap.get(Key.of(namespace, value)); - if (buildableItem == null) return null; - return buildableItem.build(player, placeholders); - } - - /** - * Build an ItemStack using an ItemBuilder for a player. - * - * @param player The player for whom the ItemStack is being built. - * @param builder The ItemBuilder used to construct the ItemStack. - * @return The constructed ItemStack. - */ - @NotNull - @Override - public ItemStack build(Player player, ItemBuilder builder) { - return build(player, builder, new HashMap<>()); - } - - /** - * Retrieve a BuildableItem by its namespace and value. - * - * @param namespace The namespace of the BuildableItem. - * @param value The value of the BuildableItem. - * @return The BuildableItem with the specified namespace and value, or null if not found. - */ - @Override - @Nullable - public BuildableItem getBuildableItem(String namespace, String value) { - return buildableItemMap.get(Key.of(namespace, value)); - } - - /** - * Get the item ID associated with the given ItemStack by checking all available item libraries. - * The detection order is determined by the configuration. - * - * @param itemStack The ItemStack to retrieve the item ID from. - * @return The item ID or "AIR" if not found or if the ItemStack is null or empty. - */ - @NotNull - @Override - public String getAnyPluginItemID(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return "AIR"; - for (ItemLibrary library : itemDetectionArray) { - String id = library.getItemID(itemStack); - if (id != null) { - return id; - } - } - // should not reach this because vanilla library would always work - return "AIR"; - } - - /** - * Build an ItemStack for a player based on the provided item ID. - * - * @param player The player for whom the ItemStack is being built. - * @param id The item ID, which may include a namespace (e.g., "namespace:id"). - * @return The constructed ItemStack or null if the ID is not valid. - */ - @Override - public ItemStack buildAnyPluginItemByID(Player player, String id) { - if (id.contains(":")) { - String[] split = id.split(":", 2); - return itemLibraryMap.get(split[0]).buildItem(player, split[1]); - } else { - try { - return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH))); - } catch (IllegalArgumentException e) { - return new ItemStack(Material.COD); - } - } - } - - /** - * Checks if the provided ItemStack is a custom fishing item - * - * @param itemStack The ItemStack to check. - * @return True if the ItemStack is a custom fishing item; otherwise, false. - */ - @Override - public boolean isCustomFishingItem(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) return false; - NBTItem nbtItem = new NBTItem(itemStack); - return nbtItem.hasTag("CustomFishing") && !nbtItem.getCompound("CustomFishing").getString("id").equals(""); - } - - /** - * Get the item ID associated with the given ItemStack, if available. - * - * @param itemStack The ItemStack to retrieve the item ID from. - * @return The item ID or null if not found or if the ItemStack is null or empty. - */ - @Nullable - @Override - public String getCustomFishingItemID(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) return null; - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound == null) return null; - return cfCompound.getString("id"); - } - - /** - * Create a CFBuilder instance for an item configuration section - * - * @param section The configuration section containing item settings. - * @param type The type of the item (e.g., "rod", "bait"). - * @param id The unique identifier for the item. - * @return A CFBuilder instance representing the configured item, or null if the section is null. - */ - @Nullable - @Override - public CFBuilder getItemBuilder(ConfigurationSection section, String type, String id) { - if (section == null) return null; - String material = section.getString("material", type.equals("rod") ? "FISHING_ROD" : "PAPER"); - CFBuilder itemCFBuilder; - if (material.contains(":")) { - String[] split = material.split(":", 2); - itemCFBuilder = CFBuilder.of(split[0], split[1]); - } else { - itemCFBuilder = CFBuilder.of("vanilla", material.toUpperCase(Locale.ENGLISH)); - } - itemCFBuilder - .stackable(section.getBoolean("stackable", true)) - .size(ConfigUtils.getFloatPair(section.getString("size"))) - .price((float) section.getDouble("price.base"), (float) section.getDouble("price.bonus")) - .customModelData(section.getInt("custom-model-data")) - .nbt(section.getConfigurationSection("nbt")) - .maxDurability(section.getInt("max-durability")) - .itemFlag(section.getStringList("item-flags").stream().map(flag -> ItemFlag.valueOf(flag.toUpperCase())).toList()) - .enchantment(ConfigUtils.getEnchantmentPair(section.getConfigurationSection("enchantments")), false) - .enchantment(ConfigUtils.getEnchantmentPair(section.getConfigurationSection("stored-enchantments")), true) - .enchantmentPool(ConfigUtils.getEnchantAmountPair(section.getConfigurationSection("enchantment-pool.amount")), ConfigUtils.getEnchantPoolPair(section.getConfigurationSection("enchantment-pool.pool")), false) - .enchantmentPool(ConfigUtils.getEnchantAmountPair(section.getConfigurationSection("stored-enchantment-pool.amount")), ConfigUtils.getEnchantPoolPair(section.getConfigurationSection("stored-enchantment-pool.pool")), true) - .randomEnchantments(ConfigUtils.getEnchantmentTuple(section.getConfigurationSection("random-enchantments")), false) - .randomEnchantments(ConfigUtils.getEnchantmentTuple(section.getConfigurationSection("random-stored-enchantments")), true) - .tag(section.getBoolean("tag", true), type, id) - .randomDamage(section.getBoolean("random-durability", false)) - .unbreakable(section.getBoolean("unbreakable", false)) - .preventGrabbing(section.getBoolean("prevent-grabbing", true)) - .placeable(section.getBoolean("placeable", false)) - .head(section.getString("head64")) - .name(section.getString("display.name")) - .lore(section.getStringList("display.lore")); - if (section.get("amount") instanceof String s) { - Pair pair = ConfigUtils.getIntegerPair(s); - itemCFBuilder.amount(pair.left(), pair.right()); - } else { - itemCFBuilder.amount(section.getInt("amount", 1)); - } - return itemCFBuilder; - } - - /** - * Build an ItemStack using the provided ItemBuilder, player, and placeholders. - * - * @param player The player for whom the item is being built. - * @param builder The ItemBuilder that defines the item's properties. - * @param placeholders A map of placeholders and their corresponding values to be applied to the item. - * @return The constructed ItemStack. - */ - @Override - @NotNull - public ItemStack build(Player player, ItemBuilder builder, Map placeholders) { - ItemStack temp = itemLibraryMap.get(builder.getLibrary()).buildItem(player, builder.getId()); - if (temp.getType() == Material.AIR) { - return temp; - } - int amount = builder.getAmount(); - temp.setAmount(amount); - NBTItem nbtItem = new NBTItem(temp); - for (ItemBuilder.ItemPropertyEditor editor : builder.getEditors()) { - editor.edit(player, nbtItem, placeholders); - } - ItemUtils.updateNBTItemLore(nbtItem); - return nbtItem.getItem(); - } - - @Override - public ItemStack getItemStackAppearance(Player player, String material) { - if (material != null) { - ItemStack itemStack = buildAnyPluginItemByID(player, material); - if (itemStack != null) { - NBTItem nbtItem = new NBTItem(itemStack); - nbtItem.removeKey("display"); - return nbtItem.getItem(); - } else { - return new ItemStack(Material.BARRIER); - } - } else { - return new ItemStack(Material.STRUCTURE_VOID); - } - } - - /** - * Register an item library. - * - * @param itemLibrary The item library to register. - * @return True if the item library was successfully registered, false if it already exists. - */ - @Override - public boolean registerItemLibrary(ItemLibrary itemLibrary) { - if (itemLibraryMap.containsKey(itemLibrary.identification())) return false; - itemLibraryMap.put(itemLibrary.identification(), itemLibrary); - this.resetItemDetectionOrder(); - return true; - } - - /** - * Unregister an item library. - * - * @param identification The item library to unregister. - * @return True if the item library was successfully unregistered, false if it doesn't exist. - */ - @Override - public boolean unRegisterItemLibrary(String identification) { - boolean success = itemLibraryMap.remove(identification) != null; - if (success) - this.resetItemDetectionOrder(); - return success; - } - - @Override - public void dropItem(Player player, Location hookLocation, Location playerLocation, ItemStack item, Condition condition) { - if (item.getType() == Material.AIR) { - return; - } - - if (CFConfig.enableFishingBag && plugin.getBagManager().doesBagStoreLoots() && RequirementManager.isRequirementMet(condition, plugin.getBagManager().getCollectRequirements())) { - var bag = plugin.getBagManager().getOnlineBagInventory(player.getUniqueId()); - - FishingBagPreCollectEvent preCollectEvent = new FishingBagPreCollectEvent(player, item, bag); - Bukkit.getPluginManager().callEvent(preCollectEvent); - if (preCollectEvent.isCancelled()) { - return; - } - - int cannotPut = ItemUtils.putLootsToBag(bag, item, item.getAmount()); - // some are put into bag - if (cannotPut != item.getAmount()) { - ActionManager.triggerActions(condition, plugin.getBagManager().getCollectLootActions()); - } - // all are put - if (cannotPut == 0) { - return; - } - // bag is full - item.setAmount(cannotPut); - ActionManager.triggerActions(condition, plugin.getBagManager().getBagFullActions()); - } - - FishingLootPreSpawnEvent preSpawnEvent = new FishingLootPreSpawnEvent(player, hookLocation, item); - Bukkit.getPluginManager().callEvent(preSpawnEvent); - if (preSpawnEvent.isCancelled()) { - return; - } - - Item itemEntity = hookLocation.getWorld().dropItem(hookLocation, item); - FishingLootSpawnEvent spawnEvent = new FishingLootSpawnEvent(player, hookLocation, itemEntity); - Bukkit.getPluginManager().callEvent(spawnEvent); - if (spawnEvent.isCancelled()) { - itemEntity.remove(); - return; - } - - itemEntity.setInvulnerable(true); - plugin.getScheduler().runTaskAsyncLater(() -> { - if (itemEntity.isValid()) { - itemEntity.setInvulnerable(false); - } - }, 1, TimeUnit.SECONDS); - - Vector vector = playerLocation.subtract(hookLocation).toVector().multiply(0.105); - vector = vector.setY((vector.getY() + 0.22) * 1.18); - itemEntity.setVelocity(vector); - } - - /** - * Decreases the durability of an ItemStack by a specified amount and optionally updates its lore. - * - * @param player Player - * @param itemStack The ItemStack to modify. - * @param amount The amount by which to decrease the durability. - * @param updateLore Whether to update the lore of the ItemStack. - */ - @Override - public void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean updateLore) { - ItemUtils.decreaseDurability(player, itemStack, amount, updateLore); - } - - /** - * Increases the durability of an ItemStack by a specified amount and optionally updates its lore. - * - * @param itemStack The ItemStack to modify. - * @param amount The amount by which to increase the durability. - * @param updateLore Whether to update the lore of the ItemStack. - */ - @Override - public void increaseDurability(ItemStack itemStack, int amount, boolean updateLore) { - ItemUtils.increaseDurability(itemStack, amount, updateLore); - } - - /** - * Sets the durability of an ItemStack to a specific amount and optionally updates its lore. - * - * @param itemStack The ItemStack to modify. - * @param amount The new durability value. - * @param updateLore Whether to update the lore of the ItemStack. - */ - @Override - public void setDurability(ItemStack itemStack, int amount, boolean updateLore) { - ItemUtils.setDurability(itemStack, amount, updateLore); - } - - public static class CFBuilder implements ItemBuilder { - - private final String library; - private final String id; - private int min_amount; - private int max_amount; - private final LinkedHashMap editors; - - public CFBuilder(String library, String id) { - this.id = id; - this.library = library; - this.editors = new LinkedHashMap<>(); - this.min_amount = (max_amount = 1); - } - - public static CFBuilder of(String library, String id) { - return new CFBuilder(library, id); - } - - @Override - public ItemStack build(Player player, Map placeholders) { - return ItemManagerImpl.instance.build(player, this, placeholders); - } - - @Override - public boolean persist() { - return false; - } - - @Override - public ItemBuilder customModelData(int value) { - if (value == 0) return this; - editors.put("custom-model-data", (player, nbtItem, placeholders) -> nbtItem.setInteger("CustomModelData", value)); - return this; - } - - @Override - public ItemBuilder name(String name) { - if (name == null) return this; - editors.put("name", (player, nbtItem, placeholders) -> { - NBTCompound displayCompound = nbtItem.getOrCreateCompound("display"); - displayCompound.setString("Name", AdventureHelper.getInstance().componentToJson( - AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + PlaceholderManagerImpl.getInstance().parse(player, name, placeholders) - ) - )); - }); - return this; - } - - @Override - public ItemBuilder amount(int amount) { - this.min_amount = amount; - this.max_amount = amount; - return this; - } - - @Override - public ItemBuilder amount(int min_amount, int max_amount) { - this.min_amount = min_amount; - this.max_amount = max_amount; - return this; - } - - @Override - public ItemBuilder tag(boolean tag, String type, String id) { - editors.put("tag", (player, nbtItem, placeholders) -> { - if (!tag) return; - NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing"); - cfCompound.setString("type", type); - cfCompound.setString("id", id); - }); - return this; - } - - @Override - public ItemBuilder unbreakable(boolean unbreakable) { - editors.put("unbreakable", (player, nbtItem, placeholders) -> { - if (!unbreakable) return; - nbtItem.setByte("Unbreakable", (byte) 1); - }); - return this; - } - - @Override - public ItemBuilder placeable(boolean placeable) { - editors.put("placeable", (player, nbtItem, placeholders) -> { - if (!placeable) return; - NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing"); - cfCompound.setByte("placeable", (byte) 1); - }); - return this; - } - - @Override - public ItemBuilder lore(List lore) { - if (lore.size() == 0) return this; - editors.put("lore", (player, nbtItem, placeholders) -> { - NBTCompound displayCompound = nbtItem.getOrCreateCompound("display"); - NBTList list = displayCompound.getStringList("Lore"); - list.clear(); - list.addAll(lore.stream().map(s -> AdventureHelper.getInstance().componentToJson( - AdventureHelper.getInstance().getComponentFromMiniMessage( - "" + PlaceholderManagerImpl.getInstance().parse(player, s, placeholders) - ) - )).toList()); - }); - return this; - } - - @Override - public ItemBuilder nbt(Map nbt) { - if (nbt.size() == 0) return this; - editors.put("nbt", (player, nbtItem, placeholders) -> NBTUtils.setTagsFromBukkitYAML(player, placeholders, nbtItem, nbt)); - return this; - } - - @Override - public ItemBuilder nbt(ConfigurationSection section) { - if (section == null) return this; - editors.put("nbt", (player, nbtItem, placeholders) -> NBTUtils.setTagsFromBukkitYAML(player, placeholders, nbtItem, section.getValues(false))); - return this; - } - - @Override - public ItemBuilder itemFlag(List itemFlags) { - if (itemFlags.size() == 0) return this; - editors.put("item-flag", (player, nbtItem, placeholders) -> { - int flag = 0; - for (ItemFlag itemFlag : itemFlags) { - flag = flag | 1 << itemFlag.ordinal(); - } - nbtItem.setInteger("HideFlags", flag); - }); - return this; - } - - @Override - public ItemBuilder enchantment(List> enchantments, boolean store) { - if (enchantments.size() == 0) return this; - editors.put("enchantment", (player, nbtItem, placeholders) -> { - NBTCompoundList list = nbtItem.getCompoundList(store ? "StoredEnchantments" : "Enchantments"); - for (Pair pair : enchantments) { - NBTCompound nbtCompound = list.addCompound(); - nbtCompound.setString("id", pair.left()); - nbtCompound.setShort("lvl", pair.right()); - } - }); - return this; - } - - @Override - public ItemBuilder randomEnchantments(List> enchantments, boolean store) { - if (enchantments.size() == 0) return this; - editors.put("random-enchantment", (player, nbtItem, placeholders) -> { - NBTCompoundList list = nbtItem.getCompoundList(store ? "StoredEnchantments" : "Enchantments"); - HashSet ids = new HashSet<>(); - for (Tuple pair : enchantments) { - if (Math.random() < pair.getLeft() && !ids.contains(pair.getMid())) { - NBTCompound nbtCompound = list.addCompound(); - nbtCompound.setString("id", pair.getMid()); - nbtCompound.setShort("lvl", pair.getRight()); - ids.add(pair.getMid()); - } - } - }); - return this; - } - - @Override - public ItemBuilder enchantmentPool(List> amountPairs, List, Value>> enchantments, boolean store) { - if (enchantments.size() == 0 || amountPairs.size() == 0) return this; - editors.put("enchantment-pool", (player, nbtItem, placeholders) -> { - List> parsedAmountPair = new ArrayList<>(amountPairs.size()); - Map map = new HashMap<>(); - for (Pair rawValue : amountPairs) { - parsedAmountPair.add(Pair.of(rawValue.left(), rawValue.right().get(player, map))); - } - - int amount = WeightUtils.getRandom(parsedAmountPair); - if (amount <= 0) return; - NBTCompoundList list = nbtItem.getCompoundList(store ? "StoredEnchantments" : "Enchantments"); - - HashSet addedEnchantments = new HashSet<>(); - - List, Double>> cloned = new ArrayList<>(enchantments.size()); - for (Pair, Value> rawValue : enchantments) { - cloned.add(Pair.of(rawValue.left(), rawValue.right().get(player, map))); - } - - int i = 0; - outer: - while (i < amount && cloned.size() != 0) { - Pair enchantPair = WeightUtils.getRandom(cloned); - Enchantment enchantment = Enchantment.getByKey(NamespacedKey.fromString(enchantPair.left())); - if (enchantment == null) { - throw new NullPointerException("Enchantment: " + enchantPair.left() + " doesn't exist on your server."); - } - for (Enchantment added : addedEnchantments) { - if (enchantment.conflictsWith(added)) { - cloned.removeIf(pair -> pair.left().left().equals(enchantPair.left())); - continue outer; - } - } - NBTCompound nbtCompound = list.addCompound(); - nbtCompound.setString("id", enchantPair.left()); - nbtCompound.setShort("lvl", enchantPair.right()); - addedEnchantments.add(enchantment); - cloned.removeIf(pair -> pair.left().left().equals(enchantPair.left())); - i++; - } - }); - return this; - } - - @Override - public ItemBuilder maxDurability(int max) { - if (max == 0) return this; - editors.put("durability", (player, nbtItem, placeholders) -> { - NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing"); - cfCompound.setInteger("max_dur", max); - cfCompound.setInteger("cur_dur", max); - }); - return this; - } - - @Override - public ItemBuilder price(float base, float bonus) { - if (base == 0 && bonus == 0) return this; - editors.put("price", (player, nbtItem, placeholders) -> { - placeholders.put("{base}", String.format("%.2f", base)); - placeholders.put("{BASE}", String.valueOf(base)); - placeholders.put("{bonus}", String.format("%.2f", bonus)); - placeholders.put("{BONUS}", String.valueOf(bonus)); - double price; - price = CustomFishingPlugin.get().getMarketManager().getFishPrice( - player, - placeholders - ); - nbtItem.setDouble("Price", price); - placeholders.put("{price}", String.format("%.2f", price)); - placeholders.put("{PRICE}", String.valueOf(price)); - }); - return this; - } - - @Override - public ItemBuilder size(Pair size) { - if (size == null) return this; - editors.put("size", (player, nbtItem, placeholders) -> { - NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing"); - float random = size.left() + (size.left() >= size.right() ? 0 : ThreadLocalRandom.current().nextFloat(size.right() - size.left())); - float bonus = Float.parseFloat(placeholders.getOrDefault("{size-multiplier}", "1.0")); - double fixed = Double.parseDouble(placeholders.getOrDefault("{size-fixed}", "0.0")); - random *= bonus; - random += fixed; - if (CFConfig.restrictedSizeRange) { - if (random > size.right()) { - random = size.right(); - } else if (random < size.left()) { - random = size.left(); - } - } - cfCompound.setFloat("size", random); - placeholders.put("{size}", String.format("%.2f", random)); - placeholders.put("{SIZE}", String.valueOf(random)); - placeholders.put("{min_size}", String.valueOf(size.left())); - placeholders.put("{max_size}", String.valueOf(size.right())); - }); - return this; - } - - @Override - public ItemBuilder stackable(boolean stackable) { - if (stackable) return this; - editors.put("stackable", (player, nbtItem, placeholders) -> { - NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing"); - cfCompound.setUUID("uuid", UUID.randomUUID()); - }); - return this; - } - - @Override - public ItemBuilder preventGrabbing(boolean prevent) { - if (!prevent) return this; - editors.put("grabbing", (player, nbtItem, placeholders) -> { - nbtItem.setString("owner", placeholders.get("player")); - }); - return this; - } - - @Override - public ItemBuilder head(String base64) { - if (base64 == null) return this; - editors.put("head", (player, nbtItem, placeholders) -> { - NBTCompound nbtCompound = nbtItem.addCompound("SkullOwner"); - nbtCompound.setUUID("Id", UUID.nameUUIDFromBytes(id.getBytes())); - NBTListCompound texture = nbtCompound.addCompound("Properties").getCompoundList("textures").addCompound(); - texture.setString("Value", base64); - }); - return this; - } - - @Override - public ItemBuilder randomDamage(boolean damage) { - if (!damage) return this; - editors.put("damage", (player, nbtItem, placeholders) -> { - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null) { - int i = cfCompound.getInteger("max_dur"); - if (i != 0) { - int dur = ThreadLocalRandom.current().nextInt(i); - cfCompound.setInteger("cur_dur", dur); - nbtItem.setInteger("Damage", (int) (nbtItem.getItem().getType().getMaxDurability() * ((double) dur / i))); - } else { - nbtItem.setInteger("Damage", ThreadLocalRandom.current().nextInt(nbtItem.getItem().getType().getMaxDurability())); - } - } else { - nbtItem.setInteger("Damage", ThreadLocalRandom.current().nextInt(nbtItem.getItem().getType().getMaxDurability())); - } - }); - return this; - } - - @Override - public @NotNull String getId() { - return id; - } - - @Override - public @NotNull String getLibrary() { - return library; - } - - @Override - public int getAmount() { - return ThreadLocalRandom.current().nextInt(min_amount, max_amount + 1); - } - - @Override - public Collection getEditors() { - return editors.values(); - } - - @Override - public ItemBuilder removeEditor(String type) { - editors.remove(type); - return this; - } - - @Override - public ItemBuilder registerCustomEditor(String type, ItemPropertyEditor editor) { - editors.put(type, editor); - return this; - } - } - - /** - * Handles item pickup by players. - * - * @param event The PlayerAttemptPickupItemEvent. - */ - @EventHandler (ignoreCancelled = true) - public void onPickUp(PlayerAttemptPickupItemEvent event) { - ItemStack itemStack = event.getItem().getItemStack(); - NBTItem nbtItem = new NBTItem(itemStack); - if (!nbtItem.hasTag("owner")) return; - if (!Objects.equals(nbtItem.getString("owner"), event.getPlayer().getName())) { - event.setCancelled(true); - } else { - nbtItem.removeKey("owner"); - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - } - - /** - * Handles item movement in inventories. - * - * @param event The InventoryPickupItemEvent. - */ - @EventHandler (ignoreCancelled = true) - public void onInvPickItem(InventoryPickupItemEvent event) { - ItemStack itemStack = event.getItem().getItemStack(); - NBTItem nbtItem = new NBTItem(itemStack); - if (!nbtItem.hasTag("owner")) return; - nbtItem.removeKey("owner"); - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Handles item consumption by players. - * - * @param event The PlayerItemConsumeEvent. - */ - @EventHandler (ignoreCancelled = true) - public void onConsumeItem(PlayerItemConsumeEvent event) { - ItemStack itemStack = event.getItem(); - String id = getAnyPluginItemID(itemStack); - Loot loot = plugin.getLootManager().getLoot(id); - if (loot != null) { - Condition condition = new Condition(event.getPlayer()); - if (!loot.disableGlobalAction()) - GlobalSettings.triggerLootActions(ActionTrigger.CONSUME, condition); - loot.triggerActions(ActionTrigger.CONSUME, condition); - } - } - - /** - * Handles the repair of custom items in an anvil. - * - * @param event The PrepareAnvilEvent. - */ - @EventHandler (ignoreCancelled = true) - public void onRepairItem(PrepareAnvilEvent event) { - ItemStack result = event.getInventory().getResult(); - if (result == null || result.getType() == Material.AIR) return; - NBTItem nbtItem = new NBTItem(result); - NBTCompound compound = nbtItem.getCompound("CustomFishing"); - if (compound == null || !compound.hasTag("max_dur")) return; - if (!(result.getItemMeta() instanceof Damageable damageable)) { - return; - } - int max_dur = compound.getInteger("max_dur"); - compound.setInteger("cur_dur", (int) (max_dur * (1 - (double) damageable.getDamage() / result.getType().getMaxDurability()))); - event.setResult(nbtItem.getItem()); - } - - /** - * Handles the mending of custom items. - * - * @param event The PlayerItemMendEvent. - */ - @EventHandler (ignoreCancelled = true) - public void onMending(PlayerItemMendEvent event) { - ItemStack itemStack = event.getItem(); - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound compound = nbtItem.getCompound("CustomFishing"); - if (compound == null) return; - event.setCancelled(true); - ItemUtils.increaseDurability(itemStack, event.getRepairAmount(), true); - } - - /** - * Handles interactions with custom utility items. - * - * @param event The PlayerInteractEvent. - */ - @EventHandler - public void onInteractWithItems(PlayerInteractEvent event) { - if (event.useItemInHand() == Event.Result.DENY) - return; - if (event.getHand() != EquipmentSlot.HAND) - return; - ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand(); - if (itemStack.getType() == Material.AIR) - return; - if (event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_AIR && event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) - return; - String id = getAnyPluginItemID(itemStack); - Condition condition = new Condition(event.getPlayer()); - - Loot loot = plugin.getLootManager().getLoot(id); - if (loot != null) { - loot.triggerActions(ActionTrigger.INTERACT, condition); - return; - } - - // because the id can be from other plugins, so we can't infer the type of the item - for (String type : List.of("util", "bait", "hook")) { - EffectCarrier carrier = plugin.getEffectManager().getEffectCarrier(type, id); - if (carrier != null) { - Action[] actions = carrier.getActions(ActionTrigger.INTERACT); - if (actions != null) - for (Action action : actions) { - action.trigger(condition); - } - break; - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceBlock(BlockPlaceEvent event) { - ItemStack itemStack = event.getItemInHand(); - if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0 || !itemStack.hasItemMeta()) { - return; - } - - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound compound = nbtItem.getCompound("CustomFishing"); - if (compound != null) { - - if (!compound.hasTag("placeable") || compound.getByte("placeable") != 1) { - event.setCancelled(true); - return; - } - - Block block = event.getBlock(); - if (block.getState() instanceof Skull) { - PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); - ItemStack cloned = itemStack.clone(); - cloned.setAmount(1); - pdc.set(new NamespacedKey(plugin, LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING, ItemUtils.toBase64(cloned)); - } else { - event.setCancelled(true); - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onBreakBlock(BlockBreakEvent event) { - final Block block = event.getBlock(); - if (block.getState() instanceof Skull) { - PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); - String base64 = pdc.get(new NamespacedKey(plugin, LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING); - if (base64 != null) { - ItemStack itemStack = ItemUtils.fromBase64(base64); - event.setDropItems(false); - block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack); - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onPiston(BlockPistonExtendEvent event) { - for (Block block : event.getBlocks()) { - if (block.getState() instanceof Skull) { - PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); - if (pdc.has(new NamespacedKey(plugin, LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING)) { - event.setCancelled(true); - return; - } - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onPiston(BlockPistonRetractEvent event) { - for (Block block : event.getBlocks()) { - if (block.getState() instanceof Skull) { - PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); - if (pdc.has(new NamespacedKey(plugin, LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING)) { - event.setCancelled(true); - return; - } - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onExplosion(BlockExplodeEvent event) { - handleExplode(event.blockList()); - } - - @EventHandler (ignoreCancelled = true) - public void onExplosion(EntityExplodeEvent event) { - handleExplode(event.blockList()); - } - - private void handleExplode(List blocks) { - ArrayList blockToRemove = new ArrayList<>(); - for (Block block : blocks) { - if (block.getState() instanceof Skull) { - PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); - var nk = new NamespacedKey(plugin, LocationUtils.toChunkPosString(block.getLocation())); - String base64 = pdc.get(nk, PersistentDataType.STRING); - if (base64 != null) { - ItemStack itemStack = ItemUtils.fromBase64(base64); - block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack); - blockToRemove.add(block); - block.setType(Material.AIR); - pdc.remove(nk); - } - } - } - blocks.removeAll(blockToRemove); - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/loot/LootManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/loot/LootManagerImpl.java deleted file mode 100644 index d2f0dd86..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/loot/LootManagerImpl.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.loot; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.manager.LootManager; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.Effect; -import net.momirealms.customfishing.api.mechanic.loot.CFLoot; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.loot.LootType; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; -import net.momirealms.customfishing.api.mechanic.statistic.StatisticsKey; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.WeightUtils; -import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -public class LootManagerImpl implements LootManager { - - private final CustomFishingPlugin plugin; - // A map that associates loot IDs with their respective loot configurations. - private final HashMap lootMap; - // A map that associates loot group IDs with lists of loot IDs. - private final HashMap> lootGroupMap; - - public LootManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.lootMap = new HashMap<>(); - this.lootGroupMap = new HashMap<>(); - } - - public void load() { - this.loadLootsFromPluginFolder(); - } - - public void unload() { - this.lootMap.clear(); - this.lootGroupMap.clear(); - } - - public void disable() { - unload(); - } - - /** - * Loads loot configurations from the plugin's content folders. - * This method scans the "item," "entity," and "block" subfolders within the plugin's data folder - * and loads loot configurations from YAML files. - * If the subfolders or default loot files don't exist, it creates them. - */ - @SuppressWarnings("DuplicatedCode") - public void loadLootsFromPluginFolder() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("item", "entity", "block")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - loadSingleFile(subFile, type); - } - } - } - } - } - - /** - * Retrieves a list of loot IDs associated with a loot group key. - * - * @param key The key of the loot group. - * @return A list of loot IDs belonging to the specified loot group, or null if not found. - */ - @Nullable - @Override - public List getLootGroup(String key) { - return lootGroupMap.get(key); - } - - /** - * Retrieves a loot configuration based on a provided loot key. - * - * @param key The key of the loot configuration. - * @return The Loot object associated with the specified loot key, or null if not found. - */ - @Nullable - @Override - public Loot getLoot(String key) { - return lootMap.get(key); - } - - /** - * Retrieves a collection of all loot configuration keys. - * - * @return A collection of all loot configuration keys. - */ - @Override - public Collection getAllLootKeys() { - return lootMap.keySet(); - } - - /** - * Retrieves a collection of all loot configurations. - * - * @return A collection of all loot configurations. - */ - @Override - public Collection getAllLoots() { - return lootMap.values(); - } - - /** - * Retrieves loot configurations with weights based on a given condition. - * - * @param condition The condition used to filter loot configurations. - * @return A mapping of loot configuration keys to their associated weights. - */ - @Override - public HashMap getLootWithWeight(Condition condition) { - return ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition); - } - - /** - * Get a collection of possible loot keys based on a given condition. - * - * @param condition The condition to determine possible loot. - * @return A collection of loot keys. - */ - @Override - public Collection getPossibleLootKeys(Condition condition) { - return ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition).keySet(); - } - - /** - * Get a map of possible loot keys with their corresponding weights, considering fishing effect and condition. - * - * @param effect The effect to apply weight modifiers. - * @param condition The condition to determine possible loot. - * @return A map of loot keys and their weights. - */ - @NotNull - @Override - public Map getPossibleLootKeysWithWeight(Effect effect, Condition condition) { - Map lootWithWeight = ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition); - Player player = condition.getPlayer(); - for (Pair pair : effect.getWeightModifier()) { - Double previous = lootWithWeight.get(pair.left()); - if (previous != null) - lootWithWeight.put(pair.left(), pair.right().modify(player, previous)); - } - for (Pair pair : effect.getWeightModifierIgnored()) { - double previous = lootWithWeight.getOrDefault(pair.left(), 0d); - lootWithWeight.put(pair.left(), pair.right().modify(player, previous)); - } - return lootWithWeight; - } - - /** - * Get the next loot item based on fishing effect and condition. - * - * @param effect The effect to apply weight modifiers. - * @param condition The condition to determine possible loot. - * @return The next loot item, or null if it doesn't exist. - */ - @Override - @Nullable - public Loot getNextLoot(Effect effect, Condition condition) { - String key = WeightUtils.getRandom(getPossibleLootKeysWithWeight(effect, condition)); - if (key == null) { - return null; - } - Loot loot = getLoot(key); - if (loot == null) { - LogUtils.warn(String.format("Loot %s doesn't exist in any of the subfolders[item/entity/block].", key)); - return null; - } - return loot; - } - - /** - * Loads loot configurations from a single YAML file and populates the lootMap and lootGroupMap. - * - * @param file The YAML file containing loot configurations. - * @param namespace The namespace indicating the type of loot (e.g., "item," "entity," "block"). - */ - private void loadSingleFile(File file, String namespace) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : yaml.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - var loot = getSingleSectionItem( - file.getPath(), - section, - namespace, - entry.getKey() - ); - // Check for duplicate loot configurations and log an error if found. - if (lootMap.containsKey(entry.getKey())) { - LogUtils.severe("Duplicated loot found: " + entry.getKey() + "."); - } else { - lootMap.put(entry.getKey(), loot); - } - String[] group = loot.getLootGroup(); - // If the loot configuration belongs to one or more groups, update lootGroupMap. - if (group != null) { - for (String g : group) { - List groupMembers = lootGroupMap.computeIfAbsent(g, k -> new ArrayList<>()); - groupMembers.add(loot.getID()); - } - } - // legacy format support - if (section.contains("requirements") && section.contains("weight")) { - plugin.getRequirementManager().putLegacyLootToMap( - loot.getID(), - plugin.getRequirementManager().getRequirements(section.getConfigurationSection("requirements"), false), - section.getDouble("weight", 0) - ); - } - } - } - } - - /** - * Creates a single loot configuration item from a ConfigurationSection. - * - * @param section The ConfigurationSection containing loot configuration data. - * @param namespace The namespace indicating the type of loot (e.g., "item," "entity," "block"). - * @param key The unique key identifying the loot configuration. - * @return A CFLoot object representing the loot configuration. - */ - private CFLoot getSingleSectionItem(String filePath, ConfigurationSection section, String namespace, String key) { - return new CFLoot.Builder(key, LootType.valueOf(namespace.toUpperCase(Locale.ENGLISH))) - .filePath(filePath) - .disableStats(section.getBoolean("disable-stat", CFConfig.globalDisableStats)) - .disableGames(section.getBoolean("disable-game", CFConfig.globalDisableGame)) - .instantGame(section.getBoolean("instant-game", CFConfig.globalInstantGame)) - .showInFinder(section.getBoolean("show-in-fishfinder", CFConfig.globalShowInFinder)) - .disableGlobalActions(section.getBoolean("disable-global-event", false)) - .score(section.getDouble("score")) - .baseEffect(plugin.getEffectManager().getBaseEffect(section.getConfigurationSection("effects"))) - .lootGroup(ConfigUtils.stringListArgs(section.get("group")).toArray(new String[0])) - .nick(section.getString("nick", section.getString("display.name", key))) - .addActions(plugin.getActionManager().getActionMap(section.getConfigurationSection("events"))) - .addTimesActions(plugin.getActionManager().getTimesActionMap(section.getConfigurationSection("events.success-times"))) - .statsKey(new StatisticsKey(section.getString("statistics.amount", key), section.getString("statistics.size", key))) - .build(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUI.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUI.java deleted file mode 100644 index 17b75777..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketGUI.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.market; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.EarningData; -import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder; -import net.momirealms.customfishing.api.util.InventoryUtils; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.util.ItemUtils; -import net.momirealms.customfishing.util.NumberUtils; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -public class MarketGUI { - - // A map that associates characters with MarketGUI elements. - private final HashMap itemsCharMap; - // A map that associates slot indices with MarketGUI elements. - private final HashMap itemsSlotMap; - private final Inventory inventory; - private final MarketManagerImpl manager; - private final Player owner; - private final EarningData earningData; - - /** - * Constructor for creating a MarketGUI. - * - * @param manager The Market Manager implementation associated with this MarketGUI. - * @param player The player who owns this MarketGUI. - * @param earningData Data related to earnings for this MarketGUI. - */ - public MarketGUI(MarketManagerImpl manager, Player player, EarningData earningData) { - this.manager = manager; - this.owner = player; - this.earningData = earningData; - this.itemsCharMap = new HashMap<>(); - this.itemsSlotMap = new HashMap<>(); - var holder = new MarketGUIHolder(); - this.inventory = InventoryUtils.createInventory( - holder, - manager.getLayout().length * 9, - AdventureHelper.getInstance().getComponentFromMiniMessage(manager.getTitle()) - ); - holder.setInventory(this.inventory); - } - - /** - * Initialize the GUI layout by mapping elements to inventory slots. - */ - private void init() { - int line = 0; - for (String content : manager.getLayout()) { - for (int index = 0; index < 9; index++) { - char symbol; - if (index < content.length()) { - symbol = content.charAt(index); - } else { - symbol = ' '; - } - MarketGUIElement element = itemsCharMap.get(symbol); - if (element != null) { - element.addSlot(index + line * 9); - itemsSlotMap.put(index + line * 9, element); - } - } - line++; - } - for (Map.Entry entry : itemsSlotMap.entrySet()) { - this.inventory.setItem(entry.getKey(), entry.getValue().getItemStack().clone()); - } - } - - /** - * Add one or more elements to the GUI. - * @param elements Elements to be added. - * @return The MarketGUI instance. - */ - @SuppressWarnings("UnusedReturnValue") - public MarketGUI addElement(MarketGUIElement... elements) { - for (MarketGUIElement element : elements) { - itemsCharMap.put(element.getSymbol(), element); - } - return this; - } - - /** - * Build and initialize the GUI. - */ - public MarketGUI build() { - init(); - return this; - } - - /** - * Show the GUI to a player if the player is the owner. - * @param player The player to show the GUI to. - */ - public void show(Player player) { - if (player != owner) return; - player.openInventory(inventory); - } - - /** - * Get the MarketGUIElement associated with a specific inventory slot. - * @param slot The slot index in the inventory. - * @return The associated MarketGUIElement or null if not found. - */ - @Nullable - public MarketGUIElement getElement(int slot) { - return itemsSlotMap.get(slot); - } - - /** - * Get the MarketGUIElement associated with a specific character symbol. - * @param slot The character symbol. - * @return The associated MarketGUIElement or null if not found. - */ - @Nullable - public MarketGUIElement getElement(char slot) { - return itemsCharMap.get(slot); - } - - /** - * Refresh the GUI, updating the display based on current data. - * @return The MarketGUI instance. - */ - public MarketGUI refresh() { - double earningLimit = manager.getEarningLimit(owner); - MarketDynamicGUIElement sellElement = (MarketDynamicGUIElement) getElement(manager.getSellSlot()); - if (sellElement != null && sellElement.getSlots().size() > 0) { - double totalWorth = getTotalWorthInMarketGUI(); - int soldAmount = getSoldAmount(); - if (totalWorth <= 0) { - sellElement.setItemStack( - manager.getSellIconDenyBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(soldAmount) - ) - ) - ); - } else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) { - sellElement.setItemStack( - manager.getSellIconLimitBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(soldAmount) - ) - ) - ); - } else { - sellElement.setItemStack( - manager.getSellIconAllowBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(soldAmount) - ) - ) - ); - } - } - - MarketDynamicGUIElement sellAllElement = (MarketDynamicGUIElement) getElement(manager.getSellAllSlot()); - if (sellAllElement != null && sellAllElement.getSlots().size() > 0) { - double totalWorth = manager.getInventoryTotalWorth(owner.getInventory()); - int sellAmount = manager.getInventorySellAmount(owner.getInventory()); - if (manager.sellFishingBag() && CustomFishingPlugin.get().getBagManager().isEnabled()) { - Inventory bag = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(owner.getUniqueId()); - if (bag != null) { - totalWorth += manager.getInventoryTotalWorth(bag); - sellAmount += manager.getInventorySellAmount(bag); - } - } - if (totalWorth <= 0) { - sellAllElement.setItemStack( - manager.getSellAllIconDenyBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(sellAmount) - ) - ) - ); - } else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) { - sellAllElement.setItemStack( - manager.getSellAllIconLimitBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(sellAmount) - ) - ) - ); - } else { - sellAllElement.setItemStack( - manager.getSellAllIconAllowBuilder().build(owner, - Map.of("{money}", NumberUtils.money(totalWorth) - ,"{money_formatted}", String.format("%.2f", totalWorth) - ,"{player}", owner.getName() - ,"{rest}", NumberUtils.money(earningLimit - earningData.earnings) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - earningData.earnings)) - ,"{sold-item-amount}", String.valueOf(sellAmount) - ) - ) - ); - } - } - for (Map.Entry entry : itemsSlotMap.entrySet()) { - if (entry.getValue() instanceof MarketDynamicGUIElement dynamicGUIElement) { - this.inventory.setItem(entry.getKey(), dynamicGUIElement.getItemStack().clone()); - } - } - return this; - } - - /** - * Calculate and return the total worth of items in the inventory. - * @return The total worth of items. - */ - public double getTotalWorthInMarketGUI() { - double money = 0d; - MarketGUIElement itemElement = getElement(manager.getItemSlot()); - if (itemElement == null) { - LogUtils.warn("No item slot available. Please check if GUI layout contains the item slot symbol."); - return money; - } - for (int slot : itemElement.getSlots()) { - money += manager.getItemPrice(this.inventory.getItem(slot)); - } - return money; - } - - /** - * Get the inventory associated with this MarketGUI. - * @return The Inventory object. - */ - public Inventory getInventory() { - return inventory; - } - - /** - * Clear items with non-zero value from the inventory. - */ - public void clearWorthyItems() { - MarketGUIElement itemElement = getElement(manager.getItemSlot()); - if (itemElement == null) { - return; - } - for (int slot : itemElement.getSlots()) { - double money = manager.getItemPrice(inventory.getItem(slot)); - if (money != 0) { - inventory.setItem(slot, new ItemStack(Material.AIR)); - } - } - } - - /** - * Get an empty slot in the item section of the inventory. - * @return The index of an empty slot or -1 if none are found. - */ - public int getEmptyItemSlot() { - MarketGUIElement itemElement = getElement(manager.getItemSlot()); - if (itemElement == null) { - return -1; - } - for (int slot : itemElement.getSlots()) { - ItemStack itemStack = inventory.getItem(slot); - if (itemStack == null || itemStack.getType() == Material.AIR) { - return slot; - } - } - return -1; - } - - /** - * Return items to the owner's inventory. - */ - public void returnItems() { - MarketGUIElement itemElement = getElement(manager.getItemSlot()); - if (itemElement == null) { - return; - } - for (int slot : itemElement.getSlots()) { - ItemStack itemStack = inventory.getItem(slot); - if (itemStack != null && itemStack.getType() != Material.AIR) { - ItemUtils.giveItem(owner, itemStack, itemStack.getAmount()); - inventory.setItem(slot, new ItemStack(Material.AIR)); - } - } - } - - /** - * Get the earning data associated with this MarketGUI. - * @return The EarningData object. - */ - public EarningData getEarningData() { - return earningData; - } - - public int getSoldAmount() { - int amount = 0; - MarketGUIElement itemElement = getElement(manager.getItemSlot()); - if (itemElement == null) { - return amount; - } - for (int slot : itemElement.getSlots()) { - ItemStack itemStack = inventory.getItem(slot); - double money = manager.getItemPrice(itemStack); - if (money > 0 && itemStack != null) { - amount += itemStack.getAmount(); - } - } - return amount; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketManagerImpl.java deleted file mode 100644 index cad0fdac..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/market/MarketManagerImpl.java +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.market; - -import de.tr7zw.changeme.nbtapi.NBTItem; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.EarningData; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import net.momirealms.customfishing.api.manager.MarketManager; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.item.BuildableItem; -import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.util.ConfigUtils; -import net.momirealms.customfishing.util.NumberUtils; -import net.objecthunter.exp4j.ExpressionBuilder; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class MarketManagerImpl implements MarketManager, Listener { - - private final CustomFishingPlugin plugin; - private final HashMap priceMap; - private String[] layout; - private String title; - private String formula; - private final HashMap decorativeIcons; - private char itemSlot; - private char sellSlot; - private char sellAllSlot; - private BuildableItem sellIconAllowBuilder; - private BuildableItem sellIconDenyBuilder; - private BuildableItem sellIconLimitBuilder; - private BuildableItem sellAllIconAllowBuilder; - private BuildableItem sellAllIconDenyBuilder; - private BuildableItem sellAllIconLimitBuilder; - private Action[] sellDenyActions; - private Action[] sellAllowActions; - private Action[] sellLimitActions; - private Action[] sellAllDenyActions; - private Action[] sellAllAllowActions; - private Action[] sellAllLimitActions; - private String earningLimitExpression; - private boolean allowItemWithNoPrice; - private boolean sellFishingBag; - private final ConcurrentHashMap marketGUIMap; - private boolean enable; - private CancellableTask resetEarningsTask; - private int date; - - public MarketManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.priceMap = new HashMap<>(); - this.decorativeIcons = new HashMap<>(); - this.marketGUIMap = new ConcurrentHashMap<>(); - this.date = getDate(); - } - - public void load() { - this.loadConfig(); - Bukkit.getPluginManager().registerEvents(this, plugin); - if (!enable) return; - this.resetEarningsTask = plugin.getScheduler().runTaskAsyncTimer(() -> { - int now = getDate(); - if (this.date != now) { - this.date = now; - for (OnlineUser onlineUser : plugin.getStorageManager().getOnlineUsers()) { - onlineUser.getEarningData().date = now; - onlineUser.getEarningData().earnings = 0d; - } - } - }, 1, 1, TimeUnit.SECONDS); - } - - public void unload() { - HandlerList.unregisterAll(this); - this.priceMap.clear(); - this.decorativeIcons.clear(); - if (this.resetEarningsTask != null && !this.resetEarningsTask.isCancelled()) { - this.resetEarningsTask.cancel(); - this.resetEarningsTask = null; - } - } - - public void disable() { - unload(); - } - - // Load configuration from the plugin's config file - private void loadConfig() { - YamlConfiguration config = plugin.getConfig("market.yml"); - this.enable = config.getBoolean("enable", true); - this.formula = config.getString("price-formula", "{base} + {bonus} * {size}"); - if (!this.enable) return; - - // Load various configuration settings - this.layout = config.getStringList("layout").toArray(new String[0]); - this.title = config.getString("title", "market.title"); - this.itemSlot = config.getString("item-slot.symbol", "I").charAt(0); - this.allowItemWithNoPrice = config.getBoolean("item-slot.allow-items-with-no-price", true); - - ConfigurationSection sellAllSection = config.getConfigurationSection("sell-all-icons"); - if (sellAllSection != null) { - this.sellAllSlot = sellAllSection.getString("symbol", "S").charAt(0); - this.sellFishingBag = sellAllSection.getBoolean("fishingbag", true); - - this.sellAllIconAllowBuilder = plugin.getItemManager().getItemBuilder(sellAllSection.getConfigurationSection("allow-icon"), "gui", "sell-all"); - this.sellAllIconDenyBuilder = plugin.getItemManager().getItemBuilder(sellAllSection.getConfigurationSection("deny-icon"), "gui", "sell-all"); - this.sellAllIconLimitBuilder = plugin.getItemManager().getItemBuilder(sellAllSection.getConfigurationSection("limit-icon"), "gui", "sell-all"); - - this.sellAllAllowActions = plugin.getActionManager().getActions(sellAllSection.getConfigurationSection("allow-icon.action")); - this.sellAllDenyActions = plugin.getActionManager().getActions(sellAllSection.getConfigurationSection("deny-icon.action")); - this.sellAllLimitActions = plugin.getActionManager().getActions(sellAllSection.getConfigurationSection("limit-icon.action")); - } - - ConfigurationSection sellSection = config.getConfigurationSection("sell-icons"); - if (sellSection == null) { - // for old config compatibility - sellSection = config.getConfigurationSection("functional-icons"); - } - if (sellSection != null) { - this.sellSlot = sellSection.getString("symbol", "B").charAt(0); - - this.sellIconAllowBuilder = plugin.getItemManager().getItemBuilder(sellSection.getConfigurationSection("allow-icon"), "gui", "allow"); - this.sellIconDenyBuilder = plugin.getItemManager().getItemBuilder(sellSection.getConfigurationSection("deny-icon"), "gui", "deny"); - this.sellIconLimitBuilder = plugin.getItemManager().getItemBuilder(sellSection.getConfigurationSection("limit-icon"), "gui", "limit"); - - this.sellAllowActions = plugin.getActionManager().getActions(sellSection.getConfigurationSection("allow-icon.action")); - this.sellDenyActions = plugin.getActionManager().getActions(sellSection.getConfigurationSection("deny-icon.action")); - this.sellLimitActions = plugin.getActionManager().getActions(sellSection.getConfigurationSection("limit-icon.action")); - } - - this.earningLimitExpression = config.getBoolean("limitation.enable", true) ? config.getString("limitation.earnings", "10000") : "-1"; - - // Load item prices from the configuration - ConfigurationSection priceSection = config.getConfigurationSection("item-price"); - if (priceSection != null) { - for (Map.Entry entry : priceSection.getValues(false).entrySet()) { - this.priceMap.put(entry.getKey(), ConfigUtils.getDoubleValue(entry.getValue())); - } - } - - // Load decorative icons from the configuration - ConfigurationSection decorativeSection = config.getConfigurationSection("decorative-icons"); - if (decorativeSection != null) { - for (Map.Entry entry : decorativeSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - char symbol = Objects.requireNonNull(innerSection.getString("symbol")).charAt(0); - var builder = plugin.getItemManager().getItemBuilder(innerSection, "gui", entry.getKey()); - decorativeIcons.put(symbol, builder); - } - } - } - } - - /** - * Open the market GUI for a player - * - * @param player player - */ - @Override - public void openMarketGUI(Player player) { - if (!isEnable()) return; - OnlineUser user = plugin.getStorageManager().getOnlineUser(player.getUniqueId()); - if (user == null) { - LogUtils.warn("Player " + player.getName() + "'s market data is not loaded yet."); - return; - } - - MarketGUI gui = new MarketGUI(this, player, user.getEarningData()); - gui.addElement(new MarketGUIElement(getItemSlot(), new ItemStack(Material.AIR))); - gui.addElement(new MarketDynamicGUIElement(getSellSlot(), new ItemStack(Material.AIR))); - gui.addElement(new MarketDynamicGUIElement(getSellAllSlot(), new ItemStack(Material.AIR))); - for (Map.Entry entry : decorativeIcons.entrySet()) { - gui.addElement(new MarketGUIElement(entry.getKey(), entry.getValue().build(player))); - } - gui.build().refresh().show(player); - marketGUIMap.put(player.getUniqueId(), gui); - } - - /** - * This method handles the closing of an inventory. - * - * @param event The InventoryCloseEvent that triggered this method. - */ - @EventHandler - public void onCloseInv(InventoryCloseEvent event) { - if (!(event.getPlayer() instanceof Player player)) - return; - if (!(event.getInventory().getHolder() instanceof MarketGUIHolder)) - return; - MarketGUI gui = marketGUIMap.remove(player.getUniqueId()); - if (gui != null) - gui.returnItems(); - } - - /** - * This method handles a player quitting the server. - * - * @param event The PlayerQuitEvent that triggered this method. - */ - @EventHandler - public void onQuit(PlayerQuitEvent event) { - MarketGUI gui = marketGUIMap.remove(event.getPlayer().getUniqueId()); - if (gui != null) - gui.returnItems(); - } - - /** - * This method handles dragging items in an inventory. - * - * @param event The InventoryDragEvent that triggered this method. - */ - @EventHandler - public void onDragInv(InventoryDragEvent event) { - if (event.isCancelled()) - return; - Inventory inventory = event.getInventory(); - if (!(inventory.getHolder() instanceof MarketGUIHolder)) - return; - Player player = (Player) event.getWhoClicked(); - MarketGUI gui = marketGUIMap.get(player.getUniqueId()); - if (gui == null) { - event.setCancelled(true); - player.closeInventory(); - return; - } - - MarketGUIElement element = gui.getElement(itemSlot); - if (element == null) { - event.setCancelled(true); - return; - } - - List slots = element.getSlots(); - for (int dragSlot : event.getRawSlots()) { - if (!slots.contains(dragSlot)) { - event.setCancelled(true); - return; - } - } - - plugin.getScheduler().runTaskSyncLater(gui::refresh, player.getLocation(), 50, TimeUnit.MILLISECONDS); - } - - /** - * This method handles inventory click events. - * - * @param event The InventoryClickEvent that triggered this method. - */ - @EventHandler - public void onClickInv(InventoryClickEvent event) { - if (event.isCancelled()) - return; - - Inventory clickedInv = event.getClickedInventory(); - if (clickedInv == null) - return; - - Player player = (Player) event.getWhoClicked(); - - // Check if the clicked inventory is a MarketGUI - if (!(event.getInventory().getHolder() instanceof MarketGUIHolder)) - return; - - MarketGUI gui = marketGUIMap.get(player.getUniqueId()); - if (gui == null) { - event.setCancelled(true); - player.closeInventory(); - return; - } - - if (clickedInv != player.getInventory()) { - EarningData data = gui.getEarningData(); - if (data.date != getCachedDate()) { - data.date = getCachedDate(); - data.earnings = 0; - } - - int slot = event.getSlot(); - MarketGUIElement element = gui.getElement(slot); - if (element == null) { - event.setCancelled(true); - return; - } - - if (element.getSymbol() != itemSlot) { - event.setCancelled(true); - } - - if (element.getSymbol() == sellSlot) { - double worth = gui.getTotalWorthInMarketGUI(); - int amount = gui.getSoldAmount(); - double earningLimit = getEarningLimit(player); - Condition condition = new Condition(player, new HashMap<>(Map.of( - "{money}", NumberUtils.money(worth), - "{rest}", NumberUtils.money(earningLimit - data.earnings), - "{money_formatted}", String.format("%.2f", worth) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - data.earnings)) - ,"{sold-item-amount}", String.valueOf(amount) - ))); - if (worth > 0) { - if (earningLimit != -1 && (earningLimit - data.earnings) < worth) { - // Can't earn more money - if (getSellLimitActions() != null) { - for (Action action : getSellLimitActions()) { - action.trigger(condition); - } - } - } else { - // Clear items and update earnings - gui.clearWorthyItems(); - data.earnings += worth; - condition.insertArg("{rest}", NumberUtils.money(earningLimit - data.earnings)); - condition.insertArg("{rest_formatted}", String.format("%.2f", (earningLimit - data.earnings))); - if (getSellAllowActions() != null) { - for (Action action : getSellAllowActions()) { - action.trigger(condition); - } - } - } - } else { - // Nothing to sell - if (getSellDenyActions() != null) { - for (Action action : getSellDenyActions()) { - action.trigger(condition); - } - } - } - } else if (element.getSymbol() == sellAllSlot) { - double worth = getInventoryTotalWorth(player.getInventory()); - int amount = getInventorySellAmount(player.getInventory()); - double earningLimit = getEarningLimit(player); - if (sellFishingBag() && CustomFishingPlugin.get().getBagManager().isEnabled()) { - Inventory bag = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(player.getUniqueId()); - if (bag != null) { - worth += getInventoryTotalWorth(bag); - amount += getInventorySellAmount(bag); - } - } - Condition condition = new Condition(player, new HashMap<>(Map.of( - "{money}", NumberUtils.money(worth), - "{rest}", NumberUtils.money(earningLimit - data.earnings), - "{money_formatted}", String.format("%.2f", worth) - ,"{rest_formatted}", String.format("%.2f", (earningLimit - data.earnings)) - ,"{sold-item-amount}", String.valueOf(amount) - ))); - if (worth > 0) { - if (earningLimit != -1 && (earningLimit - data.earnings) < worth) { - // Can't earn more money - if (getSellAllLimitActions() != null) { - for (Action action : getSellAllLimitActions()) { - action.trigger(condition); - } - } - } else { - // Clear items and update earnings - clearWorthyItems(player.getInventory()); - if (sellFishingBag() && CustomFishingPlugin.get().getBagManager().isEnabled()) { - Inventory bag = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(player.getUniqueId()); - if (bag != null) { - clearWorthyItems(bag); - } - } - data.earnings += worth; - condition.insertArg("{rest}", NumberUtils.money(earningLimit - data.earnings)); - condition.insertArg("{rest_formatted}", String.format("%.2f", (earningLimit - data.earnings))); - if (getSellAllAllowActions() != null) { - for (Action action : getSellAllAllowActions()) { - action.trigger(condition); - } - } - } - } else { - // Nothing to sell - if (getSellAllDenyActions() != null) { - for (Action action : getSellAllDenyActions()) { - action.trigger(condition); - } - } - } - } - } else { - // Handle interactions with the player's inventory - ItemStack current = event.getCurrentItem(); - if (!allowItemWithNoPrice) { - double price = getItemPrice(current); - if (price <= 0) { - event.setCancelled(true); - return; - } - } - - if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT) - && (current != null && current.getType() != Material.AIR)) { - event.setCancelled(true); - MarketGUIElement element = gui.getElement(itemSlot); - if (element == null) return; - for (int slot : element.getSlots()) { - ItemStack itemStack = gui.getInventory().getItem(slot); - if (itemStack != null && itemStack.getType() != Material.AIR) { - if (current.getType() == itemStack.getType() - && itemStack.getAmount() != itemStack.getType().getMaxStackSize() - && current.getItemMeta().equals(itemStack.getItemMeta()) - ) { - int left = itemStack.getType().getMaxStackSize() - itemStack.getAmount(); - if (current.getAmount() <= left) { - itemStack.setAmount(itemStack.getAmount() + current.getAmount()); - current.setAmount(0); - break; - } else { - current.setAmount(current.getAmount() - left); - itemStack.setAmount(itemStack.getType().getMaxStackSize()); - } - } - } else { - gui.getInventory().setItem(slot, current.clone()); - current.setAmount(0); - break; - } - } - } - } - - // Refresh the GUI - plugin.getScheduler().runTaskSyncLater(gui::refresh, player.getLocation(), 50, TimeUnit.MILLISECONDS); - } - - @Override - public int getCachedDate() { - return date; - } - - @Override - public int getDate() { - Calendar calendar = Calendar.getInstance(); - return (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE); - } - - @Override - public double getItemPrice(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return 0; - - NBTItem nbtItem = new NBTItem(itemStack); - Double price = nbtItem.getDouble("Price"); - if (price != null && price != 0) { - // If a custom price is defined in the ItemStack's NBT data, use it. - return price * itemStack.getAmount(); - } - - // If no custom price is defined, attempt to fetch the price from a predefined price map. - String itemID = itemStack.getType().name(); - if (nbtItem.hasTag("CustomModelData")) { - itemID = itemID + ":" + nbtItem.getInteger("CustomModelData"); - } - - // Use the price from the price map, or default to 0 if not found. - return priceMap.getOrDefault(itemID, 0d) * itemStack.getAmount(); - } - - @Override - public String getFormula() { - return formula; - } - - @Override - public double getFishPrice(Player player, Map vars) { - String temp = PlaceholderManagerImpl.getInstance().parse(player, formula, vars); - var placeholders = PlaceholderManagerImpl.getInstance().detectPlaceholders(temp); - for (String placeholder : placeholders) { - temp = temp.replace(placeholder, "0"); - } - return new ExpressionBuilder(temp).build().evaluate(); - } - - @Override - public char getItemSlot() { - return itemSlot; - } - - @Override - public char getSellSlot() { - return sellSlot; - } - - @Override - public char getSellAllSlot() { - return sellAllSlot; - } - - @Override - public String[] getLayout() { - return layout; - } - - @Override - public String getTitle() { - return title; - } - - @Override - public double getEarningLimit(Player player) { - return new ExpressionBuilder( - PlaceholderManagerImpl.getInstance().parse( - player, - earningLimitExpression, - new HashMap<>() - )) - .build() - .evaluate(); - } - - public BuildableItem getSellIconLimitBuilder() { - return sellIconLimitBuilder; - } - - public BuildableItem getSellIconAllowBuilder() { - return sellIconAllowBuilder; - } - - public BuildableItem getSellIconDenyBuilder() { - return sellIconDenyBuilder; - } - - public BuildableItem getSellAllIconAllowBuilder() { - return sellAllIconAllowBuilder; - } - - public BuildableItem getSellAllIconDenyBuilder() { - return sellAllIconDenyBuilder; - } - - public BuildableItem getSellAllIconLimitBuilder() { - return sellAllIconLimitBuilder; - } - - public Action[] getSellDenyActions() { - return sellDenyActions; - } - - public Action[] getSellAllowActions() { - return sellAllowActions; - } - - public Action[] getSellLimitActions() { - return sellLimitActions; - } - - public Action[] getSellAllDenyActions() { - return sellAllDenyActions; - } - - public Action[] getSellAllAllowActions() { - return sellAllAllowActions; - } - - public Action[] getSellAllLimitActions() { - return sellAllLimitActions; - } - - @Override - public boolean isEnable() { - return enable; - } - - @Override - public boolean sellFishingBag() { - return sellFishingBag; - } - - @Override - public double getInventoryTotalWorth(Inventory inventory) { - double total = 0d; - for (ItemStack itemStack : inventory.getStorageContents()) { - double price = getItemPrice(itemStack); - total += price; - } - return total; - } - - @Override - public int getInventorySellAmount(Inventory inventory) { - int amount = 0; - for (ItemStack itemStack : inventory.getStorageContents()) { - double price = getItemPrice(itemStack); - if (price > 0 && itemStack != null) { - amount += itemStack.getAmount(); - } - } - return amount; - } - - public void clearWorthyItems(Inventory inventory) { - for (ItemStack itemStack : inventory.getStorageContents()) { - double price = getItemPrice(itemStack); - if (price > 0 && itemStack != null) { - itemStack.setAmount(0); - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/ChatCatcherManager.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/ChatCatcherManager.java deleted file mode 100644 index 2dcb9177..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/misc/ChatCatcherManager.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.misc; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.gui.SectionPage; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class ChatCatcherManager implements Listener { - - private final CustomFishingPlugin plugin; - private final ConcurrentHashMap> pageMap; - - public ChatCatcherManager(CustomFishingPlugin plugin) { - this.pageMap = new ConcurrentHashMap<>(); - this.plugin = plugin; - } - - public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); - } - - public void unload() { - this.pageMap.clear(); - HandlerList.unregisterAll(this); - } - - public void disable() { - unload(); - } - - public void catchMessage(Player player, String key, SectionPage page) { - this.pageMap.put(player.getUniqueId(), Pair.of(key, page)); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - pageMap.remove(event.getPlayer().getUniqueId()); - } - - @EventHandler - public void onChat(AsyncPlayerChatEvent event) { - var uuid = event.getPlayer().getUniqueId(); - var pair = pageMap.remove(uuid); - if (pair == null) return; - event.setCancelled(true); - plugin.getScheduler().runTaskSync(() -> { - pair.right().getSection().set(pair.left(), event.getMessage()); - pair.right().reOpen(); - }, event.getPlayer().getLocation()); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/ConditionalElement.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/ConditionalElement.java deleted file mode 100644 index 0f60dfd3..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/ConditionalElement.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.requirement; - -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.List; - -public class ConditionalElement { - - private final List> modifierList; - private final HashMap subLoots; - private final Requirement[] requirements; - - public ConditionalElement( - Requirement[] requirements, - List> modifierList, - HashMap subElements - ) { - this.modifierList = modifierList; - this.requirements = requirements; - this.subLoots = subElements; - } - - /** - * Combines the weight modifiers for this element. - * - * @param player The player for whom the modifiers are applied. - * @param weightMap The map of weight modifiers. - */ - synchronized public void combine(Player player, HashMap weightMap) { - for (Pair modifierPair : this.modifierList) { - double previous = weightMap.getOrDefault(modifierPair.left(), 0d); - weightMap.put(modifierPair.left(), modifierPair.right().modify(player, previous)); - } - } - - public Requirement[] getRequirements() { - return requirements; - } - - public HashMap getSubElements() { - return subLoots; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/RequirementManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/RequirementManagerImpl.java deleted file mode 100644 index 169cd2e1..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/requirement/RequirementManagerImpl.java +++ /dev/null @@ -1,1394 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.requirement; - -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.integration.LevelInterface; -import net.momirealms.customfishing.api.integration.SeasonInterface; -import net.momirealms.customfishing.api.manager.RequirementManager; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.loot.Loot; -import net.momirealms.customfishing.api.mechanic.requirement.Requirement; -import net.momirealms.customfishing.api.mechanic.requirement.RequirementExpansion; -import net.momirealms.customfishing.api.mechanic.requirement.RequirementFactory; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.VaultHook; -import net.momirealms.customfishing.compatibility.papi.ParseUtils; -import net.momirealms.customfishing.util.ClassUtils; -import net.momirealms.customfishing.util.ConfigUtils; -import net.momirealms.customfishing.util.MoonPhase; -import net.momirealms.sparrow.heart.SparrowHeart; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.MemorySection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; - -public class RequirementManagerImpl implements RequirementManager { - - public static Requirement[] mechanicRequirements; - private final CustomFishingPluginImpl plugin; - private final HashMap requirementFactoryMap; - private final LinkedHashMap conditionalLootsMap; - private final LinkedHashMap conditionalGamesMap; - private final String EXPANSION_FOLDER = "expansions/requirement"; - - public RequirementManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - this.requirementFactoryMap = new HashMap<>(); - this.conditionalLootsMap = new LinkedHashMap<>(); - this.conditionalGamesMap = new LinkedHashMap<>(); - this.registerInbuiltRequirements(); - } - - public void load() { - this.loadExpansions(); - this.loadRequirementGroupFileConfig(); - } - - public void unload() { - this.conditionalLootsMap.clear(); - } - - public void disable() { - this.requirementFactoryMap.clear(); - this.conditionalLootsMap.clear(); - } - - @Override - public boolean putLegacyLootToMap(String key, Requirement[] requirements, double weight) { - if (conditionalLootsMap.containsKey("LEGACY_" + key)) { - return false; - } else { - conditionalLootsMap.put("LEGACY_" + key, new ConditionalElement(requirements, List.of(Pair.of(key, (player, origin) -> weight + origin)), new HashMap<>())); - return true; - } - } - - /** - * Loads requirement group configuration data from various configuration files. - */ - public void loadRequirementGroupFileConfig() { - // Load mechanic requirements from the main configuration file - YamlConfiguration main = plugin.getConfig("config.yml"); - mechanicRequirements = getRequirements(main.getConfigurationSection("mechanics.mechanic-requirements"), true); - - // Load conditional loot data from the loot conditions configuration file - YamlConfiguration config1 = plugin.getConfig("loot-conditions.yml"); - for (Map.Entry entry : config1.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - conditionalLootsMap.put(entry.getKey(), getConditionalElements(section)); - } - } - - // Load conditional game data from the game conditions configuration file - YamlConfiguration config2 = plugin.getConfig("game-conditions.yml"); - for (Map.Entry entry : config2.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - conditionalGamesMap.put(entry.getKey(), getConditionalElements(section)); - } - } - } - - /** - * Registers a custom requirement type with its corresponding factory. - * - * @param type The type identifier of the requirement. - * @param requirementFactory The factory responsible for creating instances of the requirement. - * @return True if registration was successful, false if the type is already registered. - */ - @Override - public boolean registerRequirement(String type, RequirementFactory requirementFactory) { - if (this.requirementFactoryMap.containsKey(type)) return false; - this.requirementFactoryMap.put(type, requirementFactory); - return true; - } - - /** - * Unregisters a custom requirement type. - * - * @param type The type identifier of the requirement to unregister. - * @return True if unregistration was successful, false if the type is not registered. - */ - @Override - public boolean unregisterRequirement(String type) { - return this.requirementFactoryMap.remove(type) != null; - } - - /** - * Retrieves a ConditionalElement from a given ConfigurationSection. - * - * @param section The ConfigurationSection containing the conditional element data. - * @return A ConditionalElement instance representing the data in the section. - */ - private ConditionalElement getConditionalElements(ConfigurationSection section) { - var sub = section.getConfigurationSection("sub-groups"); - if (sub == null) { - return new ConditionalElement( - getRequirements(section.getConfigurationSection("conditions"), false), - ConfigUtils.getModifiers(section.getStringList("list")), - null - ); - } else { - HashMap subElements = new HashMap<>(); - for (Map.Entry entry : sub.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - subElements.put(entry.getKey(), getConditionalElements(innerSection)); - } - } - return new ConditionalElement( - getRequirements(section.getConfigurationSection("conditions"), false), - ConfigUtils.getModifiers(section.getStringList("list")), - subElements - ); - } - } - - private void registerInbuiltRequirements() { - this.registerTimeRequirement(); - this.registerYRequirement(); - this.registerContainRequirement(); - this.registerStartWithRequirement(); - this.registerEndWithRequirement(); - this.registerEqualsRequirement(); - this.registerBiomeRequirement(); - this.registerMoonPhaseRequirement(); - this.registerDateRequirement(); - this.registerPluginLevelRequirement(); - this.registerPermissionRequirement(); - this.registerWorldRequirement(); - this.registerWeatherRequirement(); - this.registerSeasonRequirement(); - this.registerLavaFishingRequirement(); - this.registerRodRequirement(); - this.registerBaitRequirement(); - this.registerGreaterThanRequirement(); - this.registerAndRequirement(); - this.registerOrRequirement(); - this.registerLevelRequirement(); - this.registerRandomRequirement(); - this.registerIceFishingRequirement(); - this.registerOpenWaterRequirement(); - this.registerCoolDownRequirement(); - this.registerGroupRequirement(); - this.registerLootRequirement(); - this.registerLessThanRequirement(); - this.registerNumberEqualRequirement(); - this.registerRegexRequirement(); - this.registerItemInHandRequirement(); - this.registerMoneyRequirement(); - this.registerInBagRequirement(); - this.registerHookRequirement(); - this.registerCompetitionRequirement(); - this.registerListRequirement(); - this.registerEnvironmentRequirement(); - this.registerPotionEffectRequirement(); - this.registerSizeRequirement(); - this.registerHasStatsRequirement(); - this.registerLootTypeRequirement(); - this.registerInListRequirement(); - } - - public HashMap getLootWithWeight(Condition condition) { - return getString2DoubleMap(condition, conditionalLootsMap); - } - - public HashMap getGameWithWeight(Condition condition) { - return getString2DoubleMap(condition, conditionalGamesMap); - } - - /** - * Retrieves a mapping of strings to doubles based on conditional elements and a player's condition. - * - * @param condition The player's condition. - * @param conditionalGamesMap The map of conditional elements representing loots/games. - * @return A HashMap with strings as keys and doubles as values representing loot/game weights. - */ - @NotNull - private HashMap getString2DoubleMap(Condition condition, LinkedHashMap conditionalGamesMap) { - HashMap lootWeightMap = new HashMap<>(); - Queue> lootQueue = new LinkedList<>(); - lootQueue.add(conditionalGamesMap); - Player player = condition.getPlayer(); - while (!lootQueue.isEmpty()) { - HashMap currentLootMap = lootQueue.poll(); - for (ConditionalElement loots : currentLootMap.values()) { - if (RequirementManager.isRequirementMet(condition, loots.getRequirements())) { - loots.combine(player, lootWeightMap); - if (loots.getSubElements() != null) { - lootQueue.add(loots.getSubElements()); - } - } - } - } - return lootWeightMap; - } - - /** - * Retrieves an array of requirements based on a configuration section. - * - * @param section The configuration section containing requirement definitions. - * @param advanced A flag indicating whether to use advanced requirements. - * @return An array of Requirement objects based on the configuration section - */ - @NotNull - @Override - public Requirement[] getRequirements(ConfigurationSection section, boolean advanced) { - List requirements = new ArrayList<>(); - if (section == null) { - return requirements.toArray(new Requirement[0]); - } - for (Map.Entry entry : section.getValues(false).entrySet()) { - String typeOrName = entry.getKey(); - if (hasRequirement(typeOrName)) { - requirements.add(getRequirement(typeOrName, entry.getValue())); - } else { - requirements.add(getRequirement(section.getConfigurationSection(typeOrName), advanced)); - } - } - return requirements.toArray(new Requirement[0]); - } - - @Override - public boolean hasRequirement(String type) { - return requirementFactoryMap.containsKey(type); - } - - /** - * Retrieves a Requirement object based on a configuration section and advanced flag. - * - * @param section The configuration section containing requirement definitions. - * @param advanced A flag indicating whether to use advanced requirements. - * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid. - */ - @NotNull - @Override - public Requirement getRequirement(ConfigurationSection section, boolean advanced) { - if (section == null) return EmptyRequirement.instance; - List actionList = null; - if (advanced) { - actionList = new ArrayList<>(); - if (section.contains("not-met-actions")) { - for (Map.Entry entry : Objects.requireNonNull(section.getConfigurationSection("not-met-actions")).getValues(false).entrySet()) { - if (entry.getValue() instanceof MemorySection inner) { - actionList.add(plugin.getActionManager().getAction(inner)); - } - } - } - } - String type = section.getString("type"); - if (type == null) { - LogUtils.warn("No requirement type found at " + section.getCurrentPath()); - return EmptyRequirement.instance; - } - var builder = getRequirementFactory(type); - if (builder == null) { - return EmptyRequirement.instance; - } - return builder.build(section.get("value"), actionList, advanced); - } - - /** - * Gets a requirement based on the provided key and value. - * If a valid RequirementFactory is found for the key, it is used to create the requirement. - * If no factory is found, a warning is logged, and an empty requirement instance is returned. - * - * @param type The key representing the requirement type. - * @param value The value associated with the requirement. - * @return A Requirement instance based on the key and value, or an empty requirement if not found. - */ - @Override - @NotNull - public Requirement getRequirement(String type, Object value) { - RequirementFactory factory = getRequirementFactory(type); - if (factory == null) { - LogUtils.warn("Requirement type: " + type + " doesn't exist."); - return EmptyRequirement.instance; - } - return factory.build(value); - } - - /** - * Retrieves a RequirementFactory based on the specified requirement type. - * - * @param type The requirement type for which to retrieve a factory. - * @return A RequirementFactory for the specified type, or null if no factory is found. - */ - @Override - @Nullable - public RequirementFactory getRequirementFactory(String type) { - return requirementFactoryMap.get(type); - } - - private void registerTimeRequirement() { - registerRequirement("time", (args, actions, advanced) -> { - List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return condition -> { - long time = condition.getLocation().getWorld().getTime(); - for (Pair pair : timePairs) - if (time >= pair.left() && time <= pair.right()) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerGroupRequirement() { - registerRequirement("group", (args, actions, advanced) -> { - HashSet arg = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String lootID = condition.getArg("{loot}"); - if (lootID == null) return false; - Loot loot = plugin.getLootManager().getLoot(lootID); - if (loot == null) return false; - String[] groups = loot.getLootGroup(); - if (groups != null) { - for (String g : groups) { - if (arg.contains(g)) { - return true; - } - } - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!group", (args, actions, advanced) -> { - HashSet arg = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String lootID = condition.getArg("{loot}"); - Loot loot = plugin.getLootManager().getLoot(lootID); - String[] groups = loot.getLootGroup(); - if (groups == null) { - return true; - } - outer: { - for (String g : groups) { - if (arg.contains(g)) { - break outer; - } - } - return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerLootRequirement() { - registerRequirement("loot", (args, actions, advanced) -> { - HashSet arg = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String lootID = condition.getArg("{loot}"); - if (arg.contains(lootID)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!loot", (args, actions, advanced) -> { - HashSet arg = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String lootID = condition.getArg("{loot}"); - if (!arg.contains(lootID)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerYRequirement() { - registerRequirement("ypos", (args, actions, advanced) -> { - List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return condition -> { - int y = condition.getLocation().getBlockY(); - for (Pair pair : timePairs) - if (y >= pair.left() && y <= pair.right()) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerOrRequirement() { - registerRequirement("||", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - Requirement[] requirements = getRequirements(section, advanced); - return condition -> { - for (Requirement requirement : requirements) { - if (requirement.isConditionMet(condition)) { - return true; - } - } - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at || requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerAndRequirement() { - registerRequirement("&&", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - Requirement[] requirements = getRequirements(section, advanced); - return condition -> { - outer: { - for (Requirement requirement : requirements) { - if (!requirement.isConditionMet(condition)) { - break outer; - } - } - return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at && requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerLavaFishingRequirement() { - registerRequirement("lava-fishing", (args, actions, advanced) -> { - boolean inLava = (boolean) args; - return condition -> { - String current = condition.getArgs().getOrDefault("{lava}","false"); - if (current.equals(String.valueOf(inLava))) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerOpenWaterRequirement() { - registerRequirement("open-water", (args, actions, advanced) -> { - boolean inLava = (boolean) args; - return condition -> { - String current = condition.getArgs().getOrDefault("{open-water}", "false"); - if (current.equals(String.valueOf(inLava))) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerIceFishingRequirement() { - registerRequirement("ice-fishing", (args, actions, advanced) -> { - boolean iceFishing = (boolean) args; - return condition -> { - Location location = condition.getLocation(); - int water = 0; - int ice = 0; - for (int i = -2; i <= 2; i++) { - for (int j = -1; j <= 2; j++) { - for (int k = -2; k <= 2; k++) { - Block block = location.clone().add(i, j, k).getBlock(); - Material material = block.getType(); - switch (material) { - case ICE -> ice++; - case WATER -> water++; - } - } - } - } - if ((ice >= 16 && water >= 25) == iceFishing) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerLevelRequirement() { - registerRequirement("level", (args, actions, advanced) -> { - int level = (int) args; - return condition -> { - int current = condition.getPlayer().getLevel(); - if (current >= level) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerMoneyRequirement() { - registerRequirement("money", (args, actions, advanced) -> { - double money = ConfigUtils.getDoubleValue(args); - return condition -> { - double current = VaultHook.getEconomy().getBalance(condition.getPlayer()); - if (current >= money) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerRandomRequirement() { - registerRequirement("random", (args, actions, advanced) -> { - double random = ConfigUtils.getDoubleValue(args); - return condition -> { - if (Math.random() < random) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerBiomeRequirement() { - registerRequirement("biome", (args, actions, advanced) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(condition.getLocation()); - if (biomes.contains(currentBiome)) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!biome", (args, actions, advanced) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(condition.getLocation()); - if (!biomes.contains(currentBiome)) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerMoonPhaseRequirement() { - registerRequirement("moon-phase", (args, actions, advanced) -> { - HashSet moonPhases = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - long days = condition.getLocation().getWorld().getFullTime() / 24_000; - if (moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!moon-phase", (args, actions, advanced) -> { - HashSet moonPhases = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - long days = condition.getLocation().getWorld().getFullTime() / 24_000; - if (!moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerWorldRequirement() { - registerRequirement("world", (args, actions, advanced) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - if (worlds.contains(condition.getLocation().getWorld().getName())) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!world", (args, actions, advanced) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - if (!worlds.contains(condition.getLocation().getWorld().getName())) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerWeatherRequirement() { - registerRequirement("weather", (args, actions, advanced) -> { - List weathers = ConfigUtils.stringListArgs(args); - return condition -> { - String currentWeather; - World world = condition.getLocation().getWorld(); - if (world.isClearWeather()) currentWeather = "clear"; - else if (world.isThundering()) currentWeather = "thunder"; - else currentWeather = "rain"; - for (String weather : weathers) - if (weather.equalsIgnoreCase(currentWeather)) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerCoolDownRequirement() { - registerRequirement("cooldown", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String key = section.getString("key"); - int time = section.getInt("time"); - return condition -> { - if (!plugin.getCoolDownManager().isCoolDown(condition.getPlayer().getUniqueId(), key, time)) { - return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at cooldown requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerDateRequirement() { - registerRequirement("date", (args, actions, advanced) -> { - HashSet dates = new HashSet<>(ConfigUtils.stringListArgs(args)); - return condition -> { - Calendar calendar = Calendar.getInstance(); - String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE); - if (dates.contains(current)) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerPermissionRequirement() { - registerRequirement("permission", (args, actions, advanced) -> { - List perms = ConfigUtils.stringListArgs(args); - return condition -> { - for (String perm : perms) - if (condition.getPlayer().hasPermission(perm)) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!permission", (args, actions, advanced) -> { - List perms = ConfigUtils.stringListArgs(args); - return condition -> { - for (String perm : perms) - if (condition.getPlayer().hasPermission(perm)) { - if (advanced) triggerActions(actions, condition); - return false; - } - return true; - }; - }); - } - - private void registerSeasonRequirement() { - registerRequirement("season", (args, actions, advanced) -> { - List seasons = ConfigUtils.stringListArgs(args); - return condition -> { - SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); - if (seasonInterface == null) return true; - String season = seasonInterface.getSeason(condition.getLocation().getWorld()); - if (seasons.contains(season)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerGreaterThanRequirement() { - registerRequirement(">=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) >= Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at >= requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement(">", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) > Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at > requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerRegexRequirement() { - registerRequirement("regex", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("papi", ""); - String v2 = section.getString("regex", ""); - return condition -> { - if (ParseUtils.setPlaceholders(condition.getPlayer(), v1).matches(v2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at regex requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerNumberEqualRequirement() { - registerRequirement("==", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) == Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) != Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyRequirement.instance; - } - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerLessThanRequirement() { - registerRequirement("<", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) < Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at < requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("<=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) <= Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at <= requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerStartWithRequirement() { - registerRequirement("startsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (p1.startsWith(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at startsWith requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!startsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (!p1.startsWith(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerEndWithRequirement() { - registerRequirement("endsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (p1.endsWith(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at endsWith requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!endsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (!p1.endsWith(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !endsWith requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerContainRequirement() { - registerRequirement("contains", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (p1.contains(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at contains requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!contains", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (!p1.contains(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !contains requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerInListRequirement() { - registerRequirement("in-list", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - List values = ConfigUtils.stringListArgs(section.get("values")); - return condition -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), papi) : papi; - if (values.contains(p1)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at in-list requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!in-list", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - List values = ConfigUtils.stringListArgs(section.get("values")); - return condition -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), papi) : papi; - if (!values.contains(p1)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at in-list requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerEqualsRequirement() { - registerRequirement("equals", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (p1.equals(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at equals requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!equals", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return condition -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(condition.getPlayer(), v2) : v2; - if (!p1.equals(p2)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !equals requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerRodRequirement() { - registerRequirement("rod", (args, actions, advanced) -> { - List rods = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{rod}"); - if (rods.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!rod", (args, actions, advanced) -> { - List rods = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{rod}"); - if (!rods.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerItemInHandRequirement() { - registerRequirement("item-in-hand", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - boolean mainOrOff = section.getString("hand","main").equalsIgnoreCase("main"); - int amount = section.getInt("amount", 1); - List items = ConfigUtils.stringListArgs(section.get("item")); - return condition -> { - ItemStack itemStack = mainOrOff ? - condition.getPlayer().getInventory().getItemInMainHand() - : condition.getPlayer().getInventory().getItemInOffHand(); - String id = plugin.getItemManager().getAnyPluginItemID(itemStack); - if (items.contains(id) && itemStack.getAmount() >= amount) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at item-in-hand requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerBaitRequirement() { - registerRequirement("bait", (args, actions, advanced) -> { - List baits = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{bait}"); - if (baits.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!bait", (args, actions, advanced) -> { - List baits = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{bait}"); - if (!baits.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("has-bait", (args, actions, advanced) -> { - boolean has = (boolean) args; - return condition -> { - String id = condition.getArg("{bait}"); - if (id != null && has) return true; - if (id == null && !has) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerSizeRequirement() { - registerRequirement("has-size", (args, actions, advanced) -> { - boolean has = (boolean) args; - return condition -> { - String size = condition.getArg("{SIZE}"); - if (size != null && has) return true; - if (size == null && !has) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerHasStatsRequirement() { - registerRequirement("has-stats", (args, actions, advanced) -> { - boolean has = (boolean) args; - return condition -> { - String loot = condition.getArg("{loot}"); - Loot lootInstance = plugin.getLootManager().getLoot(loot); - if (lootInstance != null) { - if (!lootInstance.disableStats() && has) return true; - if (lootInstance.disableStats() && !has) return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerLootTypeRequirement() { - registerRequirement("loot-type", (args, actions, advanced) -> { - List types = ConfigUtils.stringListArgs(args); - return condition -> { - String loot = condition.getArg("{loot}"); - Loot lootInstance = plugin.getLootManager().getLoot(loot); - if (lootInstance != null) { - if (types.contains(lootInstance.getType().name().toLowerCase(Locale.ENGLISH))) return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!loot-type", (args, actions, advanced) -> { - List types = ConfigUtils.stringListArgs(args); - return condition -> { - String loot = condition.getArg("{loot}"); - Loot lootInstance = plugin.getLootManager().getLoot(loot); - if (lootInstance != null) { - if (!types.contains(lootInstance.getType().name().toLowerCase(Locale.ENGLISH))) return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerEnvironmentRequirement() { - registerRequirement("environment", (args, actions, advanced) -> { - List environments = ConfigUtils.stringListArgs(args); - return condition -> { - var name = condition.getLocation().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); - if (environments.contains(name)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!environment", (args, actions, advanced) -> { - List environments = ConfigUtils.stringListArgs(args); - return condition -> { - var name = condition.getLocation().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); - if (!environments.contains(name)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerHookRequirement() { - registerRequirement("hook", (args, actions, advanced) -> { - List hooks = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{hook}"); - if (hooks.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("!hook", (args, actions, advanced) -> { - List hooks = ConfigUtils.stringListArgs(args); - return condition -> { - String id = condition.getArg("{hook}"); - if (!hooks.contains(id)) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - registerRequirement("has-hook", (args, actions, advanced) -> { - boolean has = (boolean) args; - return condition -> { - String id = condition.getArg("{hook}"); - if (id != null && has) return true; - if (id == null && !has) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerInBagRequirement() { - registerRequirement("in-fishingbag", (args, actions, advanced) -> { - boolean arg = (boolean) args; - return condition -> { - String inBag = condition.getArg("{in-bag}"); - if (inBag == null && !arg) return true; - if (inBag != null && inBag.equals(String.valueOf(arg))) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerListRequirement() { - registerRequirement("list", (args, actions, advanced) -> { - LogUtils.severe("It seems that you made a mistake where you put \"list\" into \"conditions\" section."); - ArrayList list = ConfigUtils.stringListArgs(args); - LogUtils.warn("list:"); - for (String e : list) { - LogUtils.warn(" - " + e); - } - return EmptyRequirement.instance; - }); - } - - private void registerCompetitionRequirement() { - registerRequirement("competition", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - boolean onCompetition = section.getBoolean("ongoing", true); - List ids = ConfigUtils.stringListArgs(section.get("id")); - return condition -> { - if (ids.size() == 0) { - if (plugin.getCompetitionManager().getOnGoingCompetition() != null == onCompetition) { - return true; - } - } else { - FishingCompetition competition = plugin.getCompetitionManager().getOnGoingCompetition(); - if (onCompetition) { - if (competition != null) { - if (ids.contains(competition.getConfig().getKey())) { - return true; - } - } - } else { - if (competition == null) { - return true; - } - } - } - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at competition requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerPluginLevelRequirement() { - registerRequirement("plugin-level", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String pluginName = section.getString("plugin"); - int level = section.getInt("level"); - String target = section.getString("target"); - return condition -> { - LevelInterface levelInterface = plugin.getIntegrationManager().getLevelPlugin(pluginName); - if (levelInterface == null) { - LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."); - return true; - } - if (levelInterface.getLevel(condition.getPlayer(), target) >= level) - return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at plugin-level requirement."); - return EmptyRequirement.instance; - } - }); - } - - - private void registerPotionEffectRequirement() { - registerRequirement("potion-effect", (args, actions, advanced) -> { - String potions = (String) args; - String[] split = potions.split("(<=|>=|<|>|==)", 2); - PotionEffectType type = PotionEffectType.getByName(split[0]); - if (type == null) { - LogUtils.warn("Potion effect doesn't exist: " + split[0]); - return EmptyRequirement.instance; - } - int required = Integer.parseInt(split[1]); - String operator = potions.substring(split[0].length(), potions.length() - split[1].length()); - return condition -> { - int level = -1; - PotionEffect potionEffect = condition.getPlayer().getPotionEffect(type); - if (potionEffect != null) { - level = potionEffect.getAmplifier(); - } - boolean result = false; - switch (operator) { - case ">=" -> { - if (level >= required) result = true; - } - case ">" -> { - if (level > required) result = true; - } - case "==" -> { - if (level == required) result = true; - } - case "!=" -> { - if (level != required) result = true; - } - case "<=" -> { - if (level <= required) result = true; - } - case "<" -> { - if (level < required) result = true; - } - } - if (result) { - return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - /** - * Triggers a list of actions with the given condition. - * If the list of actions is not null, each action in the list is triggered. - * - * @param actions The list of actions to trigger. - * @param condition The condition associated with the actions. - */ - private void triggerActions(List actions, Condition condition) { - if (actions != null) - for (Action action : actions) - action.trigger(condition); - } - - /** - * Loads requirement expansions from external JAR files located in the expansion folder. - * Each expansion JAR should contain classes that extends the RequirementExpansion class. - * Expansions are registered and used to create custom requirements. - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, RequirementExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - RequirementExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterRequirement(expansion.getRequirementType()); - registerRequirement(expansion.getRequirementType(), expansion.getRequirementFactory()); - LogUtils.info("Loaded requirement expansion: " + expansion.getRequirementType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor()); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/ActivatedTotem.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/ActivatedTotem.java deleted file mode 100644 index 35f70ee6..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/ActivatedTotem.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.totem; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; -import net.momirealms.customfishing.api.mechanic.totem.TotemParticle; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Location; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -public class ActivatedTotem { - - private final List subTasks; - private final Location coreLocation; - private final TotemConfig totemConfig; - private final long expireTime; - private final EffectCarrier effectCarrier; - - public ActivatedTotem(Location coreLocation, TotemConfig config) { - this.subTasks = new ArrayList<>(); - this.expireTime = System.currentTimeMillis() + config.getDuration() * 1000L; - this.coreLocation = coreLocation.clone().add(0.5,0,0.5); - this.totemConfig = config; - this.effectCarrier = CustomFishingPlugin.get().getEffectManager().getEffectCarrier("totem", config.getKey()); - for (TotemParticle particleSetting : config.getParticleSettings()) { - this.subTasks.add(particleSetting.start(coreLocation, config.getRadius())); - } - } - - public TotemConfig getTotemConfig() { - return totemConfig; - } - - public Location getCoreLocation() { - return coreLocation; - } - - public void cancel() { - for (CancellableTask task : subTasks) { - task.cancel(); - } - } - - public long getExpireTime() { - return expireTime; - } - - public void doTimerAction() { - HashMap args = new HashMap<>(); - args.put("{time_left}", String.valueOf((expireTime - System.currentTimeMillis())/1000)); - Condition condition = new Condition(coreLocation, null, args); - if (effectCarrier != null) { - Action[] actions = effectCarrier.getActions(ActionTrigger.TIMER); - if (actions != null) { - for (Action action : actions) { - action.trigger(condition); - } - } - } - } - - public EffectCarrier getEffectCarrier() { - return effectCarrier; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java deleted file mode 100644 index 92c65703..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.mechanic.totem; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.common.SimpleLocation; -import net.momirealms.customfishing.api.event.TotemActivateEvent; -import net.momirealms.customfishing.api.manager.TotemManager; -import net.momirealms.customfishing.api.mechanic.action.Action; -import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; -import net.momirealms.customfishing.api.mechanic.condition.Condition; -import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; -import net.momirealms.customfishing.api.mechanic.totem.TotemConfig; -import net.momirealms.customfishing.api.mechanic.totem.TotemModel; -import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock; -import net.momirealms.customfishing.api.mechanic.totem.block.property.AxisImpl; -import net.momirealms.customfishing.api.mechanic.totem.block.property.FaceImpl; -import net.momirealms.customfishing.api.mechanic.totem.block.property.HalfImpl; -import net.momirealms.customfishing.api.mechanic.totem.block.property.TotemBlockProperty; -import net.momirealms.customfishing.api.mechanic.totem.block.type.TypeCondition; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.mechanic.totem.particle.DustParticleSetting; -import net.momirealms.customfishing.mechanic.totem.particle.ParticleSetting; -import net.momirealms.customfishing.util.LocationUtils; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.Bisected; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class TotemManagerImpl implements TotemManager, Listener { - - private final CustomFishingPlugin plugin; - private final HashMap> totemConfigMap; - private final List allMaterials; - private final ConcurrentHashMap activatedTotems; - private CancellableTask timerCheckTask; - - public TotemManagerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.totemConfigMap = new HashMap<>(); - this.allMaterials = Arrays.stream(Material.values()).map(Enum::name).toList(); - this.activatedTotems = new ConcurrentHashMap<>(); - } - - public void load() { - this.loadConfig(); - Bukkit.getPluginManager().registerEvents(this, plugin); - this.timerCheckTask = plugin.getScheduler().runTaskAsyncTimer(() -> { - long time = System.currentTimeMillis(); - ArrayList removed = new ArrayList<>(); - for (Map.Entry entry : activatedTotems.entrySet()) { - if (time > entry.getValue().getExpireTime()) { - removed.add(entry.getKey()); - entry.getValue().cancel(); - } else { - entry.getValue().doTimerAction(); - } - } - for (SimpleLocation simpleLocation : removed) { - activatedTotems.remove(simpleLocation); - } - }, 1, 1, TimeUnit.SECONDS); - } - - public void unload() { - this.totemConfigMap.clear(); - for (ActivatedTotem activatedTotem : activatedTotems.values()) { - activatedTotem.cancel(); - } - activatedTotems.clear(); - HandlerList.unregisterAll(this); - if (this.timerCheckTask != null && !this.timerCheckTask.isCancelled()) - this.timerCheckTask.cancel(); - } - - public void disable() { - unload(); - } - - /** - * Get the EffectCarrier associated with an activated totem located near the specified location. - * - * @param location The location to search for activated totems. - * @return The EffectCarrier associated with the nearest activated totem or null if none are found. - */ - @Override - @Nullable - public EffectCarrier getTotemEffect(Location location) { - for (ActivatedTotem activatedTotem : activatedTotems.values()) { - if (LocationUtils.getDistance(activatedTotem.getCoreLocation(), location) < activatedTotem.getTotemConfig().getRadius()) { - return activatedTotem.getEffectCarrier(); - } - } - return null; - } - - @EventHandler - public void onBreakTotemCore(BlockBreakEvent event) { - if (event.isCancelled()) - return; - Location location = event.getBlock().getLocation(); - SimpleLocation simpleLocation = SimpleLocation.getByBukkitLocation(location); - ActivatedTotem activatedTotem = activatedTotems.remove(simpleLocation); - if (activatedTotem != null) - activatedTotem.cancel(); - } - - @EventHandler (ignoreCancelled = true) - public void onInteractBlock(PlayerInteractEvent event) { - if (event.isBlockInHand()) - return; - if (event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK) - return; - if (event.getHand() != EquipmentSlot.HAND) - return; - Block block = event.getClickedBlock(); - assert block != null; - String id = plugin.getBlockManager().getAnyPluginBlockID(block); - List configs = totemConfigMap.get(id); - if (configs == null) - return; - TotemConfig config = null; - for (TotemConfig temp : configs) { - if (temp.isRightPattern(block.getLocation())) { - config = temp; - break; - } - } - if (config == null) - return; - String totemKey = config.getKey(); - EffectCarrier carrier = plugin.getEffectManager().getEffectCarrier("totem", totemKey); - if (carrier == null) - return; - Condition condition = new Condition(block.getLocation(), event.getPlayer(), new HashMap<>()); - if (!carrier.isConditionMet(condition)) - return; - - TotemActivateEvent totemActivateEvent = new TotemActivateEvent(event.getPlayer(), block.getLocation(), config); - Bukkit.getPluginManager().callEvent(totemActivateEvent); - if (totemActivateEvent.isCancelled()) { - return; - } - - Action[] actions = carrier.getActionMap().get(ActionTrigger.ACTIVATE); - if (actions != null) - for (Action action : actions) { - action.trigger(condition); - } - Location location = block.getLocation(); - ActivatedTotem activatedTotem = new ActivatedTotem(location, config); - SimpleLocation simpleLocation = SimpleLocation.getByBukkitLocation(location); - - ActivatedTotem previous = this.activatedTotems.put(simpleLocation, activatedTotem); - if (previous != null) { - previous.cancel(); - } - } - - @SuppressWarnings("DuplicatedCode") - private void loadConfig() { - Deque fileDeque = new ArrayDeque<>(); - for (String type : List.of("totem")) { - File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type); - if (!typeFolder.exists()) { - if (!typeFolder.mkdirs()) return; - plugin.saveResource("contents" + File.separator + type + File.separator + "default.yml", false); - } - fileDeque.push(typeFolder); - while (!fileDeque.isEmpty()) { - File file = fileDeque.pop(); - File[] files = file.listFiles(); - if (files == null) continue; - for (File subFile : files) { - if (subFile.isDirectory()) { - fileDeque.push(subFile); - } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { - this.loadSingleFile(subFile); - } - } - } - } - } - - private void loadSingleFile(File file) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - TotemConfig totemConfig = new TotemConfig.Builder(entry.getKey()) - .setTotemModels(getTotemModels(section.getConfigurationSection("pattern"))) - .setParticleSettings(getParticleSettings(section.getConfigurationSection("particles"))) - .setRequirements(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("requirements"), true)) - .setRadius(section.getDouble("radius", 8)) - .setDuration(section.getInt("duration", 300)) - .build(); - - HashSet coreMaterials = new HashSet<>(); - for (TotemBlock totemBlock : totemConfig.getTotemCore()) { - String text = totemBlock.getTypeCondition().getRawText(); - if (text.startsWith("*")) { - String sub = text.substring(1); - coreMaterials.addAll(allMaterials.stream().filter(it -> it.endsWith(sub)).toList()); - } else if (text.endsWith("*")) { - String sub = text.substring(0, text.length() - 1); - coreMaterials.addAll(allMaterials.stream().filter(it -> it.startsWith(sub)).toList()); - } else { - coreMaterials.add(text); - } - } - for (String material : coreMaterials) { - putTotemConfigToMap(material, totemConfig); - } - } - } - } - - private void putTotemConfigToMap(String material, TotemConfig totemConfig) { - List configs = this.totemConfigMap.getOrDefault(material, new ArrayList<>()); - configs.add(totemConfig); - this.totemConfigMap.put(material, configs); - } - - public ParticleSetting[] getParticleSettings(ConfigurationSection section) { - List particleSettings = new ArrayList<>(); - if (section != null) - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - particleSettings.add(getParticleSetting(innerSection)); - } - } - return particleSettings.toArray(new ParticleSetting[0]); - } - - public ParticleSetting getParticleSetting(ConfigurationSection section) { - Particle particle = Particle.valueOf(section.getString("type","REDSTONE")); - String formulaHorizontal = section.getString("polar-coordinates-formula.horizontal"); - String formulaVertical = section.getString("polar-coordinates-formula.vertical"); - List> ranges = section.getStringList("theta.range") - .stream().map(it -> { - String[] split = it.split("~"); - return Pair.of(Double.parseDouble(split[0]) * Math.PI / 180, Double.parseDouble(split[1]) * Math.PI / 180); - }).toList(); - - double interval = section.getDouble("theta.draw-interval", 3d); - int delay = section.getInt("task.delay", 0); - int period = section.getInt("task.period", 0); - if (particle == Particle.REDSTONE) { - String color = section.getString("options.color","0,0,0"); - String[] colorSplit = color.split(","); - return new DustParticleSetting( - formulaHorizontal, - formulaVertical, - particle, - interval, - ranges, - delay, - period, - new Particle.DustOptions( - Color.fromRGB( - Integer.parseInt(colorSplit[0]), - Integer.parseInt(colorSplit[1]), - Integer.parseInt(colorSplit[2]) - ), - (float) section.getDouble("options.scale", 1) - ) - ); - } else if (particle == Particle.DUST_COLOR_TRANSITION) { - String color = section.getString("options.from","0,0,0"); - String[] colorSplit = color.split(","); - String toColor = section.getString("options.to","255,255,255"); - String[] toColorSplit = toColor.split(","); - return new DustParticleSetting( - formulaHorizontal, - formulaVertical, - particle, - interval, - ranges, - delay, - period, - new Particle.DustTransition( - Color.fromRGB( - Integer.parseInt(colorSplit[0]), - Integer.parseInt(colorSplit[1]), - Integer.parseInt(colorSplit[2]) - ), - Color.fromRGB( - Integer.parseInt(toColorSplit[0]), - Integer.parseInt(toColorSplit[1]), - Integer.parseInt(toColorSplit[2]) - ), - (float) section.getDouble("options.scale", 1) - ) - ); - } else { - return new ParticleSetting( - formulaHorizontal, - formulaVertical, - particle, - interval, - ranges, - delay, - period - ); - } - } - - public TotemModel[] getTotemModels(ConfigurationSection section) { - TotemModel originalModel = parseModel(section); - List modelList = new ArrayList<>(); - for (int i = 0; i < 4; i++) { - originalModel = originalModel.deepClone().rotate90(); - modelList.add(originalModel); - if (i % 2 == 0) { - modelList.add(originalModel.mirrorVertically()); - } else { - modelList.add(originalModel.mirrorHorizontally()); - } - } - return modelList.toArray(new TotemModel[0]); - } - - @SuppressWarnings("unchecked") - public TotemModel parseModel(ConfigurationSection section) { - ConfigurationSection layerSection = section.getConfigurationSection("layer"); - List totemBlocksList = new ArrayList<>(); - if (layerSection != null) { - var set = layerSection.getValues(false).entrySet(); - TotemBlock[][][][] totemBlocks = new TotemBlock[set.size()][][][]; - for (Map.Entry entry : set) { - if (entry.getValue() instanceof List list) { - totemBlocks[Integer.parseInt(entry.getKey())-1] = parseLayer((List) list); - } - } - totemBlocksList.addAll(List.of(totemBlocks)); - } - - String[] core = section.getString("core","1,1,1").split(","); - int x = Integer.parseInt(core[2]) - 1; - int z = Integer.parseInt(core[1]) - 1; - int y = Integer.parseInt(core[0]) - 1; - return new TotemModel( - x,y,z, - totemBlocksList.toArray(new TotemBlock[0][][][]) - ); - } - - public TotemBlock[][][] parseLayer(List lines) { - List totemBlocksList = new ArrayList<>(); - for (String line : lines) { - totemBlocksList.add(parseSingleLine(line)); - } - return totemBlocksList.toArray(new TotemBlock[0][][]); - } - - public TotemBlock[][] parseSingleLine(String line) { - List totemBlocksList = new ArrayList<>(); - String[] splits = line.split("\\s+"); - for (String split : splits) { - totemBlocksList.add(parseSingleElement(split)); - } - return totemBlocksList.toArray(new TotemBlock[0][]); - } - - public TotemBlock[] parseSingleElement(String element) { - String[] orBlocks = element.split("\\|\\|"); - List totemBlockList = new ArrayList<>(); - for (String block : orBlocks) { - int index = block.indexOf("{"); - List propertyList = new ArrayList<>(); - if (index == -1) { - index = block.length(); - } else { - String propertyStr = block.substring(index+1, block.length()-1); - String[] properties = propertyStr.split(";"); - for (String property : properties) { - String[] split = property.split("="); - if (split.length < 2) continue; - String key = split[0]; - String value = split[1]; - switch (key) { - // Block face - case "face" -> { - BlockFace blockFace = BlockFace.valueOf(value.toUpperCase(Locale.ENGLISH)); - propertyList.add(new FaceImpl(blockFace)); - } - // Block axis - case "axis" -> { - Axis axis = Axis.valueOf(value.toUpperCase(Locale.ENGLISH)); - propertyList.add(new AxisImpl(axis)); - } - // Slab, Stair half - case "half" -> { - Bisected.Half half = Bisected.Half.valueOf(value.toUpperCase(Locale.ENGLISH)); - propertyList.add(new HalfImpl(half)); - } - } - } - } - String type = block.substring(0, index); - TotemBlock totemBlock = new TotemBlock( - TypeCondition.getTypeCondition(type), - propertyList.toArray(new TotemBlockProperty[0]) - ); - totemBlockList.add(totemBlock); - } - return totemBlockList.toArray(new TotemBlock[0]); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/scheduler/BukkitSchedulerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/scheduler/BukkitSchedulerImpl.java deleted file mode 100644 index b3de4a61..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/scheduler/BukkitSchedulerImpl.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.scheduler; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.scheduler.BukkitTask; - -/** - * A scheduler implementation for synchronous tasks using Bukkit's Scheduler. - */ -public class BukkitSchedulerImpl implements SyncScheduler { - - private final CustomFishingPlugin plugin; - - public BukkitSchedulerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - } - - /** - * Runs a synchronous task on the main server thread using Bukkit's Scheduler. - * If already on the main thread, the task is executed immediately. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - @Override - public void runSyncTask(Runnable runnable, Location location) { - if (Bukkit.isPrimaryThread()) - runnable.run(); - else - Bukkit.getScheduler().runTask(plugin, runnable); - } - - /** - * Runs a synchronous task repeatedly with a specified delay and period using Bukkit's Scheduler. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay in ticks before the first execution. - * @param period The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) { - return new BukkitCancellableTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period)); - } - - /** - * Runs a synchronous task with a specified delay using Bukkit's Scheduler. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) { - if (delay == 0) { - if (Bukkit.isPrimaryThread()) runnable.run(); - else Bukkit.getScheduler().runTask(plugin, runnable); - return new BukkitCancellableTask(null); - } - return new BukkitCancellableTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay)); - } - - /** - * Represents a scheduled task using Bukkit's Scheduler that can be cancelled. - */ - public static class BukkitCancellableTask implements CancellableTask { - - private final BukkitTask bukkitTask; - - public BukkitCancellableTask(BukkitTask bukkitTask) { - this.bukkitTask = bukkitTask; - } - - @Override - public void cancel() { - if (this.bukkitTask != null) - this.bukkitTask.cancel(); - } - - @Override - public boolean isCancelled() { - if (this.bukkitTask == null) return true; - return this.bukkitTask.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/scheduler/FoliaSchedulerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/scheduler/FoliaSchedulerImpl.java deleted file mode 100644 index 838e5f28..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/scheduler/FoliaSchedulerImpl.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.scheduler; - -import io.papermc.paper.threadedregions.scheduler.ScheduledTask; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Bukkit; -import org.bukkit.Location; - -/** - * A scheduler implementation for "synchronous" tasks using Folia's RegionScheduler. - */ -public class FoliaSchedulerImpl implements SyncScheduler { - - private final CustomFishingPlugin plugin; - - public FoliaSchedulerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - } - - /** - * Runs a "synchronous" task on the region thread using Folia's RegionScheduler. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - @Override - public void runSyncTask(Runnable runnable, Location location) { - if (location == null) { - Bukkit.getGlobalRegionScheduler().execute(plugin, runnable); - } else { - Bukkit.getRegionScheduler().execute(plugin, location, runnable); - } - } - - /** - * Runs a "synchronous" task repeatedly with a specified delay and period using Folia's RegionScheduler. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay in ticks before the first execution. - * @param period The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) { - if (location == null) { - return new FoliaCancellableTask(Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, (scheduledTask -> runnable.run()), delay, period)); - } - return new FoliaCancellableTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, (scheduledTask -> runnable.run()), delay, period)); - } - - /** - * Runs a "synchronous" task with a specified delay using Folia's RegionScheduler. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) { - if (delay == 0) { - if (location == null) { - return new FoliaCancellableTask(Bukkit.getGlobalRegionScheduler().run(plugin, (scheduledTask -> runnable.run()))); - } - return new FoliaCancellableTask(Bukkit.getRegionScheduler().run(plugin, location, (scheduledTask -> runnable.run()))); - } - if (location == null) { - return new FoliaCancellableTask(Bukkit.getGlobalRegionScheduler().runDelayed(plugin, (scheduledTask -> runnable.run()), delay)); - } - return new FoliaCancellableTask(Bukkit.getRegionScheduler().runDelayed(plugin, location, (scheduledTask -> runnable.run()), delay)); - } - - /** - * Represents a scheduled task using Folia's RegionScheduler that can be cancelled. - */ - public static class FoliaCancellableTask implements CancellableTask { - - private final ScheduledTask scheduledTask; - - public FoliaCancellableTask(ScheduledTask scheduledTask) { - this.scheduledTask = scheduledTask; - } - - @Override - public void cancel() { - this.scheduledTask.cancel(); - } - - @Override - public boolean isCancelled() { - return this.scheduledTask.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java deleted file mode 100644 index 267ec3bc..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SchedulerImpl.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.scheduler; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.scheduler.Scheduler; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import org.bukkit.Location; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * A scheduler implementation responsible for scheduling and managing tasks in a multi-threaded environment. - */ -public class SchedulerImpl implements Scheduler { - - private final SyncScheduler syncScheduler; - private final ScheduledThreadPoolExecutor schedule; - private final CustomFishingPlugin plugin; - - public SchedulerImpl(CustomFishingPlugin plugin) { - this.plugin = plugin; - this.syncScheduler = plugin.getVersionManager().hasRegionScheduler() ? - new FoliaSchedulerImpl(plugin) : new BukkitSchedulerImpl(plugin); - this.schedule = new ScheduledThreadPoolExecutor(1); - this.schedule.setMaximumPoolSize(1); - this.schedule.setKeepAliveTime(30, TimeUnit.SECONDS); - this.schedule.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); - } - - /** - * Reloads the scheduler configuration based on CustomFishingPlugin settings. - */ - public void reload() { - try { - this.schedule.setMaximumPoolSize(CFConfig.maximumPoolSize); - this.schedule.setCorePoolSize(CFConfig.corePoolSize); - this.schedule.setKeepAliveTime(CFConfig.keepAliveTime, TimeUnit.SECONDS); - } catch (IllegalArgumentException e) { - LogUtils.warn("Failed to create thread pool. Please lower the corePoolSize in config.yml.", e); - } - } - - /** - * Shuts down the scheduler. - */ - public void shutdown() { - if (this.schedule != null && !this.schedule.isShutdown()) - this.schedule.shutdown(); - } - - /** - * Runs a task synchronously on the main server thread or region thread. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - @Override - public void runTaskSync(Runnable runnable, Location location) { - this.syncScheduler.runSyncTask(runnable, location); - } - - /** - * Runs a task asynchronously. - * - * @param runnable The task to run. - */ - @Override - public void runTaskAsync(Runnable runnable) { - try { - this.schedule.execute(runnable); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Runs a task synchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the first execution. - * @param periodTicks The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks) { - return this.syncScheduler.runTaskSyncTimer(runnable, location, delayTicks, periodTicks); - } - - /** - * Runs a task asynchronously with a specified delay. - * - * @param runnable The task to run. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) { - return new ScheduledTask(schedule.schedule(() -> { - try { - runnable.run(); - } catch (Exception e) { - e.printStackTrace(); - } - }, delay, timeUnit)); - } - - /** - * Runs a task synchronously with a specified delay. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit) { - return new ScheduledTask(schedule.schedule(() -> { - runTaskSync(runnable, location); - }, delay, timeUnit)); - } - - /** - * Runs a task synchronously with a specified delay in ticks. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks) { - return this.syncScheduler.runTaskSyncLater(runnable, location, delayTicks); - } - - /** - * Runs a task asynchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param delay The delay before the first execution. - * @param period The period between subsequent executions. - * @param timeUnit The time unit for the delay and period. - * @return A CancellableTask for managing the scheduled task. - */ - @Override - public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) { - return new ScheduledTask(schedule.scheduleAtFixedRate(() -> { - try { - runnable.run(); - } catch (Exception e) { - e.printStackTrace(); - } - }, delay, period, timeUnit)); - } - - /** - * Represents a thread-pool task that can be cancelled. - */ - public static class ScheduledTask implements CancellableTask { - - private final ScheduledFuture scheduledFuture; - - public ScheduledTask(ScheduledFuture scheduledFuture) { - this.scheduledFuture = scheduledFuture; - } - - @Override - public void cancel() { - this.scheduledFuture.cancel(false); - } - - @Override - public boolean isCancelled() { - return this.scheduledFuture.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SyncScheduler.java b/plugin/src/main/java/net/momirealms/customfishing/scheduler/SyncScheduler.java deleted file mode 100644 index 9d706dc1..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/scheduler/SyncScheduler.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.scheduler; - -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import org.bukkit.Location; - -public interface SyncScheduler { - - /** - * Runs a task synchronously on the main server thread or region thread. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - void runSyncTask(Runnable runnable, Location location); - - /** - * Runs a task synchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the first execution. - * @param periodTicks The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks); - - /** - * Runs a task synchronously with a specified delay in ticks. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks); -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/setting/CFConfig.java b/plugin/src/main/java/net/momirealms/customfishing/setting/CFConfig.java deleted file mode 100644 index 8fa8282f..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/setting/CFConfig.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.setting; - -import dev.dejvokep.boostedyaml.YamlDocument; -import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; -import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; -import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; -import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; -import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.api.util.OffsetUtils; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventPriority; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -public class CFConfig { - - // config version - public static String configVersion = "32"; - // Debug mode - public static boolean debug; - // language - public static String language; - - // update checker - public static boolean updateChecker; - - // BStats - public static boolean metrics; - - // fishing event priority - public static EventPriority eventPriority; - - // thread pool settings - public static int corePoolSize; - public static int maximumPoolSize; - public static int keepAliveTime; - - // detection order for item id - public static List itemDetectOrder = new ArrayList<>(); - public static List blockDetectOrder = new ArrayList<>(); - - // fishing bag - public static boolean enableFishingBag; - - // Fishing wait time - public static boolean overrideVanilla; - public static int waterMinTime; - public static int waterMaxTime; - // Lava fishing - public static int lavaMinTime; - public static int lavaMaxTime; - - // Competition - public static boolean redisRanking; - public static String serverGroup; - public static int placeholderLimit; - - // Data save interval - public static int dataSaveInterval; - // Lock data on join - public static boolean lockData; - public static boolean logDataSaving; - - public static boolean restrictedSizeRange; - - // Legacy color code support - public static boolean legacyColorSupport; - // Durability lore - public static List durabilityLore; - - public static boolean globalShowInFinder; - public static boolean globalDisableStats; - public static boolean globalDisableGame; - public static boolean globalInstantGame; - - public static int multipleLootSpawnDelay; - - public static void load() { - try { - YamlDocument.create( - new File(CustomFishingPlugin.getInstance().getDataFolder(), "config.yml"), - Objects.requireNonNull(CustomFishingPlugin.getInstance().getResource("config.yml")), - GeneralSettings.DEFAULT, - LoaderSettings - .builder() - .setAutoUpdate(true) - .build(), - DumperSettings.DEFAULT, - UpdaterSettings - .builder() - .setVersioning(new BasicVersioning("config-version")) - .addIgnoredRoute(configVersion, "mechanics.mechanic-requirements", '.') - .addIgnoredRoute(configVersion, "mechanics.global-events", '.') - .addIgnoredRoute(configVersion, "mechanics.global-effects", '.') - .addIgnoredRoute(configVersion, "mechanics.fishing-bag.collect-actions", '.') - .addIgnoredRoute(configVersion, "mechanics.fishing-bag.full-actions", '.') - .addIgnoredRoute(configVersion, "other-settings.placeholder-register", '.') - .build() - ); - loadSettings(CustomFishingPlugin.getInstance().getConfig("config.yml")); - } catch (IOException e) { - LogUtils.warn(e.getMessage()); - } - } - - private static void loadSettings(YamlConfiguration config) { - debug = config.getBoolean("debug", false); - - language = config.getString("lang", "english"); - updateChecker = config.getBoolean("update-checker"); - metrics = config.getBoolean("metrics"); - eventPriority = EventPriority.valueOf(config.getString("other-settings.event-priority", "NORMAL").toUpperCase(Locale.ENGLISH)); - - corePoolSize = config.getInt("other-settings.thread-pool-settings.corePoolSize", 1); - maximumPoolSize = config.getInt("other-settings.thread-pool-settings.maximumPoolSize", 1); - keepAliveTime = config.getInt("other-settings.thread-pool-settings.keepAliveTime", 30); - - itemDetectOrder = config.getStringList("other-settings.item-detection-order"); - blockDetectOrder = config.getStringList("other-settings.block-detection-order"); - - enableFishingBag = config.getBoolean("mechanics.fishing-bag.enable", true); - - overrideVanilla = config.getBoolean("mechanics.fishing-wait-time.override-vanilla", false); - waterMinTime = config.getInt("mechanics.fishing-wait-time.min-wait-time", 100); - waterMaxTime = config.getInt("mechanics.fishing-wait-time.min-wait-time", 600); - - lavaMinTime = config.getInt("mechanics.lava-fishing.min-wait-time", 100); - lavaMaxTime = config.getInt("mechanics.lava-fishing.max-wait-time", 600); - - restrictedSizeRange = config.getBoolean("mechanics.size.restricted-size-range", true); - - globalShowInFinder = config.getBoolean("mechanics.global-loot-property.show-in-fishfinder", true); - globalDisableStats = config.getBoolean("mechanics.global-loot-property.disable-stat", false); - globalDisableGame = config.getBoolean("mechanics.global-loot-property.disable-game", false); - globalInstantGame = config.getBoolean("mechanics.global-loot-property.instant-game", false); - - redisRanking = config.getBoolean("mechanics.competition.redis-ranking", false); - placeholderLimit = config.getInt("mechanics.competition.placeholder-limit", 3); - serverGroup = config.getString("mechanics.competition.server-group","default"); - - multipleLootSpawnDelay = config.getInt("mechanics.multiple-loot-spawn-delay", 0); - - dataSaveInterval = config.getInt("other-settings.data-saving-interval", 600); - logDataSaving = config.getBoolean("other-settings.log-data-saving", true); - lockData = config.getBoolean("other-settings.lock-data", true); - legacyColorSupport = config.getBoolean("other-settings.legacy-color-code-support", false); - - durabilityLore = config.getStringList("other-settings.custom-durability-format").stream().map(it -> "" + it).toList(); - - OffsetUtils.loadConfig(config.getConfigurationSection("other-settings.offset-characters")); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/setting/CFLocale.java b/plugin/src/main/java/net/momirealms/customfishing/setting/CFLocale.java deleted file mode 100644 index 0eb42ca7..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/setting/CFLocale.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.setting; - -import dev.dejvokep.boostedyaml.YamlDocument; -import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; -import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; -import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; -import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; -import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.util.LogUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -public class CFLocale { - public static String MSG_Total_Size; - public static String MSG_Catch_Amount; - public static String MSG_Total_Score; - public static String MSG_Max_Size; - public static String MSG_No_Player; - public static String MSG_No_Score; - public static String MSG_Prefix; - public static String MSG_Reload; - public static String MSG_Competition_Not_Exist; - public static String MSG_No_Competition_Ongoing; - public static String MSG_End_Competition; - public static String MSG_Stop_Competition; - public static String MSG_No_Rank; - public static String MSG_Item_Not_Exists; - public static String MSG_Get_Item; - public static String MSG_Give_Item; - public static String MSG_Never_Played; - public static String MSG_Unsafe_Modification; - public static String MSG_Data_Not_Loaded; - public static String MSG_Market_GUI_Open; - public static String MSG_Fishing_Bag_Open; - public static String MSG_Split_Char; - public static String MSG_Possible_Loots; - public static String FORMAT_Day; - public static String FORMAT_Hour; - public static String FORMAT_Minute; - public static String FORMAT_Second; - public static String GUI_SCROLL_DOWN; - public static String GUI_SCROLL_UP; - public static String GUI_CANNOT_SCROLL_UP; - public static String GUI_CANNOT_SCROLL_DOWN; - public static String GUI_NEXT_PAGE; - public static String GUI_GOTO_NEXT_PAGE; - public static String GUI_CANNOT_GOTO_NEXT_PAGE; - public static String GUI_PREVIOUS_PAGE; - public static String GUI_GOTO_PREVIOUS_PAGE; - public static String GUI_CANNOT_GOTO_PREVIOUS_PAGE; - public static String GUI_BACK_TO_PARENT_PAGE; - public static String GUI_BACK_TO_PARENT_FOLDER; - public static String GUI_CURRENT_VALUE; - public static String GUI_CLICK_TO_TOGGLE; - public static String GUI_LEFT_CLICK_EDIT; - public static String GUI_RIGHT_CLICK_RESET; - public static String GUI_RIGHT_CLICK_DELETE; - public static String GUI_LOOT_SHOW_IN_FINDER; - public static String GUI_LOOT_SCORE; - public static String GUI_LOOT_NICK; - public static String GUI_LOOT_INSTANT_GAME; - public static String GUI_LOOT_DISABLE_STATS; - public static String GUI_LOOT_DISABLE_GAME; - public static String GUI_ITEM_AMOUNT; - public static String GUI_ITEM_MODEL_DATA; - public static String GUI_ITEM_DISPLAY_NAME; - public static String GUI_ITEM_DURABILITY; - public static String GUI_ITEM_ENCHANTMENT; - public static String GUI_ITEM_HEAD64; - public static String GUI_ITEM_FLAG; - public static String GUI_ITEM_LORE; - public static String GUI_ITEM_MATERIAL; - public static String GUI_ITEM_NBT; - public static String GUI_ITEM_PREVENT_GRAB; - public static String GUI_ITEM_PRICE; - public static String GUI_ITEM_PRICE_BASE; - public static String GUI_ITEM_PRICE_BONUS; - public static String GUI_ITEM_RANDOM_DURABILITY; - public static String GUI_ITEM_SIZE; - public static String GUI_ITEM_STACKABLE; - public static String GUI_ITEM_STORED_ENCHANTMENT; - public static String GUI_ITEM_TAG; - public static String GUI_ITEM_UNBREAKABLE; - public static String GUI_DELETE_PROPERTY; - public static String GUI_NEW_VALUE; - public static String GUI_CLICK_CONFIRM; - public static String GUI_INVALID_NUMBER; - public static String GUI_ILLEGAL_FORMAT; - public static String GUI_TITLE_AMOUNT; - public static String GUI_TITLE_MODEL_DATA; - public static String GUI_TITLE_DISPLAY_NAME; - public static String GUI_NEW_DISPLAY_NAME; - public static String GUI_TITLE_CUSTOM_DURABILITY; - public static String GUI_TITLE_ENCHANTMENT; - public static String GUI_TITLE_STORED_ENCHANTMENT; - public static String GUI_SELECT_ONE_ENCHANTMENT; - public static String GUI_ADD_NEW_ENCHANTMENT; - public static String GUI_TITLE_ITEM_FLAG; - public static String GUI_TITLE_LORE; - public static String GUI_ADD_NEW_LORE; - public static String GUI_SELECT_ONE_LORE; - public static String GUI_TITLE_MATERIAL; - public static String GUI_TITLE_NBT_COMPOUND; - public static String GUI_TITLE_NBT_LIST; - public static String GUI_TITLE_NBT_KEY; - public static String GUI_NBT_INVALID_KEY; - public static String GUI_RIGHT_CLICK_CANCEL; - public static String GUI_NBT_ADD_COMPOUND; - public static String GUI_NBT_ADD_LIST; - public static String GUI_NBT_ADD_VALUE; - public static String GUI_NBT_PREVIEW; - public static String GUI_NBT_BACK_TO_COMPOUND; - public static String GUI_NBT_SET_VALUE_TITLE; - public static String GUI_NBT_EDIT_TITLE; - public static String GUI_NICK_TITLE; - public static String GUI_NICK_NEW; - public static String GUI_PRICE_TITLE; - public static String GUI_PRICE_BASE; - public static String GUI_PRICE_BONUS; - public static String GUI_SCORE_TITLE; - public static String GUI_SIZE_TITLE; - public static String GUI_SIZE_MIN; - public static String GUI_SIZE_MAX; - public static String GUI_SIZE_MAX_NO_LESS; - public static String GUI_SELECT_FILE; - public static String GUI_SELECT_ITEM; - public static String GUI_ADD_NEW_KEY; - public static String GUI_DUPE_INVALID_KEY; - public static String GUI_SEARCH; - public static String GUI_TEMP_NEW_KEY; - public static String GUI_SET_NEW_KEY; - public static String GUI_EDIT_KEY; - - public static void load() { - InputStream inputStream = CustomFishingPlugin.getInstance().getResource("messages/" + CFConfig.language + ".yml"); - if (inputStream != null) { - try { - YamlDocument.create( - new File(CustomFishingPlugin.getInstance().getDataFolder(), "messages/" + CFConfig.language + ".yml"), - inputStream, - GeneralSettings.DEFAULT, - LoaderSettings - .builder() - .setAutoUpdate(true) - .build(), - DumperSettings.DEFAULT, - UpdaterSettings - .builder() - .setVersioning(new BasicVersioning("config-version")) - .build() - ); - inputStream.close(); - } catch (IOException e) { - LogUtils.warn(e.getMessage()); - } - } - loadSettings(CustomFishingPlugin.get().getConfig("messages/" + CFConfig.language + ".yml")); - } - - private static void loadSettings(YamlConfiguration locale) { - ConfigurationSection msgSection = locale.getConfigurationSection("messages"); - if (msgSection != null) { - MSG_Prefix = msgSection.getString("prefix"); - MSG_Reload = msgSection.getString("reload"); - MSG_Competition_Not_Exist = msgSection.getString("competition-not-exist"); - MSG_No_Competition_Ongoing = msgSection.getString("no-competition-ongoing"); - MSG_Stop_Competition = msgSection.getString("stop-competition"); - MSG_End_Competition = msgSection.getString("end-competition"); - MSG_No_Player = msgSection.getString("no-player"); - MSG_No_Score = msgSection.getString("no-score"); - MSG_No_Rank = msgSection.getString("no-rank"); - MSG_Catch_Amount = msgSection.getString("goal-catch-amount"); - MSG_Max_Size = msgSection.getString("goal-max-size"); - MSG_Total_Score = msgSection.getString("goal-total-score"); - MSG_Total_Size = msgSection.getString("goal-total-size"); - MSG_Item_Not_Exists = msgSection.getString("item-not-exist"); - MSG_Get_Item = msgSection.getString("get-item"); - MSG_Give_Item = msgSection.getString("give-item"); - MSG_Never_Played = msgSection.getString("never-played"); - MSG_Unsafe_Modification = msgSection.getString("unsafe-modification"); - MSG_Data_Not_Loaded = msgSection.getString("data-not-loaded"); - MSG_Market_GUI_Open = msgSection.getString("open-market-gui"); - MSG_Fishing_Bag_Open = msgSection.getString("open-fishing-bag"); - MSG_Split_Char = msgSection.getString("split-char"); - MSG_Possible_Loots = msgSection.getString("possible-loots"); - FORMAT_Day = msgSection.getString("format-day"); - FORMAT_Hour = msgSection.getString("format-hour"); - FORMAT_Minute = msgSection.getString("format-minute"); - FORMAT_Second = msgSection.getString("format-second"); - } - ConfigurationSection guiSection = locale.getConfigurationSection("gui"); - if (guiSection != null) { - GUI_SEARCH = guiSection.getString("search"); - GUI_EDIT_KEY = guiSection.getString("edit-key"); - GUI_DELETE_PROPERTY = guiSection.getString("delete-property"); - GUI_DUPE_INVALID_KEY = guiSection.getString("dupe-invalid-key"); - GUI_SELECT_ITEM = guiSection.getString("select-item"); - GUI_SELECT_FILE = guiSection.getString("select-file"); - GUI_TEMP_NEW_KEY = guiSection.getString("temp-new-key"); - GUI_SET_NEW_KEY = guiSection.getString("set-new-key"); - GUI_ADD_NEW_KEY = guiSection.getString("page-add-new-key"); - GUI_SCROLL_UP = guiSection.getString("scroll-up"); - GUI_SCROLL_DOWN = guiSection.getString("scroll-down"); - GUI_CANNOT_SCROLL_UP = guiSection.getString("cannot-scroll-up"); - GUI_CANNOT_SCROLL_DOWN = guiSection.getString("cannot-scroll-down"); - GUI_NEXT_PAGE = guiSection.getString("next-page"); - GUI_GOTO_NEXT_PAGE = guiSection.getString("goto-next-page"); - GUI_CANNOT_GOTO_NEXT_PAGE = guiSection.getString("cannot-goto-next-page"); - GUI_PREVIOUS_PAGE = guiSection.getString("previous-page"); - GUI_GOTO_PREVIOUS_PAGE = guiSection.getString("goto-previous-page"); - GUI_CANNOT_GOTO_PREVIOUS_PAGE = guiSection.getString("cannot-goto-previous-page"); - GUI_BACK_TO_PARENT_PAGE = guiSection.getString("back-to-parent-page"); - GUI_BACK_TO_PARENT_FOLDER = guiSection.getString("back-to-parent-folder"); - GUI_CURRENT_VALUE = guiSection.getString("current-value"); - GUI_CLICK_TO_TOGGLE = guiSection.getString("click-to-toggle"); - GUI_LEFT_CLICK_EDIT = guiSection.getString("left-click-edit"); - GUI_RIGHT_CLICK_RESET = guiSection.getString("right-click-reset"); - GUI_RIGHT_CLICK_DELETE = guiSection.getString("right-click-delete"); - GUI_RIGHT_CLICK_CANCEL = guiSection.getString("right-click-cancel"); - GUI_LOOT_SHOW_IN_FINDER = guiSection.getString("loot-show-in-finder"); - GUI_LOOT_SCORE = guiSection.getString("loot-score"); - GUI_LOOT_NICK = guiSection.getString("loot-nick"); - GUI_LOOT_INSTANT_GAME = guiSection.getString("loot-instant-game"); - GUI_LOOT_DISABLE_STATS = guiSection.getString("loot-disable-statistics"); - GUI_LOOT_DISABLE_GAME = guiSection.getString("loot-disable-game"); - GUI_ITEM_AMOUNT = guiSection.getString("item-amount"); - GUI_ITEM_MODEL_DATA = guiSection.getString("item-custom-model-data"); - GUI_ITEM_DISPLAY_NAME = guiSection.getString("item-display-name"); - GUI_ITEM_DURABILITY = guiSection.getString("item-custom-durability"); - GUI_ITEM_ENCHANTMENT = guiSection.getString("item-enchantment"); - GUI_ITEM_HEAD64 = guiSection.getString("item-head64"); - GUI_ITEM_FLAG = guiSection.getString("item-item-flag"); - GUI_ITEM_LORE = guiSection.getString("item-lore"); - GUI_ITEM_MATERIAL = guiSection.getString("item-material"); - GUI_ITEM_NBT = guiSection.getString("item-nbt"); - GUI_ITEM_PREVENT_GRAB = guiSection.getString("item-prevent-grab"); - GUI_ITEM_PRICE = guiSection.getString("item-price"); - GUI_ITEM_PRICE_BASE = guiSection.getString("item-price-base"); - GUI_ITEM_PRICE_BONUS = guiSection.getString("item-price-bonus"); - GUI_ITEM_RANDOM_DURABILITY = guiSection.getString("item-random-durability"); - GUI_ITEM_SIZE = guiSection.getString("item-size"); - GUI_ITEM_STACKABLE = guiSection.getString("item-stackable"); - GUI_ITEM_STORED_ENCHANTMENT = guiSection.getString("item-stored-enchantment"); - GUI_ITEM_TAG = guiSection.getString("item-tag"); - GUI_ITEM_UNBREAKABLE = guiSection.getString("item-unbreakable"); - GUI_NEW_VALUE = guiSection.getString("new-value"); - GUI_CLICK_CONFIRM = guiSection.getString("click-confirm"); - GUI_INVALID_NUMBER = guiSection.getString("invalid-number"); - GUI_ILLEGAL_FORMAT = guiSection.getString("illegal-format"); - GUI_TITLE_AMOUNT = guiSection.getString("page-amount-title"); - GUI_TITLE_MODEL_DATA = guiSection.getString("page-model-data-title"); - GUI_TITLE_DISPLAY_NAME = guiSection.getString("page-display-name-title"); - GUI_NEW_DISPLAY_NAME = guiSection.getString("page-new-display-name"); - GUI_TITLE_CUSTOM_DURABILITY = guiSection.getString("page-custom-durability-title"); - GUI_TITLE_ENCHANTMENT = guiSection.getString("page-enchantment-title"); - GUI_TITLE_STORED_ENCHANTMENT = guiSection.getString("page-stored-enchantment-title"); - GUI_SELECT_ONE_ENCHANTMENT = guiSection.getString("page-select-one-enchantment"); - GUI_ADD_NEW_ENCHANTMENT = guiSection.getString("page-add-new-enchantment"); - GUI_TITLE_ITEM_FLAG = guiSection.getString("page-item-flag-title"); - GUI_TITLE_LORE = guiSection.getString("page-lore-title"); - GUI_ADD_NEW_LORE = guiSection.getString("page-add-new-lore"); - GUI_SELECT_ONE_LORE = guiSection.getString("page-select-one-lore"); - GUI_TITLE_MATERIAL = guiSection.getString("page-material-title"); - GUI_TITLE_NBT_COMPOUND = guiSection.getString("page-nbt-compound-key-title"); - GUI_TITLE_NBT_LIST = guiSection.getString("page-nbt-list-key-title"); - GUI_TITLE_NBT_KEY = guiSection.getString("page-nbt-key-title"); - GUI_NBT_INVALID_KEY = guiSection.getString("page-nbt-invalid-key"); - GUI_NBT_ADD_COMPOUND = guiSection.getString("page-nbt-add-new-compound"); - GUI_NBT_ADD_LIST = guiSection.getString("page-nbt-add-new-list"); - GUI_NBT_ADD_VALUE = guiSection.getString("page-nbt-add-new-value"); - GUI_NBT_PREVIEW = guiSection.getString("page-nbt-preview"); - GUI_NBT_BACK_TO_COMPOUND = guiSection.getString("page-nbt-back-to-compound"); - GUI_NBT_SET_VALUE_TITLE = guiSection.getString("page-nbt-set-value-title"); - GUI_NBT_EDIT_TITLE = guiSection.getString("page-nbt-edit-title"); - GUI_NICK_TITLE = guiSection.getString("page-nick-title"); - GUI_NICK_NEW = guiSection.getString("page-new-nick"); - GUI_PRICE_TITLE = guiSection.getString("page-price-title"); - GUI_PRICE_BASE = guiSection.getString("page-base-price"); - GUI_PRICE_BONUS = guiSection.getString("page-base-bonus"); - GUI_SCORE_TITLE = guiSection.getString("page-score-title"); - GUI_SIZE_TITLE = guiSection.getString("page-size-title"); - GUI_SIZE_MIN = guiSection.getString("page-size-min"); - GUI_SIZE_MAX = guiSection.getString("page-size-max"); - GUI_SIZE_MAX_NO_LESS = guiSection.getString("page-size-max-no-less-min"); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/StorageManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/storage/StorageManagerImpl.java deleted file mode 100644 index d9fbbd6d..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/StorageManagerImpl.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.storage; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.DataStorageInterface; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StorageType; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import net.momirealms.customfishing.api.manager.StorageManager; -import net.momirealms.customfishing.api.scheduler.CancellableTask; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import net.momirealms.customfishing.storage.method.database.nosql.MongoDBImpl; -import net.momirealms.customfishing.storage.method.database.nosql.RedisManager; -import net.momirealms.customfishing.storage.method.database.sql.H2Impl; -import net.momirealms.customfishing.storage.method.database.sql.MariaDBImpl; -import net.momirealms.customfishing.storage.method.database.sql.MySQLImpl; -import net.momirealms.customfishing.storage.method.database.sql.SQLiteImpl; -import net.momirealms.customfishing.storage.method.file.JsonImpl; -import net.momirealms.customfishing.storage.method.file.YAMLImpl; -import net.momirealms.customfishing.storage.user.OfflineUserImpl; -import net.momirealms.customfishing.storage.user.OnlineUserImpl; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.HashSet; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -/** - * This class implements the StorageManager interface and is responsible for managing player data storage. - * It includes methods to handle player data retrieval, storage, and serialization. - */ -public class StorageManagerImpl implements StorageManager, Listener { - - private final CustomFishingPlugin plugin; - private DataStorageInterface dataSource; - private StorageType previousType; - private final ConcurrentHashMap onlineUserMap; - private final HashSet locked; - private boolean hasRedis; - private RedisManager redisManager; - private String uniqueID; - private CancellableTask timerSaveTask; - private final Gson gson; - - public StorageManagerImpl(CustomFishingPluginImpl plugin) { - this.plugin = plugin; - this.locked = new HashSet<>(); - this.onlineUserMap = new ConcurrentHashMap<>(); - this.gson = new GsonBuilder().create(); - Bukkit.getPluginManager().registerEvents(this, plugin); - } - - /** - * Reloads the storage manager configuration. - */ - public void reload() { - YamlConfiguration config = plugin.getConfig("database.yml"); - this.uniqueID = config.getString("unique-server-id", "default"); - - // Check if storage type has changed and reinitialize if necessary - StorageType storageType = StorageType.valueOf(config.getString("data-storage-method", "H2")); - if (storageType != previousType) { - if (this.dataSource != null) this.dataSource.disable(); - this.previousType = storageType; - switch (storageType) { - case H2 -> this.dataSource = new H2Impl(plugin); - case JSON -> this.dataSource = new JsonImpl(plugin); - case YAML -> this.dataSource = new YAMLImpl(plugin); - case SQLite -> this.dataSource = new SQLiteImpl(plugin); - case MySQL -> this.dataSource = new MySQLImpl(plugin); - case MariaDB -> this.dataSource = new MariaDBImpl(plugin); - case MongoDB -> this.dataSource = new MongoDBImpl(plugin); - } - if (this.dataSource != null) this.dataSource.initialize(); - else LogUtils.severe("No storage type is set."); - } - - // Handle Redis configuration - if (!this.hasRedis && config.getBoolean("Redis.enable", false)) { - this.hasRedis = true; - this.redisManager = new RedisManager(plugin); - this.redisManager.initialize(); - } - - // Disable Redis if it was enabled but is now disabled - if (this.hasRedis && !config.getBoolean("Redis.enable", false) && this.redisManager != null) { - this.redisManager.disable(); - this.redisManager = null; - } - - // Cancel any existing timerSaveTask - if (this.timerSaveTask != null && !this.timerSaveTask.isCancelled()) { - this.timerSaveTask.cancel(); - } - - // Schedule periodic data saving if dataSaveInterval is configured - if (CFConfig.dataSaveInterval != -1 && CFConfig.dataSaveInterval != 0) - this.timerSaveTask = this.plugin.getScheduler().runTaskAsyncTimer( - () -> { - long time1 = System.currentTimeMillis(); - this.dataSource.updateManyPlayersData(this.onlineUserMap.values(), !CFConfig.lockData); - if (CFConfig.logDataSaving) - LogUtils.info("Data Saved for online players. Took " + (System.currentTimeMillis() - time1) + "ms."); - }, - CFConfig.dataSaveInterval, - CFConfig.dataSaveInterval, - TimeUnit.SECONDS - ); - } - - /** - * Disables the storage manager and cleans up resources. - */ - public void disable() { - HandlerList.unregisterAll(this); - this.dataSource.updateManyPlayersData(onlineUserMap.values(), true); - this.onlineUserMap.clear(); - if (this.dataSource != null) - this.dataSource.disable(); - if (this.redisManager != null) - this.redisManager.disable(); - } - - /** - * Gets the unique server identifier. - * - * @return The unique server identifier. - */ - @NotNull - @Override - public String getUniqueID() { - return uniqueID; - } - - /** - * Gets an OnlineUser instance for the specified UUID. - * - * @param uuid The UUID of the player. - * @return An OnlineUser instance if the player is online, or null if not. - */ - @Override - public OnlineUser getOnlineUser(UUID uuid) { - return onlineUserMap.get(uuid); - } - - @Override - public Collection getOnlineUsers() { - return onlineUserMap.values(); - } - - /** - * Asynchronously retrieves an OfflineUser instance for the specified UUID. - * - * @param uuid The UUID of the player. - * @param lock Whether to lock the data during retrieval. - * @return A CompletableFuture that resolves to an Optional containing the OfflineUser instance if found, or empty if not found or locked. - */ - @Override - public CompletableFuture> getOfflineUser(UUID uuid, boolean lock) { - var optionalDataFuture = dataSource.getPlayerData(uuid, lock); - return optionalDataFuture.thenCompose(optionalUser -> { - if (optionalUser.isEmpty()) { - // locked - return CompletableFuture.completedFuture(Optional.empty()); - } - PlayerData data = optionalUser.get(); - if (data.isLocked()) { - return CompletableFuture.completedFuture(Optional.of(OfflineUserImpl.LOCKED_USER)); - } else { - OfflineUser offlineUser = new OfflineUserImpl(uuid, data.getName(), data); - return CompletableFuture.completedFuture(Optional.of(offlineUser)); - } - }); - } - - @Override - public boolean isLockedData(OfflineUser offlineUser) { - return OfflineUserImpl.LOCKED_USER == offlineUser; - } - - /** - * Asynchronously saves user data for an OfflineUser. - * - * @param offlineUser The OfflineUser whose data needs to be saved. - * @param unlock Whether to unlock the data after saving. - * @return A CompletableFuture that resolves to a boolean indicating the success of the data saving operation. - */ - @Override - public CompletableFuture saveUserData(OfflineUser offlineUser, boolean unlock) { - return dataSource.updatePlayerData(offlineUser.getUUID(), offlineUser.getPlayerData(), unlock); - } - - /** - * Gets the data source used for data storage. - * - * @return The data source. - */ - @Override - public DataStorageInterface getDataSource() { - return dataSource; - } - - /** - * Event handler for when a player joins the server. - * Locks the player's data and initiates data retrieval if Redis is not used, - * otherwise, it starts a Redis data retrieval task. - */ - @EventHandler - public void onJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - UUID uuid = player.getUniqueId(); - locked.add(uuid); - if (!hasRedis) { - waitForDataLockRelease(uuid, 1); - } else { - plugin.getScheduler().runTaskAsyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(changeServer -> { - if (!changeServer) { - waitForDataLockRelease(uuid, 3); - } else { - new RedisGetDataTask(uuid); - } - }), 500, TimeUnit.MILLISECONDS); - } - } - - /** - * Event handler for when a player quits the server. - * If the player is not locked, it removes their OnlineUser instance, - * updates the player's data in Redis and the data source. - */ - @EventHandler - public void onQuit(PlayerQuitEvent event) { - Player player = event.getPlayer(); - UUID uuid = player.getUniqueId(); - if (locked.contains(uuid)) - return; - - OnlineUser onlineUser = onlineUserMap.remove(uuid); - if (onlineUser == null) return; - PlayerData data = onlineUser.getPlayerData(); - - if (hasRedis) { - redisManager.setChangeServer(uuid).thenRun( - () -> redisManager.updatePlayerData(uuid, data, true).thenRun( - () -> dataSource.updatePlayerData(uuid, data, true).thenAccept( - result -> { - if (result) locked.remove(uuid); - }))); - } else { - dataSource.updatePlayerData(uuid, data, true).thenAccept( - result -> { - if (result) locked.remove(uuid); - }); - } - } - - /** - * Runnable task for asynchronously retrieving data from Redis. - * Retries up to 6 times and cancels the task if the player is offline. - */ - public class RedisGetDataTask implements Runnable { - - private final UUID uuid; - private int triedTimes; - private final CancellableTask task; - - public RedisGetDataTask(UUID uuid) { - this.uuid = uuid; - this.task = plugin.getScheduler().runTaskAsyncTimer(this, 0, 333, TimeUnit.MILLISECONDS); - } - - @Override - public void run() { - triedTimes++; - Player player = Bukkit.getPlayer(uuid); - if (player == null || !player.isOnline()) { - // offline - task.cancel(); - return; - } - if (triedTimes >= 6) { - waitForDataLockRelease(uuid, 3); - return; - } - redisManager.getPlayerData(uuid, false).thenAccept(optionalData -> { - if (optionalData.isPresent()) { - putDataInCache(player, optionalData.get()); - task.cancel(); - if (CFConfig.lockData) dataSource.lockOrUnlockPlayerData(uuid, true); - } - }); - } - } - - /** - * Waits for data lock release with a delay and a maximum of three retries. - * - * @param uuid The UUID of the player. - * @param times The number of times this method has been retried. - */ - public void waitForDataLockRelease(UUID uuid, int times) { - plugin.getScheduler().runTaskAsyncLater(() -> { - var player = Bukkit.getPlayer(uuid); - if (player == null || !player.isOnline()) - return; - if (times > 3) { - LogUtils.warn("Tried 3 times when getting data for " + uuid + ". Giving up."); - return; - } - this.dataSource.getPlayerData(uuid, CFConfig.lockData).thenAccept(optionalData -> { - // Data should not be empty - if (optionalData.isEmpty()) { - LogUtils.severe("Unexpected error: Data is null"); - return; - } - - if (optionalData.get().isLocked()) { - waitForDataLockRelease(uuid, times + 1); - } else { - try { - putDataInCache(player, optionalData.get()); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - }, 1, TimeUnit.SECONDS); - } - - /** - * Puts player data in cache and removes the player from the locked set. - * - * @param player The player whose data is being cached. - * @param playerData The data to be cached. - */ - public void putDataInCache(Player player, PlayerData playerData) { - locked.remove(player.getUniqueId()); - OnlineUserImpl bukkitUser = new OnlineUserImpl(player, playerData); - onlineUserMap.put(player.getUniqueId(), bukkitUser); - } - - /** - * Checks if Redis is enabled. - * - * @return True if Redis is enabled; otherwise, false. - */ - @Override - public boolean isRedisEnabled() { - return hasRedis; - } - - /** - * Gets the RedisManager instance. - * - * @return The RedisManager instance. - */ - @Nullable - public RedisManager getRedisManager() { - return redisManager; - } - - /** - * Converts PlayerData to bytes. - * - * @param data The PlayerData to be converted. - * @return The byte array representation of PlayerData. - */ - @NotNull - @Override - public byte[] toBytes(@NotNull PlayerData data) { - return toJson(data).getBytes(StandardCharsets.UTF_8); - } - - /** - * Converts PlayerData to JSON format. - * - * @param data The PlayerData to be converted. - * @return The JSON string representation of PlayerData. - */ - @Override - @NotNull - public String toJson(@NotNull PlayerData data) { - return gson.toJson(data); - } - - /** - * Converts JSON string to PlayerData. - * - * @param json The JSON string to be converted. - * @return The PlayerData object. - */ - @NotNull - @Override - public PlayerData fromJson(String json) { - try { - return gson.fromJson(json, PlayerData.class); - } catch (JsonSyntaxException e) { - LogUtils.severe("Failed to parse PlayerData from json"); - LogUtils.info("Json: " + json); - throw new RuntimeException(e); - } - } - - /** - * Converts bytes to PlayerData. - * - * @param data The byte array to be converted. - * @return The PlayerData object. - */ - @Override - @NotNull - public PlayerData fromBytes(byte[] data) { - return fromJson(new String(data, StandardCharsets.UTF_8)); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java b/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java deleted file mode 100644 index 29f242b2..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/database/sql/AbstractHikariDatabase.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.storage.method.database.sql; - -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.*; -import net.momirealms.customfishing.api.util.LogUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -/** - * An abstract base class for SQL databases using the HikariCP connection pool, which handles player data storage. - */ -public abstract class AbstractHikariDatabase extends AbstractSQLDatabase implements LegacyDataStorageInterface { - - private HikariDataSource dataSource; - private final String driverClass; - private final String sqlBrand; - - public AbstractHikariDatabase(CustomFishingPlugin plugin) { - super(plugin); - this.driverClass = getStorageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver"; - this.sqlBrand = getStorageType() == StorageType.MariaDB ? "MariaDB" : "MySQL"; - try { - Class.forName(this.driverClass); - } catch (ClassNotFoundException e1) { - if (getStorageType() == StorageType.MariaDB) { - LogUtils.warn("No MariaDB driver is found"); - } else if (getStorageType() == StorageType.MySQL) { - try { - Class.forName("com.mysql.jdbc.Driver"); - } catch (ClassNotFoundException e2) { - LogUtils.warn("No MySQL driver is found"); - } - } - } - } - - /** - * Initialize the database connection pool and create tables if they don't exist. - */ - @Override - public void initialize() { - YamlConfiguration config = plugin.getConfig("database.yml"); - ConfigurationSection section = config.getConfigurationSection(sqlBrand); - - if (section == null) { - LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one."); - return; - } - - super.tablePrefix = section.getString("table-prefix", "customfishing"); - HikariConfig hikariConfig = new HikariConfig(); - hikariConfig.setUsername(section.getString("user", "root")); - hikariConfig.setPassword(section.getString("password", "pa55w0rd")); - hikariConfig.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", - sqlBrand.toLowerCase(Locale.ENGLISH), - section.getString("host", "localhost"), - section.getString("port", "3306"), - section.getString("database", "minecraft"), - section.getString("connection-parameters") - )); - hikariConfig.setDriverClassName(driverClass); - hikariConfig.setMaximumPoolSize(section.getInt("Pool-Settings.max-pool-size", 10)); - hikariConfig.setMinimumIdle(section.getInt("Pool-Settings.min-idle", 10)); - hikariConfig.setMaxLifetime(section.getLong("Pool-Settings.max-lifetime", 180000L)); - hikariConfig.setConnectionTimeout(section.getLong("Pool-Settings.time-out", 20000L)); - hikariConfig.setPoolName("CustomFishingHikariPool"); - try { - hikariConfig.setKeepaliveTime(section.getLong("Pool-Settings.keep-alive-time", 60000L)); - } catch (NoSuchMethodError ignored) { - } - - final Properties properties = new Properties(); - properties.putAll( - Map.of("cachePrepStmts", "true", - "prepStmtCacheSize", "250", - "prepStmtCacheSqlLimit", "2048", - "useServerPrepStmts", "true", - "useLocalSessionState", "true", - "useLocalTransactionState", "true" - )); - properties.putAll( - Map.of( - "rewriteBatchedStatements", "true", - "cacheResultSetMetadata", "true", - "cacheServerConfiguration", "true", - "elideSetAutoCommits", "true", - "maintainTimeStats", "false") - ); - hikariConfig.setDataSourceProperties(properties); - dataSource = new HikariDataSource(hikariConfig); - super.createTableIfNotExist(); - } - - /** - * Disable the database by closing the connection pool. - */ - @Override - public void disable() { - if (dataSource != null && !dataSource.isClosed()) - dataSource.close(); - } - - /** - * Get a connection to the SQL database from the connection pool. - * - * @return A database connection. - * @throws SQLException If there is an error establishing a connection. - */ - @Override - public Connection getConnection() throws SQLException { - return dataSource.getConnection(); - } - - /** - * Retrieve legacy player data from the SQL database. - * - * @param uuid The UUID of the player. - * @return A CompletableFuture containing the optional legacy player data. - */ - @Override - public CompletableFuture> getLegacyPlayerData(UUID uuid) { - var future = new CompletableFuture>(); - plugin.getScheduler().runTaskAsync(() -> { - try ( - Connection connection = getConnection() - ) { - var builder = new PlayerData.Builder().setName(""); - PreparedStatement statementOne = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("fishingbag"))); - statementOne.setString(1, uuid.toString()); - ResultSet rsOne = statementOne.executeQuery(); - if (rsOne.next()) { - int size = rsOne.getInt("size"); - String contents = rsOne.getString("contents"); - builder.setBagData(new InventoryData(contents, size)); - } else { - builder.setBagData(InventoryData.empty()); - } - - PreparedStatement statementTwo = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("selldata"))); - statementTwo.setString(1, uuid.toString()); - ResultSet rsTwo = statementTwo.executeQuery(); - if (rsTwo.next()) { - int date = rsTwo.getInt("date"); - double money = rsTwo.getInt("money"); - builder.setEarningData(new EarningData(money, date)); - } else { - builder.setEarningData(EarningData.empty()); - } - - PreparedStatement statementThree = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("statistics"))); - statementThree.setString(1, uuid.toString()); - ResultSet rsThree = statementThree.executeQuery(); - if (rsThree.next()) { - String stats = rsThree.getString("stats"); - var amountMap = (Map) Arrays.stream(stats.split(";")) - .map(element -> element.split(":")) - .filter(pair -> pair.length == 2) - .collect(Collectors.toMap(pair -> pair[0], pair -> Integer.parseInt(pair[1]))); - builder.setStats(new StatisticData(amountMap, new HashMap<>())); - } else { - builder.setStats(StatisticData.empty()); - } - future.complete(Optional.of(builder.build())); - } catch (SQLException e) { - LogUtils.warn("Failed to get " + uuid + "'s data.", e); - future.completeExceptionally(e); - } - }); - return future; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/method/file/YAMLImpl.java b/plugin/src/main/java/net/momirealms/customfishing/storage/method/file/YAMLImpl.java deleted file mode 100644 index e3ac1db4..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/method/file/YAMLImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.storage.method.file; - -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.*; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.storage.method.AbstractStorage; -import net.momirealms.customfishing.util.ConfigUtils; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -/** - * A data storage implementation that uses YAML files to store player data, with support for legacy data. - */ -public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterface { - - @SuppressWarnings("ResultOfMethodCallIgnored") - public YAMLImpl(CustomFishingPlugin plugin) { - super(plugin); - File folder = new File(plugin.getDataFolder(), "data"); - if (!folder.exists()) folder.mkdirs(); - } - - @Override - public StorageType getStorageType() { - return StorageType.YAML; - } - - /** - * Get the file associated with a player's UUID for storing YAML data. - * - * @param uuid The UUID of the player. - * @return The file for the player's data. - */ - public File getPlayerDataFile(UUID uuid) { - return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".yml"); - } - - @Override - public CompletableFuture> getPlayerData(UUID uuid, boolean lock) { - File dataFile = getPlayerDataFile(uuid); - if (!dataFile.exists()) { - if (Bukkit.getPlayer(uuid) != null) { - return CompletableFuture.completedFuture(Optional.of(PlayerData.empty())); - } else { - return CompletableFuture.completedFuture(Optional.empty()); - } - } - YamlConfiguration data = ConfigUtils.readData(dataFile); - - PlayerData playerData = new PlayerData.Builder() - .setBagData(new InventoryData(data.getString("bag", ""), data.getInt("size", 9))) - .setEarningData(new EarningData(data.getDouble("earnings"), data.getInt("date"))) - .setStats(getStatistics(data.getConfigurationSection("stats"))) - .setName(data.getString("name")) - .build(); - return CompletableFuture.completedFuture(Optional.of(playerData)); - } - - @Override - public CompletableFuture updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) { - YamlConfiguration data = new YamlConfiguration(); - data.set("name", playerData.getName()); - data.set("bag", playerData.getBagData().serialized); - data.set("size", playerData.getBagData().size); - data.set("date", playerData.getEarningData().date); - data.set("earnings", playerData.getEarningData().earnings); - ConfigurationSection section = data.createSection("stats"); - ConfigurationSection amountSection = section.createSection("amount"); - ConfigurationSection sizeSection = section.createSection("size"); - for (Map.Entry entry : playerData.getStatistics().amountMap.entrySet()) { - amountSection.set(entry.getKey(), entry.getValue()); - } - for (Map.Entry entry : playerData.getStatistics().sizeMap.entrySet()) { - sizeSection.set(entry.getKey(), entry.getValue()); - } - try { - data.save(getPlayerDataFile(uuid)); - } catch (IOException e) { - LogUtils.warn("Failed to save player data", e); - } - return CompletableFuture.completedFuture(true); - } - - @Override - public Set getUniqueUsers(boolean legacy) { - File folder; - if (legacy) { - folder = new File(plugin.getDataFolder(), "data/fishingbag"); - } else { - folder = new File(plugin.getDataFolder(), "data"); - } - Set uuids = new HashSet<>(); - if (folder.exists()) { - File[] files = folder.listFiles(); - if (files != null) { - for (File file : files) { - uuids.add(UUID.fromString(file.getName().substring(0, file.getName().length() - 4))); - } - } - } - return uuids; - } - - /** - * Parse statistics data from a YAML ConfigurationSection. - * - * @param section The ConfigurationSection containing statistics data. - * @return The parsed StatisticData object. - */ - private StatisticData getStatistics(ConfigurationSection section) { - HashMap amountMap = new HashMap<>(); - HashMap sizeMap = new HashMap<>(); - if (section == null) { - return new StatisticData(amountMap, sizeMap); - } - ConfigurationSection amountSection = section.getConfigurationSection("amount"); - if (amountSection != null) { - for (Map.Entry entry : amountSection.getValues(false).entrySet()) { - amountMap.put(entry.getKey(), (Integer) entry.getValue()); - } - } - ConfigurationSection sizeSection = section.getConfigurationSection("size"); - if (sizeSection != null) { - for (Map.Entry entry : sizeSection.getValues(false).entrySet()) { - sizeMap.put(entry.getKey(), ((Double) entry.getValue()).floatValue()); - } - } - return new StatisticData(amountMap, sizeMap); - } - - @Override - public CompletableFuture> getLegacyPlayerData(UUID uuid) { - // Retrieve legacy player data (YAML format) for a given UUID. - var builder = new PlayerData.Builder().setName(""); - File bagFile = new File(plugin.getDataFolder(), "data/fishingbag/" + uuid + ".yml"); - if (bagFile.exists()) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(bagFile); - String contents = yaml.getString("contents", ""); - int size = yaml.getInt("size", 9); - builder.setBagData(new InventoryData(contents, size)); - } else { - builder.setBagData(InventoryData.empty()); - } - - File statFile = new File(plugin.getDataFolder(), "data/statistics/" + uuid + ".yml"); - if (statFile.exists()) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(statFile); - HashMap map = new HashMap<>(); - for (Map.Entry entry : yaml.getValues(false).entrySet()) { - if (entry.getValue() instanceof Integer integer) { - map.put(entry.getKey(), integer); - } - } - builder.setStats(new StatisticData(map, new HashMap<>())); - } else { - builder.setStats(StatisticData.empty()); - } - - File sellFile = new File(plugin.getDataFolder(), "data/sell/" + uuid + ".yml"); - if (sellFile.exists()) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(sellFile); - builder.setEarningData(new EarningData(yaml.getDouble("earnings"), yaml.getInt("date"))); - } else { - builder.setEarningData(EarningData.empty()); - } - - return CompletableFuture.completedFuture(Optional.of(builder.build())); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/user/OfflineUserImpl.java b/plugin/src/main/java/net/momirealms/customfishing/storage/user/OfflineUserImpl.java deleted file mode 100644 index 066a2ef4..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/user/OfflineUserImpl.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.storage.user; - -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.data.EarningData; -import net.momirealms.customfishing.api.data.InventoryData; -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.StatisticData; -import net.momirealms.customfishing.api.data.user.OfflineUser; -import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder; -import net.momirealms.customfishing.api.mechanic.statistic.Statistics; -import net.momirealms.customfishing.api.util.InventoryUtils; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -/** - * Implementation of the OfflineUser interface for representing offline player data. - */ -public class OfflineUserImpl implements OfflineUser { - - private final UUID uuid; - private final String name; - private final FishingBagHolder holder; - private final EarningData earningData; - private final Statistics statistics; - public static OfflineUserImpl LOCKED_USER = new OfflineUserImpl(UUID.randomUUID(), "-locked-", PlayerData.empty()); - - /** - * Constructor to create an OfflineUserImpl instance. - * - * @param uuid The UUID of the player. - * @param name The name of the player. - * @param playerData The player's data, including bag contents, earnings, and statistics. - */ - public OfflineUserImpl(UUID uuid, String name, PlayerData playerData) { - this.name = name; - this.uuid = uuid; - this.holder = new FishingBagHolder(uuid); - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); - // Set up the inventory for the FishingBagHolder - this.holder.setInventory(InventoryUtils.createInventory(this.holder, playerData.getBagData().size, - AdventureHelper.getInstance().getComponentFromMiniMessage( - PlaceholderManagerImpl.getInstance().parse( - offlinePlayer, - CustomFishingPlugin.get().getBagManager().getBagTitle(), - Map.of("{player}", Optional.ofNullable(offlinePlayer.getName()).orElse(String.valueOf(uuid))) - ) - ))); - this.holder.setItems(InventoryUtils.getInventoryItems(playerData.getBagData().serialized)); - this.earningData = playerData.getEarningData(); - int date = CustomFishingPlugin.get().getMarketManager().getDate(); - if (earningData.date != date) { - earningData.date = date; - earningData.earnings = 0d; - } - this.statistics = new Statistics(playerData.getStatistics()); - } - - @Override - public String getName() { - return name; - } - - @Override - public UUID getUUID() { - return uuid; - } - - @Override - public FishingBagHolder getHolder() { - return holder; - } - - @Override - public EarningData getEarningData() { - return earningData; - } - - @Override - public Statistics getStatistics() { - return statistics; - } - - @Override - public boolean isOnline() { - Player player = Bukkit.getPlayer(uuid); - return player != null && player.isOnline(); - } - - @Override - public PlayerData getPlayerData() { - // Create a new PlayerData instance based on the stored information - return new PlayerData.Builder() - .setBagData(new InventoryData(InventoryUtils.stacksToBase64(holder.getInventory().getStorageContents()), holder.getInventory().getSize())) - .setEarningData(earningData) - .setStats(new StatisticData(statistics.getStatisticMap(), statistics.getSizeMap())) - .setName(name) - .build(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/storage/user/OnlineUserImpl.java b/plugin/src/main/java/net/momirealms/customfishing/storage/user/OnlineUserImpl.java deleted file mode 100644 index ea8f63d1..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/storage/user/OnlineUserImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.storage.user; - -import net.momirealms.customfishing.api.data.PlayerData; -import net.momirealms.customfishing.api.data.user.OnlineUser; -import org.bukkit.entity.Player; - -/** - * Implementation of the OnlineUser interface, extending OfflineUserImpl to represent online player data. - */ -public class OnlineUserImpl extends OfflineUserImpl implements OnlineUser { - - private final Player player; - - /** - * Constructor to create an OnlineUserImpl instance. - * - * @param player The online player associated with this user. - * @param playerData The player's data, including bag contents, earnings, and statistics. - */ - public OnlineUserImpl(Player player, PlayerData playerData) { - super(player.getUniqueId(), player.getName(), playerData); - this.player = player; - } - - @Override - public Player getPlayer() { - return player; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/ArmorStandUtils.java b/plugin/src/main/java/net/momirealms/customfishing/util/ArmorStandUtils.java deleted file mode 100644 index df9d4ab0..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/util/ArmorStandUtils.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.util; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.*; -import com.google.common.collect.Lists; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.momirealms.customfishing.CustomFishingPluginImpl; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.util.*; -import java.util.concurrent.TimeUnit; - -/** - * Utility class for managing armor stands and sending related packets. - */ -public class ArmorStandUtils { - - private ArmorStandUtils() {} - - /** - * Creates a destroy packet for removing an armor stand entity. - * - * @param id The ID of the armor stand entity to destroy - * @return The PacketContainer representing the destroy packet - */ - public static PacketContainer getDestroyPacket(int id) { - PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY); - destroyPacket.getIntLists().write(0, List.of(id)); - return destroyPacket; - } - - /** - * Creates a spawn packet for an armor stand entity at the specified location. - * - * @param id The ID of the armor stand entity to spawn - * @param location The location where the armor stand entity should be spawned - * @return The PacketContainer representing the spawn packet - */ - public static PacketContainer getSpawnPacket(int id, Location location) { - PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY); - try { - entityPacket.getModifier().write(0, id); - entityPacket.getModifier().write(1, UUID.randomUUID()); - entityPacket.getEntityTypeModifier().write(0, EntityType.ARMOR_STAND); - entityPacket.getDoubles().write(0, location.getX()); - entityPacket.getDoubles().write(1, location.getY()); - entityPacket.getDoubles().write(2, location.getZ()); - if (CustomFishingPlugin.get().getVersionManager().isVersionNewerThan1_19()) { - entityPacket.getBytes().write(0, (byte) ((location.getYaw() % 360) * 128 / 180)); - } else { - entityPacket.getIntegers().write(5, (int) ((location.getYaw() % 360) * 128 / 180)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return entityPacket; - } - - /** - * Creates a metadata packet for updating the metadata of an armor stand entity. - * - * @param id The ID of the armor stand entity - * @return The PacketContainer representing the metadata packet - */ - public static PacketContainer getMetaPacket(int id) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getIntegers().write(0, id); - if (CustomFishingPlugin.get().getVersionManager().isVersionNewerThan1_19_R2()) { - WrappedDataWatcher wrappedDataWatcher = createDataWatcher(); - setValueList(metaPacket, wrappedDataWatcher); - } else { - metaPacket.getWatchableCollectionModifier().write(0, createDataWatcher().getWatchableObjects()); - } - return metaPacket; - } - - /** - * Sets the value list in a PacketContainer's DataWatcher from a WrappedDataWatcher. - * - * @param metaPacket The PacketContainer representing the metadata packet - * @param wrappedDataWatcher The WrappedDataWatcher containing the value list - */ - @SuppressWarnings("DuplicatedCode") - private static void setValueList(PacketContainer metaPacket, WrappedDataWatcher wrappedDataWatcher) { - List wrappedDataValueList = Lists.newArrayList(); - wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> { - final WrappedDataWatcher.WrappedDataWatcherObject dataWatcherObject = entry.getWatcherObject(); - wrappedDataValueList.add(new WrappedDataValue(dataWatcherObject.getIndex(), dataWatcherObject.getSerializer(), entry.getRawValue())); - }); - metaPacket.getDataValueCollectionModifier().write(0, wrappedDataValueList); - } - - /** - * Creates a metadata packet for updating the metadata of an armor stand entity with a custom Component. - * - * @param id The ID of the armor stand entity - * @param component The Component to set as metadata - * @return The PacketContainer representing the metadata packet - */ - public static PacketContainer getMetaPacket(int id, Component component) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getIntegers().write(0, id); - if (CustomFishingPlugin.get().getVersionManager().isVersionNewerThan1_19_R2()) { - WrappedDataWatcher wrappedDataWatcher = createDataWatcher(component); - setValueList(metaPacket, wrappedDataWatcher); - } else { - metaPacket.getWatchableCollectionModifier().write(0, createDataWatcher(component).getWatchableObjects()); - } - return metaPacket; - } - - /** - * Creates a DataWatcher for an invisible armor stand entity. - * - * @return The created DataWatcher - */ - public static WrappedDataWatcher createDataWatcher() { - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer1 = WrappedDataWatcher.Registry.get(Boolean.class); - WrappedDataWatcher.Serializer serializer2 = WrappedDataWatcher.Registry.get(Byte.class); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, serializer1), false); - byte flag = 0x20; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, serializer2), flag); - return wrappedDataWatcher; - } - - /** - * Creates a DataWatcher for an invisible armor stand entity with a custom Component. - * - * @param component The Component to set in the DataWatcher - * @return The created DataWatcher - */ - public static WrappedDataWatcher createDataWatcher(Component component) { - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - WrappedDataWatcher.Serializer serializer1 = WrappedDataWatcher.Registry.get(Boolean.class); - WrappedDataWatcher.Serializer serializer2 = WrappedDataWatcher.Registry.get(Byte.class); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true)), Optional.of(WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(component)).getHandle())); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, serializer1), true); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(15, serializer2), (byte) 0x01); - byte flag = 0x20; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, serializer2), flag); - return wrappedDataWatcher; - } - - /** - * Creates an equipment packet for equipping an armor stand with an ItemStack. - * - * @param id The ID of the armor stand entity - * @param itemStack The ItemStack to equip - * @return The PacketContainer representing the equipment packet - */ - public static PacketContainer getEquipPacket(int id, ItemStack itemStack) { - PacketContainer equipPacket = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); - equipPacket.getIntegers().write(0, id); - List> pairs = new ArrayList<>(); - pairs.add(new Pair<>(EnumWrappers.ItemSlot.HEAD, itemStack)); - equipPacket.getSlotStackPairLists().write(0, pairs); - return equipPacket; - } - - /** - * Sends a fake armor stand entity with item on head to a player at the specified location. - * - * @param player The player to send the entity to - * @param location The location where the entity should appear - * @param itemStack The ItemStack to represent the entity - * @param seconds The duration (in seconds) the entity should be displayed - */ - public static void sendFakeItem(Player player, Location location, ItemStack itemStack, int seconds) { - int id = new Random().nextInt(Integer.MAX_VALUE); - if (CustomFishingPlugin.get().getVersionManager().isVersionNewerThan1_19_R3()) { - CustomFishingPluginImpl.sendPackets(player, getSpawnPacket(id, location.clone().subtract(0,1,0)), getMetaPacket(id), getEquipPacket(id, itemStack)); - } else { - CustomFishingPluginImpl.sendPacket(player, getSpawnPacket(id, location.clone().subtract(0,1,0))); - CustomFishingPluginImpl.sendPacket(player, getMetaPacket(id)); - CustomFishingPluginImpl.sendPacket(player, getEquipPacket(id, itemStack)); - } - CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), seconds * 50L, TimeUnit.MILLISECONDS); - } - - /** - * Sends a hologram (armor stand with custom text) to a player at the specified location. - * - * @param player The player to send the hologram to - * @param location The location where the hologram should appear - * @param component The Component representing the hologram's text - * @param seconds The duration (in seconds) the hologram should be displayed - */ - public static void sendHologram(Player player, Location location, Component component, int seconds) { - int id = new Random().nextInt(Integer.MAX_VALUE); - if (CustomFishingPlugin.get().getVersionManager().isVersionNewerThan1_19_R3()) { - CustomFishingPluginImpl.sendPackets(player, getSpawnPacket(id, location.clone().subtract(0,1,0)), getMetaPacket(id, component)); - } else { - CustomFishingPluginImpl.sendPacket(player, getSpawnPacket(id, location.clone().subtract(0,1,0))); - CustomFishingPluginImpl.sendPacket(player, getMetaPacket(id, component)); - } - CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), seconds * 50L, TimeUnit.MILLISECONDS); - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/ConfigUtils.java b/plugin/src/main/java/net/momirealms/customfishing/util/ConfigUtils.java deleted file mode 100644 index b3f4b513..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/util/ConfigUtils.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.util; - -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.common.Tuple; -import net.momirealms.customfishing.api.mechanic.misc.Value; -import net.momirealms.customfishing.api.mechanic.misc.WeightModifier; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import net.momirealms.customfishing.mechanic.misc.value.ExpressionValue; -import net.momirealms.customfishing.mechanic.misc.value.PlainValue; -import net.objecthunter.exp4j.ExpressionBuilder; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Utility class for configuration-related operations. - */ -public class ConfigUtils { - - private ConfigUtils() {} - - /** - * Converts an object into an ArrayList of strings. - * - * @param object The input object - * @return An ArrayList of strings - */ - @SuppressWarnings("unchecked") - public static ArrayList stringListArgs(Object object) { - ArrayList list = new ArrayList<>(); - if (object instanceof String member) { - list.add(member); - } else if (object instanceof List members) { - list.addAll((Collection) members); - } else if (object instanceof String[] strings) { - list.addAll(List.of(strings)); - } - return list; - } - - /** - * Splits a string into a pair of integers using the "~" delimiter. - * - * @param value The input string - * @return A Pair of integers - */ - public static Pair splitStringIntegerArgs(String value, String regex) { - String[] split = value.split(regex); - return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } - - /** - * Converts a list of strings in the format "key:value" into a list of Pairs with keys and doubles. - * - * @param list The input list of strings - * @return A list of Pairs containing keys and doubles - */ - public static List> getWeights(List list) { - List> result = new ArrayList<>(list.size()); - for (String member : list) { - String[] split = member.split(":",2); - String key = split[0]; - result.add(Pair.of(key, Double.parseDouble(split[1]))); - } - return result; - } - - /** - * Converts an object into a double value. - * - * @param arg The input object - * @return A double value - */ - public static double getDoubleValue(Object arg) { - if (arg instanceof Double d) { - return d; - } else if (arg instanceof Integer i) { - return Double.valueOf(i); - } - return 0; - } - - /** - * Converts an object into an integer value. - * - * @param arg The input object - * @return An integer value - */ - public static int getIntegerValue(Object arg) { - if (arg instanceof Integer i) { - return i; - } else if (arg instanceof Double d) { - return d.intValue(); - } - return 0; - } - - /** - * Converts an object into a "value". - * - * @param arg int / double / expression - * @return Value - */ - public static Value getValue(Object arg) { - if (arg instanceof Integer i) { - return new PlainValue(i); - } else if (arg instanceof Double d) { - return new PlainValue(d); - } else if (arg instanceof String s) { - return new ExpressionValue(s); - } - throw new IllegalArgumentException("Illegal value type"); - } - - /** - * Parses a string representing a size range and returns a pair of floats. - * - * @param string The size string in the format "min~max". - * @return A pair of floats representing the minimum and maximum size. - */ - @Nullable - public static Pair getFloatPair(String string) { - if (string == null) return null; - String[] split = string.split("~", 2); - if (split.length != 2) { - LogUtils.warn("Illegal size argument: " + string); - LogUtils.warn("Correct usage example: 10.5~25.6"); - throw new IllegalArgumentException("Illegal float range"); - } - return Pair.of(Float.parseFloat(split[0]), Float.parseFloat(split[1])); - } - - /** - * Parses a string representing a size range and returns a pair of ints. - * - * @param string The size string in the format "min~max". - * @return A pair of ints representing the minimum and maximum size. - */ - @Nullable - public static Pair getIntegerPair(String string) { - if (string == null) return null; - String[] split = string.split("~", 2); - if (split.length != 2) { - LogUtils.warn("Illegal size argument: " + string); - LogUtils.warn("Correct usage example: 10~20"); - throw new IllegalArgumentException("Illegal int range"); - } - return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } - - /** - * Converts a list of strings in the format "key:value" into a list of Pairs with keys and WeightModifiers. - * - * @param modList The input list of strings - * @return A list of Pairs containing keys and WeightModifiers - */ - public static List> getModifiers(List modList) { - List> result = new ArrayList<>(modList.size()); - for (String member : modList) { - String[] split = member.split(":",2); - String key = split[0]; - result.add(Pair.of(key, getModifier(split[1]))); - } - return result; - } - - /** - * Retrieves a list of enchantment pairs from a configuration section. - * - * @param section The configuration section to extract enchantment data from. - * @return A list of enchantment pairs. - */ - @NotNull - public static List> getEnchantmentPair(ConfigurationSection section) { - List> list = new ArrayList<>(); - if (section == null) return list; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof Integer integer) { - list.add(Pair.of(entry.getKey(), Short.parseShort(String.valueOf(Math.max(1, Math.min(Short.MAX_VALUE, integer)))))); - } - } - return list; - } - - public static List> getEnchantAmountPair(ConfigurationSection section) { - List> list = new ArrayList<>(); - if (section == null) return list; - for (Map.Entry entry : section.getValues(false).entrySet()) { - list.add(Pair.of(Integer.parseInt(entry.getKey()), getValue(entry.getValue()))); - } - return list; - } - - public static List, Value>> getEnchantPoolPair(ConfigurationSection section) { - List, Value>> list = new ArrayList<>(); - if (section == null) return list; - for (Map.Entry entry : section.getValues(false).entrySet()) { - list.add(Pair.of(getEnchantmentPair(entry.getKey()), getValue(entry.getValue()))); - } - return list; - } - - public static Pair getEnchantmentPair(String value) { - int last = value.lastIndexOf(":"); - if (last == -1 || last == 0 || last == value.length() - 1) { - throw new IllegalArgumentException("Invalid format of the input enchantment"); - } - return Pair.of(value.substring(0, last), Short.parseShort(value.substring(last + 1))); - } - - /** - * Retrieves a list of enchantment tuples from a configuration section. - * - * @param section The configuration section to extract enchantment data from. - * @return A list of enchantment tuples. - */ - @NotNull - public static List> getEnchantmentTuple(ConfigurationSection section) { - List> list = new ArrayList<>(); - if (section == null) return list; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - Tuple tuple = Tuple.of( - inner.getDouble("chance"), - inner.getString("enchant"), - Short.valueOf(String.valueOf(inner.getInt("level"))) - ); - list.add(tuple); - } - } - return list; - } - - public static String getString(Object o) { - if (o instanceof String s) { - return s; - } else if (o instanceof Integer i) { - return String.valueOf(i); - } else if (o instanceof Double d) { - return String.valueOf(d); - } - throw new IllegalArgumentException("Illegal string format: " + o); - } - - /** - * Reads data from a YAML configuration file and creates it if it doesn't exist. - * - * @param file The file path - * @return The YamlConfiguration - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - public static YamlConfiguration readData(File file) { - if (!file.exists()) { - try { - file.getParentFile().mkdirs(); - file.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - LogUtils.warn("Failed to generate data files!"); - } - } - return YamlConfiguration.loadConfiguration(file); - } - - /** - * Parses a WeightModifier from a string representation. - * - * @param text The input string - * @return A WeightModifier based on the provided text - * @throws IllegalArgumentException if the weight format is invalid - */ - public static WeightModifier getModifier(String text) { - if (text.length() == 0) { - throw new IllegalArgumentException("Weight format is invalid."); - } - switch (text.charAt(0)) { - case '/' -> { - double arg = Double.parseDouble(text.substring(1)); - return (player, weight) -> weight / arg; - } - case '*' -> { - double arg = Double.parseDouble(text.substring(1)); - return (player, weight) -> weight * arg; - } - case '-' -> { - double arg = Double.parseDouble(text.substring(1)); - return (player, weight) -> weight - arg; - } - case '%' -> { - double arg = Double.parseDouble(text.substring(1)); - return (player, weight) -> weight % arg; - } - case '+' -> { - double arg = Double.parseDouble(text.substring(1)); - return (player, weight) -> weight + arg; - } - case '=' -> { - String formula = text.substring(1); - return (player, weight) -> getExpressionValue(player, formula, Map.of("{0}", String.valueOf(weight))); - } - default -> throw new IllegalArgumentException("Invalid weight: " + text); - } - } - - public static double getExpressionValue(Player player, String formula, Map vars) { - formula = PlaceholderManagerImpl.getInstance().parse(player, formula, vars); - return new ExpressionBuilder(formula).build().evaluate(); - } - - public static ArrayList getReadableSection(Map map) { - ArrayList list = new ArrayList<>(); - mapToReadableStringList(map, list, 0, false); - return list; - } - - @SuppressWarnings("unchecked") - public static void mapToReadableStringList(Map map, List readableList, int loop_times, boolean isMapList) { - boolean first = true; - for (Map.Entry entry : map.entrySet()) { - Object nbt = entry.getValue(); - if (nbt instanceof String value) { - if (isMapList && first) { - first = false; - readableList.add(" ".repeat(loop_times - 1) + "- " + entry.getKey() + ": " + value); - } else { - readableList.add(" ".repeat(loop_times) + "" + entry.getKey() + ": " + value); - } - } else if (nbt instanceof List list) { - if (isMapList && first) { - first = false; - readableList.add(" ".repeat(loop_times - 1) + "- " + entry.getKey() + ":"); - } else { - readableList.add(" ".repeat(loop_times) + "" + entry.getKey() + ":"); - } - for (Object value : list) { - if (value instanceof Map nbtMap) { - mapToReadableStringList((Map) nbtMap, readableList, loop_times + 2, true); - } else { - readableList.add(" ".repeat(loop_times + 1) + "- " + value); - } - } - } else if (nbt instanceof ConfigurationSection section) { - if (isMapList && first) { - first = false; - readableList.add(" ".repeat(loop_times - 1) + "- " + entry.getKey() + ":"); - } else { - readableList.add(" ".repeat(loop_times) + "" + entry.getKey() + ":"); - } - mapToReadableStringList(section.getValues(false), readableList, loop_times + 1, false); - } else if (nbt instanceof Map innerMap) { - if (isMapList && first) { - first = false; - readableList.add(" ".repeat(loop_times - 1) + "- " + entry.getKey() + ":"); - } else { - readableList.add(" ".repeat(loop_times) + "" + entry.getKey() + ":"); - } - mapToReadableStringList((Map) innerMap, readableList, loop_times + 1, false); - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/FakeItemUtils.java b/plugin/src/main/java/net/momirealms/customfishing/util/FakeItemUtils.java deleted file mode 100644 index d0ae3c8e..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/util/FakeItemUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.util; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.WrappedDataValue; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.google.common.collect.Lists; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * Utility class for managing fake item entities using PacketContainers. - */ -public class FakeItemUtils { - - private FakeItemUtils() {} - - /** - * Creates a destroy packet for removing a fake item entity. - * - * @param id The ID of the fake item entity to destroy - * @return The PacketContainer representing the destroy packet - */ - public static PacketContainer getDestroyPacket(int id) { - PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY); - destroyPacket.getIntLists().write(0, List.of(id)); - return destroyPacket; - } - - /** - * Creates a spawn packet for a fake item entity at the specified location. - * - * @param id The ID of the fake item entity to spawn - * @param location The location where the fake item entity should be spawned - * @return The PacketContainer representing the spawn packet - */ - public static PacketContainer getSpawnPacket(int id, Location location) { - PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY); - entityPacket.getModifier().write(0, id); - entityPacket.getModifier().write(1, UUID.randomUUID()); - entityPacket.getEntityTypeModifier().write(0, EntityType.DROPPED_ITEM); - entityPacket.getDoubles().write(0, location.getX()); - entityPacket.getDoubles().write(1, location.getY() - 0.5); - entityPacket.getDoubles().write(2, location.getZ()); - return entityPacket; - } - - /** - * Creates a metadata packet for updating the metadata of a fake item entity. - * - * @param id The ID of the fake item entity - * @param itemStack The ItemStack to update the metadata with - * @return The PacketContainer representing the metadata packet - */ - public static PacketContainer getMetaPacket(int id, ItemStack itemStack) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getIntegers().write(0, id); - if (CustomFishingPlugin.getInstance().getVersionManager().isVersionNewerThan1_19_R2()) { - WrappedDataWatcher wrappedDataWatcher = createDataWatcher(itemStack); - setValueList(metaPacket, wrappedDataWatcher); - } else { - metaPacket.getWatchableCollectionModifier().write(0, createDataWatcher(itemStack).getWatchableObjects()); - } - return metaPacket; - } - - /** - * Creates a teleport packet for moving a fake item entity to the specified location. - * - * @param id The ID of the fake item entity to teleport - * @param location The location to teleport the fake item entity to - * @return The PacketContainer representing the teleport packet - */ - public static PacketContainer getTpPacket(int id, Location location) { - PacketContainer tpPacket = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT); - tpPacket.getModifier().write(0, id); - tpPacket.getDoubles().write(0, location.getX()); - tpPacket.getDoubles().write(1, location.getY() - 0.5); - tpPacket.getDoubles().write(2, location.getZ()); - return tpPacket; - } - - /** - * Creates a velocity packet for applying velocity to a fake item entity. - * - * @param id The ID of the fake item entity - * @param vector The velocity vector to apply - * @return The PacketContainer representing the velocity packet - */ - public static PacketContainer getVelocityPacket(int id, Vector vector) { - PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.ENTITY_VELOCITY); - entityPacket.getModifier().write(0, id); - entityPacket.getIntegers().write(1, (int) (vector.getX() * 8000)); - entityPacket.getIntegers().write(2, (int) (vector.getY() * 8000)); - entityPacket.getIntegers().write(3, (int) (vector.getZ() * 8000)); - return entityPacket; - } - - /** - * Creates a DataWatcher for a given ItemStack. - * - * @param itemStack The ItemStack to create the DataWatcher for - * @return The created DataWatcher - */ - public static WrappedDataWatcher createDataWatcher(ItemStack itemStack) { - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(8, WrappedDataWatcher.Registry.getItemStackSerializer(false)), itemStack); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(5, WrappedDataWatcher.Registry.get(Boolean.class)), true); - return wrappedDataWatcher; - } - - /** - * Sets the value list in a PacketContainer's DataWatcher from a WrappedDataWatcher. - * - * @param metaPacket The PacketContainer representing the metadata packet - * @param wrappedDataWatcher The WrappedDataWatcher containing the value list - */ - @SuppressWarnings("DuplicatedCode") - private static void setValueList(PacketContainer metaPacket, WrappedDataWatcher wrappedDataWatcher) { - List wrappedDataValueList = Lists.newArrayList(); - wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> { - final WrappedDataWatcher.WrappedDataWatcherObject dataWatcherObject = entry.getWatcherObject(); - wrappedDataValueList.add(new WrappedDataValue(dataWatcherObject.getIndex(), dataWatcherObject.getSerializer(), entry.getRawValue())); - }); - metaPacket.getDataValueCollectionModifier().write(0, wrappedDataValueList); - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/ItemUtils.java b/plugin/src/main/java/net/momirealms/customfishing/util/ItemUtils.java deleted file mode 100644 index a2d7d6c9..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/util/ItemUtils.java +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.util; - -import de.tr7zw.changeme.nbtapi.NBTCompound; -import de.tr7zw.changeme.nbtapi.NBTItem; -import de.tr7zw.changeme.nbtapi.NBTList; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ScoreComponent; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.momirealms.customfishing.adventure.AdventureHelper; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.api.common.Pair; -import net.momirealms.customfishing.api.mechanic.hook.HookSetting; -import net.momirealms.customfishing.api.util.LogUtils; -import net.momirealms.customfishing.setting.CFConfig; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerItemDamageEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * Utility class for various item-related operations. - */ -public class ItemUtils { - - private ItemUtils() {} - - /** - * Updates the lore of an NBTItem based on its custom NBT tags. - * - * @param nbtItem The NBTItem to update - * @return The updated NBTItem - */ - public static NBTItem updateNBTItemLore(NBTItem nbtItem) { - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound == null) - return nbtItem; - - NBTCompound displayCompound = nbtItem.getOrCreateCompound("display"); - NBTList lore = displayCompound.getStringList("Lore"); - lore.removeIf(it -> GsonComponentSerializer.gson().deserialize(it) instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")); - - if (cfCompound.hasTag("hook_id")) { - String hookID = cfCompound.getString("hook_id"); - HookSetting setting = CustomFishingPlugin.get().getHookManager().getHookSetting(hookID); - if (setting == null) { - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_item"); - cfCompound.removeKey("hook_dur"); - } else { - for (String newLore : setting.getLore()) { - ScoreComponent.Builder builder = Component.score().name("cf").objective("hook"); - builder.append(AdventureHelper.getInstance().getComponentFromMiniMessage( - newLore.replace("{dur}", String.valueOf(cfCompound.getInteger("hook_dur"))) - .replace("{max}", String.valueOf(setting.getMaxDurability())) - )); - lore.add(GsonComponentSerializer.gson().serialize(builder.build())); - } - } - } - - if (cfCompound.hasTag("max_dur")) { - int max = cfCompound.getInteger("max_dur"); - int current = cfCompound.getInteger("cur_dur"); - for (String newLore : CFConfig.durabilityLore) { - ScoreComponent.Builder builder = Component.score().name("cf").objective("durability"); - builder.append(AdventureHelper.getInstance().getComponentFromMiniMessage( - newLore.replace("{dur}", String.valueOf(current)) - .replace("{max}", String.valueOf(max)) - )); - lore.add(GsonComponentSerializer.gson().serialize(builder.build())); - } - } - return nbtItem; - } - - /** - * Updates the lore of an ItemStack based on its custom NBT tags. - * - * @param itemStack The ItemStack to update - */ - public static void updateItemLore(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - NBTItem nbtItem = updateNBTItemLore(new NBTItem(itemStack)); - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Reduces the durability of a fishing hook item. - * - * @param rod The fishing rod ItemStack - * @param updateLore Whether to update the lore after reducing durability - */ - public static void decreaseHookDurability(ItemStack rod, int amount, boolean updateLore) { - if (rod == null || rod.getType() != Material.FISHING_ROD) - return; - NBTItem nbtItem = new NBTItem(rod); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("hook_dur")) { - int hookDur = cfCompound.getInteger("hook_dur"); - if (hookDur != -1) { - hookDur = Math.max(0, hookDur - amount); - if (hookDur > 0) { - cfCompound.setInteger("hook_dur", hookDur); - } else { - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_dur"); - cfCompound.removeKey("hook_item"); - } - } - } - if (updateLore) updateNBTItemLore(nbtItem); - rod.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Increases the durability of a fishing hook by a specified amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The amount by which to increase the durability. - * @param updateLore Whether to update the lore of the fishing rod. - */ - public static void increaseHookDurability(ItemStack rod, int amount, boolean updateLore) { - if (rod == null || rod.getType() != Material.FISHING_ROD) - return; - NBTItem nbtItem = new NBTItem(rod); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("hook_dur")) { - int hookDur = cfCompound.getInteger("hook_dur"); - if (hookDur != -1) { - String id = cfCompound.getString("hook_id"); - HookSetting setting = CustomFishingPlugin.get().getHookManager().getHookSetting(id); - if (setting == null) { - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_dur"); - cfCompound.removeKey("hook_item"); - } else { - hookDur = Math.min(setting.getMaxDurability(), hookDur + amount); - cfCompound.setInteger("hook_dur", hookDur); - } - } - } - if (updateLore) updateNBTItemLore(nbtItem); - rod.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Sets the durability of a fishing hook to a specific amount and optionally updates its lore. - * - * @param rod The fishing rod ItemStack to modify. - * @param amount The new durability value to set. - * @param updateLore Whether to update the lore of the fishing rod. - */ - public static void setHookDurability(ItemStack rod, int amount, boolean updateLore) { - if (rod == null || rod.getType() != Material.FISHING_ROD) - return; - NBTItem nbtItem = new NBTItem(rod); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("hook_dur")) { - int hookDur = cfCompound.getInteger("hook_dur"); - if (hookDur != -1) { - String id = cfCompound.getString("hook_id"); - HookSetting setting = CustomFishingPlugin.get().getHookManager().getHookSetting(id); - if (setting == null) { - cfCompound.removeKey("hook_id"); - cfCompound.removeKey("hook_dur"); - cfCompound.removeKey("hook_item"); - } else { - hookDur = Math.min(setting.getMaxDurability(), amount); - cfCompound.setInteger("hook_dur", hookDur); - } - } - } - if (updateLore) updateNBTItemLore(nbtItem); - rod.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Decreases the durability of an item and updates its lore. - * - * @param itemStack The ItemStack to reduce durability for - * @param amount The amount by which to reduce durability - * @param updateLore Whether to update the lore after reducing durability - */ - public static void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean updateLore) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("max_dur")) { - int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY); - if (Math.random() > (double) 1 / (unBreakingLevel + 1)) { - return; - } - if (nbtItem.getByte("Unbreakable") == 1) { - return; - } - int max = cfCompound.getInteger("max_dur"); - int current = cfCompound.getInteger("cur_dur") - amount; - cfCompound.setInteger("cur_dur", current); - int damage = (int) (itemStack.getType().getMaxDurability() * (1 - ((double) current / max))); - nbtItem.setInteger("Damage", damage); - if (current > 0) { - if (updateLore) updateNBTItemLore(nbtItem); - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } else { - itemStack.setAmount(0); - } - } else { - ItemMeta previousMeta = itemStack.getItemMeta().clone(); - PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(player, itemStack, amount); - Bukkit.getPluginManager().callEvent(itemDamageEvent); - if (!itemStack.getItemMeta().equals(previousMeta) || itemDamageEvent.isCancelled()) { - return; - } - int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY); - if (Math.random() > (double) 1 / (unBreakingLevel + 1)) { - return; - } - if (nbtItem.getByte("Unbreakable") == 1) { - return; - } - int damage = nbtItem.getInteger("Damage") + amount; - if (damage > itemStack.getType().getMaxDurability()) { - itemStack.setAmount(0); - } else { - nbtItem.setInteger("Damage", damage); - if (updateLore) updateNBTItemLore(nbtItem); - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - } - } - - /** - * Increases the durability of an item and updates its lore. - * - * @param itemStack The ItemStack to increase durability for - * @param amount The amount by which to increase durability - * @param updateLore Whether to update the lore after increasing durability - */ - public static void increaseDurability(ItemStack itemStack, int amount, boolean updateLore) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - NBTItem nbtItem = new NBTItem(itemStack); - if (nbtItem.getByte("Unbreakable") == 1) { - return; - } - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("max_dur")) { - int max = cfCompound.getInteger("max_dur"); - int current = Math.min(max, cfCompound.getInteger("cur_dur") + amount); - cfCompound.setInteger("cur_dur", current); - int damage = (int) (itemStack.getType().getMaxDurability() * (1 - ((double) current / max))); - nbtItem.setInteger("Damage", damage); - if (updateLore) updateNBTItemLore(nbtItem); - } else { - int damage = Math.max(nbtItem.getInteger("Damage") - amount, 0); - nbtItem.setInteger("Damage", damage); - } - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Sets the durability of an item and updates its lore. - * - * @param itemStack The ItemStack to set durability for - * @param amount The new durability value - * @param updateLore Whether to update the lore after setting durability - */ - public static void setDurability(ItemStack itemStack, int amount, boolean updateLore) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - if (amount <= 0) { - itemStack.setAmount(0); - return; - } - NBTItem nbtItem = new NBTItem(itemStack); - if (nbtItem.getByte("Unbreakable") == 1) { - return; - } - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("max_dur")) { - int max = cfCompound.getInteger("max_dur"); - amount = Math.min(amount, max); - cfCompound.setInteger("cur_dur", amount); - int damage = (int) (itemStack.getType().getMaxDurability() * (1 - ((double) amount / max))); - nbtItem.setInteger("Damage", damage); - if (updateLore) updateNBTItemLore(nbtItem); - } else { - nbtItem.setInteger("Damage", itemStack.getType().getMaxDurability() - amount); - } - itemStack.setItemMeta(nbtItem.getItem().getItemMeta()); - } - - /** - * Retrieves the current durability of an item. - * - * @param itemStack The ItemStack to get durability from - * @return The current durability value - */ - public static Pair getCustomDurability(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return Pair.of(0, 0); - if (itemStack.getItemMeta() instanceof Damageable damageable && damageable.isUnbreakable()) - return Pair.of(-1, -1); - NBTItem nbtItem = new NBTItem(itemStack); - NBTCompound cfCompound = nbtItem.getCompound("CustomFishing"); - if (cfCompound != null && cfCompound.hasTag("max_dur")) { - return Pair.of(cfCompound.getInteger("max_dur"), cfCompound.getInteger("cur_dur")); - } else { - return Pair.of(0, 0); - } - } - - /** - * Gives a certain amount of an item to a player, handling stacking and item drops. - * - * @param player The player to give the item to - * @param itemStack The ItemStack to give - * @param amount The amount of items to give - * @return The actual amount of items given - */ - public static int giveItem(Player player, ItemStack itemStack, int amount) { - PlayerInventory inventory = player.getInventory(); - ItemMeta meta = itemStack.getItemMeta(); - int maxStackSize = itemStack.getMaxStackSize(); - - if (amount > maxStackSize * 100) { - LogUtils.warn("Detected too many items spawning. Lowering the amount to " + (maxStackSize * 100)); - amount = maxStackSize * 100; - } - - int actualAmount = amount; - - for (ItemStack other : inventory.getStorageContents()) { - if (other != null) { - if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { - if (other.getAmount() < maxStackSize) { - int delta = maxStackSize - other.getAmount(); - if (amount > delta) { - other.setAmount(maxStackSize); - amount -= delta; - } else { - other.setAmount(amount + other.getAmount()); - return actualAmount; - } - } - } - } - } - - if (amount > 0) { - for (ItemStack other : inventory.getStorageContents()) { - if (other == null) { - if (amount > maxStackSize) { - amount -= maxStackSize; - ItemStack cloned = itemStack.clone(); - cloned.setAmount(maxStackSize); - inventory.addItem(cloned); - } else { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(amount); - inventory.addItem(cloned); - return actualAmount; - } - } - } - } - - if (amount > 0) { - for (int i = 0; i < amount / maxStackSize; i++) { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(maxStackSize); - player.getWorld().dropItem(player.getLocation(), cloned); - } - int left = amount % maxStackSize; - if (left != 0) { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(left); - player.getWorld().dropItem(player.getLocation(), cloned); - } - } - - return actualAmount; - } - - public static String toBase64(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return ""; - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) { - dataOutput.writeObject(itemStack); - byte[] byteArr = outputStream.toByteArray(); - dataOutput.close(); - outputStream.close(); - return Base64Coder.encodeLines(byteArr); - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - public static ItemStack fromBase64(String base64) { - if (base64 == null || base64.equals("")) - return new ItemStack(Material.AIR); - ByteArrayInputStream inputStream; - try { - inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64)); - } catch (IllegalArgumentException e) { - return new ItemStack(Material.AIR); - } - ItemStack stack = null; - try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) { - stack = (ItemStack) dataInput.readObject(); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } - return stack; - } - - public static ItemStack removeOwner(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) return itemStack; - NBTItem nbtItem = new NBTItem(itemStack); - if (nbtItem.hasTag("owner")) { - nbtItem.removeKey("owner"); - return nbtItem.getItem(); - } - return itemStack; - } - - /** - * @return the amount of items that can't be put in the inventory - */ - public static int putLootsToBag(Inventory inventory, ItemStack itemStack, int amount) { - itemStack = removeOwner(itemStack.clone()); - ItemMeta meta = itemStack.getItemMeta(); - int maxStackSize = itemStack.getMaxStackSize(); - for (ItemStack other : inventory.getStorageContents()) { - if (other != null) { - if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { - if (other.getAmount() < maxStackSize) { - int delta = maxStackSize - other.getAmount(); - if (amount > delta) { - other.setAmount(maxStackSize); - amount -= delta; - } else { - other.setAmount(amount + other.getAmount()); - return 0; - } - } - } - } - } - - if (amount > 0) { - for (ItemStack other : inventory.getStorageContents()) { - if (other == null) { - if (amount > maxStackSize) { - amount -= maxStackSize; - ItemStack cloned = itemStack.clone(); - cloned.setAmount(maxStackSize); - inventory.addItem(cloned); - } else { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(amount); - inventory.addItem(cloned); - return 0; - } - } - } - } - - return amount; - } -} diff --git a/plugin/src/main/java/net/momirealms/customfishing/util/NBTUtils.java b/plugin/src/main/java/net/momirealms/customfishing/util/NBTUtils.java deleted file mode 100644 index 075dea65..00000000 --- a/plugin/src/main/java/net/momirealms/customfishing/util/NBTUtils.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customfishing.util; - -import de.tr7zw.changeme.nbtapi.NBTCompound; -import de.tr7zw.changeme.nbtapi.NBTListCompound; -import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT; -import de.tr7zw.changeme.nbtapi.utils.MinecraftVersion; -import de.tr7zw.changeme.nbtapi.utils.VersionChecker; -import net.momirealms.customfishing.api.CustomFishingPlugin; -import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl; -import org.bukkit.Bukkit; -import org.bukkit.configuration.MemorySection; -import org.bukkit.entity.Player; - -import java.lang.reflect.Field; -import java.util.*; - -/** - * Utility class for working with NBT (Named Binary Tag) data. - */ -public class NBTUtils { - - private NBTUtils() {} - - public static void disableNBTAPILogs() { - MinecraftVersion.disableBStats(); - MinecraftVersion.disableUpdateCheck(); - VersionChecker.hideOk = true; - try { - Field field = MinecraftVersion.class.getDeclaredField("version"); - field.setAccessible(true); - MinecraftVersion minecraftVersion; - try { - String serverVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; - minecraftVersion = MinecraftVersion.valueOf(serverVersion.replace("v", "MC")); - } catch (Exception ex) { - minecraftVersion = VERSION_TO_REVISION.getOrDefault(Bukkit.getServer().getBukkitVersion().split("-")[0], - MinecraftVersion.UNKNOWN); - } - field.set(MinecraftVersion.class, minecraftVersion); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - boolean hasGsonSupport; - try { - Class.forName("com.google.gson.Gson"); - hasGsonSupport = true; - } catch (Exception ex) { - hasGsonSupport = false; - } - try { - Field field= MinecraftVersion.class.getDeclaredField("hasGsonSupport"); - field.setAccessible(true); - field.set(Boolean.class, hasGsonSupport); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - private static final Map VERSION_TO_REVISION = new HashMap<>() { - { - this.put("1.20", MinecraftVersion.MC1_20_R1); - this.put("1.20.1", MinecraftVersion.MC1_20_R1); - this.put("1.20.2", MinecraftVersion.MC1_20_R2); - this.put("1.20.3", MinecraftVersion.MC1_20_R3); - this.put("1.20.4", MinecraftVersion.MC1_20_R3); - this.put("1.20.5", MinecraftVersion.MC1_20_R4); - this.put("1.20.6", MinecraftVersion.MC1_20_R4); - } - }; - - - /** - * Inner class representing a stack element used during NBT data conversion. - */ - public static class StackElement { - final Map currentMap; - final NBTCompound currentNbtCompound; - - StackElement(Map map, NBTCompound nbtCompound) { - this.currentMap = map; - this.currentNbtCompound = nbtCompound; - } - } - - /** - * Converts data from a Bukkit YAML configuration to NBT tags. - * - * @param nbtCompound The target NBT compound - * @param map The source map from Bukkit YAML - */ - @SuppressWarnings("unchecked") - public static void setTagsFromBukkitYAML(Player player, Map placeholders, NBTCompound nbtCompound, Map map) { - - Deque stack = new ArrayDeque<>(); - stack.push(new StackElement(map, nbtCompound)); - - while (!stack.isEmpty()) { - StackElement currentElement = stack.pop(); - Map currentMap = currentElement.currentMap; - NBTCompound currentNbtCompound = currentElement.currentNbtCompound; - - for (Map.Entry entry : currentMap.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - - if (value instanceof MemorySection memorySection) { - stack.push(new StackElement(memorySection.getValues(false), currentNbtCompound.addCompound(key))); - } else if (value instanceof List list) { - for (Object o : list) { - if (o instanceof String stringValue) { - setListValue(player, placeholders, key, stringValue, currentNbtCompound); - } else if (o instanceof Map mapValue) { - NBTListCompound nbtListCompound = currentNbtCompound.getCompoundList(key).addCompound(); - stack.push(new StackElement((Map) mapValue, nbtListCompound)); - } - } - } else if (value instanceof String stringValue) { - setSingleValue(player, placeholders, key, stringValue, currentNbtCompound); - } - } - } - } - - // Private helper method - private static void setListValue(Player player, Map placeholders, String key, String value, NBTCompound nbtCompound) { - String[] parts = getTypeAndData(value); - String type = parts[0]; - String data = getParsedData(player, placeholders, parts[1]); - switch (type) { - case "String" -> nbtCompound.getStringList(key).add(data); - case "UUID" -> nbtCompound.getUUIDList(key).add(UUID.fromString(data)); - case "Double" -> nbtCompound.getDoubleList(key).add(Double.parseDouble(data)); - case "Long" -> nbtCompound.getLongList(key).add(Long.parseLong(data)); - case "Float" -> nbtCompound.getFloatList(key).add(Float.parseFloat(data)); - case "Int" -> nbtCompound.getIntegerList(key).add(Integer.parseInt(data)); - case "IntArray" -> { - String[] split = data.replace("[", "").replace("]", "").replaceAll("\\s", "").split(","); - int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); - nbtCompound.getIntArrayList(key).add(array); - } - default -> throw new IllegalArgumentException("Invalid value type: " + type); - } - } - - // Private helper method - private static void setSingleValue(Player player, Map placeholders, String key, String value, NBTCompound nbtCompound) { - String[] parts = getTypeAndData(value); - String type = parts[0]; - String data = getParsedData(player, placeholders, parts[1]); - switch (type) { - case "Int" -> nbtCompound.setInteger(key, Integer.parseInt(data)); - case "String" -> nbtCompound.setString(key, data); - case "Long" -> nbtCompound.setLong(key, Long.parseLong(data)); - case "Float" -> nbtCompound.setFloat(key, Float.parseFloat(data)); - case "Double" -> nbtCompound.setDouble(key, Double.parseDouble(data)); - case "Short" -> nbtCompound.setShort(key, Short.parseShort(data)); - case "Boolean" -> nbtCompound.setBoolean(key, Boolean.parseBoolean(data)); - case "UUID" -> nbtCompound.setUUID(key, UUID.nameUUIDFromBytes(data.getBytes())); - case "Byte" -> nbtCompound.setByte(key, Byte.parseByte(data)); - case "ByteArray" -> { - String[] split = splitValue(value); - byte[] bytes = new byte[split.length]; - for (int i = 0; i < split.length; i++){ - bytes[i] = Byte.parseByte(split[i]); - } - nbtCompound.setByteArray(key, bytes); - } - case "IntArray" -> { - String[] split = splitValue(value); - int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray(); - nbtCompound.setIntArray(key, array); - } - default -> throw new IllegalArgumentException("Invalid value type: " + type); - } - } - - public static String getParsedData(Player player, Map placeholders, String data) { - if (data.length() >= 3) - switch (data.substring(0,3)) { - case "-P:" -> data = PlaceholderManagerImpl.getInstance().parse(player, data.substring(3), placeholders); - case "-E:" -> { - double value = ConfigUtils.getExpressionValue(player, data.substring(3), placeholders); - if (value % 1 == 0) { - data = Long.toString((long) value); - } else { - data = Double.toString(value); - } - } - } - return data; - } - - /** - * Converts an NBT compound to a map of key-value pairs. - * - * @param nbtCompound The source NBT compound - * @return A map representing the NBT data - */ - public static Map compoundToMap(ReadWriteNBT nbtCompound){ - Map map = new HashMap<>(); - for (String key : nbtCompound.getKeys()) { - switch (nbtCompound.getType(key)){ - case NBTTagByte -> map.put(key, "(Byte) " + nbtCompound.getByte(key)); - case NBTTagInt -> map.put(key, "(Int) " + nbtCompound.getInteger(key)); - case NBTTagDouble -> map.put(key, "(Double) " + nbtCompound.getDouble(key)); - case NBTTagLong -> map.put(key, "(Long) " + nbtCompound.getLong(key)); - case NBTTagFloat -> map.put(key, "(Float) " + nbtCompound.getFloat(key)); - case NBTTagShort -> map.put(key, "(Short) " + nbtCompound.getShort(key)); - case NBTTagString -> map.put(key, "(String) " + nbtCompound.getString(key)); - case NBTTagByteArray -> map.put(key, "(ByteArray) " + Arrays.toString(nbtCompound.getByteArray(key))); - case NBTTagIntArray -> map.put(key, "(IntArray) " + Arrays.toString(nbtCompound.getIntArray(key))); - case NBTTagCompound -> { - Map map1 = compoundToMap(Objects.requireNonNull(nbtCompound.getCompound(key))); - if (!map1.isEmpty()) map.put(key, map1); - } - case NBTTagList -> { - List list = new ArrayList<>(); - switch (Objects.requireNonNull(nbtCompound.getListType(key))) { - case NBTTagCompound -> nbtCompound.getCompoundList(key).forEach(a -> list.add(compoundToMap(a))); - case NBTTagInt -> nbtCompound.getIntegerList(key).forEach(a -> list.add("(Int) " + a)); - case NBTTagDouble -> nbtCompound.getDoubleList(key).forEach(a -> list.add("(Double) " + a)); - case NBTTagString -> nbtCompound.getStringList(key).forEach(a -> list.add("(String) " + a)); - case NBTTagFloat -> nbtCompound.getFloatList(key).forEach(a -> list.add("(Float) " + a)); - case NBTTagLong -> nbtCompound.getLongList(key).forEach(a -> list.add("(Long) " + a)); - case NBTTagIntArray -> nbtCompound.getIntArrayList(key).forEach(a -> list.add("(IntArray) " + Arrays.toString(a))); - } - if (!list.isEmpty()) map.put(key, list); - } - } - } - return map; - } - - /** - * Splits a value into type and data components. - * - * @param str The input value string - * @return An array containing type and data strings - */ - public static String[] getTypeAndData(String str) { - String[] parts = str.split("\\s+", 2); - if (parts.length == 1) { - return new String[]{"String", str}; - } - if (parts.length != 2) { - throw new IllegalArgumentException("Invalid value format: " + str); - } - String type = parts[0].substring(1, parts[0].length() - 1); - String data = parts[1]; - return new String[]{type, data}; - } - - /** - * Splits a value containing arrays into individual elements. - * - * @param value The input value containing arrays - * @return An array of individual elements - */ - public static String[] splitValue(String value) { - return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']')) - .replaceAll("\\s", "") - .split(","); - } -} \ No newline at end of file diff --git a/plugin/src/main/resources/market.yml b/plugin/src/main/resources/market.yml deleted file mode 100644 index e53f5380..00000000 --- a/plugin/src/main/resources/market.yml +++ /dev/null @@ -1,158 +0,0 @@ -enable: true - -# Container title -title: 'Fish Market' - -limitation: - enable: true - # Support expression and placeholders - earnings: '10000' - -# Market menu layout -layout: - - 'AAAAAAAAA' - - 'AIIIIIIIA' - - 'AIIIIIIIA' - - 'AIIIIIIIA' - - 'AAAABAAAA' - -# Price formula (For CustomFishing loots) -price-formula: '{BASE} + {BONUS} * {SIZE}' - -# Item price (For vanilla items & other plugin items that have CustomModelData) -item-price: - # Vanilla Items - COD: 10 - PUFFERFISH: 10 - SALMON: 10 - TROPICAL_FISH: 10 - # PAPER (CustomModelData: 999) - PAPER:999: 5 - -# Slots to put items in -item-slot: - symbol: 'I' - allow-items-with-no-price: true - -# This is an icon that allows players to sell all the fish from their inventory and fishingbag -# You can enable it by putting the symbol into layout -sell-all-icons: - symbol: 'S' - # Should the fish in fishing bag be sold - fishingbag: true - allow-icon: - material: IRON_BLOCK - display: - name: '<#00CED1>Ship the fish' - lore: - - 'You will get {money_formatted}$ by selling the fish from inventory and bag' - action: - sound_action: - type: sound - value: - key: 'minecraft:block.amethyst_block.place' - source: 'player' - volume: 1 - pitch: 1 - message_action: - type: message - value: 'You earned {money_formatted}$ by selling the fish! You can still get {rest_formatted}$ from market today' - command_action: - type: command - value: 'money give {player} {money}' -# Requires Vault and any economy plugin -# money_action: -# type: give-money -# value: '{money}' - deny-icon: - material: REDSTONE_BLOCK - display: - name: 'Denied trade' - lore: - - 'Nothing to sell!' - action: - sound_action: - type: sound - value: - key: 'minecraft:entity.villager.no' - source: 'player' - volume: 1 - pitch: 1 - limit-icon: - material: REDSTONE_BLOCK - display: - name: 'Denied trade' - lore: - - 'The worth of items exceeds the money that can be earned for the rest of today!' - action: - sound_action: - type: sound - value: - key: 'minecraft:block.anvil.land' - source: 'player' - volume: 1 - pitch: 1 - -# Sell icon -sell-icons: - symbol: 'B' - allow-icon: - material: IRON_BLOCK - display: - name: '<#00CED1>Ship the fish' - lore: - - 'You will get {money_formatted}$ by selling the fish' - action: - sound_action: - type: sound - value: - key: 'minecraft:block.amethyst_block.place' - source: 'player' - volume: 1 - pitch: 1 - message_action: - type: message - value: 'You earned {money_formatted}$ by selling the fish! You can still get {rest_formatted}$ from market today' - command_action: - type: command - value: 'money give {player} {money}' -# Requires Vault and any economy plugin -# money_action: -# type: give-money -# value: '{money}' - deny-icon: - material: REDSTONE_BLOCK - display: - name: 'Denied trade' - lore: - - 'Nothing to sell!' - action: - sound_action: - type: sound - value: - key: 'minecraft:entity.villager.no' - source: 'player' - volume: 1 - pitch: 1 - limit-icon: - material: REDSTONE_BLOCK - display: - name: 'Denied trade' - lore: - - 'The worth of items exceeds the money that can be earned for the rest of today!' - action: - sound_action: - type: sound - value: - key: 'minecraft:block.anvil.land' - source: 'player' - volume: 1 - pitch: 1 - -# Decorative icons -decorative-icons: - glass-pane: - symbol: 'A' - material: BLACK_STAINED_GLASS_PANE - display: - name: ' ' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/en.yml b/plugin/src/main/resources/messages/en.yml deleted file mode 100644 index 82ef3fa2..00000000 --- a/plugin/src/main/resources/messages/en.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Don't change this -config-version: '31' - -messages: - prefix: '[CustomFishing] ' - reload: 'Reloaded. Took {time}ms.' - item-not-exist: 'Item not found.' - give-item: 'Successfully given player {player} {amount}x {item}.' - get-item: 'Successfully got {amount}x {item}.' - possible-loots: 'Possible loots here: ' - split-char: ', ' - competition-not-exist: 'Competition {id} does not exist.' - no-competition-ongoing: "There's no competition ongoing." - stop-competition: 'Stopped the current competition.' - end-competition: 'Ended the current competition.' - no-score: 'No Score' - no-player: 'No Player' - no-rank: 'No Rank' - goal-catch-amount: 'Fish count caught' - goal-max-size: 'Largest fish caught' - goal-total-size: 'Total length of fish caught' - goal-total-score: 'Cumulative score of fish caught' - unsafe-modification: "Cannot modify a player's fishing bag if they're active on another linked server." - never-played: "The player hasn't joined the server before. Can't modify a nonexistent player's fishing bag." - data-not-loaded: "Data hasn't loaded. Please re-enter the server. If issues persist, reach out to the server admin." - open-market-gui: "Successfully opened the market gui for {player}" - open-fishing-bag: "Successfully opened the fishing bag for {player}" - format-day: 'd' - format-hour: 'h' - format-minute: 'm' - format-second: 's' - -gui: - search: "Search" - select-file: 'Select file' - select-item: "Select item" - dupe-invalid-key: "● Duplicated or invalid key" - new-value: "New value: " - temp-new-key: 'New key' - set-new-key: "Set new key" - edit-key: 'Edit {0}' - delete-property: "<#00CED1>● Delete property" - click-confirm: "<#00FF7F> -> Click to confirm" - invalid-number: "● Invalid number" - illegal-format: "● Illegal format" - scroll-up: '● Scroll up' - scroll-down: '● Scroll down' - cannot-scroll-up: "You've reached the top" - cannot-scroll-down: "You can't scroll further down" - next-page: '● Next Page' - goto-next-page: 'Go to page {0} / {1}' - cannot-goto-next-page: 'There are no more pages' - previous-page: '● Previous page' - goto-previous-page: 'Go to page {0} / {1}' - cannot-goto-previous-page: "You can't go further back" - back-to-parent-page: "<#FF8C00>Back to parent page" - back-to-parent-folder: "<#FF8C00>Back to parent folder" - current-value: "Current value: " - click-to-toggle: "<#00FF7F> -> Click to toggle" - left-click-edit: "<#00FF7F> -> Left click to edit" - right-click-reset: "<#FF6347> -> Right click to reset" - right-click-delete: "<#FF6347> -> Right click to delete" - right-click-cancel: "<#00CED1> -> Right click to cancel" - loot-show-in-finder: "<#5F9EA0>● Show In Fish Finder" - loot-score: "<#FF1493>● Score" - loot-nick: "<#00FF00>● Nick" - loot-instant-game: "<#7B68EE>● Instant Game" - loot-disable-statistics: "<#CD853F>● Disable Statistics" - loot-disable-game: "<#8B4513>● Disable Game" - item-amount: "<#1E90FF>● Amount" - item-custom-model-data: "<#FFC0CB>● Custom Model Data" - item-display-name: "<#FAFAD2>● Display Name" - item-custom-durability: "<#1E90FF>● Custom Durability" - item-enchantment: "<#8A2BE2>● Enchantment" - item-head64: "<#2E8B57>● Head64" - item-item-flag: "<#E6E6FA>● Item Flag" - item-lore: "<#FA8072>● Lore" - item-material: "<#FF00FF>● Material" - item-nbt: "<#FA8072>● NBT" - item-prevent-grab: "<#FF4500>● Prevent Grabbing" - item-price: "<#FFD700>● Price" - item-price-base: " - base: " - item-price-bonus: " - bonus: " - item-random-durability: "<#FFFF00>● Random Durability" - item-size: "<#FFF0F5>● Size" - item-stackable: "<#9370DB>● Stackable" - item-stored-enchantment: "<#9370DB>● Stored Enchantment" - item-tag: "<#2E8B57>● Tag" - item-unbreakable: "<#C0C0C0>● Unbreakable" - page-amount-title: "Edit Amount" - page-model-data-title: "Edit CustomModelData" - page-display-name-title: "Edit display name" - page-new-display-name: "New name" - page-custom-durability-title: "Edit custom durability" - page-stored-enchantment-title: "Edit stored enchantment" - page-enchantment-title: "Edit enchantment" - page-select-one-enchantment: "Select one enchantment" - page-add-new-enchantment: "[+] Add a new enchantment" - page-item-flag-title: "Edit item flag" - page-lore-title: "Edit lore" - page-add-new-lore: "[+] Add a new line" - page-select-one-lore: "Select one line" - page-material-title: "Edit Material" - page-nbt-compound-key-title: "Edit compound key" - page-nbt-list-key-title: "Edit list key" - page-nbt-key-title: "Edit key" - page-nbt-invalid-key: "Invaild key" - page-nbt-add-new-compound: "[+] Add a new compound" - page-nbt-add-new-list: "[+] Add a new list" - page-nbt-add-new-value: "[+] Add a new value" - page-add-new-key: "[+] Add a new key" - page-nbt-preview: "● NBT Preview" - page-nbt-back-to-compound: "Back to parent compound" - page-nbt-set-value-title: "Set value" - page-nbt-edit-title: "Edit NBT" - page-nick-title: "Edit nick" - page-new-nick: "New nick" - page-price-title: "Edit price" - page-base-price: "Base" - page-base-bonus: "Bonus" - page-score-title: "Edit score" - page-size-title: "Edit size" - page-size-min: "Minimum" - page-size-max: "Maximum" - page-size-max-no-less-min: "● Max must be no less than min" diff --git a/plugin/src/main/resources/messages/es.yml b/plugin/src/main/resources/messages/es.yml deleted file mode 100644 index 11405083..00000000 --- a/plugin/src/main/resources/messages/es.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Don't change this -config-version: '31' - -messages: - prefix: '[CustomFishing] ' - reload: 'Recargado! Tardó {time}ms.' - item-not-exist: 'Item no encontrado.' - give-item: 'Se ha entregado {amount}x {item} al jugador {player}.' - get-item: 'Se ha recibido {amount}x {item}.' - possible-loots: 'Posibles recompensas: ' - split-char: ', ' - competition-not-exist: 'La Competición {id} no existe.' - no-competition-ongoing: "No hay una competencia activa." - stop-competition: 'Se ha detenido la competición.' - end-competition: 'Ha finalizado la competición.' - no-score: 'Sin puntaje' - no-player: 'Sin jugador' - no-rank: 'Sin rango' - goal-catch-amount: 'Peces atrapados' - goal-max-size: 'Pez más grande capturado' - goal-total-size: 'Longitud total de peces capturados' - goal-total-score: 'Puntaje acumulado por peces capturados' - unsafe-modification: "No se puede modificar la bolsa de pesca de un jugador si sigue activo en otro servidor vinculado." - never-played: "Este jugador nunca se ha unido al servidor. No puedes modificar la bolsa de pesca de un jugador inexistente." - data-not-loaded: "No se pudo cargar la información. Por favor, vuelve a ingresar al servidor. Si el problema persiste, contacta a un administrador." - open-market-gui: "Se ha abierto la interfaz para {player}" - open-fishing-bag: "Se ha abierto la bolsa de pesca de {player}" - format-day: 'd' - format-hour: 'h' - format-minute: 'm' - format-second: 's' - -gui: - search: "Buscar" - select-file: 'Seleccionar archivo' - select-item: "Seleccionar item" - dupe-invalid-key: "● ID inválida o duplicada" - new-value: "Nuevo valor: " - temp-new-key: 'Nueva ID' - set-new-key: "Definir nueva ID" - edit-key: 'Editar {0}' - delete-property: "<#00CED1>● Eliminar propiedad" - click-confirm: "<#00FF7F> -> Click para confirmar" - invalid-number: "● Número inválido" - illegal-format: "● Formato inválido" - scroll-up: '● Arriba' - scroll-down: '● Abajo' - cannot-scroll-up: "Estás en el límite superior" - cannot-scroll-down: "Estás en el límite inferior" - next-page: '● Siguiente Página' - goto-next-page: 'Ir a la página {0} / {1}' - cannot-goto-next-page: 'No hay más páginas' - previous-page: '● Página anterior' - goto-previous-page: 'Ir a la página {0} / {1}' - cannot-goto-previous-page: "No hay más páginas atrás" - back-to-parent-page: "<#FF8C00>Volver al menú anterior" - back-to-parent-folder: "<#FF8C00>Volver a la carpeta anterior" - current-value: "Valor actual: " - click-to-toggle: "<#00FF7F> -> Click para cambiar" - left-click-edit: "<#00FF7F> -> Click izquierdo para editar" - right-click-reset: "<#FF6347> -> Click derecho para restablecer" - right-click-delete: "<#FF6347> -> Click derecho para eliminar" - right-click-cancel: "<#00CED1> -> Click derecho para cancelar" - loot-show-in-finder: "<#5F9EA0>● Mostar en el rastreador de peces" - loot-score: "<#FF1493>● Puntaje" - loot-nick: "<#00FF00>● Apodo" - loot-instant-game: "<#7B68EE>● Juego Instantáneo" - loot-disable-statistics: "<#CD853F>● Deshabilitar Estadísticas" - loot-disable-game: "<#8B4513>● Deshabilitar Juego" - item-amount: "<#1E90FF>● Cantidad" - item-custom-model-data: "<#FFC0CB>● Custom Model Data" - item-display-name: "<#FAFAD2>● Nombre a mostrar" - item-custom-durability: "<#1E90FF>● Durabilidad" - item-enchantment: "<#8A2BE2>● Encantamiento" - item-head64: "<#2E8B57>● Head64" - item-item-flag: "<#E6E6FA>● Item Flag" - item-lore: "<#FA8072>● Descripción" - item-material: "<#FF00FF>● Material" - item-nbt: "<#FA8072>● NBT" - item-prevent-grab: "<#FF4500>● Evitar recoger item" - item-price: "<#FFD700>● Precio" - item-price-base: " - base: " - item-price-bonus: " - bonus: " - item-random-durability: "<#FFFF00>● Durabilidad Aleatoria" - item-size: "<#FFF0F5>● Tamaño" - item-stackable: "<#9370DB>● Acumulable" - item-stored-enchantment: "<#9370DB>● Encantamiento Almacenado" - item-tag: "<#2E8B57>● Etiqueta" - item-unbreakable: "<#C0C0C0>● Irrompible" - page-amount-title: "Editar Cantidad" - page-model-data-title: "Editar CustomModelData" - page-display-name-title: "Editar nombre a mostrar" - page-new-display-name: "Nuevo nombre" - page-custom-durability-title: "Edit durabilidad" - page-stored-enchantment-title: "Edit encantamiento almacenado" - page-enchantment-title: "Editar enchantmentamiento" - page-select-one-enchantment: "Seleccionar un encantamiento" - page-add-new-enchantment: "[+] Añadir un encantamiento" - page-item-flag-title: "Editar item flag" - page-lore-title: "Editar descripción" - page-add-new-lore: "[+] Añadir nueva línea" - page-select-one-lore: "Seleccionar una línea" - page-material-title: "Editar Material" - page-nbt-compound-key-title: "Editar ID de NBT compuesta" - page-nbt-list-key-title: "Editar lista de IDs" - page-nbt-key-title: "Editar ID" - page-nbt-invalid-key: "ID inválida" - page-nbt-add-new-compound: "[+] Añadir nueva NBT compuesta" - page-nbt-add-new-list: "[+] Añadir nueva lista" - page-nbt-add-new-value: "[+] Añadir nuevo valor" - page-add-new-key: "[+] Añadir nueva ID" - page-nbt-preview: "● Vista previa de NBT" - page-nbt-back-to-compound: "Volver al compuesto padre" - page-nbt-set-value-title: "Definir nuevo valor" - page-nbt-edit-title: "Editar NBT" - page-nick-title: "Editar apodo" - page-new-nick: "Nuevo apodo" - page-price-title: "Editar precio" - page-base-price: "Base" - page-base-bonus: "Bonus" - page-score-title: "Editar puntuación" - page-size-title: "Editar tamaño" - page-size-min: "Mínimo" - page-size-max: "Máximo" - page-size-max-no-less-min: "● El valor máximo no puede ser inferior al mínimo" diff --git a/plugin/src/main/resources/messages/fr.yml b/plugin/src/main/resources/messages/fr.yml deleted file mode 100644 index abf04c67..00000000 --- a/plugin/src/main/resources/messages/fr.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Don't change this -config-version: '30' - -messages: - prefix: '[CustomFishing] ' - reload: 'Rechargé. A pris {time}ms.' - item-not-exist: 'Objet non trouvé.' - give-item: 'Joueur {player} a reçu avec succès {amount}x {item}.' - get-item: 'A obtenu avec succès {amount}x {item}.' - possible-loots: 'Butins possibles ici : ' - split-char: ', ' - competition-not-exist: "La compétition {id} n'existe pas." - no-competition-ongoing: 'Aucune compétition en cours.' - stop-competition: 'Compétition actuelle arrêtée.' - end-competition: 'Compétition actuelle terminée.' - no-score: 'Pas de score' - no-player: 'Pas de joueur' - no-rank: 'Pas de classement' - goal-catch-amount: 'Nombre de poissons attrapés' - goal-max-size: 'Plus gros poisson attrapé' - goal-total-size: 'Longueur totale des poissons attrapés' - goal-total-score: 'Score cumulatif des poissons attrapés' - unsafe-modification: "Impossible de modifier le sac de pêche d'un joueur s'il est actif sur un autre serveur lié." - never-played: "Le joueur n'a jamais rejoint le serveur auparavant. Impossible de modifier le sac de pêche d'un joueur inexistant." - data-not-loaded: "Les données n'ont pas été chargées. Veuillez réessayer de rejoindre le serveur. Si les problèmes persistent, contactez l'administrateur du serveur." - open-market-gui: "L'interface du marché a été ouverte avec succès pour {player}." - open-fishing-bag: "Le sac de pêche de {player} a été ouvert avec succès." - format-day: 'j' - format-hour: 'h' - format-minute: 'm' - format-second: 's' - -gui: - search: "Rechercher" - select-file: "Sélectionner un fichier" - select-item: "Sélectionner un élément" - dupe-invalid-key: "● Clé en double ou invalide" - new-value: "Nouvelle valeur : " - temp-new-key: "Nouvelle clé" - set-new-key: "Définir une nouvelle clé" - edit-key: "Modifier {0}" - delete-property: "<#00CED1>● Supprimer la propriété" - click-confirm: "<#00FF7F> -> Cliquez pour confirmer" - invalid-number: "● Nombre invalide" - illegal-format: "● Format illégal" - scroll-up: "● Faire défiler vers le haut" - scroll-down: "● Faire défiler vers le bas" - cannot-scroll-up: "Vous avez atteint le haut" - cannot-scroll-down: "Vous ne pouvez pas faire défiler plus bas" - next-page: "● Page suivante" - goto-next-page: "Aller à la page {0} / {1}" - cannot-goto-next-page: "Il n'y a plus de pages" - previous-page: "● Page précédente" - goto-previous-page: "Aller à la page {0} / {1}" - cannot-goto-previous-page: "Vous ne pouvez pas revenir en arrière" - back-to-parent-page: "<#FF8C00>Retour à la page parente" - back-to-parent-folder: "<#FF8C00>Retour au dossier parent" - current-value: "Valeur actuelle : " - click-to-toggle: "<#00FF7F> -> Cliquez pour basculer" - left-click-edit: "<#00FF7F> -> Cliquez gauche pour modifier" - right-click-reset: "<#FF6347> -> Cliquez droit pour réinitialiser" - right-click-delete: "<#FF6347> -> Cliquez droit pour supprimer" - right-click-cancel: "<#00CED1> -> Cliquez droit pour annuler" - loot-show-in-finder: "<#5F9EA0>● Afficher dans le repère de poisson" - loot-score: "<#FF1493>● Score" - loot-nick: "<#00FF00>● Surnom" - loot-instant-game: "<#7B68EE>● Jeu instantané" - loot-disable-statistics: "<#CD853F>● Désactiver les statistiques" - loot-disable-game: "<#8B4513>● Désactiver le jeu" - item-amount: "<#1E90FF>● Quantité" - item-custom-model-data: "<#FFC0CB>● Données de modèle personnalisées" - item-display-name: "<#FAFAD2>● Nom affiché" - item-custom-durability: "<#1E90FF>● Durabilité personnalisée" - item-enchantment: "<#8A2BE2>● Enchantement" - item-head64: "<#2E8B57>● Tête64" - item-item-flag: "<#E6E6FA>● Drapeau d'élément" - item-lore: "<#FA8072>● Légende" - item-material: "<#FF00FF>● Matériau" - item-nbt: "<#FA8072>● NBT" - item-prevent-grab: "<#FF4500>● Empêcher la saisie" - item-price: "<#FFD700>● Prix" - item-price-base: " - de base : " - item-price-bonus: " - bonus : " - item-random-durability: "<#FFFF00>● Durabilité aléatoire" - item-size: "<#FFF0F5>● Taille" - item-stackable: "<#9370DB>● Empilable" - item-stored-enchantment: "<#9370DB>● Enchantement stocké" - item-tag: "<#2E8B57>● Étiquette" - item-unbreakable: "<#C0C0C0>● Incassable" - page-amount-title: "Modifier la quantité" - page-model-data-title: "Modifier CustomModelData" - page-display-name-title: "Modifier le nom affiché" - page-new-display-name: "Nouveau nom" - page-custom-durability-title: "Modifier la durabilité personnalisée" - page-stored-enchantment-title: "Modifier l'enchantement stocké" - page-enchantment-title: "Modifier l'enchantement" - page-select-one-enchantment: "Sélectionner un enchantement" - page-add-new-enchantment: "[+] Ajouter un nouvel enchantement" - page-item-flag-title: "Modifier le drapeau d'élément" - page-lore-title: "Modifier la légende" - page-add-new-lore: "[+] Ajouter une nouvelle ligne" - page-select-one-lore: "Sélectionner une ligne" - page-material-title: "Modifier le matériau" - page-nbt-compound-key-title: "Modifier la clé composée" - page-nbt-list-key-title: "Modifier la clé de liste" - page-nbt-key-title: "Modifier la clé" - page-nbt-invalid-key: "Clé invalide" - page-nbt-add-new-compound: "[+] Ajouter une nouvelle composante" - page-nbt-add-new-list: "[+] Ajouter une nouvelle liste" - page-nbt-add-new-value: "[+] Ajouter une nouvelle valeur" - page-add-new-key: "[+] Ajouter une nouvelle clé" - page-nbt-preview: "● Aperçu NBT" - page-nbt-back-to-compound: "Retour à la composante parente" - page-nbt-set-value-title: "Définir la valeur" - page-nbt-edit-title: "Modifier NBT" - page-nick-title: "Modifier le surnom" - page-new-nick: "Nouveau surnom" - page-price-title: "Modifier le prix" - page-base-price: "De base" - page-base-bonus: "Bonus" - page-score-title: "Modifier le score" - page-size-title: "Modifier la taille" - page-size-min: "Minimum" - page-size-max: "Maximum" - page-size-max-no-less-min: "Le maximum ne doit pas être inférieur au minimum" diff --git a/plugin/src/main/resources/messages/hu.yml b/plugin/src/main/resources/messages/hu.yml deleted file mode 100644 index 49ac3b02..00000000 --- a/plugin/src/main/resources/messages/hu.yml +++ /dev/null @@ -1,126 +0,0 @@ -# Don't change this -config-version: '30' - -messages: - prefix: '[CustomFishing] ' - reload: 'Újratöltve. Időtartam: {time}ms.' - item-not-exist: 'Tétel nem található.' - give-item: 'Sikeresen adva {player} játékosnak {amount}x {item}.' - get-item: 'Sikeresen megszerezve {amount}x {item}.' - possible-loots: 'Lehetséges kapások itt: ' - split-char: ', ' - competition-not-exist: 'A(z) {id} verseny nem létezik.' - no-competition-ongoing: "Nincs folyamatban lévő verseny." - stop-competition: 'Leállítottad a jelenlegi versenyt.' - end-competition: 'Befejezted a jelenlegi versenyt.' - no-score: 'Nincs pontszám' - no-player: 'Nincs játékos' - no-rank: 'Nincs rang' - goal-catch-amount: 'Kifogott halak száma' - goal-max-size: 'Legnagyobb kifogott hal' - goal-total-size: 'Összes kifogott hal hossza' - goal-total-score: 'Kifogott halak összpontszáma' - unsafe-modification: "Nem lehet módosítani egy játékos horgász táskáját, ha aktív egy másik szerveren." - never-played: "A játékos még nem csatlakozott a szerverhez. Nem lehet módosítani egy nem létező játékos horgász táskáját." - data-not-loaded: "Az adatok nem lettek betöltve. Kérjük, csatlakozz újra a szerverhez. Ha a probléma továbbra is fennáll, fordulj a szerveradminisztrátorhoz." - open-market-gui: "A piac menü sikeresen meg lett nyitva {player} részére" - open-fishing-bag: "A horgász táska sikeresen meg lett nyitva {player} részére" - format-day: 'n' - format-hour: 'ó' - format-minute: 'p' - format-second: 'mp' - -# We're looking for a translator :D -gui: - search: "Search" - select-file: 'Select file' - select-item: "Select item" - dupe-invalid-key: "● Duplicated or invalid key" - new-value: "New value: " - temp-new-key: 'New key' - set-new-key: "Set new key" - edit-key: 'Edit {0}' - delete-property: "<#00CED1>● Delete property" - click-confirm: "<#00FF7F> -> Click to confirm" - invalid-number: "● Invalid number" - illegal-format: "● Illegal format" - scroll-up: '● Scroll up' - scroll-down: '● Scroll down' - cannot-scroll-up: "You've reached the top" - cannot-scroll-down: "You can't scroll further down" - next-page: '● Next Page' - goto-next-page: 'Go to page {0} / {1}' - cannot-goto-next-page: 'There are no more pages' - previous-page: '● Previous page' - goto-previous-page: 'Go to page {0} / {1}' - cannot-goto-previous-page: "You can't go further back" - back-to-parent-page: "<#FF8C00>Back to parent page" - back-to-parent-folder: "<#FF8C00>Back to parent folder" - current-value: "Current value: " - click-to-toggle: "<#00FF7F> -> Click to toggle" - left-click-edit: "<#00FF7F> -> Left click to edit" - right-click-reset: "<#FF6347> -> Right click to reset" - right-click-delete: "<#FF6347> -> Right click to delete" - right-click-cancel: "<#00CED1> -> Right click to cancel" - loot-show-in-finder: "<#5F9EA0>● Show In Fish Finder" - loot-score: "<#FF1493>● Score" - loot-nick: "<#00FF00>● Nick" - loot-instant-game: "<#7B68EE>● Instant Game" - loot-disable-statistics: "<#CD853F>● Disable Statistics" - loot-disable-game: "<#8B4513>● Disable Game" - item-amount: "<#1E90FF>● Amount" - item-custom-model-data: "<#FFC0CB>● Custom Model Data" - item-display-name: "<#FAFAD2>● Display Name" - item-custom-durability: "<#1E90FF>● Custom Durability" - item-enchantment: "<#8A2BE2>● Enchantment" - item-head64: "<#2E8B57>● Head64" - item-item-flag: "<#E6E6FA>● Item Flag" - item-lore: "<#FA8072>● Lore" - item-material: "<#FF00FF>● Material" - item-nbt: "<#FA8072>● NBT" - item-prevent-grab: "<#FF4500>● Prevent Grabbing" - item-price: "<#FFD700>● Price" - item-price-base: " - base: " - item-price-bonus: " - bonus: " - item-random-durability: "<#FFFF00>● Random Durability" - item-size: "<#FFF0F5>● Size" - item-stackable: "<#9370DB>● Stackable" - item-stored-enchantment: "<#9370DB>● Stored Enchantment" - item-tag: "<#2E8B57>● Tag" - item-unbreakable: "<#C0C0C0>● Unbreakable" - page-amount-title: "Edit Amount" - page-model-data-title: "Edit CustomModelData" - page-display-name-title: "Edit display name" - page-new-display-name: "New name" - page-custom-durability-title: "Edit custom durability" - page-stored-enchantment-title: "Edit stored enchantment" - page-enchantment-title: "Edit enchantment" - page-select-one-enchantment: "Select one enchantment" - page-add-new-enchantment: "[+] Add a new enchantment" - page-item-flag-title: "Edit item flag" - page-lore-title: "Edit lore" - page-add-new-lore: "[+] Add a new line" - page-select-one-lore: "Select one line" - page-material-title: "Edit Material" - page-nbt-compound-key-title: "Edit compound key" - page-nbt-list-key-title: "Edit list key" - page-nbt-key-title: "Edit key" - page-nbt-invalid-key: "Invaild key" - page-nbt-add-new-compound: "[+] Add a new compound" - page-nbt-add-new-list: "[+] Add a new list" - page-nbt-add-new-value: "[+] Add a new value" - page-add-new-key: "[+] Add a new key" - page-nbt-preview: "● NBT Preview" - page-nbt-back-to-compound: "Back to parent compound" - page-nbt-set-value-title: "Set value" - page-nbt-edit-title: "Edit NBT" - page-nick-title: "Edit nick" - page-new-nick: "New nick" - page-price-title: "Edit price" - page-base-price: "Base" - page-base-bonus: "Bonus" - page-score-title: "Edit score" - page-size-title: "Edit size" - page-size-min: "Minimum" - page-size-max: "Maximum" - page-size-max-no-less-min: "● Max must be no less than min" diff --git a/plugin/src/main/resources/messages/kr.yml b/plugin/src/main/resources/messages/kr.yml deleted file mode 100644 index da3cc49b..00000000 --- a/plugin/src/main/resources/messages/kr.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Don't change this -config-version: '31' - -messages: - prefix: '[CustomFishing] ' - reload: '리로드 했습니다. {time}ms 소요.' - item-not-exist: '아이템을 찾을 수 없습니다.' - give-item: '플레이어 {player}에게 {item} {amount}개를 지급했습니다.' - get-item: '{item} {amount}개를 얻었습니다.' - possible-loots: '획득 가능한 전리품: ' - split-char: ', ' - competition-not-exist: '대회 {id}은(는) 존재하지 않습니다.' - no-competition-ongoing: "진행 중인 대회가 없습니다." - stop-competition: '현재 진행중인 대회를 중단했습니다.' - end-competition: '현재 진행중인 대회를 종료했습니다.' - no-score: '점수 없음' - no-player: '플레이어 없음' - no-rank: '랭크 없음' - goal-catch-amount: '잡은 물고기의 수' - goal-max-size: '잡은 가장 큰 물고기의 크기' - goal-total-size: '잡은 물고기의 총 길이' - goal-total-score: '잡은 물고기의 누적 점수' - unsafe-modification: "연결된 다른 서버에서 플레이어의 낚시 가방이 활성화된 경우 플레이어의 낚시 가방을 수정할 수 없습니다." - never-played: "해당 플레이어는 이전에 서버에 참여한 적이 없습니다. 존재하지 않는 플레이어의 낚시 가방을 수정할 수 없습니다." - data-not-loaded: "데이터가 불러와지지 않았습니다. 서버에 다시 참여해주세요. 해당 문제가 계속 발생한다면. 관리자에게 문의하세요." - open-market-gui: "성공적으로 {player}에게 상점 GUI를 열었습니다" - open-fishing-bag: "성공적으로 {player}에게 낚시 가방을 열었습니다" - format-day: '일' - format-hour: '시간' - format-minute: '분' - format-second: '초' - -gui: - search: "검색" - select-file: '파일 선택' - select-item: "아이템 선택" - dupe-invalid-key: "● 중복되거나 유효하지 않은 키" - new-value: "새로운 값: " - temp-new-key: '새 키' - set-new-key: "새 키 설정" - edit-key: '{0} 수정' - delete-property: "<#00CED1>● 구성 제거" - click-confirm: "<#00FF7F> -> 클릭하여 확인" - invalid-number: "● 잘못된 숫자" - illegal-format: "● 잘못된 형식" - scroll-up: '● 위로 올리기' - scroll-down: '● 아래로 내리기' - cannot-scroll-up: "현재 최상단에 있습니다" - cannot-scroll-down: "더 내릴 수 없습니다" - next-page: '● 다음 페이지' - goto-next-page: '페이지 이동 {0} / {1}' - cannot-goto-next-page: '페이지가 더 없습니다' - previous-page: '● 이전 페이지' - goto-previous-page: '페이지 이동 {0} / {1}' - cannot-goto-previous-page: "뒤로 갈 수 없습니다" - back-to-parent-page: "<#FF8C00>상위 페이지로 돌아가기" - back-to-parent-folder: "<#FF8C00>상위 폴더로 돌아가기" - current-value: "현재 값: " - click-to-toggle: "<#00FF7F> -> 클릭하여 전환" - left-click-edit: "<#00FF7F> -> 왼쪽 클릭으로 수정" - right-click-reset: "<#FF6347> -> 오른쪽 클릭으로 초기화" - right-click-delete: "<#FF6347> -> 오른쪽 클릭으로 삭제" - right-click-cancel: "<#00CED1> -> 오른쪽 클릭으로 취소" - loot-show-in-finder: "<#5F9EA0>● 물고기 탐지기에 표시" - loot-score: "<#FF1493>● 점수" - loot-nick: "<#00FF00>● 이름" - loot-instant-game: "<#7B68EE>● 즉시 미니게임 시작" - loot-disable-statistics: "<#CD853F>● 통계 비활성화" - loot-disable-game: "<#8B4513>● 미니게임 비활성화" - item-amount: "<#1E90FF>● 수량" - item-custom-model-data: "<#FFC0CB>● Custom Model Data" - item-display-name: "<#FAFAD2>● 표시 이름" - item-custom-durability: "<#1E90FF>● 커스텀 내구도" - item-enchantment: "<#8A2BE2>● 마법 부여" - item-head64: "<#2E8B57>● Head64" - item-item-flag: "<#E6E6FA>● 아이템 플래그" - item-lore: "<#FA8072>● 설명" - item-material: "<#FF00FF>● 재료" - item-nbt: "<#FA8072>● NBT" - item-prevent-grab: "<#FF4500>● 잡기 방지" - item-price: "<#FFD700>● 가격" - item-price-base: " - 기본: " - item-price-bonus: " - 추가: " - item-random-durability: "<#FFFF00>● 무작위 내구도" - item-size: "<#FFF0F5>● 크기" - item-stackable: "<#9370DB>● 겹침 가능 여부" - item-stored-enchantment: "<#9370DB>● 저장된 마법 부여" - item-tag: "<#2E8B57>● 태그" - item-unbreakable: "<#C0C0C0>● 부서지지 않음" - page-amount-title: "수량 수정" - page-model-data-title: "CustomModelData 수정" - page-display-name-title: "표시 이름 수정" - page-new-display-name: "새 이름" - page-custom-durability-title: "커스텀 내구도 수정" - page-stored-enchantment-title: "저장된 마법 부여 수정" - page-enchantment-title: "마법 부여 수정" - page-select-one-enchantment: "한개의 마법 부여를 선택하세요" - page-add-new-enchantment: "[+] 새 마법 부여 추가" - page-item-flag-title: "아이템 플래그 수정" - page-lore-title: "아이템 설명 수정" - page-add-new-lore: "[+] 새 줄 추가" - page-select-one-lore: "줄 하나를 선택하세요" - page-material-title: "재료 수정" - page-nbt-compound-key-title: "컴파운드 키 수정" - page-nbt-list-key-title: "목록 키 수정" - page-nbt-key-title: "키 수정" - page-nbt-invalid-key: "잘못된 키" - page-nbt-add-new-compound: "[+] 새 컴파운드 추가" - page-nbt-add-new-list: "[+] 새 목록 추가" - page-nbt-add-new-value: "[+] 새 값 추가" - page-add-new-key: "[+] 새 키 추가" - page-nbt-preview: "● NBT 미리 보기" - page-nbt-back-to-compound: "상위 컴파운드로 돌아가기" - page-nbt-set-value-title: "값 설정" - page-nbt-edit-title: "NBT 수정" - page-nick-title: "별명 수정" - page-new-nick: "새 별명" - page-price-title: "가격 수정" - page-base-price: "기본" - page-base-bonus: "추가" - page-score-title: "점수 수정" - page-size-title: "크기 수정" - page-size-min: "최소" - page-size-max: "최대" - page-size-max-no-less-min: "● 최댓값은 최솟값보다 커야 합니다" \ No newline at end of file diff --git a/plugin/src/main/resources/messages/zh_cn.yml b/plugin/src/main/resources/messages/zh_cn.yml deleted file mode 100644 index 9f8a39ba..00000000 --- a/plugin/src/main/resources/messages/zh_cn.yml +++ /dev/null @@ -1,125 +0,0 @@ -# Don't change this -config-version: '31' - -messages: - prefix: '[CustomFishing] ' - reload: '重载完成. 耗时 {time}ms.' - item-not-exist: '物品不存在.' - give-item: '给予玩家 {player} {amount}x {item}.' - get-item: '获得 {amount}x {item}.' - possible-loots: '可能的战利品: ' - split-char: ', ' - competition-not-exist: '比赛 {id} 不存在.' - no-competition-ongoing: "没有正在进行的比赛." - stop-competition: '停止了当前的比赛.' - end-competition: '结束了当前的比赛.' - no-score: '无比分' - no-player: '无选手' - no-rank: '无排名' - goal-catch-amount: '捕鱼总数' - goal-max-size: '最大尺寸' - goal-total-size: '捕鱼总尺寸' - goal-total-score: '捕鱼总分' - unsafe-modification: "你不能修改一个正在其他子服游玩的玩家钓鱼背包." - never-played: "此玩家从未玩过服务器." - data-not-loaded: "数据未能正常加载. 请尝试切换子服或重进. 如果问题依然存在请联系服务器管理员." - open-market-gui: "为玩家 {player} 打开了市场" - open-fishing-bag: "为玩家 {player} 打开了钓鱼背包" - format-day: '天' - format-hour: '小时' - format-minute: '分' - format-second: '秒' - -gui: - search: 搜索 - select-file: 选择文件 - select-item-to-edit: 选择要编辑的物品 - dupe-invalid-key: ● 重复或无效的键 - new-value: '新值: ' - temp-new-key: '新键' - set-new-key: '设置新键' - edit-key: '修改 {0}' - delete-property: <#00CED1>● 删除属性 - click-confirm: <#00FF7F> -> 点击确认 - invalid-number: ● 无效的数字 - illegal-format: ● 非法的格式 - scroll-up: ● 向上翻 - scroll-down: ● 向下翻 - cannot-scroll-up: 你已经到顶了 - cannot-scroll-down: 你不能再往下了 - next-page: ● 下一页 - goto-next-page: 前往 {0} / {1} - cannot-goto-next-page: 没有更多页了 - previous-page: ● 上一页 - goto-previous-page: 前往 {0} / {1} - cannot-goto-previous-page: 已经到达第一页了 - back-to-parent-page: <#FF8C00>返回上一界面 - back-to-parent-folder: <#FF8C00>返回父文件夹 - current-value: '当前值: ' - click-to-toggle: <#00FF7F> -> 点击切换 - left-click-edit: <#00FF7F> -> 左键编辑 - right-click-reset: <#FF6347> -> 右键重置 - right-click-delete: <#FF6347> -> 右键删除 - right-click-cancel: <#00CED1> -> 右键取消 - loot-show-in-finder: <#5F9EA0>● 在找鱼器中可见 - loot-score: <#FF1493>● 比赛分数 - loot-nick: <#00FF00>● 昵称 - loot-instant-game: <#7B68EE>● 咬钩立即游戏 - loot-disable-statistics: <#CD853F>● 禁用统计数据 - loot-disable-game: <#8B4513>● 禁用游戏 - item-amount: <#1E90FF>● 数量 - item-custom-model-data: <#FFC0CB>● 自定义模型值 - item-display-name: <#FAFAD2>● 名称 - item-custom-durability: <#1E90FF>● 自定义耐久度 - item-enchantment: <#8A2BE2>● 附魔 - item-head64: <#2E8B57>● 头颅base64 - item-item-flag: <#E6E6FA>● 物品标签 - item-lore: <#FA8072>● 描述 - item-material: <#FF00FF>● 材质 - item-nbt: <#FA8072>● NBT - item-prevent-grab: <#FF4500>● 防止抢夺 - item-price: <#FFD700>● 价格 - item-price-base: ' - 基础: ' - item-price-bonus: ' - 尺寸增益: ' - item-random-durability: <#FFFF00>● 随机耐久 - item-size: <#FFF0F5>● 尺寸 - item-stackable: <#9370DB>● 是否可以堆叠 - item-stored-enchantment: <#9370DB>● 存储附魔 - item-tag: <#2E8B57>● 启用CustomFishing标签 - item-unbreakable: <#C0C0C0>● 不可破坏 - page-amount-title: 修改数量 - page-model-data-title: 修改自定义模型值 - page-display-name-title: 修改名称 - page-new-display-name: 新名称 - page-custom-durability-title: 修改自定义耐久度 - page-stored-enchantment-title: 修改存储附魔 - page-enchantment-title: 修改附魔 - page-select-one-enchantment: 选择一个附魔 - page-add-new-enchantment: [+] 新增一个附魔 - page-item-flag-title: 修改物品标签 - page-lore-title: 修改描述 - page-add-new-lore: [+] 新增一行描述 - page-select-one-lore: 选择一行描述 - page-material-title: 修改材质 - page-nbt-compound-key-title: 修改复合键名 - page-nbt-list-key-title: 修改列表名 - page-nbt-key-title: 修改键 - page-nbt-invalid-key: 无效的键 - page-nbt-add-new-compound: [+] 新增一个复合NBT - page-nbt-add-new-list: [+] 新增一个列表 - page-nbt-add-new-value: [+] 新增一个值 - page-add-new-key: [+] 新增一个键 - page-nbt-preview: ● NBT 预览 - page-nbt-back-to-compound: 返回父复合NBT - page-nbt-set-value-title: 设置值 - page-nbt-edit-title: 修改NBT - page-nick-title: 修改昵称 - page-new-nick: 新昵称 - page-price-title: 修改价格 - page-base-price: 基础 - page-base-bonus: 尺寸增益 - page-score-title: 修改分数 - page-size-title: 修改尺寸 - page-size-min: 最小值 - page-size-max: 最大值 - page-size-max-no-less-min: ● 最大值必须大于最小值 diff --git a/settings.gradle.kts b/settings.gradle.kts index d2bc3642..24d8ad1d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ rootProject.name = "CustomFishing" include("api") -include("plugin") +include("common") +include("core") +include("compatibility")