From bbde4ebd471e700a0442dfdf7da820202bf55815 Mon Sep 17 00:00:00 2001
From: XiaoMoMi <70987828+Xiao-MoMi@users.noreply.github.com>
Date: Sat, 31 Aug 2024 22:57:45 +0800
Subject: [PATCH] 3.6
---
...sic_Pack.zip => CustomCrops_Basic_Pack.zip | Bin
README.md | 50 +-
api/build.gradle.kts | 57 +-
.../api/BukkitCustomCropsPlugin.java | 178 ++
.../customcrops/api/CustomCropsPlugin.java | 129 -
.../api/action/AbstractActionManager.java | 891 ++++++
.../Variation.java => action/Action.java} | 23 +-
.../api/action/ActionExpansion.java | 55 +
.../ActionFactory.java} | 22 +-
.../customcrops/api/action/ActionManager.java | 159 +
.../customcrops/api/action/ActionTrigger.java | 10 +-
.../customcrops/api/action/EmptyAction.java | 20 +-
.../customcrops/api/common/Tuple.java | 59 -
.../customcrops/api/common/item/KeyItem.java | 28 -
.../api/context/AbstractContext.java | 64 +
.../api/context/BlockContextImpl.java | 19 +
.../customcrops/api/context/Context.java | 152 +
.../customcrops/api/context/ContextKeys.java | 103 +
.../api/context/PlayerContextImpl.java | 45 +
.../api/core/AbstractCustomEventListener.java | 120 +
.../api/core/AbstractItemManager.java | 55 +
.../api/core/BuiltInBlockMechanics.java | 43 +
.../api/core/BuiltInItemMechanics.java | 30 +
.../api/core/ClearableMappedRegistry.java | 17 +
.../api/core/ClearableRegistry.java | 6 +
.../customcrops/api/core/ConfigManager.java | 422 +++
.../customcrops/api/core/CustomForm.java | 22 +
.../api/core/CustomItemProvider.java | 34 +
.../customcrops/api/core/ExistenceForm.java | 8 +
.../api/core/FurnitureRotation.java | 79 +
.../customcrops/api/core/IdMap.java | 32 +
.../api/core/InteractionResult.java | 8 +
.../customcrops/api/core/ItemManager.java | 63 +
.../customcrops/api/core/MappedRegistry.java | 58 +
.../customcrops/api/core/Registries.java | 42 +
.../customcrops/api/core/Registry.java | 16 +
.../customcrops/api/core/RegistryAccess.java | 21 +
.../api/core/SimpleRegistryAccess.java | 54 +
.../customcrops/api/core/StatedItem.java | 9 +
.../SynchronizedCompoundMap.java | 21 +-
.../api/core/WriteableRegistry.java | 6 +
.../core/block/AbstractCustomCropsBlock.java | 82 +
.../item => core/block}/BoneMeal.java | 72 +-
.../api/core/block/BreakReason.java | 12 +
.../customcrops/api/core/block/CropBlock.java | 398 +++
.../api/core/block/CropConfig.java | 104 +
.../api/core/block/CropConfigImpl.java | 345 ++
.../api/core/block/CropStageConfig.java | 61 +
.../api/core/block/CropStageConfigImpl.java | 176 ++
.../api/core/block/CrowAttack.java | 115 +
.../api/core/block/CustomCropsBlock.java | 29 +
.../api/core/block/DeathCondition.java | 40 +
.../api/core/block/GreenhouseBlock.java | 96 +
.../api/core/block/GrowCondition.java | 25 +
.../customcrops/api/core/block/PotBlock.java | 574 ++++
.../customcrops/api/core/block/PotConfig.java | 103 +
.../api/core/block/PotConfigImpl.java | 339 ++
.../api/core/block/ScarecrowBlock.java | 94 +
.../api/core/block/SprinklerBlock.java | 310 ++
.../api/core/block/SprinklerConfig.java | 148 +
.../api/core/block/SprinklerConfigImpl.java | 375 +++
.../api/core/block/VariationData.java | 20 +-
.../core/item/AbstractCustomCropsItem.java | 29 +
.../core/item/AbstractFertilizerConfig.java | 121 +
.../api/core/item/CustomCropsItem.java | 15 +
.../customcrops/api/core/item/Fertilizer.java | 40 +
.../api/core/item/FertilizerConfig.java | 44 +
.../api/core/item/FertilizerImpl.java | 51 +
.../api/core/item/FertilizerItem.java | 113 +
.../api/core/item/FertilizerType.java | 150 +
.../customcrops/api/core/item/Quality.java | 31 +
.../api/core/item/QualityImpl.java | 52 +
.../customcrops/api/core/item/SeedItem.java | 130 +
.../customcrops/api/core/item/SoilRetain.java | 28 +
.../api/core/item/SoilRetainImpl.java | 44 +
.../customcrops/api/core/item/SpeedGrow.java | 30 +
.../api/core/item/SpeedGrowImpl.java | 51 +
.../api/core/item/SprinklerItem.java | 111 +
.../customcrops/api/core/item/Variation.java | 31 +
.../api/core/item/VariationImpl.java | 56 +
.../api/core/item/WateringCanConfig.java | 108 +
.../api/core/item/WateringCanConfigImpl.java | 342 ++
.../api/core/item/WateringCanItem.java | 358 +++
.../api/core/item/YieldIncrease.java | 30 +
.../api/core/item/YieldIncreaseImpl.java | 51 +
.../api/core/water/AbstractMethod.java | 50 +
.../water/FillMethod.java} | 11 +-
.../water/WateringMethod.java} | 19 +-
.../{mechanic => core}/world/BlockPos.java | 36 +-
.../{mechanic => core}/world/ChunkPos.java | 22 +-
.../api/core/world/CustomCropsBlockState.java | 15 +
.../core/world/CustomCropsBlockStateImpl.java | 52 +
.../api/core/world/CustomCropsChunk.java | 195 ++
.../api/core/world/CustomCropsChunkImpl.java | 288 ++
.../api/core/world/CustomCropsRegion.java | 36 +-
.../api/core/world/CustomCropsRegionImpl.java | 86 +
.../api/core/world/CustomCropsSection.java | 52 +
.../core/world/CustomCropsSectionImpl.java | 61 +
.../api/core/world/CustomCropsWorld.java | 215 ++
.../api/core/world/CustomCropsWorldImpl.java | 474 +++
.../world/DataBlock.java} | 19 +-
.../api/core/world/DelayedTickTask.java | 11 +-
.../customcrops/api/core/world/Pos3.java | 87 +
.../{mechanic => core}/world/RegionPos.java | 5 +-
.../customcrops/api/core/world/Season.java | 53 +
.../api/core/world/SerializableChunk.java | 12 +-
.../world/SerializableSection.java} | 17 +-
.../world/WorldExtraData.java} | 32 +-
.../api/core/world/WorldManager.java | 31 +
.../level => core/world}/WorldSetting.java | 36 +-
.../world/adaptor/AbstractWorldAdaptor.java | 124 +
.../api/core/world/adaptor/WorldAdaptor.java | 45 +
.../api/core/wrapper/WrappedBreakEvent.java | 103 +
.../core/wrapper/WrappedInteractAirEvent.java | 43 +
.../core/wrapper/WrappedInteractEvent.java | 94 +
.../api/core/wrapper/WrappedPlaceEvent.java | 67 +
.../api/event/BoneMealUseEvent.java | 49 +-
.../customcrops/api/event/CropBreakEvent.java | 146 +-
.../api/event/CropInteractEvent.java | 46 +-
.../customcrops/api/event/CropPlantEvent.java | 30 +-
.../api/event/CustomCropsReloadEvent.java | 10 +-
.../api/event/FertilizerUseEvent.java | 37 +-
.../api/event/GreenhouseGlassBreakEvent.java | 54 +-
...java => GreenhouseGlassInteractEvent.java} | 94 +-
.../api/event/GreenhouseGlassPlaceEvent.java | 19 +-
.../customcrops/api/event/PotBreakEvent.java | 62 +-
.../customcrops/api/event/PotFillEvent.java | 59 +-
.../api/event/PotInteractEvent.java | 30 +-
.../customcrops/api/event/PotPlaceEvent.java | 44 +-
.../api/event/ScarecrowBreakEvent.java | 49 +-
.../api/event/ScarecrowInteractEvent.java | 114 +
.../api/event/ScarecrowPlaceEvent.java | 19 +-
.../api/event/SeasonChangeEvent.java | 73 -
.../api/event/SprinklerBreakEvent.java | 68 +-
.../api/event/SprinklerFillEvent.java | 73 +-
.../api/event/SprinklerInteractEvent.java | 35 +-
.../api/event/SprinklerPlaceEvent.java | 35 +-
.../api/event/WateringCanFillEvent.java | 34 +-
...ent.java => WateringCanWaterPotEvent.java} | 67 +-
.../event/WateringCanWaterSprinklerEvent.java | 119 +
.../ExternalProvider.java} | 17 +-
.../api/integration/IntegrationManager.java | 78 +
.../api/integration/ItemLibrary.java | 49 -
.../api/integration/ItemProvider.java | 49 +
...velInterface.java => LevelerProvider.java} | 15 +-
...asonInterface.java => SeasonProvider.java} | 43 +-
.../api/manager/ActionManager.java | 101 -
.../api/manager/AdventureManager.java | 63 -
.../api/manager/ConditionManager.java | 106 -
.../api/manager/ConfigManager.java | 185 --
.../api/manager/IntegrationManager.java | 58 -
.../customcrops/api/manager/ItemManager.java | 413 ---
.../api/manager/MessageManager.java | 54 -
.../api/manager/PlaceholderManager.java | 47 -
.../api/manager/RequirementManager.java | 110 -
.../api/manager/VersionManager.java | 91 -
.../customcrops/api/manager/WorldManager.java | 420 ---
.../api/mechanic/action/Action.java | 30 -
.../api/mechanic/action/ActionExpansion.java | 49 -
.../api/mechanic/action/ActionTrigger.java | 36 -
.../api/mechanic/condition/Condition.java | 32 -
.../condition/ConditionExpansion.java | 49 -
.../mechanic/condition/ConditionFactory.java | 29 -
.../api/mechanic/condition/Conditions.java | 19 -
.../mechanic/condition/DeathConditions.java | 46 -
.../customcrops/api/mechanic/item/Crop.java | 206 --
.../api/mechanic/item/Fertilizer.java | 80 -
.../api/mechanic/item/FertilizerType.java | 26 -
.../api/mechanic/item/ItemCarrier.java | 27 -
.../api/mechanic/item/ItemType.java | 26 -
.../customcrops/api/mechanic/item/Pot.java | 137 -
.../api/mechanic/item/Scarecrow.java | 30 -
.../api/mechanic/item/Sprinkler.java | 134 -
.../api/mechanic/item/WateringCan.java | 138 -
.../item/custom/AbstractCustomListener.java | 349 ---
.../mechanic/item/custom/CustomProvider.java | 132 -
.../mechanic/item/fertilizer/SpeedGrow.java | 30 -
.../item/fertilizer/YieldIncrease.java | 30 -
.../item/water/AbstractFillMethod.java | 65 -
.../api/mechanic/misc/CRotation.java | 58 -
.../api/mechanic/misc/MatchRule.java | 8 -
.../customcrops/api/mechanic/misc/Reason.java | 29 -
.../customcrops/api/mechanic/misc/Value.java | 31 -
.../api/mechanic/requirement/Requirement.java | 29 -
.../requirement/RequirementFactory.java | 45 -
.../api/mechanic/requirement/State.java | 73 -
.../mechanic/world/AbstractWorldAdaptor.java | 49 -
.../api/mechanic/world/CustomCropsBlock.java | 31 -
.../api/mechanic/world/SimpleLocation.java | 147 -
.../api/mechanic/world/Tickable.java | 12 -
.../world/level/AbstractCustomCropsBlock.java | 105 -
.../world/level/CustomCropsChunk.java | 344 --
.../world/level/CustomCropsRegion.java | 76 -
.../world/level/CustomCropsSection.java | 81 -
.../world/level/CustomCropsWorld.java | 412 ---
.../api/mechanic/world/level/DataBlock.java | 55 -
.../api/mechanic/world/level/WorldCrop.java | 52 -
.../api/mechanic/world/level/WorldGlass.java | 23 -
.../api/mechanic/world/level/WorldPot.java | 94 -
.../mechanic/world/level/WorldScarecrow.java | 23 -
.../mechanic/world/level/WorldSprinkler.java | 52 -
.../misc/image => misc}/WaterBar.java | 2 +-
.../cooldown}/CoolDownManager.java | 50 +-
.../placeholder/BukkitPlaceholderManager.java | 145 +
.../misc/placeholder/PlaceholderAPIUtils.java | 51 +
.../misc/placeholder/PlaceholderManager.java | 88 +
.../api/misc/value/DynamicText.java | 76 +
.../misc/value/ExpressionMathValueImpl.java | 41 +
.../customcrops/api/misc/value/MathValue.java | 123 +
.../misc/value/PlaceholderTextValueImpl.java | 42 +
.../api/misc/value/PlainMathValueImpl.java | 13 +-
.../api/misc/value/PlainTextValueImpl.java | 18 +-
.../api/misc/value/RangedDoubleValueImpl.java | 41 +
.../api/misc/value/RangedIntValueImpl.java | 41 +
.../customcrops/api/misc/value/TextValue.java | 96 +
.../AbstractRequirementManager.java | 1118 +++++++
.../api}/requirement/EmptyRequirement.java | 18 +-
.../api/requirement/Requirement.java | 41 +
.../requirement/RequirementExpansion.java | 28 +-
.../api/requirement/RequirementFactory.java | 50 +
.../api/requirement/RequirementManager.java | 133 +
.../api/scheduler/CancellableTask.java | 33 -
.../customcrops/api/scheduler/Scheduler.java | 104 -
.../api/util/DisplayEntityUtils.java | 15 -
.../customcrops/api/util/EventUtils.java | 18 +-
.../customcrops/api/util/FakeCancellable.java | 18 +
.../customcrops/api/util/InventoryUtils.java | 108 +
.../customcrops/api/util/LocationUtils.java | 13 +-
.../customcrops/api/util/LogUtils.java | 78 -
.../customcrops/api/util/MoonPhase.java | 47 +-
.../customcrops/api}/util/ParticleUtils.java | 2 +-
.../customcrops/api/util/PlayerUtils.java | 158 +
.../customcrops/api/util/PluginUtils.java | 20 +
.../customcrops/api/util/StringUtils.java | 10 +
build.gradle.kts | 104 +-
common/build.gradle.kts | 39 +
.../common/annotation/DoNotUse.java | 11 +
.../command/AbstractCommandFeature.java | 85 +
.../command/AbstractCommandManager.java | 179 ++
.../common/command/CommandBuilder.java | 58 +
.../common/command/CommandConfig.java | 77 +
.../common/command/CommandFeature.java | 43 +
.../command/CustomCropsCommandManager.java | 56 +
.../common/config/ConfigLoader.java | 69 +
.../common/dependency/Dependency.java | 268 ++
.../DependencyDownloadException.java | 2 +-
.../common/dependency}/DependencyManager.java | 2 +-
.../dependency}/DependencyManagerImpl.java | 98 +-
.../dependency}/DependencyRegistry.java | 4 +-
.../dependency}/DependencyRepository.java | 31 +-
.../classloader/IsolatedClassLoader.java | 2 +-
.../dependency}/relocation/Relocation.java | 2 +-
.../relocation/RelocationHandler.java | 13 +-
.../relocation/RelocationHelper.java | 3 +-
.../common/helper/AdventureHelper.java | 285 ++
.../common/helper/ExpressionHelper.java | 27 +-
.../customcrops/common/helper/GsonHelper.java | 59 +
.../common/helper/VersionHelper.java | 172 +-
.../common/item}/AbstractItem.java | 53 +-
.../common/item/ComponentKeys.java | 32 +
.../customcrops/common/item/Item.java | 157 +
.../customcrops/common/item}/ItemFactory.java | 37 +-
.../locale/CustomCropsCaptionFormatter.java | 35 +
.../common/locale/CustomCropsCaptionKeys.java | 18 +-
.../locale/CustomCropsCaptionProvider.java | 37 +
.../common/locale/MessageConstants.java | 32 +
.../MiniMessageTranslationRegistry.java | 73 +
.../MiniMessageTranslationRegistryImpl.java | 235 ++
.../common/locale/MiniMessageTranslator.java | 47 +
.../locale/MiniMessageTranslatorImpl.java | 92 +
.../common/locale/TranslationManager.java | 201 ++
.../common/plugin/CustomCropsPlugin.java | 138 +
.../common/plugin/CustomCropsProperties.java | 61 +
.../plugin}/classpath/ClassPathAppender.java | 2 +-
.../ReflectionClassPathAppender.java | 8 +-
.../classpath/URLClassLoaderAccess.java | 12 +-
.../common/plugin/feature}/Reloadable.java | 30 +-
.../plugin/logging/JavaPluginLogger.java | 52 +-
.../plugin/logging/Log4jPluginLogger.java | 61 +
.../common/plugin/logging/PluginLogger.java | 23 +-
.../plugin/logging/Slf4jPluginLogger.java | 61 +
.../scheduler/AbstractJavaScheduler.java | 151 +
.../plugin/scheduler/RegionExecutor.java | 33 +
.../plugin/scheduler/SchedulerAdapter.java | 111 +
.../plugin/scheduler/SchedulerTask.java | 16 +-
.../common/sender/AbstractSender.java | 116 +
.../common/sender/DummyConsoleSender.java | 62 +
.../customcrops/common/sender/Sender.java | 121 +
.../common/sender/SenderFactory.java | 82 +
.../customcrops/common/util/ArrayUtils.java | 102 +
.../customcrops/common}/util/ClassUtils.java | 58 +-
.../common/util/CompletableFutures.java | 73 +
.../customcrops/common/util/Either.java | 113 +
.../customcrops/common/util/EitherImpl.java | 130 +
.../customcrops/common/util/FileUtils.java | 112 +
.../customcrops/common/util/Key.java | 56 +
.../customcrops/common/util/ListUtils.java | 49 +
.../customcrops/common/util/Pair.java | 90 +
.../customcrops/common/util/RandomUtils.java | 140 +
.../customcrops/common/util/TriConsumer.java | 13 +-
.../customcrops/common/util/TriFunction.java | 17 +-
.../customcrops/common/util/Tristate.java | 98 +
.../customcrops/common/util/Tuple.java | 114 +
.../customcrops/common/util/UUIDUtils.java | 87 +
.../customcrops/common/util/WeightUtils.java | 108 +
.../main/resources/custom-crops.properties | 20 +
compatibility-asp-r1/build.gradle.kts | 27 +
.../adaptor/asp_r1/SlimeWorldAdaptorR1.java | 227 ++
.../build.gradle.kts | 22 +-
.../itemsadder_r1/ItemsAdderListener.java | 79 +
.../itemsadder_r1/ItemsAdderProvider.java | 104 +
.../.gitignore | 0
compatibility-oraxen-r1/build.gradle.kts | 25 +
.../custom/oraxen_r1/OraxenListener.java | 118 +
.../custom/oraxen_r1/OraxenProvider.java | 97 +
.../.gitignore | 0
.../build.gradle.kts | 7 +
.../custom/oraxen_r2/OraxenListener.java | 118 +
.../custom/oraxen_r2/OraxenProvider.java | 97 +
compatibility/build.gradle.kts | 72 +
.../libs/AdvancedSeasons-API.jar | Bin
.../libs/BattlePass-4.0.6-api.jar | Bin
.../libs/ClueScrolls-api.jar | Bin
.../libs/RealisticSeasons-api.jar | Bin
{plugin => compatibility}/libs/mcMMO-api.jar | Bin
.../libs/zaphkiel-2.0.24.jar | Bin
.../bukkit/integration/VaultHook.java | 79 +
.../item/CustomFishingItemProvider.java | 57 +
.../item/MMOItemsItemProvider.java | 22 +-
.../item/MythicMobsItemProvider.java | 23 +-
.../item/NeigeItemsItemProvider.java | 20 +-
.../item/ZaphkielItemProvider.java | 24 +-
.../level/AuraSkillsLevelerProvider.java | 18 +-
.../level/AureliumSkillsProvider.java | 27 +-
.../level/EcoJobsLevelerProvider.java | 18 +-
.../level/EcoSkillsLevelerProvider.java | 18 +-
.../level/JobsRebornLevelerProvider.java | 28 +-
.../level/MMOCoreLevelerProvider.java | 18 +-
.../level/McMMOLevelerProvider.java | 18 +-
.../integration/papi/CustomCropsPapi.java | 81 +
.../season/AdvancedSeasonsProvider.java | 33 +-
.../season/RealisticSeasonsProvider.java | 44 +
gradle.properties | 50 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
legacy-api/build.gradle.kts | 16 -
.../customcrops/api/object/ItemMode.java | 31 -
.../customcrops/api/object/ItemType.java | 30 -
.../api/object/OfflineReplaceTask.java | 33 -
.../api/object/crop/GrowingCrop.java | 48 -
.../api/object/fertilizer/Fertilizer.java | 44 -
.../customcrops/api/object/pot/Pot.java | 52 -
.../api/object/sprinkler/Sprinkler.java | 48 -
.../customcrops/api/object/world/CCChunk.java | 77 -
.../api/object/world/ChunkCoordinate.java | 72 -
.../api/object/world/SimpleLocation.java | 108 -
.../item/custom/oraxen/OraxenListener.java | 108 -
.../item/custom/oraxen/OraxenProvider.java | 104 -
oraxen-legacy/.gitignore | 42 -
.../oraxenlegacy/LegacyOraxenListener.java | 108 -
.../oraxenlegacy/LegacyOraxenProvider.java | 104 -
plugin/build.gradle.kts | 120 +-
plugin/libs/Sparrow-Heart-0.19.jar | Bin 177287 -> 0 bytes
.../customcrops/CustomCropsPluginImpl.java | 192 --
.../BukkitBootstrap.java} | 30 +-
.../bukkit/BukkitCustomCropsPluginImpl.java | 223 ++
.../bukkit/action/BlockActionManager.java | 110 +
.../bukkit/action/PlayerActionManager.java | 524 ++++
.../bukkit/command/BukkitCommandFeature.java | 61 +
.../bukkit/command/BukkitCommandManager.java | 70 +
.../command/feature/DebugDataCommand.java | 73 +
.../bukkit/command/feature/ReloadCommand.java | 50 +
.../bukkit/config/BukkitConfigManager.java | 249 ++
.../customcrops/bukkit/config/ConfigType.java | 320 ++
.../integration/BukkitIntegrationManager.java | 164 +
.../adaptor/BukkitWorldAdaptor.java | 424 +++
.../item}/BukkitItemFactory.java | 13 +-
.../bukkit/item/BukkitItemManager.java | 442 +++
.../item}/ComponentItemFactory.java | 7 +-
.../item}/UniversalItemFactory.java | 7 +-
.../bukkit/misc/HologramManager.java | 172 +
.../requirement/BlockRequirementManager.java | 52 +
.../requirement/PlayerRequirementManager.java | 247 ++
.../scheduler/BukkitSchedulerAdapter.java | 53 +
.../bukkit/scheduler/DummyTask.java | 15 +
.../bukkit/scheduler/impl/BukkitExecutor.java | 95 +
.../bukkit/scheduler/impl/FoliaExecutor.java | 100 +
.../bukkit/sender/BukkitSenderFactory.java | 112 +
.../bukkit/world/BukkitWorldManager.java | 327 ++
.../compatibility/IntegrationManagerImpl.java | 153 -
.../customcrops/compatibility/VaultHook.java | 40 -
.../compatibility/papi/CCPapi.java | 103 -
.../compatibility/papi/ParseUtils.java | 33 -
.../compatibility/quest/BattlePassHook.java | 91 -
.../compatibility/quest/BetonQuestHook.java | 199 --
.../compatibility/quest/ClueScrollsHook.java | 67 -
.../compatibility/season/InBuiltSeason.java | 74 -
.../season/RealisticSeasonsImpl.java | 67 -
.../libraries/dependencies/Dependency.java | 211 --
.../libraries/loader/JarInJarClassLoader.java | 155 -
.../manager/AdventureManagerImpl.java | 214 --
.../customcrops/manager/CommandManager.java | 290 --
.../manager/ConfigManagerImpl.java | 277 --
.../customcrops/manager/HologramManager.java | 182 --
.../manager/MessageManagerImpl.java | 99 -
.../customcrops/manager/PacketManager.java | 64 -
.../manager/PlaceholderManagerImpl.java | 120 -
.../mechanic/action/ActionManagerImpl.java | 1206 -------
.../condition/ConditionManagerImpl.java | 760 -----
.../mechanic/item/AbstractEventItem.java | 43 -
.../mechanic/item/ItemManagerImpl.java | 2766 -----------------
.../custom/crucible/CrucibleListener.java | 49 -
.../custom/crucible/CrucibleProvider.java | 124 -
.../custom/itemsadder/ItemsAdderListener.java | 94 -
.../custom/itemsadder/ItemsAdderProvider.java | 115 -
.../mechanic/item/factory/ComponentKeys.java | 17 -
.../mechanic/item/factory/Item.java | 39 -
.../mechanic/item/function/CFunction.java | 76 -
.../item/function/wrapper/BreakWrapper.java | 35 -
.../function/wrapper/ConditionWrapper.java | 40 -
.../wrapper/InteractBlockWrapper.java | 42 -
.../wrapper/InteractFurnitureWrapper.java | 46 -
.../function/wrapper/InteractWrapper.java | 35 -
.../function/wrapper/PlaceBlockWrapper.java | 36 -
.../item/function/wrapper/PlaceWrapper.java | 42 -
.../item/impl/AbstractFertilizer.java | 102 -
.../mechanic/item/impl/CropConfig.java | 241 --
.../mechanic/item/impl/PotConfig.java | 177 --
.../mechanic/item/impl/SprinklerConfig.java | 165 -
.../mechanic/item/impl/WateringCanConfig.java | 212 --
.../impl/fertilizer/QualityCropConfig.java | 62 -
.../impl/fertilizer/SoilRetainConfig.java | 53 -
.../item/impl/fertilizer/SpeedGrowConfig.java | 60 -
.../item/impl/fertilizer/VariationConfig.java | 53 -
.../impl/fertilizer/YieldIncreaseConfig.java | 60 -
.../mechanic/misc/CrowAttackAnimation.java | 97 -
.../mechanic/misc/TempFakeItem.java | 76 -
.../mechanic/misc/migrator/Migration.java | 472 ---
.../requirement/RequirementManagerImpl.java | 1041 -------
.../customcrops/mechanic/world/CChunk.java | 624 ----
.../customcrops/mechanic/world/CRegion.java | 83 -
.../customcrops/mechanic/world/CSection.java | 76 -
.../customcrops/mechanic/world/CWorld.java | 590 ----
.../mechanic/world/SerializableChunk.java | 73 -
.../mechanic/world/SerializableSection.java | 41 -
.../mechanic/world/WorldManagerImpl.java | 599 ----
.../world/adaptor/BukkitWorldAdaptor.java | 729 -----
.../world/adaptor/SlimeWorldAdaptor.java | 323 --
.../mechanic/world/block/MemoryCrop.java | 181 --
.../mechanic/world/block/MemoryGlass.java | 60 -
.../mechanic/world/block/MemoryPot.java | 272 --
.../mechanic/world/block/MemoryScarecrow.java | 60 -
.../mechanic/world/block/MemorySprinkler.java | 183 --
.../scheduler/BukkitSchedulerImpl.java | 83 -
.../scheduler/FoliaSchedulerImpl.java | 87 -
.../customcrops/scheduler/SchedulerImpl.java | 141 -
.../customcrops/scheduler/SyncScheduler.java | 33 -
.../scheduler/task/ReplaceTask.java | 46 -
.../customcrops/util/ConfigUtils.java | 415 ---
.../customcrops/util/FakeEntityUtils.java | 147 -
.../customcrops/util/ItemUtils.java | 116 -
.../customcrops/util/RotationUtils.java | 64 -
plugin/src/main/resources/commands.yml | 38 +
plugin/src/main/resources/config.yml | 52 +-
.../main/resources/contents/crops/default.yml | 19 +-
.../main/resources/contents/pots/default.yml | 7 +-
plugin/src/main/resources/messages/en.yml | 8 -
plugin/src/main/resources/messages/es.yml | 8 -
plugin/src/main/resources/messages/fr.yml | 8 -
plugin/src/main/resources/messages/pt.yml | 8 -
plugin/src/main/resources/messages/ru.yml | 8 -
plugin/src/main/resources/messages/zh_cn.yml | 8 -
plugin/src/main/resources/plugin.yml | 9 +-
plugin/src/main/resources/translations/en.yml | 46 +
.../src/main/resources/translations/zh_cn.yml | 46 +
settings.gradle.kts | 12 +
475 files changed, 24733 insertions(+), 24341 deletions(-)
rename CustomCrops_3.4_Basic_Pack.zip => CustomCrops_Basic_Pack.zip (100%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/BukkitCustomCropsPlugin.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/CustomCropsPlugin.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/action/AbstractActionManager.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/item/fertilizer/Variation.java => action/Action.java} (53%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/action/ActionExpansion.java
rename api/src/main/java/net/momirealms/customcrops/api/{common/item/EventItem.java => action/ActionFactory.java} (58%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/action/ActionManager.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionTrigger.java => api/src/main/java/net/momirealms/customcrops/api/action/ActionTrigger.java (79%)
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/EmptyCondition.java => api/src/main/java/net/momirealms/customcrops/api/action/EmptyAction.java (58%)
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/common/Tuple.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/common/item/KeyItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/context/AbstractContext.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/context/BlockContextImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/context/Context.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/context/ContextKeys.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/context/PlayerContextImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/BuiltInBlockMechanics.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/BuiltInItemMechanics.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/ClearableMappedRegistry.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/ClearableRegistry.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/CustomForm.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/CustomItemProvider.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/ExistenceForm.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/FurnitureRotation.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/IdMap.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/InteractionResult.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/ItemManager.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/MappedRegistry.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/Registries.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/Registry.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/RegistryAccess.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/SimpleRegistryAccess.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/StatedItem.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/world => core}/SynchronizedCompoundMap.java (87%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/WriteableRegistry.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/AbstractCustomCropsBlock.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/item => core/block}/BoneMeal.java (55%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/BreakReason.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfigImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfigImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/CustomCropsBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/DeathCondition.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/GreenhouseBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/GrowCondition.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/PotBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfigImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/ScarecrowBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerBlock.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfigImpl.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/VariationCrop.java => api/src/main/java/net/momirealms/customcrops/api/core/block/VariationData.java (67%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractCustomCropsItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractFertilizerConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/CustomCropsItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/Fertilizer.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerType.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/Quality.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/QualityImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SeedItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetain.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetainImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrow.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrowImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/SprinklerItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/Variation.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/VariationImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfig.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfigImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanItem.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncrease.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncreaseImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/water/AbstractMethod.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/item/water/PositiveFillMethod.java => core/water/FillMethod.java} (72%)
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/item/water/PassiveFillMethod.java => core/water/WateringMethod.java} (76%)
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic => core}/world/BlockPos.java (68%)
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic => core}/world/ChunkPos.java (77%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockState.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockStateImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunk.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunkImpl.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakBlockWrapper.java => api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegion.java (58%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegionImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSection.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSectionImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/item/fertilizer/SoilRetain.java => core/world/DataBlock.java} (65%)
rename plugin/src/main/java/net/momirealms/customcrops/scheduler/task/TickTask.java => api/src/main/java/net/momirealms/customcrops/api/core/world/DelayedTickTask.java (80%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/Pos3.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic => core}/world/RegionPos.java (95%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/Season.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceFurnitureWrapper.java => api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableChunk.java (67%)
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/world/season/Season.java => core/world/SerializableSection.java} (72%)
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/world/level/WorldInfoData.java => core/world/WorldExtraData.java} (69%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/WorldManager.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/world/level => core/world}/WorldSetting.java (86%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/AbstractWorldAdaptor.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/WorldAdaptor.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedBreakEvent.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractAirEvent.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractEvent.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedPlaceEvent.java
rename api/src/main/java/net/momirealms/customcrops/api/event/{BoneMealDispenseEvent.java => GreenhouseGlassInteractEvent.java} (57%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowInteractEvent.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/event/SeasonChangeEvent.java
rename api/src/main/java/net/momirealms/customcrops/api/event/{WateringCanWaterEvent.java => WateringCanWaterPotEvent.java} (62%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterSprinklerEvent.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/action/ActionFactory.java => integration/ExternalProvider.java} (64%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/integration/IntegrationManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/integration/ItemLibrary.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/integration/ItemProvider.java
rename api/src/main/java/net/momirealms/customcrops/api/integration/{LevelInterface.java => LevelerProvider.java} (64%)
rename api/src/main/java/net/momirealms/customcrops/api/integration/{SeasonInterface.java => SeasonProvider.java} (53%)
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/ActionManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/AdventureManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/ConditionManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/ConfigManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/IntegrationManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/ItemManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/MessageManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/PlaceholderManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/RequirementManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/VersionManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/action/Action.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionExpansion.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionTrigger.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Condition.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionExpansion.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionFactory.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Conditions.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/DeathConditions.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Crop.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Fertilizer.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/FertilizerType.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemCarrier.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemType.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Scarecrow.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Sprinkler.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/WateringCan.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/AbstractCustomListener.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/CustomProvider.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SpeedGrow.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/YieldIncrease.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/AbstractFillMethod.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/CRotation.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/MatchRule.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Reason.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Value.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/Requirement.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementFactory.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/State.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/CustomCropsBlock.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/Tickable.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/AbstractCustomCropsBlock.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsSection.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/DataBlock.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldCrop.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldGlass.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldPot.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldScarecrow.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSprinkler.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic/misc/image => misc}/WaterBar.java (95%)
rename api/src/main/java/net/momirealms/customcrops/api/{manager => misc/cooldown}/CoolDownManager.java (54%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/BukkitPlaceholderManager.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderAPIUtils.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderManager.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/DynamicText.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/ExpressionMathValueImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/MathValue.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/PlaceholderTextValueImpl.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/PlainValue.java => api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainMathValueImpl.java (71%)
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/action/EmptyAction.java => api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainTextValueImpl.java (64%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedDoubleValueImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedIntValueImpl.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/misc/value/TextValue.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/requirement/AbstractRequirementManager.java
rename {plugin/src/main/java/net/momirealms/customcrops/mechanic => api/src/main/java/net/momirealms/customcrops/api}/requirement/EmptyRequirement.java (60%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/requirement/Requirement.java
rename api/src/main/java/net/momirealms/customcrops/api/{mechanic => }/requirement/RequirementExpansion.java (53%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementFactory.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementManager.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/scheduler/CancellableTask.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/scheduler/Scheduler.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/DisplayEntityUtils.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/FakeCancellable.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/InventoryUtils.java
delete mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/LogUtils.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakFurnitureWrapper.java => api/src/main/java/net/momirealms/customcrops/api/util/MoonPhase.java (51%)
rename {plugin/src/main/java/net/momirealms/customcrops => api/src/main/java/net/momirealms/customcrops/api}/util/ParticleUtils.java (96%)
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/PlayerUtils.java
create mode 100644 api/src/main/java/net/momirealms/customcrops/api/util/PluginUtils.java
create mode 100644 common/build.gradle.kts
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/annotation/DoNotUse.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandFeature.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandManager.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/CommandBuilder.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/CommandConfig.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/CommandFeature.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/command/CustomCropsCommandManager.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/config/ConfigLoader.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/dependency/Dependency.java
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/DependencyDownloadException.java (96%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/DependencyManager.java (96%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/DependencyManagerImpl.java (69%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/DependencyRegistry.java (93%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/DependencyRepository.java (86%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/classloader/IsolatedClassLoader.java (96%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/relocation/Relocation.java (97%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/relocation/RelocationHandler.java (89%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies => common/src/main/java/net/momirealms/customcrops/common/dependency}/relocation/RelocationHelper.java (95%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/helper/AdventureHelper.java
rename api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/QualityCrop.java => common/src/main/java/net/momirealms/customcrops/common/helper/ExpressionHelper.java (55%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/helper/GsonHelper.java
rename plugin/src/main/java/net/momirealms/customcrops/manager/VersionManagerImpl.java => common/src/main/java/net/momirealms/customcrops/common/helper/VersionHelper.java (64%)
rename {plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory => common/src/main/java/net/momirealms/customcrops/common/item}/AbstractItem.java (69%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/item/ComponentKeys.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/item/Item.java
rename {plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory => common/src/main/java/net/momirealms/customcrops/common/item}/ItemFactory.java (55%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionFormatter.java
rename api/src/main/java/net/momirealms/customcrops/api/common/Initable.java => common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionKeys.java (56%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionProvider.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/MessageConstants.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistry.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistryImpl.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslator.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslatorImpl.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/locale/TranslationManager.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsPlugin.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsProperties.java
rename {plugin/src/main/java/net/momirealms/customcrops/libraries => common/src/main/java/net/momirealms/customcrops/common/plugin}/classpath/ClassPathAppender.java (96%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries => common/src/main/java/net/momirealms/customcrops/common/plugin}/classpath/ReflectionClassPathAppender.java (88%)
rename {plugin/src/main/java/net/momirealms/customcrops/libraries => common/src/main/java/net/momirealms/customcrops/common/plugin}/classpath/URLClassLoaderAccess.java (95%)
rename {api/src/main/java/net/momirealms/customcrops/api/common => common/src/main/java/net/momirealms/customcrops/common/plugin/feature}/Reloadable.java (76%)
rename plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/JarInJarClassPathAppender.java => common/src/main/java/net/momirealms/customcrops/common/plugin/logging/JavaPluginLogger.java (54%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/logging/Log4jPluginLogger.java
rename plugin/src/main/java/net/momirealms/customcrops/libraries/loader/LoadingException.java => common/src/main/java/net/momirealms/customcrops/common/plugin/logging/PluginLogger.java (71%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/logging/Slf4jPluginLogger.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/scheduler/AbstractJavaScheduler.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/scheduler/RegionExecutor.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/plugin/scheduler/SchedulerAdapter.java
rename plugin/src/main/java/net/momirealms/customcrops/libraries/loader/LoaderBootstrap.java => common/src/main/java/net/momirealms/customcrops/common/plugin/scheduler/SchedulerTask.java (84%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/sender/AbstractSender.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/sender/DummyConsoleSender.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/sender/Sender.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/sender/SenderFactory.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/ArrayUtils.java
rename {plugin/src/main/java/net/momirealms/customcrops => common/src/main/java/net/momirealms/customcrops/common}/util/ClassUtils.java (50%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/CompletableFutures.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/Either.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/EitherImpl.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/FileUtils.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/Key.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/ListUtils.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/Pair.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/RandomUtils.java
rename plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionResult.java => common/src/main/java/net/momirealms/customcrops/common/util/TriConsumer.java (78%)
rename api/src/main/java/net/momirealms/customcrops/api/common/Pair.java => common/src/main/java/net/momirealms/customcrops/common/util/TriFunction.java (59%)
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/Tristate.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/Tuple.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/UUIDUtils.java
create mode 100644 common/src/main/java/net/momirealms/customcrops/common/util/WeightUtils.java
create mode 100644 common/src/main/resources/custom-crops.properties
create mode 100644 compatibility-asp-r1/build.gradle.kts
create mode 100644 compatibility-asp-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r1/SlimeWorldAdaptorR1.java
rename {oraxen-legacy => compatibility-itemsadder-r1}/build.gradle.kts (59%)
create mode 100644 compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java
create mode 100644 compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderProvider.java
rename {legacy-api => compatibility-oraxen-r1}/.gitignore (100%)
create mode 100644 compatibility-oraxen-r1/build.gradle.kts
create mode 100644 compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenListener.java
create mode 100644 compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenProvider.java
rename {oraxen-j21 => compatibility-oraxen-r2}/.gitignore (100%)
rename {oraxen-j21 => compatibility-oraxen-r2}/build.gradle.kts (70%)
create mode 100644 compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenListener.java
create mode 100644 compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenProvider.java
create mode 100644 compatibility/build.gradle.kts
rename {plugin => compatibility}/libs/AdvancedSeasons-API.jar (100%)
rename {plugin => compatibility}/libs/BattlePass-4.0.6-api.jar (100%)
rename {plugin => compatibility}/libs/ClueScrolls-api.jar (100%)
rename {plugin => compatibility}/libs/RealisticSeasons-api.jar (100%)
rename {plugin => compatibility}/libs/mcMMO-api.jar (100%)
rename {plugin => compatibility}/libs/zaphkiel-2.0.24.jar (100%)
create mode 100644 compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/VaultHook.java
create mode 100644 compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/CustomFishingItemProvider.java
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MMOItemsItemImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MMOItemsItemProvider.java (66%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MythicMobsItemImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MythicMobsItemProvider.java (67%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/item/NeigeItemsItemImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/NeigeItemsItemProvider.java (66%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/item/ZaphkielItemImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/ZaphkielItemProvider.java (61%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AuraSkillsImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AuraSkillsLevelerProvider.java (69%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AureliumSkillsImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AureliumSkillsProvider.java (52%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoJobsImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoJobsLevelerProvider.java (69%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoSkillsImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoSkillsLevelerProvider.java (67%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/JobsRebornImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/JobsRebornLevelerProvider.java (67%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/MMOCoreImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/MMOCoreLevelerProvider.java (68%)
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/level/McMMOImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/McMMOLevelerProvider.java (66%)
create mode 100644 compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/papi/CustomCropsPapi.java
rename plugin/src/main/java/net/momirealms/customcrops/compatibility/season/AdvancedSeasonsImpl.java => compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/AdvancedSeasonsProvider.java (59%)
create mode 100644 compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/RealisticSeasonsProvider.java
delete mode 100644 legacy-api/build.gradle.kts
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemMode.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemType.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/OfflineReplaceTask.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/crop/GrowingCrop.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/fertilizer/Fertilizer.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/pot/Pot.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/sprinkler/Sprinkler.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/ChunkCoordinate.java
delete mode 100644 legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/SimpleLocation.java
delete mode 100644 oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenListener.java
delete mode 100644 oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenProvider.java
delete mode 100644 oraxen-legacy/.gitignore
delete mode 100644 oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenListener.java
delete mode 100644 oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenProvider.java
delete mode 100644 plugin/libs/Sparrow-Heart-0.19.jar
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java
rename plugin/src/main/java/net/momirealms/customcrops/{mechanic/misc/value/ExpressionValue.java => bukkit/BukkitBootstrap.java} (55%)
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitCustomCropsPluginImpl.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/action/BlockActionManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/action/PlayerActionManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandFeature.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugDataCommand.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ReloadCommand.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/config/ConfigType.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/BukkitIntegrationManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java
rename plugin/src/main/java/net/momirealms/customcrops/{mechanic/item/factory => bukkit/item}/BukkitItemFactory.java (87%)
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemManager.java
rename plugin/src/main/java/net/momirealms/customcrops/{mechanic/item/factory/impl => bukkit/item}/ComponentItemFactory.java (92%)
rename plugin/src/main/java/net/momirealms/customcrops/{mechanic/item/factory/impl => bukkit/item}/UniversalItemFactory.java (89%)
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/misc/HologramManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/BlockRequirementManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/PlayerRequirementManager.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/BukkitSchedulerAdapter.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/DummyTask.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/BukkitExecutor.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/FoliaExecutor.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/sender/BukkitSenderFactory.java
create mode 100644 plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/IntegrationManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/VaultHook.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/CCPapi.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/ParseUtils.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BattlePassHook.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BetonQuestHook.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/ClueScrollsHook.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/season/InBuiltSeason.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/compatibility/season/RealisticSeasonsImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/Dependency.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/libraries/loader/JarInJarClassLoader.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/AdventureManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/ConfigManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/MessageManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/PacketManager.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/manager/PlaceholderManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/ConditionManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/AbstractEventItem.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleListener.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleProvider.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderListener.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderProvider.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ComponentKeys.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/Item.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/CFunction.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/ConditionWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractBlockWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractFurnitureWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceBlockWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceWrapper.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/AbstractFertilizer.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/CropConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/SprinklerConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/WateringCanConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/QualityCropConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SoilRetainConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SpeedGrowConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/VariationConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/YieldIncreaseConfig.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/CrowAttackAnimation.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/TempFakeItem.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/RequirementManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CSection.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableSection.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryCrop.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryGlass.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryPot.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryScarecrow.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemorySprinkler.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/scheduler/BukkitSchedulerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/scheduler/FoliaSchedulerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/scheduler/SchedulerImpl.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/scheduler/SyncScheduler.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/scheduler/task/ReplaceTask.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/util/ConfigUtils.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/util/FakeEntityUtils.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/util/ItemUtils.java
delete mode 100644 plugin/src/main/java/net/momirealms/customcrops/util/RotationUtils.java
create mode 100644 plugin/src/main/resources/commands.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/pt.yml
delete mode 100644 plugin/src/main/resources/messages/ru.yml
delete mode 100644 plugin/src/main/resources/messages/zh_cn.yml
create mode 100644 plugin/src/main/resources/translations/en.yml
create mode 100644 plugin/src/main/resources/translations/zh_cn.yml
create mode 100644 settings.gradle.kts
diff --git a/CustomCrops_3.4_Basic_Pack.zip b/CustomCrops_Basic_Pack.zip
similarity index 100%
rename from CustomCrops_3.4_Basic_Pack.zip
rename to CustomCrops_Basic_Pack.zip
diff --git a/README.md b/README.md
index 1d1e764..6696858 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,38 @@
-# Note: This project is undergoing a major refactoring. Please switch to the 3.6-dev branch for the new API.
-
# Custom-Crops

-
-
-
-[](https://github.com/Xiao-MoMi/Custom-Crops/)
-
[](https://jitpack.io/#Xiao-MoMi/Custom-Crops)
+[](https://github.com/Xiao-MoMi/Custom-Crops/)
+
+
+
+
-Ultra-customizable planting experience for Minecraft servers
-
-### Support the developer
-
-https://afdian.net/@xiaomomi
-
-https://polymart.org/resource/customcrops.2625
+CustomCrops is a Paper plugin crafted to deliver an exceptional planting experience for Minecraft servers, with a strong emphasis on customization and performance. It employs Zstd compression for data serialization, ensuring high efficiency comparable to Minecraft's own serialization techniques. The plugin optimizes server performance by running its tick system across multiple threads, reverting to the main thread only when required. Additionally, CustomCrops offers a comprehensive API that enables developers to create custom block mechanics with specific interaction and tick behaviors, such as a fish trap block that periodically provides players with fish.
## How to build
-### Windows
-
#### Command Line
-Install JDK 17 and set the JDK installation path to JAVA_HOME as an environment variable.\
-Start powershell and change directory to the project folder.\
-Execute ".\gradlew build" and get the jar at /target/CustomCrops-plugin-version.jar.
+Install JDK 17 & 21. \
+Start terminal and change directory to the project folder.\
+Execute ".\gradlew build" and get the artifact under /target folder
#### IDE
-Import the project and execute gradle build action.
+Import the project and execute gradle build action. \
+Get the artifact under /target folder
-## Use CustomCrops API
+## Support the developer
+
+Polymart: https://polymart.org/resource/customfishing.2723 \
+Afdian: https://afdian.net/@xiaomomi
+
+## CustomCrops API
### Maven
-```
+```html
jitpack
@@ -44,7 +40,7 @@ Import the project and execute gradle build action.
```
-```
+```html
com.github.Xiao-MoMi
@@ -56,24 +52,24 @@ Import the project and execute gradle build action.
```
### Gradle (Groovy)
-```
+```groovy
repositories {
maven { url 'https://jitpack.io' }
}
```
-```
+```groovy
dependencies {
compileOnly 'com.github.Xiao-MoMi:Custom-Crops:{LATEST}'
}
```
### Gradle (Kotlin)
-```
+```kotlin
repositories {
maven("https://jitpack.io/")
}
```
-```
+```kotlin
dependencies {
compileOnly("com.github.Xiao-MoMi:Custom-Crops:{LATEST}")
}
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index 4e9c7bd..dff97c2 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -1,11 +1,28 @@
-dependencies {
- compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
- implementation("com.flowpowered:flow-nbt:2.0.2")
+plugins {
+ id("io.github.goooler.shadow") version "8.1.8"
+ id("maven-publish")
}
-tasks.withType {
- options.encoding = "UTF-8"
- options.release.set(17)
+repositories {
+ mavenCentral()
+ maven("https://jitpack.io/")
+ maven("https://repo.papermc.io/repository/maven-public/")
+ maven("https://repo.rapture.pw/repository/maven-releases/") // flow nbt
+ maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}")
+ implementation("com.flowpowered:flow-nbt:${rootProject.properties["flow_nbt_version"]}")
+ implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") {
+ exclude(module = "adventure-bom")
+ exclude(module = "checker-qual")
+ exclude(module = "annotations")
+ }
+ compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT")
+ compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
+ compileOnly("com.github.Xiao-MoMi:Sparrow-Heart:${rootProject.properties["sparrow_heart_version"]}")
}
java {
@@ -14,4 +31,30 @@ java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
-}
\ No newline at end of file
+}
+
+tasks.withType {
+ options.encoding = "UTF-8"
+ options.release.set(17)
+ dependsOn(tasks.clean)
+}
+
+tasks {
+ shadowJar {
+ archiveClassifier = ""
+ archiveFileName = "CustomCrops-${rootProject.properties["project_version"]}.jar"
+ relocate("net.kyori", "net.momirealms.customcrops.libraries")
+ relocate("dev.dejvokep", "net.momirealms.customcrops.libraries")
+ }
+}
+
+publishing {
+ publications {
+ create("mavenJava") {
+ groupId = "net.momirealms"
+ artifactId = "CustomCrops"
+ version = rootProject.version.toString()
+ artifact(tasks.shadowJar)
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/BukkitCustomCropsPlugin.java b/api/src/main/java/net/momirealms/customcrops/api/BukkitCustomCropsPlugin.java
new file mode 100644
index 0000000..cdf77e6
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/BukkitCustomCropsPlugin.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api;
+
+import net.momirealms.customcrops.api.action.ActionManager;
+import net.momirealms.customcrops.api.core.AbstractItemManager;
+import net.momirealms.customcrops.api.core.ConfigManager;
+import net.momirealms.customcrops.api.core.RegistryAccess;
+import net.momirealms.customcrops.api.core.world.WorldManager;
+import net.momirealms.customcrops.api.integration.IntegrationManager;
+import net.momirealms.customcrops.api.misc.cooldown.CoolDownManager;
+import net.momirealms.customcrops.api.misc.placeholder.PlaceholderManager;
+import net.momirealms.customcrops.api.requirement.RequirementManager;
+import net.momirealms.customcrops.common.dependency.DependencyManager;
+import net.momirealms.customcrops.common.locale.TranslationManager;
+import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
+import net.momirealms.customcrops.common.plugin.scheduler.AbstractJavaScheduler;
+import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter;
+import net.momirealms.customcrops.common.sender.SenderFactory;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.plugin.Plugin;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class BukkitCustomCropsPlugin implements CustomCropsPlugin {
+
+ private static BukkitCustomCropsPlugin instance;
+ private final Plugin boostrap;
+
+ protected AbstractJavaScheduler scheduler;
+ protected DependencyManager dependencyManager;
+ protected TranslationManager translationManager;
+ protected AbstractItemManager itemManager;
+ protected PlaceholderManager placeholderManager;
+ protected CoolDownManager coolDownManager;
+ protected ConfigManager configManager;
+ protected IntegrationManager integrationManager;
+ protected WorldManager worldManager;
+ protected RegistryAccess registryAccess;
+ protected SenderFactory senderFactory;
+
+ protected final Map, ActionManager>> actionManagers = new HashMap<>();
+ protected final Map, RequirementManager>> requirementManagers = new HashMap<>();
+
+ /**
+ * Constructs a new BukkitCustomCropsPlugin instance.
+ *
+ * @param boostrap the plugin instance used to initialize this class
+ */
+ public BukkitCustomCropsPlugin(Plugin boostrap) {
+ if (!boostrap.getName().equals("CustomCrops")) {
+ throw new IllegalArgumentException("CustomCrops plugin requires custom crops plugin");
+ }
+ this.boostrap = boostrap;
+ instance = this;
+ }
+
+ /**
+ * Retrieves the singleton instance of BukkitCustomFishingPlugin.
+ *
+ * @return the singleton instance
+ * @throws IllegalArgumentException if the plugin is not initialized
+ */
+ public static BukkitCustomCropsPlugin getInstance() {
+ if (instance == null) {
+ throw new IllegalArgumentException("Plugin not initialized");
+ }
+ return instance;
+ }
+
+ /**
+ * Retrieves the plugin instance used to initialize this class.
+ *
+ * @return the {@link Plugin} instance
+ */
+ public Plugin getBoostrap() {
+ return boostrap;
+ }
+
+ /**
+ * Retrieves the DependencyManager.
+ *
+ * @return the {@link DependencyManager}
+ */
+ @Override
+ public DependencyManager getDependencyManager() {
+ return dependencyManager;
+ }
+
+ /**
+ * Retrieves the TranslationManager.
+ *
+ * @return the {@link TranslationManager}
+ */
+ @Override
+ public TranslationManager getTranslationManager() {
+ return translationManager;
+ }
+
+ public AbstractItemManager getItemManager() {
+ return itemManager;
+ }
+
+ @Override
+ public SchedulerAdapter getScheduler() {
+ return scheduler;
+ }
+
+ public SenderFactory getSenderFactory() {
+ return senderFactory;
+ }
+
+ public WorldManager getWorldManager() {
+ return worldManager;
+ }
+
+ public IntegrationManager getIntegrationManager() {
+ return integrationManager;
+ }
+
+ public PlaceholderManager getPlaceholderManager() {
+ return placeholderManager;
+ }
+
+ /**
+ * Logs a debug message.
+ *
+ * @param message the message to log
+ */
+ public abstract void debug(Object message);
+
+ public File getDataFolder() {
+ return boostrap.getDataFolder();
+ }
+
+ public RegistryAccess getRegistryAccess() {
+ return registryAccess;
+ }
+
+ @SuppressWarnings("unchecked")
+ public ActionManager getActionManager(Class type) {
+ if (type == null) {
+ throw new IllegalArgumentException("Type cannot be null");
+ }
+ return (ActionManager) actionManagers.get(type);
+ }
+
+ @SuppressWarnings("unchecked")
+ public RequirementManager getRequirementManager(Class type) {
+ if (type == null) {
+ throw new IllegalArgumentException("Type cannot be null");
+ }
+ return (RequirementManager) instance.requirementManagers.get(type);
+ }
+
+ public CoolDownManager getCoolDownManager() {
+ return coolDownManager;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/CustomCropsPlugin.java b/api/src/main/java/net/momirealms/customcrops/api/CustomCropsPlugin.java
deleted file mode 100644
index a24ef6e..0000000
--- a/api/src/main/java/net/momirealms/customcrops/api/CustomCropsPlugin.java
+++ /dev/null
@@ -1,129 +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.customcrops.api;
-
-import net.momirealms.customcrops.api.manager.*;
-import net.momirealms.customcrops.api.scheduler.Scheduler;
-import org.bukkit.plugin.java.JavaPlugin;
-
-public abstract class CustomCropsPlugin extends JavaPlugin {
-
- protected static CustomCropsPlugin instance;
- protected VersionManager versionManager;
- protected ConfigManager configManager;
- protected Scheduler scheduler;
- protected RequirementManager requirementManager;
- protected ActionManager actionManager;
- protected IntegrationManager integrationManager;
- protected CoolDownManager coolDownManager;
- protected WorldManager worldManager;
- protected ItemManager itemManager;
- protected AdventureManager adventure;
- protected MessageManager messageManager;
- protected ConditionManager conditionManager;
- protected PlaceholderManager placeholderManager;
-
- public CustomCropsPlugin() {
- instance = this;
- }
-
- public static CustomCropsPlugin getInstance() {
- return instance;
- }
-
- public static CustomCropsPlugin get() {
- return instance;
- }
-
- /* Get version manager */
- public VersionManager getVersionManager() {
- return versionManager;
- }
-
- /* Get config manager */
- public ConfigManager getConfigManager() {
- return configManager;
- }
-
- /* Get scheduler */
- public Scheduler getScheduler() {
- return scheduler;
- }
-
- /* Get requirement manager */
- public RequirementManager getRequirementManager() {
- return requirementManager;
- }
-
- /* Get integration manager */
- public IntegrationManager getIntegrationManager() {
- return integrationManager;
- }
-
- /* Get action manager */
- public ActionManager getActionManager() {
- return actionManager;
- }
-
- /* Get cool down manager */
- public CoolDownManager getCoolDownManager() {
- return coolDownManager;
- }
-
- /* Get world data manager */
- public WorldManager getWorldManager() {
- return worldManager;
- }
-
- /* Get item manager */
- public ItemManager getItemManager() {
- return itemManager;
- }
-
- /* Get message manager */
- public MessageManager getMessageManager() {
- return messageManager;
- }
-
- /* Get adventure manager */
- public AdventureManager getAdventure() {
- return adventure;
- }
-
- /* Get condition manager */
- public ConditionManager getConditionManager() {
- return conditionManager;
- }
-
- /* Get placeholder manager */
- public PlaceholderManager getPlaceholderManager() {
- return placeholderManager;
- }
-
- public abstract boolean isHookedPluginEnabled(String plugin);
-
- public abstract void debug(String debug);
-
- public abstract void reload();
-
- public abstract boolean isHookedPluginEnabled(String hooked, String... versionPrefix);
-
- public abstract boolean doesHookedPluginExist(String plugin);
-
- public abstract String getServerVersion();
-}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/action/AbstractActionManager.java b/api/src/main/java/net/momirealms/customcrops/api/action/AbstractActionManager.java
new file mode 100644
index 0000000..5edb738
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/AbstractActionManager.java
@@ -0,0 +1,891 @@
+package net.momirealms.customcrops.api.action;
+
+import dev.dejvokep.boostedyaml.block.implementation.Section;
+import net.kyori.adventure.audience.Audience;
+import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
+import net.momirealms.customcrops.api.context.Context;
+import net.momirealms.customcrops.api.context.ContextKeys;
+import net.momirealms.customcrops.api.core.*;
+import net.momirealms.customcrops.api.core.block.*;
+import net.momirealms.customcrops.api.core.item.Fertilizer;
+import net.momirealms.customcrops.api.core.item.FertilizerConfig;
+import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
+import net.momirealms.customcrops.api.core.world.CustomCropsChunk;
+import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
+import net.momirealms.customcrops.api.core.world.Pos3;
+import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
+import net.momirealms.customcrops.api.event.CropPlantEvent;
+import net.momirealms.customcrops.api.misc.placeholder.BukkitPlaceholderManager;
+import net.momirealms.customcrops.api.misc.value.MathValue;
+import net.momirealms.customcrops.api.misc.value.TextValue;
+import net.momirealms.customcrops.api.requirement.Requirement;
+import net.momirealms.customcrops.api.util.*;
+import net.momirealms.customcrops.common.helper.AdventureHelper;
+import net.momirealms.customcrops.common.helper.VersionHelper;
+import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask;
+import net.momirealms.customcrops.common.util.*;
+import net.momirealms.sparrow.heart.SparrowHeart;
+import net.momirealms.sparrow.heart.feature.entity.FakeEntity;
+import net.momirealms.sparrow.heart.feature.entity.armorstand.FakeArmorStand;
+import net.momirealms.sparrow.heart.feature.entity.display.FakeItemDisplay;
+import org.bukkit.*;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemStack;
+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 abstract class AbstractActionManager implements ActionManager {
+
+ protected final BukkitCustomCropsPlugin plugin;
+ private final HashMap> actionFactoryMap = new HashMap<>();
+ private static final String EXPANSION_FOLDER = "expansions/action";
+
+ public AbstractActionManager(BukkitCustomCropsPlugin plugin) {
+ this.plugin = plugin;
+ this.registerBuiltInActions();
+ }
+
+ protected void registerBuiltInActions() {
+ this.registerCommandAction();
+ this.registerBroadcastAction();
+ this.registerNearbyMessage();
+ this.registerNearbyActionBar();
+ this.registerNearbyTitle();
+ this.registerParticleAction();
+ this.registerQualityCropsAction();
+ this.registerDropItemsAction();
+ this.registerLegacyDropItemsAction();
+ this.registerFakeItemAction();
+ this.registerPlantAction();
+ this.registerBreakAction();
+ }
+
+ @Override
+ public boolean registerAction(ActionFactory actionFactory, String... types) {
+ for (String type : types) {
+ if (this.actionFactoryMap.containsKey(type)) return false;
+ }
+ for (String type : types) {
+ this.actionFactoryMap.put(type, actionFactory);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean unregisterAction(String type) {
+ return this.actionFactoryMap.remove(type) != null;
+ }
+
+ @Override
+ public boolean hasAction(@NotNull String type) {
+ return actionFactoryMap.containsKey(type);
+ }
+
+ @Nullable
+ @Override
+ public ActionFactory getActionFactory(@NotNull String type) {
+ return actionFactoryMap.get(type);
+ }
+
+ @Override
+ public Action parseAction(Section section) {
+ if (section == null) return Action.empty();
+ ActionFactory factory = getActionFactory(section.getString("type"));
+ if (factory == null) {
+ plugin.getPluginLogger().warn("Action type: " + section.getString("type") + " doesn't exist.");
+ return Action.empty();
+ }
+ 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 Action.empty();
+ }
+ return factory.process(args, 1);
+ }
+
+ @SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"})
+ protected void loadExpansions(Class tClass) {
+ 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 extends ActionExpansion> expansionClass = (Class extends ActionExpansion>) ClassUtils.findClass(expansionJar, ActionExpansion.class, tClass);
+ classes.add(expansionClass);
+ } catch (IOException | ClassNotFoundException e) {
+ plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e);
+ }
+ }
+ }
+ try {
+ for (Class extends ActionExpansion> expansionClass : classes) {
+ ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance();
+ unregisterAction(expansion.getActionType());
+ registerAction(expansion.getActionFactory(), expansion.getActionType());
+ 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);
+ }
+ }
+
+ protected void registerBroadcastAction() {
+ registerAction((args, chance) -> {
+ List messages = ListUtils.toList(args);
+ return context -> {
+ if (Math.random() > chance) return;
+ OfflinePlayer offlinePlayer = null;
+ if (context.holder() instanceof Player player) {
+ offlinePlayer = player;
+ }
+ List replaced = plugin.getPlaceholderManager().parse(offlinePlayer, messages, context.placeholderMap());
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ Audience audience = plugin.getSenderFactory().getAudience(player);
+ for (String text : replaced) {
+ audience.sendMessage(AdventureHelper.miniMessage(text));
+ }
+ }
+ };
+ }, "broadcast");
+ }
+
+ protected void registerNearbyMessage() {
+ registerAction((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);
+ OfflinePlayer owner = null;
+ if (context.holder() instanceof Player player) {
+ owner = player;
+ }
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ for (Player player : location.getWorld().getPlayers()) {
+ if (LocationUtils.getDistance(player.getLocation(), location) <= 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));
+ }
+ }
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at message-nearby action which should be Section");
+ return Action.empty();
+ }
+ }, "message-nearby");
+ }
+
+ protected void registerNearbyActionBar() {
+ registerAction((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;
+ OfflinePlayer owner = null;
+ if (context.holder() instanceof Player player) {
+ owner = player;
+ }
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ double realRange = range.evaluate(context);
+ for (Player player : location.getWorld().getPlayers()) {
+ if (LocationUtils.getDistance(player.getLocation(), location) <= 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));
+ }
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at actionbar-nearby action which should be Section");
+ return Action.empty();
+ }
+ }, "actionbar-nearby");
+ }
+
+ protected void registerCommandAction() {
+ registerAction((args, chance) -> {
+ List commands = ListUtils.toList(args);
+ return context -> {
+ if (Math.random() > chance) return;
+ OfflinePlayer owner = null;
+ if (context.holder() instanceof Player player) {
+ owner = player;
+ }
+ List replaced = BukkitPlaceholderManager.getInstance().parse(owner, commands, context.placeholderMap());
+ plugin.getScheduler().sync().run(() -> {
+ for (String text : replaced) {
+ Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
+ }
+ }, null);
+ };
+ }, "command");
+ registerAction((args, chance) -> {
+ List commands = ListUtils.toList(args);
+ return context -> {
+ if (Math.random() > chance) return;
+ OfflinePlayer owner = null;
+ if (context.holder() instanceof Player player) {
+ owner = player;
+ }
+ String random = commands.get(ThreadLocalRandom.current().nextInt(commands.size()));
+ random = BukkitPlaceholderManager.getInstance().parse(owner, random, context.placeholderMap());
+ String finalRandom = random;
+ plugin.getScheduler().sync().run(() -> {
+ Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom);
+ }, null);
+ };
+ }, "random-command");
+ registerAction((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;
+ OfflinePlayer owner = null;
+ if (context.holder() instanceof Player player) {
+ owner = player;
+ }
+ double realRange = range.evaluate(context);
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ for (Player player : location.getWorld().getPlayers()) {
+ if (LocationUtils.getDistance(player.getLocation(), location) <= 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);
+ }
+ }
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at command-nearby action which should be Section");
+ return Action.empty();
+ }
+ }, "command-nearby");
+ }
+
+ protected void registerBundleAction(Class tClass) {
+ registerAction((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);
+ }
+ };
+ }, "chain");
+ registerAction((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);
+ }
+ };
+ }, "delay");
+ registerAction((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);
+ };
+ }, "timer");
+ registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ Action[] actions = parseActions(section.getSection("actions"));
+ Requirement[] requirements = plugin.getRequirementManager(tClass).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 Action.empty();
+ }
+ }, "conditional");
+ registerAction((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(tClass).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 Action.empty();
+ }
+ }, "priority");
+ }
+
+ protected void registerNearbyTitle() {
+ registerAction((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));
+ for (Player player : location.getWorld().getPlayers()) {
+ if (LocationUtils.getDistance(player.getLocation(), location) <= 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
+ );
+ }
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title-nearby action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "title-nearby");
+ }
+
+ protected void registerParticleAction() {
+ registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ Particle particleType = ParticleUtils.getParticle(section.getString("particle", "ASH").toUpperCase(Locale.ENGLISH));
+ double x = section.getDouble("x",0.0);
+ double y = section.getDouble("y",0.0);
+ double z = section.getDouble("z",0.0);
+ double offSetX = section.getDouble("offset-x",0.0);
+ double offSetY = section.getDouble("offset-y",0.0);
+ double offSetZ = section.getDouble("offset-z",0.0);
+ int count = section.getInt("count", 1);
+ double extra = section.getDouble("extra", 0.0);
+ float scale = section.getDouble("scale", 1d).floatValue();
+
+ ItemStack itemStack;
+ if (section.contains("itemStack"))
+ itemStack = BukkitCustomCropsPlugin.getInstance()
+ .getItemManager()
+ .build(null, section.getString("itemStack"));
+ else
+ itemStack = null;
+
+ Color color;
+ if (section.contains("color")) {
+ String[] rgb = section.getString("color","255,255,255").split(",");
+ color = Color.fromRGB(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
+ } else {
+ color = null;
+ }
+
+ Color toColor;
+ if (section.contains("color")) {
+ String[] rgb = section.getString("to-color","255,255,255").split(",");
+ toColor = Color.fromRGB(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
+ } else {
+ toColor = null;
+ }
+
+ return context -> {
+ if (Math.random() > chance) return;
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ location.getWorld().spawnParticle(
+ particleType,
+ location.getX() + x, location.getY() + y, location.getZ() + z,
+ count,
+ offSetX, offSetY, offSetZ,
+ extra,
+ itemStack != null ? itemStack : (color != null && toColor != null ? new Particle.DustTransition(color, toColor, scale) : (color != null ? new Particle.DustOptions(color, scale) : null))
+ );
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at particle action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "particle");
+ }
+
+ protected void registerQualityCropsAction() {
+ registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ MathValue min = MathValue.auto(section.get("min"));
+ MathValue max = MathValue.auto(section.get("max"));
+ boolean toInv = section.getBoolean("to-inventory", false);
+ String[] qualityLoots = new String[ConfigManager.defaultQualityRatio().length];
+ for (int i = 1; i <= ConfigManager.defaultQualityRatio().length; i++) {
+ qualityLoots[i-1] = section.getString("items." + i);
+ if (qualityLoots[i-1] == null) {
+ plugin.getPluginLogger().warn("items." + i + " should not be null");
+ qualityLoots[i-1] = "";
+ }
+ }
+ return context -> {
+ if (Math.random() > chance) return;
+ double[] ratio = ConfigManager.defaultQualityRatio();
+ int random = RandomUtils.generateRandomInt((int) min.evaluate(context), (int) max.evaluate(context));
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ Optional> world = plugin.getWorldManager().getWorld(location.getWorld());
+ if (world.isEmpty()) {
+ return;
+ }
+ Pos3 pos3 = Pos3.from(location);
+ Fertilizer[] fertilizers = null;
+ Player player = null;
+ if (context.holder() instanceof Player p) {
+ player = p;
+ }
+ Pos3 potLocation = pos3.add(0, -1, 0);
+ Optional chunk = world.get().getChunk(potLocation.toChunkPos());
+ if (chunk.isPresent()) {
+ Optional state = chunk.get().getBlockState(potLocation);
+ if (state.isPresent()) {
+ if (state.get().type() instanceof PotBlock potBlock) {
+ fertilizers = potBlock.fertilizers(state.get());
+ }
+ }
+ }
+ ArrayList configs = new ArrayList<>();
+ if (fertilizers != null) {
+ for (Fertilizer fertilizer : fertilizers) {
+ Optional.ofNullable(fertilizer.config()).ifPresent(configs::add);
+ }
+ }
+ for (FertilizerConfig config : configs) {
+ random = config.processDroppedItemAmount(random);
+ double[] newRatio = config.overrideQualityRatio();
+ if (newRatio != null) {
+ ratio = newRatio;
+ }
+ }
+ for (int i = 0; i < random; i++) {
+ double r1 = Math.random();
+ for (int j = 0; j < ratio.length; j++) {
+ if (r1 < ratio[j]) {
+ ItemStack drop = plugin.getItemManager().build(player, qualityLoots[j]);
+ if (drop == null || drop.getType() == Material.AIR) return;
+ if (toInv && player != null) {
+ PlayerUtils.giveItem(player, drop, 1);
+ } else {
+ location.getWorld().dropItemNaturally(location, drop);
+ }
+ break;
+ }
+ }
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at quality-crops action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "quality-crops");
+ }
+
+ protected void registerDropItemsAction() {
+ registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ boolean ignoreFertilizer = section.getBoolean("ignore-fertilizer", true);
+ String item = section.getString("item");
+ MathValue min = MathValue.auto(section.get("min"));
+ MathValue max = MathValue.auto(section.get("max"));
+ boolean toInv = section.getBoolean("to-inventory", false);
+ return context -> {
+ if (Math.random() > chance) return;
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ Optional> world = plugin.getWorldManager().getWorld(location.getWorld());
+ if (world.isEmpty()) {
+ return;
+ }
+ Player player = null;
+ if (context.holder() instanceof Player p) {
+ player = p;
+ }
+ ItemStack itemStack = plugin.getItemManager().build(player, item);
+ if (itemStack != null) {
+ int random = RandomUtils.generateRandomInt((int) min.evaluate(context), (int) max.evaluate(context));
+ if (!ignoreFertilizer) {
+ Pos3 pos3 = Pos3.from(location);
+ Fertilizer[] fertilizers = null;
+ Pos3 potLocation = pos3.add(0, -1, 0);
+ Optional chunk = world.get().getChunk(potLocation.toChunkPos());
+ if (chunk.isPresent()) {
+ Optional state = chunk.get().getBlockState(potLocation);
+ if (state.isPresent()) {
+ if (state.get().type() instanceof PotBlock potBlock) {
+ fertilizers = potBlock.fertilizers(state.get());
+ }
+ }
+ }
+ ArrayList configs = new ArrayList<>();
+ if (fertilizers != null) {
+ for (Fertilizer fertilizer : fertilizers) {
+ Optional.ofNullable(fertilizer.config()).ifPresent(configs::add);
+ }
+ }
+ for (FertilizerConfig config : configs) {
+ random = config.processDroppedItemAmount(random);
+ }
+ }
+ itemStack.setAmount(random);
+ if (toInv && player != null) {
+ PlayerUtils.giveItem(player, itemStack, random);
+ } else {
+ location.getWorld().dropItemNaturally(location, itemStack);
+ }
+ } else {
+ plugin.getPluginLogger().warn("Item: " + item + " doesn't exist");
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at drop-item action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "drop-item");
+ }
+
+ protected void registerLegacyDropItemsAction() {
+ registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ List> actions = new ArrayList<>();
+ Section otherItemSection = section.getSection("other-items");
+ if (otherItemSection != null) {
+ for (Map.Entry entry : otherItemSection.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section inner) {
+ actions.add(requireNonNull(getActionFactory("drop-item")).process(inner, inner.getDouble("chance", 1D)));
+ }
+ }
+ }
+ Section qualitySection = section.getSection("quality-crops");
+ if (qualitySection != null) {
+ actions.add(requireNonNull(getActionFactory("quality-crops")).process(qualitySection, 1));
+ }
+ return context -> {
+ if (Math.random() > chance) return;
+ for (Action action : actions) {
+ action.trigger(context);
+ }
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at drop-items action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "drop-items");
+ }
+
+ private void registerFakeItemAction() {
+ registerAction(((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 useItemDisplay = section.getBoolean("use-item-display", false);
+ boolean onlyShowToOne = !section.getBoolean("visible-to-all", true);
+ String finalItemID = itemID;
+ return context -> {
+ if (Math.random() > chance) return;
+ if (context.argOrDefault(ContextKeys.OFFLINE, false)) return;
+ Player owner = null;
+ if (context.holder() instanceof Player p) {
+ owner = p;
+ }
+ Location location = position ? requireNonNull(context.arg(ContextKeys.LOCATION)).clone() : requireNonNull(owner).getLocation().clone();
+ location.add(x.evaluate(context), y.evaluate(context) - 1, z.evaluate(context));
+ location.setPitch(0);
+ location.setYaw((float) yaw.evaluate(context));
+ FakeEntity fakeEntity;
+ if (useItemDisplay && VersionHelper.isVersionNewerThan1_19_4()) {
+ location.add(0,1.5,0);
+ FakeItemDisplay itemDisplay = SparrowHeart.getInstance().createFakeItemDisplay(location);
+ itemDisplay.item(plugin.getItemManager().build(owner, finalItemID));
+ fakeEntity = itemDisplay;
+ } else {
+ FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location);
+ armorStand.invisible(true);
+ armorStand.equipment(EquipmentSlot.HEAD, plugin.getItemManager().build(owner, finalItemID));
+ fakeEntity = armorStand;
+ }
+ ArrayList viewers = new ArrayList<>();
+ if (onlyShowToOne) {
+ viewers.add(owner);
+ } else {
+ if (range > 0) {
+ for (Player player : location.getWorld().getPlayers()) {
+ if (LocationUtils.getDistance(player.getLocation(), location) <= range) {
+ viewers.add(player);
+ }
+ }
+ } else {
+ viewers.add(owner);
+ }
+ }
+ for (Player player : viewers) {
+ fakeEntity.spawn(player);
+ }
+ plugin.getScheduler().asyncLater(() -> {
+ for (Player player : viewers) {
+ if (player.isOnline() && player.isValid()) {
+ fakeEntity.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 Action.empty();
+ }
+ }), "fake-item");
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void registerPlantAction() {
+ this.registerAction((args, chance) -> {
+ if (args instanceof Section section) {
+ int point = section.getInt("point", 0);
+ String key = requireNonNull(section.getString("crop"));
+ int y = section.getInt("y", 0);
+ boolean triggerAction = section.getBoolean("trigger-event", false);
+ return context -> {
+ if (Math.random() > chance) return;
+ CropConfig cropConfig = Registries.CROP.get(key);
+ if (cropConfig == null) {
+ plugin.getPluginLogger().warn("`plant` action is not executed due to crop[" + key + "] not exists");
+ return;
+ }
+ Location cropLocation = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0,y,0);
+ Location potLocation = cropLocation.clone().subtract(0,1,0);
+ Optional> optionalWorld = plugin.getWorldManager().getWorld(cropLocation.getWorld());
+ if (optionalWorld.isEmpty()) {
+ return;
+ }
+ CustomCropsWorld> world = optionalWorld.get();
+ PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic();
+ Pos3 potPos3 = Pos3.from(potLocation);
+ String potItemID = plugin.getItemManager().blockID(potLocation);
+ PotConfig potConfig = Registries.ITEM_TO_POT.get(potItemID);
+ CustomCropsBlockState potState = potBlock.fixOrGetState(world, potPos3, potConfig, potItemID);
+ if (potState == null) {
+ plugin.getPluginLogger().warn("Pot doesn't exist below the crop when executing `plant` action at location[" + world.worldName() + "," + potPos3 + "]");
+ return;
+ }
+
+ CropBlock cropBlock = (CropBlock) BuiltInBlockMechanics.CROP.mechanic();
+ CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState();
+ cropBlock.id(state, key);
+ cropBlock.point(state, point);
+
+ if (context.holder() instanceof Player player) {
+ EquipmentSlot slot = requireNonNull(context.arg(ContextKeys.SLOT));
+ CropPlantEvent plantEvent = new CropPlantEvent(player, player.getInventory().getItem(slot), slot, cropLocation, cropConfig, state, point);
+ if (EventUtils.fireAndCheckCancel(plantEvent)) {
+ return;
+ }
+ cropBlock.point(state, plantEvent.getPoint());
+ if (triggerAction) {
+ ActionManager.trigger((Context) context, cropConfig.plantActions());
+ }
+ }
+
+ CropStageConfig stageConfigWithModel = cropConfig.stageWithModelByPoint(cropBlock.point(state));
+ world.addBlockState(Pos3.from(cropLocation), state);
+ plugin.getScheduler().sync().run(() -> {
+ plugin.getItemManager().remove(cropLocation, ExistenceForm.ANY);
+ plugin.getItemManager().place(cropLocation, stageConfigWithModel.existenceForm(), requireNonNull(stageConfigWithModel.stageID()), cropConfig.rotation() ? FurnitureRotation.random() : FurnitureRotation.NONE);
+ }, cropLocation);
+ };
+ } else {
+ plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plant action which is expected to be `Section`");
+ return Action.empty();
+ }
+ }, "plant", "replant");
+ }
+
+ protected void registerBreakAction() {
+ this.registerAction((args, chance) -> {
+ boolean triggerEvent = (boolean) args;
+ return context -> {
+ Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
+ Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld());
+ if (optionalWorld.isEmpty()) {
+ return;
+ }
+ Pos3 pos3 = Pos3.from(location);
+ CustomCropsWorld> world = optionalWorld.get();
+ Optional optionalState = world.getBlockState(pos3);
+ if (optionalState.isEmpty()) {
+ return;
+ }
+ CustomCropsBlockState state = optionalState.get();
+ if (!(state.type() instanceof CropBlock cropBlock)) {
+ return;
+ }
+ CropConfig config = cropBlock.config(state);
+ if (config == null) {
+ return;
+ }
+ if (triggerEvent) {
+ CropStageConfig stageConfig = config.stageWithModelByPoint(cropBlock.point(state));
+ Player player = null;
+ if (context.holder() instanceof Player p) {
+ player = p;
+ }
+ FakeCancellable fakeCancellable = new FakeCancellable();
+ if (player != null) {
+ EquipmentSlot slot = requireNonNull(context.arg(ContextKeys.SLOT));
+ ItemStack itemStack = player.getInventory().getItem(slot);
+ state.type().onBreak(new WrappedBreakEvent(player, null, world, location, stageConfig.stageID(), itemStack, plugin.getItemManager().id(itemStack), BreakReason.ACTION, fakeCancellable));
+ } else {
+ state.type().onBreak(new WrappedBreakEvent(null, null, world, location, stageConfig.stageID(), null, null, BreakReason.ACTION, fakeCancellable));
+ }
+ if (fakeCancellable.isCancelled()) {
+ return;
+ }
+ }
+ world.removeBlockState(pos3);
+ plugin.getItemManager().remove(location, ExistenceForm.ANY);
+ };
+ }, "break");
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/Variation.java b/api/src/main/java/net/momirealms/customcrops/api/action/Action.java
similarity index 53%
rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/Variation.java
rename to api/src/main/java/net/momirealms/customcrops/api/action/Action.java
index b11e32b..a636361 100644
--- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/Variation.java
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/Action.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) <2022>
+ * Copyright (C) <2024>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,16 +15,25 @@
* along with this program. If not, see .
*/
-package net.momirealms.customcrops.api.mechanic.item.fertilizer;
+package net.momirealms.customcrops.api.action;
-import net.momirealms.customcrops.api.mechanic.item.Fertilizer;
+import net.momirealms.customcrops.api.context.Context;
-public interface Variation extends Fertilizer {
+/**
+ * The Action interface defines a generic action that can be triggered based on a provided context.
+ *
+ * @param the type of the object that is used in the context for triggering the action.
+ */
+public interface Action {
/**
- * Get the bonus of variation chance
+ * Triggers the action based on the provided condition.
*
- * @return chance bonus
+ * @param context the context
*/
- double getChanceBonus();
+ void trigger(Context context);
+
+ static Action empty() {
+ return EmptyAction.instance();
+ }
}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/action/ActionExpansion.java b/api/src/main/java/net/momirealms/customcrops/api/action/ActionExpansion.java
new file mode 100644
index 0000000..d2dc3d3
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/ActionExpansion.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api.action;
+
+/**
+ * Abstract class representing an expansion of an action.
+ * 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();
+
+ /**
+ * 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/customcrops/api/common/item/EventItem.java b/api/src/main/java/net/momirealms/customcrops/api/action/ActionFactory.java
similarity index 58%
rename from api/src/main/java/net/momirealms/customcrops/api/common/item/EventItem.java
rename to api/src/main/java/net/momirealms/customcrops/api/action/ActionFactory.java
index c6a3ad0..61059b0 100644
--- a/api/src/main/java/net/momirealms/customcrops/api/common/item/EventItem.java
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/ActionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) <2022>
+ * Copyright (C) <2024>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,18 +15,20 @@
* along with this program. If not, see .
*/
-package net.momirealms.customcrops.api.common.item;
+package net.momirealms.customcrops.api.action;
-import net.momirealms.customcrops.api.mechanic.action.ActionTrigger;
-import net.momirealms.customcrops.api.mechanic.requirement.State;
-
-public interface EventItem {
+/**
+ * Interface representing a factory for creating actions.
+ *
+ * @param the type of object that the action will operate on
+ */
+public interface ActionFactory {
/**
- * Trigger events
+ * Constructs an action based on the provided arguments.
*
- * @param actionTrigger trigger
- * @param state state
+ * @param args the args containing the arguments needed to build the action
+ * @return the constructed action
*/
- void trigger(ActionTrigger actionTrigger, State state);
+ Action process(Object args, double chance);
}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/action/ActionManager.java b/api/src/main/java/net/momirealms/customcrops/api/action/ActionManager.java
new file mode 100644
index 0000000..082c711
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/ActionManager.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api.action;
+
+import dev.dejvokep.boostedyaml.block.implementation.Section;
+import net.momirealms.customcrops.api.context.Context;
+import net.momirealms.customcrops.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 actionFactory The factory responsible for creating instances of the action.
+ * @param alias The type identifier of the action.
+ * @return True if registration was successful, false if the type is already registered.
+ */
+ boolean registerAction(ActionFactory actionFactory, String... alias);
+
+ /**
+ * 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/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionTrigger.java b/api/src/main/java/net/momirealms/customcrops/api/action/ActionTrigger.java
similarity index 79%
rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionTrigger.java
rename to api/src/main/java/net/momirealms/customcrops/api/action/ActionTrigger.java
index 909b7d9..caa7dd0 100644
--- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionTrigger.java
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/ActionTrigger.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) <2022>
+ * Copyright (C) <2024>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +15,10 @@
* along with this program. If not, see .
*/
-package net.momirealms.customcrops.mechanic.item.function;
+package net.momirealms.customcrops.api.action;
-public enum FunctionTrigger {
+public enum ActionTrigger {
PLACE,
BREAK,
- BE_INTERACTED,
- INTERACT_AT,
- INTERACT_AIR
+ WORK, INTERACT
}
diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/EmptyCondition.java b/api/src/main/java/net/momirealms/customcrops/api/action/EmptyAction.java
similarity index 58%
rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/EmptyCondition.java
rename to api/src/main/java/net/momirealms/customcrops/api/action/EmptyAction.java
index f046062..3585f65 100644
--- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/EmptyCondition.java
+++ b/api/src/main/java/net/momirealms/customcrops/api/action/EmptyAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) <2022>
+ * Copyright (C) <2024>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,17 +15,21 @@
* along with this program. If not, see .
*/
-package net.momirealms.customcrops.mechanic.condition;
+package net.momirealms.customcrops.api.action;
-import net.momirealms.customcrops.api.mechanic.condition.Condition;
-import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock;
+import net.momirealms.customcrops.api.context.Context;
-public class EmptyCondition implements 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 EmptyCondition instance = new EmptyCondition();
+ public static EmptyAction instance() {
+ return new EmptyAction<>();
+ }
@Override
- public boolean isConditionMet(CustomCropsBlock block, boolean offline) {
- return true;
+ public void trigger(Context context) {
}
}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/common/Tuple.java b/api/src/main/java/net/momirealms/customcrops/api/common/Tuple.java
deleted file mode 100644
index f7789a0..0000000
--- a/api/src/main/java/net/momirealms/customcrops/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.customcrops.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/customcrops/api/common/item/KeyItem.java b/api/src/main/java/net/momirealms/customcrops/api/common/item/KeyItem.java
deleted file mode 100644
index 92d06af..0000000
--- a/api/src/main/java/net/momirealms/customcrops/api/common/item/KeyItem.java
+++ /dev/null
@@ -1,28 +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.customcrops.api.common.item;
-
-public interface KeyItem {
-
- /**
- * Get item's key
- *
- * @return key
- */
- String getKey();
-}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/context/AbstractContext.java b/api/src/main/java/net/momirealms/customcrops/api/context/AbstractContext.java
new file mode 100644
index 0000000..effa86c
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/context/AbstractContext.java
@@ -0,0 +1,64 @@
+package net.momirealms.customcrops.api.context;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class AbstractContext implements Context {
+
+ private final T holder;
+ private final Map, Object> args;
+ private final Map placeholderMap;
+
+ public AbstractContext(@Nullable T holder, boolean sync) {
+ this.holder = holder;
+ this.args = sync ? new ConcurrentHashMap<>() : new HashMap<>();
+ this.placeholderMap = sync ? new ConcurrentHashMap<>() : new HashMap<>();
+ }
+
+ @Override
+ public Map, Object> args() {
+ return args;
+ }
+
+ @Override
+ public Map placeholderMap() {
+ return placeholderMap;
+ }
+
+ @Override
+ public AbstractContext arg(ContextKeys key, C value) {
+ if (key == null || value == null) return this;
+ this.args.put(key, value);
+ this.placeholderMap.put("{" + key.key() + "}", value.toString());
+ return this;
+ }
+
+ @Override
+ public AbstractContext combine(Context other) {
+ this.args.putAll(other.args());
+ this.placeholderMap.putAll(other.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 T holder() {
+ return holder;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/context/BlockContextImpl.java b/api/src/main/java/net/momirealms/customcrops/api/context/BlockContextImpl.java
new file mode 100644
index 0000000..c39cf7e
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/context/BlockContextImpl.java
@@ -0,0 +1,19 @@
+package net.momirealms.customcrops.api.context;
+
+import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
+import org.jetbrains.annotations.NotNull;
+
+public class BlockContextImpl extends AbstractContext {
+
+ public BlockContextImpl(@NotNull CustomCropsBlockState block, boolean sync) {
+ super(block, sync);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockContext{" +
+ "args=" + args() +
+ ", block=" + holder() +
+ '}';
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/context/Context.java b/api/src/main/java/net/momirealms/customcrops/api/context/Context.java
new file mode 100644
index 0000000..1cb4999
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/context/Context.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api.context;
+
+import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+
+/**
+ * The Context interface represents a generic context for custom crops 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 crops 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);
+
+ /**
+ * 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.
+ */
+ default C argOrDefault(ContextKeys key, C value) {
+ C result = arg(key);
+ return result == null ? value : result;
+ }
+
+ /**
+ * Remove the key from the context
+ *
+ * @param key the ContextKeys key
+ * @return the removed value
+ * @param the type of the value being removed.
+ */
+ @Nullable
+ C remove(ContextKeys key);
+
+ /**
+ * Gets the holder of this context.
+ *
+ * @return the holder object of type T.
+ */
+ T holder();
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Creates a block-specific context.
+ *
+ * @param block the block to be used as the holder of the context.
+ * @return a new Context instance with the specified block as the holder.
+ */
+ static Context block(@NotNull CustomCropsBlockState block) {
+ return new BlockContextImpl(block, false);
+ }
+
+ /**
+ * Creates a player-specific context.
+ *
+ * @param player the player to be used as the holder of the context.
+ * @param threadSafe is the created map thread safe
+ * @return a new Context instance with the specified player as the holder.
+ */
+ static Context player(@Nullable Player player, boolean threadSafe) {
+ return new PlayerContextImpl(player, threadSafe);
+ }
+
+ /**
+ * Creates a block-specific context.
+ *
+ * @param block the block to be used as the holder of the context.
+ * @param threadSafe is the created map thread safe
+ * @return a new Context instance with the specified block as the holder.
+ */
+ static Context block(@NotNull CustomCropsBlockState block, boolean threadSafe) {
+ return new BlockContextImpl(block, threadSafe);
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/context/ContextKeys.java b/api/src/main/java/net/momirealms/customcrops/api/context/ContextKeys.java
new file mode 100644
index 0000000..9c69bb4
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/context/ContextKeys.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api.context;
+
+import org.bukkit.Location;
+import org.bukkit.inventory.EquipmentSlot;
+
+import java.util.Objects;
+
+/**
+ * Represents keys for accessing context values with specific types.
+ *
+ * @param the type of the value associated with the context key.
+ */
+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 PLAYER = of("player", String.class);
+ public static final ContextKeys SLOT = of("slot", EquipmentSlot.class);
+ public static final ContextKeys TEMP_NEAR_PLAYER = of("near", String.class);
+ public static final ContextKeys OFFLINE = of("offline", Boolean.class);
+
+ private final String key;
+ private final Class type;
+
+ protected ContextKeys(String key, Class type) {
+ this.key = key;
+ this.type = type;
+ }
+
+ /**
+ * Gets the key.
+ *
+ * @return the key.
+ */
+ public String key() {
+ return key;
+ }
+
+ /**
+ * Gets the type associated with the key.
+ *
+ * @return the type.
+ */
+ public Class type() {
+ return type;
+ }
+
+ /**
+ * Creates a new context key.
+ *
+ * @param key the key.
+ * @param type the type.
+ * @param the type of the value.
+ * @return a new ContextKeys instance.
+ */
+ 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/customcrops/api/context/PlayerContextImpl.java b/api/src/main/java/net/momirealms/customcrops/api/context/PlayerContextImpl.java
new file mode 100644
index 0000000..679563b
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/context/PlayerContextImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) <2024>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package net.momirealms.customcrops.api.context;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
+
+public final class PlayerContextImpl extends AbstractContext {
+
+ public PlayerContextImpl(@Nullable Player player, boolean sync) {
+ super(player, sync);
+ 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 String toString() {
+ return "PlayerContext{" +
+ "args=" + args() +
+ ", player=" + holder() +
+ '}';
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java
new file mode 100644
index 0000000..098b70c
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java
@@ -0,0 +1,120 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.helper.VersionHelper;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.entity.EntityType;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+import org.bukkit.event.player.PlayerInteractAtEntityEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+
+import java.util.HashSet;
+import java.util.List;
+
+public abstract class AbstractCustomEventListener implements Listener {
+
+ private final HashSet entities = new HashSet<>();
+ private final HashSet blocks = new HashSet<>();
+
+ protected final AbstractItemManager itemManager;
+
+ public AbstractCustomEventListener(AbstractItemManager itemManager) {
+ this.itemManager = itemManager;
+ this.entities.addAll(List.of(EntityType.ITEM_FRAME, EntityType.ARMOR_STAND));
+ if (VersionHelper.isVersionNewerThan1_19_4()) {
+ this.entities.addAll(List.of(EntityType.ITEM_DISPLAY, EntityType.INTERACTION));
+ }
+ this.blocks.addAll(List.of(
+ Material.NOTE_BLOCK,
+ Material.MUSHROOM_STEM, Material.BROWN_MUSHROOM_BLOCK, Material.RED_MUSHROOM_BLOCK,
+ Material.TRIPWIRE,
+ Material.CHORUS_PLANT, Material.CHORUS_FLOWER,
+ Material.ACACIA_LEAVES, Material.BIRCH_LEAVES, Material.JUNGLE_LEAVES, Material.DARK_OAK_LEAVES, Material.AZALEA_LEAVES, Material.FLOWERING_AZALEA_LEAVES, Material.OAK_LEAVES, Material.SPRUCE_LEAVES,
+ Material.CAVE_VINES, Material.TWISTING_VINES, Material.WEEPING_VINES,
+ Material.KELP,
+ Material.CACTUS
+ ));
+ if (VersionHelper.isVersionNewerThan1_19()) {
+ this.blocks.add(Material.MANGROVE_LEAVES);
+ }
+ if (VersionHelper.isVersionNewerThan1_20()) {
+ this.blocks.add(Material.CHERRY_LEAVES);
+ }
+ }
+
+ @EventHandler
+ public void onInteractAir(PlayerInteractEvent event) {
+ if (event.getAction() != Action.RIGHT_CLICK_AIR)
+ return;
+ this.itemManager.handlePlayerInteractAir(
+ event.getPlayer(),
+ event.getHand(), event.getItem()
+ );
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onInteractBlock(PlayerInteractEvent event) {
+ if (event.getAction() != Action.RIGHT_CLICK_BLOCK)
+ return;
+ Block block = event.getClickedBlock();
+ assert block != null;
+ if (blocks.contains(block.getType())) {
+ return;
+ }
+ this.itemManager.handlePlayerInteractBlock(
+ event.getPlayer(),
+ block,
+ block.getType().name(), event.getBlockFace(),
+ event.getHand(),
+ event.getItem(),
+ event
+ );
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onInteractEntity(PlayerInteractAtEntityEvent event) {
+ EntityType type = event.getRightClicked().getType();
+ if (entities.contains(type)) {
+ return;
+ }
+ this.itemManager.handlePlayerInteractFurniture(
+ event.getPlayer(),
+ event.getRightClicked().getLocation(), type.name(),
+ event.getHand(), event.getPlayer().getInventory().getItem(event.getHand()),
+ event
+ );
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onPlaceBlock(BlockPlaceEvent event) {
+ Block block = event.getBlock();
+ if (blocks.contains(block.getType())) {
+ return;
+ }
+ this.itemManager.handlePlayerPlace(
+ event.getPlayer(),
+ block.getLocation(),
+ block.getType().name(),
+ event.getHand(),
+ event.getItemInHand(),
+ event
+ );
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onBreakBlock(BlockBreakEvent event) {
+ Block block = event.getBlock();
+ if (blocks.contains(block.getType())) {
+ return;
+ }
+ this.itemManager.handlePlayerBreak(
+ event.getPlayer(),
+ block.getLocation(), event.getPlayer().getInventory().getItemInMainHand(), block.getType().name(),
+ event
+ );
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java
new file mode 100644
index 0000000..7000bef
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java
@@ -0,0 +1,55 @@
+package net.momirealms.customcrops.api.core;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemStack;
+
+public abstract class AbstractItemManager implements ItemManager {
+
+ public abstract void handlePlayerInteractAir(
+ Player player,
+ EquipmentSlot hand,
+ ItemStack itemInHand
+ );
+
+ public abstract void handlePlayerInteractBlock(
+ Player player,
+ Block block,
+ String blockID,
+ BlockFace blockFace,
+ EquipmentSlot hand,
+ ItemStack itemInHand,
+ Cancellable event
+ );
+
+ // it's not a good choice to use Entity as parameter because the entity might be fake
+ public abstract void handlePlayerInteractFurniture(
+ Player player,
+ Location location,
+ String furnitureID,
+ EquipmentSlot hand,
+ ItemStack itemInHand,
+ Cancellable event
+ );
+
+ public abstract void handlePlayerBreak(
+ Player player,
+ Location location,
+ ItemStack itemInHand,
+ String brokenID,
+ Cancellable event
+ );
+
+ public abstract void handlePlayerPlace(
+ Player player,
+ Location location,
+ String placedID,
+ EquipmentSlot hand,
+ ItemStack itemInHand,
+ Cancellable event
+ );
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInBlockMechanics.java b/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInBlockMechanics.java
new file mode 100644
index 0000000..2a7a6ce
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInBlockMechanics.java
@@ -0,0 +1,43 @@
+package net.momirealms.customcrops.api.core;
+
+import com.flowpowered.nbt.CompoundMap;
+import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
+import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
+import net.momirealms.customcrops.common.util.Key;
+
+import java.util.Objects;
+
+public class BuiltInBlockMechanics {
+
+ public static final BuiltInBlockMechanics CROP = create("crop");
+ public static final BuiltInBlockMechanics SPRINKLER = create("sprinkler");
+ public static final BuiltInBlockMechanics GREENHOUSE = create("greenhouse");
+ public static final BuiltInBlockMechanics POT = create("pot");
+ public static final BuiltInBlockMechanics SCARECROW = create("scarecrow");
+
+ private final Key key;
+
+ public BuiltInBlockMechanics(Key key) {
+ this.key = key;
+ }
+
+ static BuiltInBlockMechanics create(String id) {
+ return new BuiltInBlockMechanics(Key.key("customcrops", id));
+ }
+
+ public Key key() {
+ return key;
+ }
+
+ public CustomCropsBlockState createBlockState() {
+ return mechanic().createBlockState();
+ }
+
+ public CustomCropsBlockState createBlockState(CompoundMap data) {
+ return mechanic().createBlockState(data);
+ }
+
+ public CustomCropsBlock mechanic() {
+ return Objects.requireNonNull(Registries.BLOCK.get(key));
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInItemMechanics.java b/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInItemMechanics.java
new file mode 100644
index 0000000..5e8dad9
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/BuiltInItemMechanics.java
@@ -0,0 +1,30 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.util.Key;
+import net.momirealms.customcrops.api.core.item.CustomCropsItem;
+
+public class BuiltInItemMechanics {
+
+ public static final BuiltInItemMechanics WATERING_CAN = create("watering_can");
+ public static final BuiltInItemMechanics FERTILIZER = create("fertilizer");
+ public static final BuiltInItemMechanics SEED = create("seed");
+ public static final BuiltInItemMechanics SPRINKLER_ITEM = create("sprinkler_item");
+
+ private final Key key;
+
+ public BuiltInItemMechanics(Key key) {
+ this.key = key;
+ }
+
+ static BuiltInItemMechanics create(String id) {
+ return new BuiltInItemMechanics(Key.key("customcrops", id));
+ }
+
+ public Key key() {
+ return key;
+ }
+
+ public CustomCropsItem mechanic() {
+ return Registries.ITEM.get(key);
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ClearableMappedRegistry.java b/api/src/main/java/net/momirealms/customcrops/api/core/ClearableMappedRegistry.java
new file mode 100644
index 0000000..5fdb9bc
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/ClearableMappedRegistry.java
@@ -0,0 +1,17 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.util.Key;
+
+public class ClearableMappedRegistry extends MappedRegistry implements ClearableRegistry {
+
+ public ClearableMappedRegistry(Key key) {
+ super(key);
+ }
+
+ @Override
+ public void clear() {
+ super.byID.clear();
+ super.byKey.clear();
+ super.byValue.clear();
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ClearableRegistry.java b/api/src/main/java/net/momirealms/customcrops/api/core/ClearableRegistry.java
new file mode 100644
index 0000000..e1c539e
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/ClearableRegistry.java
@@ -0,0 +1,6 @@
+package net.momirealms.customcrops.api.core;
+
+public interface ClearableRegistry extends WriteableRegistry {
+
+ void clear();
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java
new file mode 100644
index 0000000..4b99df8
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java
@@ -0,0 +1,422 @@
+package net.momirealms.customcrops.api.core;
+
+import com.google.common.base.Preconditions;
+import dev.dejvokep.boostedyaml.YamlDocument;
+import dev.dejvokep.boostedyaml.block.implementation.Section;
+import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
+import dev.dejvokep.boostedyaml.libs.org.snakeyaml.engine.v2.common.ScalarStyle;
+import dev.dejvokep.boostedyaml.libs.org.snakeyaml.engine.v2.nodes.Tag;
+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 dev.dejvokep.boostedyaml.utils.format.NodeRole;
+import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
+import net.momirealms.customcrops.api.core.block.*;
+import net.momirealms.customcrops.api.core.item.FertilizerConfig;
+import net.momirealms.customcrops.api.core.item.FertilizerType;
+import net.momirealms.customcrops.api.core.item.WateringCanConfig;
+import net.momirealms.customcrops.api.core.water.FillMethod;
+import net.momirealms.customcrops.api.core.water.WateringMethod;
+import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
+import net.momirealms.customcrops.api.util.PluginUtils;
+import net.momirealms.customcrops.common.config.ConfigLoader;
+import net.momirealms.customcrops.common.plugin.feature.Reloadable;
+import net.momirealms.customcrops.common.util.Pair;
+import org.bukkit.entity.Player;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+public abstract class ConfigManager implements ConfigLoader, Reloadable {
+
+ private static ConfigManager instance;
+ protected final BukkitCustomCropsPlugin plugin;
+
+ protected boolean doubleCheck;
+ protected boolean metrics;
+ protected boolean checkUpdate;
+ protected boolean debug;
+ protected String absoluteWorldPath;
+
+ protected Set scarecrow;
+ protected ExistenceForm scarecrowExistenceForm;
+ protected boolean enableScarecrow;
+ protected boolean protectOriginalLore;
+ protected int scarecrowRange;
+ protected boolean scarecrowProtectChunk;
+
+ protected Set greenhouse;
+ protected boolean enableGreenhouse;
+ protected ExistenceForm greenhouseExistenceForm;
+ protected int greenhouseRange;
+
+ protected boolean syncSeasons;
+ protected String referenceWorld;
+
+ protected String[] itemDetectOrder;
+
+ protected double[] defaultQualityRatio;
+
+ protected boolean hasNamespace;
+
+ public ConfigManager(BukkitCustomCropsPlugin plugin) {
+ this.plugin = plugin;
+ instance = this;
+ }
+
+ public static boolean syncSeasons() {
+ return instance.syncSeasons;
+ }
+
+ public static String referenceWorld() {
+ return instance.referenceWorld;
+ }
+
+ public static boolean enableGreenhouse() {
+ return instance.enableGreenhouse;
+ }
+
+ public static ExistenceForm greenhouseExistenceForm() {
+ return instance.greenhouseExistenceForm;
+ }
+
+ public static int greenhouseRange() {
+ return instance.greenhouseRange;
+ }
+
+ public static int scarecrowRange() {
+ return instance.scarecrowRange;
+ }
+
+ public static boolean enableScarecrow() {
+ return instance.enableScarecrow;
+ }
+
+ public static boolean scarecrowProtectChunk() {
+ return instance.scarecrowProtectChunk;
+ }
+
+ public static boolean debug() {
+ return instance.debug;
+ }
+
+ public static boolean metrics() {
+ return instance.metrics;
+ }
+
+ public static boolean checkUpdate() {
+ return instance.checkUpdate;
+ }
+
+ public static String absoluteWorldPath() {
+ return instance.absoluteWorldPath;
+ }
+
+ public static Set greenhouse() {
+ return instance.greenhouse;
+ }
+
+ public static ExistenceForm scarecrowExistenceForm() {
+ return instance.scarecrowExistenceForm;
+ }
+
+ public static boolean doubleCheck() {
+ return instance.doubleCheck;
+ }
+
+ public static Set scarecrow() {
+ return instance.scarecrow;
+ }
+
+ public static boolean protectOriginalLore() {
+ return instance.protectOriginalLore;
+ }
+
+ public static String[] itemDetectOrder() {
+ return instance.itemDetectOrder;
+ }
+
+ public static double[] defaultQualityRatio() {
+ return instance.defaultQualityRatio;
+ }
+
+ public static boolean hasNamespace() {
+ return instance.hasNamespace;
+ }
+
+ @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.builder()
+ .setScalarFormatter((tag, value, role, def) -> {
+ if (role == NodeRole.KEY) {
+ return ScalarStyle.PLAIN;
+ } else {
+ return tag == Tag.STR ? ScalarStyle.DOUBLE_QUOTED : ScalarStyle.PLAIN;
+ }
+ })
+ .build(),
+ 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);
+ }
+ }
+
+ 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);
+ addDefaultNamespace(configFile.toFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return configFile;
+ }
+
+ public abstract void registerWateringCanConfig(WateringCanConfig config);
+
+ public abstract void registerFertilizerConfig(FertilizerConfig config);
+
+ public abstract void registerCropConfig(CropConfig config);
+
+ public abstract void registerPotConfig(PotConfig config);
+
+ public abstract void registerSprinklerConfig(SprinklerConfig config);
+
+ public WateringMethod[] getWateringMethods(Section section) {
+ ArrayList methods = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section innerSection) {
+ WateringMethod fillMethod = new WateringMethod(
+ Preconditions.checkNotNull(innerSection.getString("item"), "fill-method item should not be null"),
+ innerSection.getInt("item-amount", 1),
+ innerSection.getString("return"),
+ innerSection.getInt("return-amount", 1),
+ innerSection.getInt("amount", 1),
+ plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions")),
+ plugin.getRequirementManager(Player.class).parseRequirements(innerSection.getSection("requirements"), true)
+ );
+ methods.add(fillMethod);
+ }
+ }
+ }
+ return methods.toArray(new WateringMethod[0]);
+ }
+
+ public FillMethod[] getFillMethods(Section section) {
+ ArrayList methods = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section innerSection) {
+ FillMethod fillMethod = new FillMethod(
+ Preconditions.checkNotNull(innerSection.getString("target"), "fill-method target should not be null"),
+ innerSection.getInt("amount", 1),
+ plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions")),
+ plugin.getRequirementManager(Player.class).parseRequirements(innerSection.getSection("requirements"), true)
+ );
+ methods.add(fillMethod);
+ }
+ }
+ }
+ return methods.toArray(new FillMethod[0]);
+ }
+
+ public HashMap getInt2IntMap(Section section) {
+ HashMap map = new HashMap<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ try {
+ int i1 = Integer.parseInt(entry.getKey());
+ if (entry.getValue() instanceof Number i2) {
+ map.put(i1, i2.intValue());
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+ return map;
+ }
+
+ public double[] getQualityRatio(String ratios) {
+ String[] split = ratios.split("/");
+ double[] ratio = new double[split.length];
+ double weightTotal = Arrays.stream(split).mapToInt(Integer::parseInt).sum();
+ double temp = 0;
+ for (int i = 0; i < ratio.length; i++) {
+ temp += Integer.parseInt(split[i]);
+ ratio[i] = temp / weightTotal;
+ }
+ return ratio;
+ }
+
+ public List> getIntChancePair(Section section) {
+ ArrayList> pairs = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ int point = Integer.parseInt(entry.getKey());
+ if (entry.getValue() instanceof Number n) {
+ Pair pair = new Pair<>(n.doubleValue(), point);
+ pairs.add(pair);
+ }
+ }
+ }
+ return pairs;
+ }
+
+ public HashMap> getFertilizedPotMap(Section section) {
+ HashMap> map = new HashMap<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section innerSection) {
+ FertilizerType type = Registries.FERTILIZER_TYPE.get(entry.getKey().replace("-", "_"));
+ if (type != null) {
+ map.put(type, Pair.of(
+ Preconditions.checkNotNull(innerSection.getString("dry"), entry.getKey() + ".dry should not be null"),
+ Preconditions.checkNotNull(innerSection.getString("wet"), entry.getKey() + ".wet should not be null")
+ ));
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ public BoneMeal[] getBoneMeals(Section section) {
+ ArrayList boneMeals = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section innerSection) {
+ BoneMeal boneMeal = new BoneMeal(
+ Preconditions.checkNotNull(innerSection.getString("item"), "Bone meal item can't be null"),
+ innerSection.getInt("item-amount",1),
+ innerSection.getString("return"),
+ innerSection.getInt("return-amount",1),
+ innerSection.getBoolean("dispenser",true),
+ getIntChancePair(innerSection.getSection("chance")),
+ plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions"))
+ );
+ boneMeals.add(boneMeal);
+ }
+ }
+ }
+ return boneMeals.toArray(new BoneMeal[0]);
+ }
+
+ public DeathCondition[] getDeathConditions(Section section, ExistenceForm original) {
+ ArrayList conditions = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section inner) {
+ DeathCondition deathCondition = new DeathCondition(
+ plugin.getRequirementManager(CustomCropsBlockState.class).parseRequirements(inner.getSection("conditions"), false),
+ inner.getString("model"),
+ Optional.ofNullable(inner.getString("type")).map(ExistenceForm::valueOf).orElse(original),
+ inner.getInt("delay", 0)
+ );
+ conditions.add(deathCondition);
+ }
+ }
+ }
+ return conditions.toArray(new DeathCondition[0]);
+ }
+
+ public GrowCondition[] getGrowConditions(Section section) {
+ ArrayList conditions = new ArrayList<>();
+ if (section != null) {
+ for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) {
+ if (entry.getValue() instanceof Section inner) {
+ GrowCondition growCondition = new GrowCondition(
+ plugin.getRequirementManager(CustomCropsBlockState.class).parseRequirements(inner.getSection("conditions"), false),
+ inner.getInt("point", 1)
+ );
+ conditions.add(growCondition);
+ }
+ }
+ }
+ return conditions.toArray(new GrowCondition[0]);
+ }
+
+ protected void addDefaultNamespace(File file) {
+ boolean hasNamespace = PluginUtils.isEnabled("ItemsAdder");
+ String line;
+ StringBuilder sb = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append(System.lineSeparator());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ try (BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
+ String finalStr = sb.toString();
+ if (!hasNamespace) {
+ finalStr = finalStr.replace("CHORUS", "TRIPWIRE").replace("", "");
+ }
+ writer.write(finalStr.replace("{0}", hasNamespace ? "customcrops:" : ""));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/CustomForm.java b/api/src/main/java/net/momirealms/customcrops/api/core/CustomForm.java
new file mode 100644
index 0000000..c3d5e01
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/CustomForm.java
@@ -0,0 +1,22 @@
+package net.momirealms.customcrops.api.core;
+
+public enum CustomForm {
+
+ TRIPWIRE(ExistenceForm.BLOCK),
+ NOTE_BLOCK(ExistenceForm.BLOCK),
+ MUSHROOM(ExistenceForm.BLOCK),
+ CHORUS(ExistenceForm.BLOCK),
+ ITEM_FRAME(ExistenceForm.FURNITURE),
+ ITEM_DISPLAY(ExistenceForm.FURNITURE),
+ ARMOR_STAND(ExistenceForm.FURNITURE);
+
+ private final ExistenceForm form;
+
+ CustomForm(ExistenceForm form) {
+ this.form = form;
+ }
+
+ public ExistenceForm existenceForm() {
+ return form;
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/CustomItemProvider.java b/api/src/main/java/net/momirealms/customcrops/api/core/CustomItemProvider.java
new file mode 100644
index 0000000..eaf913b
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/CustomItemProvider.java
@@ -0,0 +1,34 @@
+package net.momirealms.customcrops.api.core;
+
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public interface CustomItemProvider {
+
+ boolean removeCustomBlock(Location location);
+
+ boolean placeCustomBlock(Location location, String id);
+
+ @Nullable
+ Entity placeFurniture(Location location, String id);
+
+ boolean removeFurniture(Entity entity);
+
+ @Nullable
+ String blockID(Block block);
+
+ @Nullable
+ String itemID(ItemStack itemStack);
+
+ @Nullable
+ ItemStack itemStack(Player player, String id);
+
+ @Nullable
+ String furnitureID(Entity entity);
+
+ boolean isFurniture(Entity entity);
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ExistenceForm.java b/api/src/main/java/net/momirealms/customcrops/api/core/ExistenceForm.java
new file mode 100644
index 0000000..1a3a53d
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/ExistenceForm.java
@@ -0,0 +1,8 @@
+package net.momirealms.customcrops.api.core;
+
+public enum ExistenceForm {
+
+ BLOCK,
+ FURNITURE,
+ ANY
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/FurnitureRotation.java b/api/src/main/java/net/momirealms/customcrops/api/core/FurnitureRotation.java
new file mode 100644
index 0000000..481776e
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/FurnitureRotation.java
@@ -0,0 +1,79 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.util.RandomUtils;
+import org.bukkit.Rotation;
+
+public enum FurnitureRotation {
+
+ NONE(0f),
+ EAST(-90f),
+ SOUTH(0f),
+ WEST(90f),
+ NORTH(180f);
+
+ private final float yaw;
+
+ FurnitureRotation(float yaw) {
+ this.yaw = yaw;
+ }
+
+ public float getYaw() {
+ return yaw;
+ }
+
+ public static FurnitureRotation random() {
+ return FurnitureRotation.values()[RandomUtils.generateRandomInt(1, 4)];
+ }
+
+ public static FurnitureRotation getByRotation(Rotation rotation) {
+ switch (rotation) {
+ default -> {
+ return FurnitureRotation.SOUTH;
+ }
+ case CLOCKWISE -> {
+ return FurnitureRotation.WEST;
+ }
+ case COUNTER_CLOCKWISE -> {
+ return FurnitureRotation.EAST;
+ }
+ case FLIPPED -> {
+ return FurnitureRotation.NORTH;
+ }
+ }
+ }
+
+ public static FurnitureRotation getByYaw(float yaw) {
+ yaw = (Math.abs(yaw + 180) % 360);
+ switch ((int) (yaw/90)) {
+ case 1 -> {
+ return FurnitureRotation.WEST;
+ }
+ case 2 -> {
+ return FurnitureRotation.NORTH;
+ }
+ case 3 -> {
+ return FurnitureRotation.EAST;
+ }
+ default -> {
+ return FurnitureRotation.SOUTH;
+ }
+ }
+ }
+
+ public Rotation getBukkitRotation() {
+ switch (this) {
+ case EAST -> {
+ return Rotation.COUNTER_CLOCKWISE;
+ }
+ case WEST -> {
+ return Rotation.CLOCKWISE;
+ }
+ case NORTH -> {
+ return Rotation.FLIPPED;
+ }
+ default -> {
+ return Rotation.NONE;
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/IdMap.java b/api/src/main/java/net/momirealms/customcrops/api/core/IdMap.java
new file mode 100644
index 0000000..4dded1c
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/IdMap.java
@@ -0,0 +1,32 @@
+package net.momirealms.customcrops.api.core;
+
+import javax.annotation.Nullable;
+
+public interface IdMap extends Iterable {
+ int DEFAULT = -1;
+
+ int getId(T value);
+
+ @Nullable
+ T byId(int index);
+
+ default T byIdOrThrow(int index) {
+ T object = this.byId(index);
+ if (object == null) {
+ throw new IllegalArgumentException("No value with id " + index);
+ } else {
+ return object;
+ }
+ }
+
+ default int getIdOrThrow(T value) {
+ int i = this.getId(value);
+ if (i == -1) {
+ throw new IllegalArgumentException("Can't find id for '" + value + "' in map " + this);
+ } else {
+ return i;
+ }
+ }
+
+ int size();
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/InteractionResult.java b/api/src/main/java/net/momirealms/customcrops/api/core/InteractionResult.java
new file mode 100644
index 0000000..f2af6f7
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/InteractionResult.java
@@ -0,0 +1,8 @@
+package net.momirealms.customcrops.api.core;
+
+public enum InteractionResult {
+
+ SUCCESS,
+ FAIL,
+ PASS
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ItemManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/ItemManager.java
new file mode 100644
index 0000000..bedbaca
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/ItemManager.java
@@ -0,0 +1,63 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.item.Item;
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface ItemManager {
+
+ void place(@NotNull Location location, @NotNull ExistenceForm form, @NotNull String id, FurnitureRotation rotation);
+
+ FurnitureRotation remove(@NotNull Location location, @NotNull ExistenceForm form);
+
+ void placeBlock(@NotNull Location location, @NotNull String id);
+
+ void placeFurniture(@NotNull Location location, @NotNull String id, FurnitureRotation rotation);
+
+ void removeBlock(@NotNull Location location);
+
+ @Nullable
+ FurnitureRotation removeFurniture(@NotNull Location location);
+
+ @NotNull
+ default String blockID(@NotNull Location location) {
+ return blockID(location.getBlock());
+ }
+
+ @NotNull
+ String blockID(@NotNull Block block);
+
+ @Nullable
+ String furnitureID(@NotNull Entity entity);
+
+ @NotNull String entityID(@NotNull Entity entity);
+
+ @Nullable
+ String furnitureID(Location location);
+
+ @NotNull
+ String anyID(Location location);
+
+ @Nullable
+ String id(Location location, ExistenceForm form);
+
+ void setCustomEventListener(@NotNull AbstractCustomEventListener listener);
+
+ void setCustomItemProvider(@NotNull CustomItemProvider provider);
+
+ String id(ItemStack itemStack);
+
+ @Nullable
+ ItemStack build(Player player, String id);
+
+ Item wrap(ItemStack itemStack);
+
+ void decreaseDamage(Player player, ItemStack itemStack, int amount);
+
+ void increaseDamage(Player holder, ItemStack itemStack, int amount);
+}
diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/MappedRegistry.java b/api/src/main/java/net/momirealms/customcrops/api/core/MappedRegistry.java
new file mode 100644
index 0000000..0d576e0
--- /dev/null
+++ b/api/src/main/java/net/momirealms/customcrops/api/core/MappedRegistry.java
@@ -0,0 +1,58 @@
+package net.momirealms.customcrops.api.core;
+
+import net.momirealms.customcrops.common.util.Key;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+public class MappedRegistry