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 ![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/Xiao-MoMi/Custom-Crops) -![Code Size](https://img.shields.io/github/languages/code-size/Xiao-MoMi/Custom-Crops) -![bStats Servers](https://img.shields.io/bstats/servers/16593) -![bStats Players](https://img.shields.io/bstats/players/16593) -[![Scc Count Badge](https://sloc.xyz/github/Xiao-MoMi/Custom-Crops/?category=codes)](https://github.com/Xiao-MoMi/Custom-Crops/) -![GitHub](https://img.shields.io/github/license/Xiao-MoMi/Custom-Crops) [![](https://jitpack.io/v/Xiao-MoMi/Custom-Crops.svg)](https://jitpack.io/#Xiao-MoMi/Custom-Crops) Gitbook +[![Scc Count Badge](https://sloc.xyz/github/Xiao-MoMi/Custom-Crops/?category=codes)](https://github.com/Xiao-MoMi/Custom-Crops/) +![Code Size](https://img.shields.io/github/languages/code-size/Xiao-MoMi/Custom-Crops) +![bStats Servers](https://img.shields.io/bstats/servers/16593) +![bStats Players](https://img.shields.io/bstats/players/16593) +![GitHub](https://img.shields.io/github/license/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> expansionClass = (Class>) 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> 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 implements WriteableRegistry { + + protected final Map byKey = new HashMap<>(1024); + protected final Map byValue = new IdentityHashMap<>(1024); + protected final ArrayList byID = new ArrayList<>(1024); + private final Key key; + + public MappedRegistry(Key key) { + this.key = key; + } + + @Override + public void register(K key, T value) { + byKey.put(key, value); + byValue.put(value, key); + } + + @Override + public Key key() { + return key; + } + + @Override + public int getId(@Nullable T value) { + return byID.indexOf(value); + } + + @Nullable + @Override + public T byId(int index) { + return byID.get(index); + } + + @Override + public int size() { + return byKey.size(); + } + + @Nullable + @Override + public T get(@Nullable K key) { + return byKey.get(key); + } + + @NotNull + @Override + public Iterator iterator() { + return this.byKey.values().iterator(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/Registries.java b/api/src/main/java/net/momirealms/customcrops/api/core/Registries.java new file mode 100644 index 0000000..e84e4de --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/Registries.java @@ -0,0 +1,42 @@ +package net.momirealms.customcrops.api.core; + +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.item.CustomCropsItem; +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.common.annotation.DoNotUse; +import net.momirealms.customcrops.common.util.Key; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; + +@ApiStatus.Internal +public class Registries { + + @DoNotUse + public static final WriteableRegistry BLOCK = new MappedRegistry<>(Key.key("mechanic", "block")); + @DoNotUse + public static final WriteableRegistry ITEM = new MappedRegistry<>(Key.key("mechanic", "item")); + @DoNotUse + public static final WriteableRegistry FERTILIZER_TYPE = new ClearableMappedRegistry<>(Key.key("mechanic", "fertilizer_type")); + @DoNotUse + public static final ClearableRegistry BLOCKS = new ClearableMappedRegistry<>(Key.key("internal", "blocks")); + @DoNotUse + public static final ClearableRegistry ITEMS = new ClearableMappedRegistry<>(Key.key("internal", "items")); + + public static final ClearableRegistry SPRINKLER = new ClearableMappedRegistry<>(Key.key("config", "sprinkler")); + public static final ClearableRegistry POT = new ClearableMappedRegistry<>(Key.key("config", "pot")); + public static final ClearableRegistry CROP = new ClearableMappedRegistry<>(Key.key("config", "crop")); + public static final ClearableRegistry FERTILIZER = new ClearableMappedRegistry<>(Key.key("config", "fertilizer")); + public static final ClearableRegistry WATERING_CAN = new ClearableMappedRegistry<>(Key.key("config", "watering_can")); + + public static final ClearableRegistry SEED_TO_CROP = new ClearableMappedRegistry<>(Key.key("fast_lookup", "seed_to_crop")); + public static final ClearableRegistry> STAGE_TO_CROP_UNSAFE = new ClearableMappedRegistry<>(Key.key("fast_lookup", "stage_to_crop")); + public static final ClearableRegistry ITEM_TO_FERTILIZER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_fertilizer")); + public static final ClearableRegistry ITEM_TO_POT = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_pot")); + public static final ClearableRegistry ITEM_TO_SPRINKLER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_sprinkler")); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/Registry.java b/api/src/main/java/net/momirealms/customcrops/api/core/Registry.java new file mode 100644 index 0000000..d092e71 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/Registry.java @@ -0,0 +1,16 @@ +package net.momirealms.customcrops.api.core; + +import net.momirealms.customcrops.common.util.Key; + +import javax.annotation.Nullable; + +public interface Registry extends IdMap { + + Key key(); + + @Override + int getId(@Nullable T value); + + @Nullable + T get(@Nullable K key); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/RegistryAccess.java b/api/src/main/java/net/momirealms/customcrops/api/core/RegistryAccess.java new file mode 100644 index 0000000..923cae2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/RegistryAccess.java @@ -0,0 +1,21 @@ +package net.momirealms.customcrops.api.core; + +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.item.CustomCropsItem; +import net.momirealms.customcrops.api.core.item.FertilizerType; + +public interface RegistryAccess { + + void registerBlockMechanic(CustomCropsBlock block); + + void registerItemMechanic(CustomCropsItem item); + + void registerFertilizerType(FertilizerType type); + + Registry getBlockRegistry(); + + Registry getItemRegistry(); + + Registry getFertilizerTypeRegistry(); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/SimpleRegistryAccess.java b/api/src/main/java/net/momirealms/customcrops/api/core/SimpleRegistryAccess.java new file mode 100644 index 0000000..861227c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/SimpleRegistryAccess.java @@ -0,0 +1,54 @@ +package net.momirealms.customcrops.api.core; + +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.item.CustomCropsItem; +import net.momirealms.customcrops.api.core.item.FertilizerType; + +public class SimpleRegistryAccess implements RegistryAccess { + + private BukkitCustomCropsPlugin plugin; + private boolean frozen; + + public SimpleRegistryAccess(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + } + + public void freeze() { + this.frozen = true; + } + + @Override + public void registerBlockMechanic(CustomCropsBlock block) { + if (frozen) throw new RuntimeException("Registries are frozen"); + Registries.BLOCK.register(block.type(), block); + } + + @Override + public void registerItemMechanic(CustomCropsItem item) { + if (frozen) throw new RuntimeException("Registries are frozen"); + Registries.ITEM.register(item.type(), item); + } + + @Override + public void registerFertilizerType(FertilizerType type) { + if (frozen) throw new RuntimeException("Registries are frozen"); + Registries.FERTILIZER_TYPE.register(type.id(), type); + } + + @Override + public Registry getBlockRegistry() { + return Registries.BLOCK; + } + + @Override + public Registry getItemRegistry() { + return Registries.ITEM; + } + + @Override + public Registry getFertilizerTypeRegistry() { + return Registries.FERTILIZER_TYPE; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/StatedItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/StatedItem.java new file mode 100644 index 0000000..f04bd0d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/StatedItem.java @@ -0,0 +1,9 @@ +package net.momirealms.customcrops.api.core; + +import net.momirealms.customcrops.api.context.Context; + +@FunctionalInterface +public interface StatedItem { + + String currentState(Context context); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SynchronizedCompoundMap.java b/api/src/main/java/net/momirealms/customcrops/api/core/SynchronizedCompoundMap.java similarity index 87% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SynchronizedCompoundMap.java rename to api/src/main/java/net/momirealms/customcrops/api/core/SynchronizedCompoundMap.java index 0a9c8b6..55fd979 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SynchronizedCompoundMap.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/SynchronizedCompoundMap.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world; +package net.momirealms.customcrops.api.core; import com.flowpowered.nbt.CompoundMap; import com.flowpowered.nbt.Tag; @@ -37,7 +37,7 @@ public class SynchronizedCompoundMap { this.compoundMap = compoundMap; } - public CompoundMap getOriginalMap() { + public CompoundMap originalMap() { return compoundMap; } @@ -59,6 +59,15 @@ public class SynchronizedCompoundMap { } } + public Tag remove(String key) { + writeLock.lock(); + try { + return compoundMap.remove(key); + } finally { + writeLock.unlock(); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -78,16 +87,16 @@ public class SynchronizedCompoundMap { Tag tag = entry.getValue(); String tagValue; switch (tag.getType()) { - case TAG_STRING, TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY, TAG_LONG, TAG_SHORT, TAG_SHORT_ARRAY, TAG_LONG_ARRAY, TAG_BYTE_ARRAY -> + case TAG_STRING, TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY, TAG_LONG, TAG_SHORT, TAG_SHORT_ARRAY, TAG_LONG_ARRAY, TAG_BYTE_ARRAY, + TAG_LIST -> tagValue = tag.getValue().toString(); - case TAG_COMPOUND -> tagValue = compoundMapToString(tag.getAsCompoundTag().get().getValue()); - case TAG_LIST -> tagValue = tag.getAsListTag().get().getValue().toString(); + case TAG_COMPOUND -> tagValue = compoundMapToString((CompoundMap) tag.getValue()); default -> { continue; } } joiner.add("\"" + entry.getKey() + "\":\"" + tagValue + "\""); } - return "{" + joiner + "}"; + return "BlockData{" + joiner + "}"; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/WriteableRegistry.java b/api/src/main/java/net/momirealms/customcrops/api/core/WriteableRegistry.java new file mode 100644 index 0000000..a839a32 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/WriteableRegistry.java @@ -0,0 +1,6 @@ +package net.momirealms.customcrops.api.core; + +public interface WriteableRegistry extends Registry { + + void register(K key, T value); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/AbstractCustomCropsBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/AbstractCustomCropsBlock.java new file mode 100644 index 0000000..8424c8c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/AbstractCustomCropsBlock.java @@ -0,0 +1,82 @@ +package net.momirealms.customcrops.api.core.block; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.IntTag; +import com.flowpowered.nbt.StringTag; +import com.flowpowered.nbt.Tag; +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; + +public abstract class AbstractCustomCropsBlock implements CustomCropsBlock { + + private final Key type; + + public AbstractCustomCropsBlock(Key type) { + this.type = type; + } + + @Override + public Key type() { + return type; + } + + @Override + public CustomCropsBlockState createBlockState() { + return CustomCropsBlockState.create(this, new CompoundMap()); + } + + @Override + public CustomCropsBlockState createBlockState(CompoundMap compoundMap) { + return CustomCropsBlockState.create(this, compoundMap); + } + + public String id(CustomCropsBlockState state) { + return state.get("key").getAsStringTag() + .map(StringTag::getValue) + .orElse(""); + } + + public void id(CustomCropsBlockState state, String id) { + state.set("key", new StringTag("key", id)); + } + + protected boolean canTick(CustomCropsBlockState state, int interval) { + if (interval <= 0) return false; + if (interval == 1) return true; + Tag tag = state.get("tick"); + int tick = 0; + if (tag != null) tick = tag.getAsIntTag().map(IntTag::getValue).orElse(0); + if (++tick >= interval) { + state.set("tick", new IntTag("tick", 0)); + return true; + } else { + state.set("tick", new IntTag("tick", tick)); + return false; + } + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + } + + @Override + public void onInteract(WrappedInteractEvent event) { + } + + @Override + public void onBreak(WrappedBreakEvent event) { + } + + @Override + public void onPlace(WrappedPlaceEvent event) { + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/BoneMeal.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/BoneMeal.java similarity index 55% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/BoneMeal.java rename to api/src/main/java/net/momirealms/customcrops/api/core/block/BoneMeal.java index 7883c7f..2dc206d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/BoneMeal.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/BoneMeal.java @@ -15,67 +15,53 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.item; +package net.momirealms.customcrops.api.core.block; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.manager.ActionManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.requirement.State; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; import java.util.List; public class BoneMeal { private final String item; - private final int usedAmount; + private final int requiredAmount; private final String returned; private final int returnedAmount; private final List> pointGainList; - private final Action[] actions; + private final Action[] actions; private final boolean dispenserAllowed; public BoneMeal( String item, - int usedAmount, + int requiredAmount, String returned, int returnedAmount, boolean dispenserAllowed, List> pointGainList, - Action[] actions + Action[] actions ) { this.item = item; this.returned = returned; this.pointGainList = pointGainList; this.actions = actions; - this.usedAmount = usedAmount; + this.requiredAmount = requiredAmount; this.returnedAmount = returnedAmount; this.dispenserAllowed = dispenserAllowed; } - /** - * Get the ID of the bone meal item - * - * @return bonemeal item id - */ - public String getItem() { + public String requiredItem() { return item; } - /** - * Get the returned item's ID - * - * @return returned item ID - */ - public String getReturned() { + public String returnedItem() { return returned; } - /** - * Get the points to gain in one try - * - * @return points - */ - public int getPoint() { + public int rollPoint() { for (Pair pair : pointGainList) { if (Math.random() < pair.left()) { return pair.right(); @@ -84,38 +70,18 @@ public class BoneMeal { return 0; } - /** - * Trigger the actions of using the bone meal - * - * @param state player state - */ - public void trigger(State state) { - ActionManager.triggerActions(state, actions); + public void triggerActions(Context context) { + ActionManager.trigger(context, actions); } - /** - * Get the amount to consume - * - * @return amount to consume - */ - public int getUsedAmount() { - return usedAmount; + public int amountOfRequiredItem() { + return requiredAmount; } - /** - * Get the amount of the returned items - * - * @return amount of the returned items - */ - public int getReturnedAmount() { + public int amountOfReturnItem() { return returnedAmount; } - /** - * If the bone meal can be used with a dispenser - * - * @return can be used or not - */ public boolean isDispenserAllowed() { return dispenserAllowed; } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/BreakReason.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/BreakReason.java new file mode 100644 index 0000000..f2b85a7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/BreakReason.java @@ -0,0 +1,12 @@ +package net.momirealms.customcrops.api.core.block; + +public enum BreakReason { + // Crop was broken by a player + BREAK, + // Crop was trampled + TRAMPLE, + // Crop was broken due to an explosion (block or entity) + EXPLODE, + // Crop was broken due to a specific action + ACTION +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java new file mode 100644 index 0000000..5143368 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropBlock.java @@ -0,0 +1,398 @@ +package net.momirealms.customcrops.api.core.block; + +import com.flowpowered.nbt.IntTag; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.*; +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.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; +import net.momirealms.customcrops.api.event.BoneMealUseEvent; +import net.momirealms.customcrops.api.event.CropBreakEvent; +import net.momirealms.customcrops.api.event.CropInteractEvent; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.PlayerUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class CropBlock extends AbstractCustomCropsBlock { + + public CropBlock() { + super(BuiltInBlockMechanics.CROP.key()); + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (!world.setting().randomTickCrop() && canTick(state, world.setting().tickCropInterval())) { + tickCrop(state, world, location); + } + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (world.setting().randomTickCrop() && canTick(state, world.setting().tickCropInterval())) { + tickCrop(state, world, location); + } + } + + @Override + public void onBreak(WrappedBreakEvent event) { + List configs = Registries.STAGE_TO_CROP_UNSAFE.get(event.brokenID()); + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + if (configs == null || configs.isEmpty()) { + world.removeBlockState(pos3); + return; + } + + CustomCropsBlockState state = fixOrGetState(world, pos3, event.brokenID()); + if (state == null) { + return; + } + + // check data for precise data + CropConfig cropConfig = config(state); + // the config not exists or it's a wrong one + if (cropConfig == null || !configs.contains(cropConfig)) { + if (configs.size() != 1) { + return; + } + cropConfig = configs.get(0); + } + + CropStageConfig stageConfig = cropConfig.stageByID(event.brokenID()); + assert stageConfig != null; + + final Player player = event.playerBreaker(); + Context context = Context.player(player); + + // check requirements + if (!RequirementManager.isSatisfied(context, cropConfig.breakRequirements())) { + event.setCancelled(true); + return; + } + if (!RequirementManager.isSatisfied(context, stageConfig.breakRequirements())) { + event.setCancelled(true); + return; + } + + CropBreakEvent breakEvent = new CropBreakEvent(event.entityBreaker(), event.blockBreaker(), cropConfig, event.brokenID(), event.location(), + state, BreakReason.BREAK); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + event.setCancelled(true); + return; + } + + ActionManager.trigger(context, stageConfig.breakActions()); + ActionManager.trigger(context, cropConfig.breakActions()); + world.removeBlockState(pos3); + } + + /** + * This can only be triggered admins because players shouldn't get the stages + */ + @Override + public void onPlace(WrappedPlaceEvent event) { + List configs = Registries.STAGE_TO_CROP_UNSAFE.get(event.placedID()); + if (configs == null || configs.size() != 1) { + return; + } + + CropConfig cropConfig = configs.get(0); + Context context = Context.player(event.player()); +// if (!RequirementManager.isSatisfied(context, cropConfig.plantRequirements())) { +// event.setCancelled(true); +// return; +// } + + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsWorld world = event.world(); + if (world.setting().cropPerChunk() >= 0) { + if (world.testChunkLimitation(pos3, this.getClass(), world.setting().cropPerChunk())) { + ActionManager.trigger(context, cropConfig.reachLimitActions()); + event.setCancelled(true); + return; + } + } + + fixOrGetState(world, pos3, event.placedID()); + } + + @Override + public void onInteract(WrappedInteractEvent event) { + final Player player = event.player(); + Context context = Context.player(player); + + // data first + CustomCropsWorld world = event.world(); + Location location = event.location(); + Pos3 pos3 = Pos3.from(location); + // fix if possible + CustomCropsBlockState state = fixOrGetState(world, pos3, event.relatedID()); + if (state == null) return; + + CropConfig cropConfig = config(state); + if (!RequirementManager.isSatisfied(context, cropConfig.interactRequirements())) { + return; + } + + int point = point(state); + CropStageConfig stageConfig = cropConfig.getFloorStageEntry(point).getValue(); + if (!RequirementManager.isSatisfied(context, stageConfig.interactRequirements())) { + return; + } + + final ItemStack itemInHand = event.itemInHand(); + // trigger event + CropInteractEvent interactEvent = new CropInteractEvent(player, itemInHand, location, state, event.hand(), cropConfig, event.relatedID()); + if (EventUtils.fireAndCheckCancel(interactEvent)) { + return; + } + + Location potLocation = location.clone().subtract(0,1,0); + String blockBelowID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(potLocation.getBlock()); + PotConfig potConfig = Registries.ITEM_TO_POT.get(blockBelowID); + if (potConfig != null) { + PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic(); + assert potBlock != null; + // fix or get data + CustomCropsBlockState potState = potBlock.fixOrGetState(world, Pos3.from(potLocation), potConfig, event.relatedID()); + if (potBlock.tryWateringPot(player, context, potState, event.hand(), event.itemID(), potConfig, potLocation, itemInHand)) + return; + } + + if (point < cropConfig.maxPoints()) { + for (BoneMeal boneMeal : cropConfig.boneMeals()) { + if (boneMeal.requiredItem().equals(event.itemID()) && boneMeal.amountOfRequiredItem() <= itemInHand.getAmount()) { + BoneMealUseEvent useEvent = new BoneMealUseEvent(player, itemInHand, location, boneMeal, state, event.hand(), cropConfig); + if (EventUtils.fireAndCheckCancel(useEvent)) + return; + if (player.getGameMode() != GameMode.CREATIVE) { + itemInHand.setAmount(itemInHand.getAmount() - boneMeal.amountOfRequiredItem()); + if (boneMeal.returnedItem() != null) { + ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, boneMeal.returnedItem()); + if (returned != null) { + PlayerUtils.giveItem(player, returned, boneMeal.amountOfReturnItem()); + } + } + } + boneMeal.triggerActions(context); + + int afterPoints = Math.min(point + boneMeal.rollPoint(), cropConfig.maxPoints()); + point(state, afterPoints); + + String afterStage = null; + ExistenceForm afterForm = null; + int tempPoints = afterPoints; + while (tempPoints >= 0) { + Map.Entry afterEntry = cropConfig.getFloorStageEntry(tempPoints); + CropStageConfig after = afterEntry.getValue(); + if (after.stageID() != null) { + afterStage = after.stageID(); + afterForm = after.existenceForm(); + break; + } + tempPoints = after.point() - 1; + } + + Objects.requireNonNull(afterForm); + Objects.requireNonNull(afterStage); + + Context blockContext = Context.block(state); + for (int i = point + 1; i <= afterPoints; i++) { + CropStageConfig stage = cropConfig.stageByPoint(i); + if (stage != null) { + ActionManager.trigger(blockContext, stage.growActions()); + } + } + + if (Objects.equals(afterStage, event.relatedID())) return; + Location bukkitLocation = location.toLocation(world.bukkitWorld()); + FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY); + if (rotation == FurnitureRotation.NONE && cropConfig.rotation()) { + rotation = FurnitureRotation.random(); + } + BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, afterForm, afterStage, rotation); + return; + } + } + } + + ActionManager.trigger(context, cropConfig.interactActions()); + ActionManager.trigger(context, stageConfig.interactActions()); + } + + public CustomCropsBlockState fixOrGetState(CustomCropsWorld world, Pos3 pos3, String stageID) { + List configList = Registries.STAGE_TO_CROP_UNSAFE.get(stageID); + if (configList == null) return null; + + Optional optionalPotState = world.getBlockState(pos3); + if (optionalPotState.isPresent()) { + CustomCropsBlockState potState = optionalPotState.get(); + if (potState.type() instanceof CropBlock cropBlock) { + if (configList.stream().map(CropConfig::id).toList().contains(cropBlock.id(potState))) { + return potState; + } + } + } + + if (configList.size() != 1) { + return null; + } + CropConfig cropConfig = configList.get(0); + CropStageConfig stageConfig = cropConfig.stageByID(stageID); + int point = stageConfig.point(); + CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState(); + point(state, point); + id(state, cropConfig.id()); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + return state; + } + + private void tickCrop(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + CropConfig config = config(state); + if (config == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Crop data is removed at location[" + world.worldName() + "," + location + "] because the crop config[" + id(state) + "] has been removed."); + world.removeBlockState(location); + return; + } + + int previousPoint = point(state); + World bukkitWorld = world.bukkitWorld(); + if (ConfigManager.doubleCheck()) { + Map.Entry nearest = config.getFloorStageEntry(previousPoint); + String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(bukkitWorld), nearest.getValue().existenceForm()); + if (!config.stageIDs().contains(blockID)) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Crop[" + config.id() + "] is removed at location[" + world.worldName() + "," + location + "] because the id of the block is [" + blockID + "]"); + world.removeBlockState(location); + return; + } + } + + Context context = Context.block(state); + for (DeathCondition deathCondition : config.deathConditions()) { + if (deathCondition.isMet(context)) { + Location bukkitLocation = location.toLocation(bukkitWorld); + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().runLater(() -> { + FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY); + world.removeBlockState(location); + Optional.ofNullable(deathCondition.deathStage()).ifPresent(it -> { + BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, deathCondition.existenceForm(), it, rotation); + }); + }, deathCondition.deathDelay(), bukkitLocation); + return; + } + } + + if (previousPoint >= config.maxPoints()) { + return; + } + + int pointToAdd = 1; + for (GrowCondition growCondition : config.growConditions()) { + if (growCondition.isMet(context)) { + pointToAdd = growCondition.pointToAdd(); + break; + } + } + + Optional optionalState = world.getBlockState(location.add(0,-1,0)); + if (optionalState.isPresent()) { + CustomCropsBlockState belowState = optionalState.get(); + if (belowState.type() instanceof PotBlock potBlock) { + for (Fertilizer fertilizer : potBlock.fertilizers(belowState)) { + FertilizerConfig fertilizerConfig = fertilizer.config(); + if (fertilizerConfig != null) { + pointToAdd = fertilizerConfig.processGainPoints(pointToAdd); + } + } + } + } + + int afterPoints = Math.min(previousPoint + pointToAdd, config.maxPoints()); + point(state, afterPoints); + + int tempPoints = previousPoint; + String preStage = null; + while (tempPoints >= 0) { + Map.Entry preEntry = config.getFloorStageEntry(tempPoints); + CropStageConfig pre = preEntry.getValue(); + if (pre.stageID() != null) { + preStage = pre.stageID(); + break; + } + tempPoints = pre.point() - 1; + } + + String afterStage = null; + ExistenceForm afterForm = null; + tempPoints = afterPoints; + while (tempPoints >= 0) { + Map.Entry afterEntry = config.getFloorStageEntry(tempPoints); + CropStageConfig after = afterEntry.getValue(); + if (after.stageID() != null) { + afterStage = after.stageID(); + afterForm = after.existenceForm(); + break; + } + tempPoints = after.point() - 1; + } + + Location bukkitLocation = location.toLocation(bukkitWorld); + + final String finalPreStage = preStage; + final String finalAfterStage = afterStage; + final ExistenceForm finalAfterForm = afterForm; + + Objects.requireNonNull(finalAfterStage); + Objects.requireNonNull(finalPreStage); + Objects.requireNonNull(finalAfterForm); + + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(() -> { + for (int i = previousPoint + 1; i <= afterPoints; i++) { + CropStageConfig stage = config.stageByPoint(i); + if (stage != null) { + ActionManager.trigger(context, stage.growActions()); + } + } + if (Objects.equals(finalAfterStage, finalPreStage)) return; + FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY); + if (rotation == FurnitureRotation.NONE && config.rotation()) { + rotation = FurnitureRotation.random(); + } + BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, finalAfterForm, finalAfterStage, rotation); + }, bukkitLocation); + } + + public int point(CustomCropsBlockState state) { + return state.get("point").getAsIntTag().map(IntTag::getValue).orElse(0); + } + + public void point(CustomCropsBlockState state, int point) { + state.set("point", new IntTag("point", point)); + } + + public CropConfig config(CustomCropsBlockState state) { + return Registries.CROP.get(id(state)); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfig.java new file mode 100644 index 0000000..a917314 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfig.java @@ -0,0 +1,104 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public interface CropConfig { + + String id(); + + String seed(); + + int maxPoints(); + + Requirement[] plantRequirements(); + + Requirement[] breakRequirements(); + + Requirement[] interactRequirements(); + + GrowCondition[] growConditions(); + + Action[] wrongPotActions(); + + Action[] interactActions(); + + Action[] breakActions(); + + Action[] plantActions(); + + Action[] reachLimitActions(); + + Action[] deathActions(); + + DeathCondition[] deathConditions(); + + BoneMeal[] boneMeals(); + + boolean rotation(); + + Set potWhitelist(); + + CropStageConfig stageByPoint(int point); + + @Nullable + CropStageConfig stageByID(String stageModel); + + CropStageConfig stageWithModelByPoint(int point); + + Collection stages(); + + Collection stageIDs(); + + Map.Entry getFloorStageEntry(int previousPoint); + + static Builder builder() { + return new CropConfigImpl.BuilderImpl(); + } + + interface Builder { + + CropConfig build(); + + Builder id(String id); + + Builder seed(String seed); + + Builder maxPoints(int maxPoints); + + Builder wrongPotActions(Action[] wrongPotActions); + + Builder interactActions(Action[] interactActions); + + Builder breakActions(Action[] breakActions); + + Builder plantActions(Action[] plantActions); + + Builder reachLimitActions(Action[] reachLimitActions); + + Builder plantRequirements(Requirement[] plantRequirements); + + Builder breakRequirements(Requirement[] breakRequirements); + + Builder interactRequirements(Requirement[] interactRequirements); + + Builder growConditions(GrowCondition[] growConditions); + + Builder deathConditions(DeathCondition[] deathConditions); + + Builder boneMeals(BoneMeal[] boneMeals); + + Builder rotation(boolean rotation); + + Builder potWhitelist(Set whitelist); + + Builder stages(Collection stages); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfigImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfigImpl.java new file mode 100644 index 0000000..bc0554a --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropConfigImpl.java @@ -0,0 +1,345 @@ +package net.momirealms.customcrops.api.core.block; + +import com.google.common.base.Preconditions; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.*; + +public class CropConfigImpl implements CropConfig { + + private final String id; + private final String seed; + private final int maxPoints; + private final Action[] wrongPotActions; + private final Action[] interactActions; + private final Action[] breakActions; + private final Action[] plantActions; + private final Action[] reachLimitActions; + private final Action[] deathActions; + private final Requirement[] plantRequirements; + private final Requirement[] breakRequirements; + private final Requirement[] interactRequirements; + private final GrowCondition[] growConditions; + private final DeathCondition[] deathConditions; + private final BoneMeal[] boneMeals; + private final boolean rotation; + private final Set potWhitelist; + private final HashMap point2Stages = new HashMap<>(); + private final NavigableMap navigablePoint2Stages = new TreeMap<>(); + private final HashMap id2Stages = new HashMap<>(); + private final Set stageIDs = new HashSet<>(); + private final HashMap cropStageWithModelMap = new HashMap<>(); + + public CropConfigImpl( + String id, + String seed, + int maxPoints, + Action[] wrongPotActions, + Action[] interactActions, + Action[] breakActions, + Action[] plantActions, + Action[] reachLimitActions, + Action[] deathActions, + Requirement[] plantRequirements, + Requirement[] breakRequirements, + Requirement[] interactRequirements, + GrowCondition[] growConditions, + DeathCondition[] deathConditions, + BoneMeal[] boneMeals, + boolean rotation, + Set potWhitelist, + Collection stageBuilders + ) { + this.id = id; + this.seed = seed; + this.maxPoints = maxPoints; + this.wrongPotActions = wrongPotActions; + this.interactActions = interactActions; + this.breakActions = breakActions; + this.plantActions = plantActions; + this.reachLimitActions = reachLimitActions; + this.deathActions = deathActions; + this.plantRequirements = plantRequirements; + this.breakRequirements = breakRequirements; + this.interactRequirements = interactRequirements; + this.growConditions = growConditions; + this.deathConditions = deathConditions; + this.boneMeals = boneMeals; + this.rotation = rotation; + this.potWhitelist = potWhitelist; + for (CropStageConfig.Builder builder : stageBuilders) { + CropStageConfig config = builder.crop(this).build(); + point2Stages.put(config.point(), config); + navigablePoint2Stages.put(config.point(), config); + String stageID = config.stageID(); + id2Stages.put(stageID, config); + stageIDs.add(stageID); + } + CropStageConfig tempConfig = null; + for (int i = 0; i <= maxPoints; i++) { + CropStageConfig config = point2Stages.get(i); + if (config != null) { + String stageModel = config.stageID(); + if (stageModel != null) { + tempConfig = config; + } + } + cropStageWithModelMap.put(i, tempConfig); + } + } + + @Override + public String id() { + return id; + } + + @Override + public String seed() { + return seed; + } + + @Override + public int maxPoints() { + return maxPoints; + } + + @Override + public Requirement[] plantRequirements() { + return plantRequirements; + } + + @Override + public Requirement[] breakRequirements() { + return breakRequirements; + } + + @Override + public Requirement[] interactRequirements() { + return interactRequirements; + } + + @Override + public GrowCondition[] growConditions() { + return growConditions; + } + + @Override + public Action[] wrongPotActions() { + return wrongPotActions; + } + + @Override + public Action[] interactActions() { + return interactActions; + } + + @Override + public Action[] breakActions() { + return breakActions; + } + + @Override + public Action[] plantActions() { + return plantActions; + } + + @Override + public Action[] reachLimitActions() { + return reachLimitActions; + } + + @Override + public Action[] deathActions() { + return deathActions; + } + + @Override + public DeathCondition[] deathConditions() { + return deathConditions; + } + + @Override + public BoneMeal[] boneMeals() { + return boneMeals; + } + + @Override + public boolean rotation() { + return rotation; + } + + @Override + public Set potWhitelist() { + return potWhitelist; + } + + @Override + public CropStageConfig stageByPoint(int id) { + return point2Stages.get(id); + } + + @Override + public CropStageConfig stageByID(String stageModel) { + return id2Stages.get(stageModel); + } + + @Override + public CropStageConfig stageWithModelByPoint(int point) { + return cropStageWithModelMap.get(Math.min(maxPoints, point)); + } + + @Override + public Collection stages() { + return point2Stages.values(); + } + + @Override + public Collection stageIDs() { + return stageIDs; + } + + @Override + public Map.Entry getFloorStageEntry(int point) { + Preconditions.checkArgument(point >= 0, "Point should be no lower than " + point); + return navigablePoint2Stages.floorEntry(point); + } + + public static class BuilderImpl implements Builder { + + private String id; + private String seed; + private ExistenceForm existenceForm; + private int maxPoints; + private Action[] wrongPotActions; + private Action[] interactActions; + private Action[] breakActions; + private Action[] plantActions; + private Action[] reachLimitActions; + private Action[] deathActions; + private Requirement[] plantRequirements; + private Requirement[] breakRequirements; + private Requirement[] interactRequirements; + private GrowCondition[] growConditions; + private DeathCondition[] deathConditions; + private BoneMeal[] boneMeals; + private boolean rotation; + private Set potWhitelist; + private Collection stages; + + @Override + public CropConfig build() { + return new CropConfigImpl(id, seed, maxPoints, wrongPotActions, interactActions, breakActions, plantActions, reachLimitActions, deathActions, plantRequirements, breakRequirements, interactRequirements, growConditions, deathConditions, boneMeals, rotation, potWhitelist, stages); + } + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder seed(String seed) { + this.seed = seed; + return this; + } + + @Override + public Builder maxPoints(int maxPoints) { + this.maxPoints = maxPoints; + return this; + } + + @Override + public Builder wrongPotActions(Action[] wrongPotActions) { + this.wrongPotActions = wrongPotActions; + return this; + } + + @Override + public Builder interactActions(Action[] interactActions) { + this.interactActions = interactActions; + return this; + } + + @Override + public Builder breakActions(Action[] breakActions) { + this.breakActions = breakActions; + return this; + } + + @Override + public Builder plantActions(Action[] plantActions) { + this.plantActions = plantActions; + return this; + } + + public Builder deathActions(Action[] deathActions) { + this.deathActions = deathActions; + return this; + } + + @Override + public Builder reachLimitActions(Action[] reachLimitActions) { + this.reachLimitActions = reachLimitActions; + return this; + } + + @Override + public Builder plantRequirements(Requirement[] plantRequirements) { + this.plantRequirements = plantRequirements; + return this; + } + + @Override + public Builder breakRequirements(Requirement[] breakRequirements) { + this.breakRequirements = breakRequirements; + return this; + } + + @Override + public Builder interactRequirements(Requirement[] interactRequirements) { + this.interactRequirements = interactRequirements; + return this; + } + + @Override + public Builder growConditions(GrowCondition[] growConditions) { + this.growConditions = growConditions; + return this; + } + + @Override + public Builder deathConditions(DeathCondition[] deathConditions) { + this.deathConditions = deathConditions; + return this; + } + + @Override + public Builder boneMeals(BoneMeal[] boneMeals) { + this.boneMeals = boneMeals; + return this; + } + + @Override + public Builder rotation(boolean rotation) { + this.rotation = rotation; + return this; + } + + @Override + public Builder potWhitelist(Set potWhitelist) { + this.potWhitelist = new HashSet<>(potWhitelist); + return this; + } + + @Override + public Builder stages(Collection stages) { + this.stages = new HashSet<>(stages); + return this; + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfig.java new file mode 100644 index 0000000..beb9448 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfig.java @@ -0,0 +1,61 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +public interface CropStageConfig { + + CropConfig crop(); + + double displayInfoOffset(); + + @Nullable + String stageID(); + + int point(); + + Requirement[] interactRequirements(); + + Requirement[] breakRequirements(); + + Action[] interactActions(); + + Action[] breakActions(); + + Action[] growActions(); + + ExistenceForm existenceForm(); + + static Builder builder() { + return new CropStageConfigImpl.BuilderImpl(); + } + + interface Builder { + + CropStageConfig build(); + + Builder crop(CropConfig crop); + + Builder displayInfoOffset(double offset); + + Builder stageID(String id); + + Builder point(int i); + + Builder interactRequirements(Requirement[] requirements); + + Builder breakRequirements(Requirement[] requirements); + + Builder interactActions(Action[] actions); + + Builder breakActions(Action[] actions); + + Builder growActions(Action[] actions); + + Builder existenceForm(ExistenceForm existenceForm); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfigImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfigImpl.java new file mode 100644 index 0000000..4650289 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CropStageConfigImpl.java @@ -0,0 +1,176 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +public class CropStageConfigImpl implements CropStageConfig { + + private final CropConfig crop; + private final ExistenceForm existenceForm; + private final double offset; + private final String stageID; + private final int point; + private final Requirement[] interactRequirements; + private final Requirement[] breakRequirements; + private final Action[] interactActions; + private final Action[] breakActions; + private final Action[] growActions; + + public CropStageConfigImpl( + CropConfig crop, + ExistenceForm existenceForm, + double offset, + String stageID, + int point, + Requirement[] interactRequirements, + Requirement[] breakRequirements, + Action[] interactActions, + Action[] breakActions, + Action[] growActions + ) { + this.crop = crop; + this.existenceForm = existenceForm; + this.offset = offset; + this.stageID = stageID; + this.point = point; + this.interactRequirements = interactRequirements; + this.breakRequirements = breakRequirements; + this.interactActions = interactActions; + this.breakActions = breakActions; + this.growActions = growActions; + } + + @Override + public CropConfig crop() { + return crop; + } + + @Override + public double displayInfoOffset() { + return offset; + } + + @Nullable + @Override + public String stageID() { + return stageID; + } + + @Override + public int point() { + return point; + } + + @Override + public Requirement[] interactRequirements() { + return interactRequirements; + } + + @Override + public Requirement[] breakRequirements() { + return breakRequirements; + } + + @Override + public Action[] interactActions() { + return interactActions; + } + + @Override + public Action[] breakActions() { + return breakActions; + } + + @Override + public Action[] growActions() { + return growActions; + } + + @Override + public ExistenceForm existenceForm() { + return existenceForm; + } + + public static class BuilderImpl implements Builder { + + private CropConfig crop; + private ExistenceForm existenceForm; + private double offset; + private String stageID; + private int point; + private Requirement[] interactRequirements; + private Requirement[] breakRequirements; + private Action[] interactActions; + private Action[] breakActions; + private Action[] growActions; + + @Override + public CropStageConfig build() { + return new CropStageConfigImpl(crop, existenceForm, offset, stageID, point, interactRequirements, breakRequirements, interactActions, breakActions, growActions); + } + + @Override + public Builder crop(CropConfig crop) { + this.crop = crop; + return this; + } + + @Override + public Builder displayInfoOffset(double offset) { + this.offset = offset; + return this; + } + + @Override + public Builder stageID(String id) { + this.stageID = id; + return this; + } + + @Override + public Builder point(int i) { + this.point = i; + return this; + } + + @Override + public Builder interactRequirements(Requirement[] requirements) { + this.interactRequirements = requirements; + return this; + } + + @Override + public Builder breakRequirements(Requirement[] requirements) { + this.breakRequirements = requirements; + return this; + } + + @Override + public Builder interactActions(Action[] actions) { + this.interactActions = actions; + return this; + } + + @Override + public Builder breakActions(Action[] actions) { + this.breakActions = actions; + return this; + } + + @Override + public Builder growActions(Action[] actions) { + this.growActions = actions; + return this; + } + + @Override + public Builder existenceForm(ExistenceForm existenceForm) { + this.existenceForm = existenceForm; + return this; + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java new file mode 100644 index 0000000..cd06a01 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for 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.core.block; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.util.LocationUtils; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customcrops.common.util.RandomUtils; +import net.momirealms.sparrow.heart.SparrowHeart; +import net.momirealms.sparrow.heart.feature.entity.armorstand.FakeArmorStand; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +public class CrowAttack { + + private SchedulerTask task; + private final Location dynamicLocation; + private final Location cropLocation; + private final Vector vectorDown; + private final Vector vectorUp; + private final Player[] viewers; + private int timer; + private final ItemStack flyModel; + private final ItemStack standModel; + + public CrowAttack(Location location, ItemStack flyModel, ItemStack standModel) { + ArrayList viewers = new ArrayList<>(); + for (Player player : location.getWorld().getPlayers()) { + if (LocationUtils.getDistance(player.getLocation(), location) <= 48) { + viewers.add(player); + } + } + this.viewers = viewers.toArray(new Player[0]); + this.cropLocation = location.clone().add(RandomUtils.generateRandomDouble(-0.25, 0.25), 0, RandomUtils.generateRandomDouble(-0.25, 0.25)); + float yaw = RandomUtils.generateRandomInt(-180, 180); + this.cropLocation.setYaw(yaw); + this.flyModel = flyModel; + this.standModel = standModel; + this.dynamicLocation = cropLocation.clone().add((10 * Math.sin((Math.PI * yaw) / 180)), 10, (- 10 * Math.cos((Math.PI * yaw) / 180))); + this.dynamicLocation.setYaw(yaw); + Location relative = cropLocation.clone().subtract(dynamicLocation); + this.vectorDown = new Vector(relative.getX() / 100, -0.1, relative.getZ() / 100); + this.vectorUp = new Vector(relative.getX() / 100, 0.1, relative.getZ() / 100); + } + + public void start() { + if (this.viewers.length == 0) return; + FakeArmorStand fake1 = SparrowHeart.getInstance().createFakeArmorStand(dynamicLocation); + fake1.invisible(true); + fake1.small(true); + fake1.equipment(EquipmentSlot.HEAD, flyModel); + FakeArmorStand fake2 = SparrowHeart.getInstance().createFakeArmorStand(cropLocation); + fake1.invisible(true); + fake1.small(true); + fake1.equipment(EquipmentSlot.HEAD, standModel); + for (Player player : this.viewers) { + fake1.spawn(player); + } + this.task = BukkitCustomCropsPlugin.getInstance().getScheduler().asyncRepeating(() -> { + timer++; + if (timer < 100) { + dynamicLocation.add(vectorDown); + for (Player player : this.viewers) { + SparrowHeart.getInstance().sendClientSideTeleportEntity(player, dynamicLocation, false, fake1.entityID()); + } + } else if (timer == 100){ + for (Player player : this.viewers) { + fake1.destroy(player); + } + for (Player player : this.viewers) { + fake2.spawn(player); + } + } else if (timer == 150) { + for (Player player : this.viewers) { + fake2.destroy(player); + } + for (Player player : this.viewers) { + fake1.spawn(player); + } + } else if (timer > 150) { + dynamicLocation.add(vectorUp); + for (Player player : this.viewers) { + SparrowHeart.getInstance().sendClientSideTeleportEntity(player, dynamicLocation, false, fake1.entityID()); + } + } + if (timer > 300) { + for (Player player : this.viewers) { + fake1.destroy(player); + } + task.cancel(); + } + }, 50, 50, TimeUnit.MILLISECONDS); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CustomCropsBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CustomCropsBlock.java new file mode 100644 index 0000000..9515fc3 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CustomCropsBlock.java @@ -0,0 +1,29 @@ +package net.momirealms.customcrops.api.core.block; + +import com.flowpowered.nbt.CompoundMap; +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; + +public interface CustomCropsBlock { + + Key type(); + + CustomCropsBlockState createBlockState(); + + CustomCropsBlockState createBlockState(CompoundMap data); + + void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location); + + void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location); + + void onInteract(WrappedInteractEvent event); + + void onBreak(WrappedBreakEvent event); + + void onPlace(WrappedPlaceEvent event); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/DeathCondition.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/DeathCondition.java new file mode 100644 index 0000000..e15a32d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/DeathCondition.java @@ -0,0 +1,40 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import org.jetbrains.annotations.Nullable; + +public class DeathCondition { + + private final Requirement[] requirements; + private final String deathStage; + private final ExistenceForm existenceForm; + private final int deathDelay; + + public DeathCondition(Requirement[] requirements, String deathStage, ExistenceForm existenceForm, int deathDelay) { + this.requirements = requirements; + this.deathStage = deathStage; + this.existenceForm = existenceForm; + this.deathDelay = deathDelay; + } + + @Nullable + public String deathStage() { + return deathStage; + } + + public int deathDelay() { + return deathDelay; + } + + public boolean isMet(Context context) { + return RequirementManager.isSatisfied(context, requirements); + } + + public ExistenceForm existenceForm() { + return existenceForm; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/GreenhouseBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/GreenhouseBlock.java new file mode 100644 index 0000000..b0dbc8e --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/GreenhouseBlock.java @@ -0,0 +1,96 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.BuiltInBlockMechanics; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; +import net.momirealms.customcrops.api.event.GreenhouseGlassBreakEvent; +import net.momirealms.customcrops.api.event.GreenhouseGlassInteractEvent; +import net.momirealms.customcrops.api.event.GreenhouseGlassPlaceEvent; +import net.momirealms.customcrops.api.util.EventUtils; + +import java.util.Optional; + +public class GreenhouseBlock extends AbstractCustomCropsBlock { + + public GreenhouseBlock() { + super(BuiltInBlockMechanics.GREENHOUSE.key()); + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + //tickGreenhouse(world, location); + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + tickGreenhouse(world, location); + } + + private void tickGreenhouse(CustomCropsWorld world, Pos3 location) { + if (!ConfigManager.doubleCheck()) return; + String id = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(world.bukkitWorld()), ConfigManager.greenhouseExistenceForm()); + if (ConfigManager.greenhouse().contains(id)) return; + // remove outdated data + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Greenhouse is removed at location[" + world.worldName() + "," + location + "] because the id of the block/furniture is [" + id + "]"); + world.removeBlockState(location); + } + + @Override + public void onInteract(WrappedInteractEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsBlockState state = getOrFixState(world, pos3); + GreenhouseGlassInteractEvent interactEvent = new GreenhouseGlassInteractEvent(event.player(), event.itemInHand(), event.location(), event.relatedID(), state, event.hand()); + EventUtils.fireAndForget(interactEvent); + } + + @Override + public void onBreak(WrappedBreakEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsBlockState state = getOrFixState(world, pos3); + GreenhouseGlassBreakEvent breakEvent = new GreenhouseGlassBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), event.brokenID(), state, event.reason()); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + return; + } + world.removeBlockState(pos3); + } + + @Override + public void onPlace(WrappedPlaceEvent event) { + CustomCropsBlockState state = createBlockState(); + GreenhouseGlassPlaceEvent placeEvent = new GreenhouseGlassPlaceEvent(event.player(), event.location(), event.itemID(), state); + if (EventUtils.fireAndCheckCancel(placeEvent)) { + return; + } + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsWorld world = event.world(); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + } + + public CustomCropsBlockState getOrFixState(CustomCropsWorld world, Pos3 pos3) { + Optional optional = world.getBlockState(pos3); + if (optional.isPresent() && optional.get().type() instanceof GreenhouseBlock) { + return optional.get(); + } + CustomCropsBlockState state = createBlockState(); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at pos3[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + return state; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/GrowCondition.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/GrowCondition.java new file mode 100644 index 0000000..b8f8561 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/GrowCondition.java @@ -0,0 +1,25 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.api.requirement.RequirementManager; + +public class GrowCondition { + + private final Requirement[] requirements; + private final int pointToAdd; + + public GrowCondition(Requirement[] requirements, int pointToAdd) { + this.requirements = requirements; + this.pointToAdd = pointToAdd; + } + + public int pointToAdd() { + return pointToAdd; + } + + public boolean isMet(Context context) { + return RequirementManager.isSatisfied(context, requirements); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/PotBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotBlock.java new file mode 100644 index 0000000..0080691 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotBlock.java @@ -0,0 +1,574 @@ +package net.momirealms.customcrops.api.core.block; + +import com.flowpowered.nbt.*; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.*; +import net.momirealms.customcrops.api.core.item.Fertilizer; +import net.momirealms.customcrops.api.core.item.FertilizerConfig; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; +import net.momirealms.customcrops.api.event.*; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.PlayerUtils; +import net.momirealms.customcrops.api.util.StringUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.Farmland; +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.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class PotBlock extends AbstractCustomCropsBlock { + + public PotBlock() { + super(BuiltInBlockMechanics.POT.key()); + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (!world.setting().randomTickPot() && canTick(state, world.setting().tickPotInterval())) { + tickPot(state, world, location); + } + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (world.setting().randomTickPot() && canTick(state, world.setting().tickPotInterval())) { + tickPot(state, world, location); + } + } + + @Override + public void onBreak(WrappedBreakEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + PotConfig config = Registries.ITEM_TO_POT.get(event.brokenID()); + if (config == null) { + world.removeBlockState(pos3); + return; + } + + final Player player = event.playerBreaker(); + Context context = Context.player(player); + if (!RequirementManager.isSatisfied(context, config.breakRequirements())) { + event.setCancelled(true); + return; + } + + Location upperLocation = event.location().clone().add(0,1,0); + String upperID = BukkitCustomCropsPlugin.getInstance().getItemManager().anyID(upperLocation); + List cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(upperID); + + CropConfig cropConfig = null; + CropStageConfig stageConfig = null; + + outer: { + if (cropConfigs != null && !cropConfigs.isEmpty()) { + CropBlock cropBlock = (CropBlock) BuiltInBlockMechanics.CROP.mechanic(); + CustomCropsBlockState state = cropBlock.fixOrGetState(world, pos3.add(0,1,0), upperID); + if (state == null) { + // remove ambiguous stage + BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY); + break outer; + } + + cropConfig = cropBlock.config(state); + if (cropConfig == null || !cropConfigs.contains(cropConfig)) { + if (cropConfigs.size() != 1) { + // remove ambiguous stage + BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY); + world.removeBlockState(pos3.add(0,1,0)); + break outer; + } + cropConfig = cropConfigs.get(0); + } + + if (!RequirementManager.isSatisfied(context, cropConfig.breakRequirements())) { + event.setCancelled(true); + return; + } + stageConfig = cropConfig.stageByID(upperID); + // should not be null + assert stageConfig != null; + if (!RequirementManager.isSatisfied(context, stageConfig.breakRequirements())) { + event.setCancelled(true); + return; + } + + CropBreakEvent breakEvent = new CropBreakEvent(event.entityBreaker(), event.blockBreaker(), cropConfig, upperID, upperLocation, state, event.reason()); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + event.setCancelled(true); + return; + } + } + } + + CustomCropsBlockState potState = fixOrGetState(world, pos3, config, event.brokenID()); + + PotBreakEvent breakEvent = new PotBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), config, potState, event.reason()); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + event.setCancelled(true); + return; + } + + ActionManager.trigger(context, config.breakActions()); + if (stageConfig != null) { + ActionManager.trigger(context, stageConfig.breakActions()); + } + if (cropConfig != null) { + ActionManager.trigger(context, cropConfig.breakActions()); + world.removeBlockState(pos3.add(0,1,0)); + BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY); + } + + world.removeBlockState(pos3); + } + + @Override + public void onPlace(WrappedPlaceEvent event) { + PotConfig config = Registries.ITEM_TO_POT.get(event.placedID()); + if (config == null) { + event.setCancelled(true); + return; + } + + Context context = Context.player(event.player()); + if (!RequirementManager.isSatisfied(context, config.placeRequirements())) { + event.setCancelled(true); + return; + } + + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + if (world.setting().potPerChunk() >= 0) { + if (world.testChunkLimitation(pos3, this.getClass(), world.setting().potPerChunk())) { + event.setCancelled(true); + ActionManager.trigger(context, config.reachLimitActions()); + return; + } + } + + CustomCropsBlockState state = BuiltInBlockMechanics.POT.createBlockState(); + id(state, config.id()); + water(state, config.isWet(event.placedID()) ? 1 : 0); + + PotPlaceEvent placeEvent = new PotPlaceEvent(event.player(), event.location(), config, state, event.item(), event.hand()); + if (EventUtils.fireAndCheckCancel(placeEvent)) { + event.setCancelled(true); + return; + } + + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + ActionManager.trigger(context, config.placeActions()); + } + + @Override + public void onInteract(WrappedInteractEvent event) { + PotConfig potConfig = Registries.ITEM_TO_POT.get(event.relatedID()); + if (potConfig == null) { + return; + } + + Location location = event.location(); + Pos3 pos3 = Pos3.from(location); + CustomCropsWorld world = event.world(); + + // fix or get data + CustomCropsBlockState state = fixOrGetState(world, pos3, potConfig, event.relatedID()); + + final Player player = event.player(); + Context context = Context.player(player); + // check use requirements + if (!RequirementManager.isSatisfied(context, potConfig.useRequirements())) { + return; + } + + final ItemStack itemInHand = event.itemInHand(); + // trigger event + PotInteractEvent interactEvent = new PotInteractEvent(player, event.hand(), itemInHand, potConfig, location, state); + if (EventUtils.fireAndCheckCancel(interactEvent)) { + return; + } + + if (tryWateringPot(player, context, state, event.hand(), event.itemID(), potConfig, location, itemInHand)) + return; + + ActionManager.trigger(context, potConfig.interactActions()); + } + + protected boolean tryWateringPot(Player player, Context context, CustomCropsBlockState state, EquipmentSlot hand, String itemID, PotConfig potConfig, Location potLocation, ItemStack itemInHand) { + int waterInPot = water(state); + for (WateringMethod method : potConfig.wateringMethods()) { + if (method.getUsed().equals(itemID) && method.getUsedAmount() <= itemInHand.getAmount()) { + if (method.checkRequirements(context)) { + if (waterInPot >= potConfig.storage()) { + ActionManager.trigger(context, potConfig.fullWaterActions()); + } else { + PotFillEvent waterEvent = new PotFillEvent(player, itemInHand, hand, potLocation, method, state, potConfig); + if (EventUtils.fireAndCheckCancel(waterEvent)) + return true; + if (player.getGameMode() != GameMode.CREATIVE) { + itemInHand.setAmount(Math.max(0, itemInHand.getAmount() - method.getUsedAmount())); + if (method.getReturned() != null) { + ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, method.getReturned()); + if (returned != null) { + PlayerUtils.giveItem(player, returned, method.getReturnedAmount()); + } + } + } + method.triggerActions(context); + ActionManager.trigger(context, potConfig.addWaterActions()); + } + } + return true; + } + } + return false; + } + + public CustomCropsBlockState fixOrGetState(CustomCropsWorld world, Pos3 pos3, PotConfig potConfig, String blockID) { + Optional optionalPotState = world.getBlockState(pos3); + if (optionalPotState.isPresent()) { + CustomCropsBlockState potState = optionalPotState.get(); + if (potState.type() instanceof PotBlock potBlock) { + if (potBlock.id(potState).equals(potConfig.id())) { + return potState; + } + } + } + CustomCropsBlockState state = BuiltInBlockMechanics.POT.createBlockState(); + id(state, potConfig.id()); + water(state, potConfig.isWet(blockID) ? 1 : 0); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + return state; + } + + private void tickPot(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + PotConfig config = config(state); + if (config == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Pot data is removed at location[" + world.worldName() + "," + location + "] because the pot config[" + id(state) + "] has been removed."); + world.removeBlockState(location); + return; + } + + World bukkitWorld = world.bukkitWorld(); + if (ConfigManager.doubleCheck()) { + String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(location.toLocation(bukkitWorld)); + if (!config.blocks().contains(blockID)) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Pot[" + config.id() + "] is removed at location[" + world.worldName() + "," + location + "] because the id of the block is [" + blockID + "]"); + world.removeBlockState(location); + return; + } + } + + boolean hasNaturalWater = false; + boolean waterChanged = false; + + if (config.isRainDropAccepted()) { + if (bukkitWorld.hasStorm() || (!bukkitWorld.isClearWeather() && !bukkitWorld.isThundering())) { + double temperature = bukkitWorld.getTemperature(location.x(), location.y(), location.z()); + if (temperature > 0.15 && temperature < 0.85) { + int y = bukkitWorld.getHighestBlockYAt(location.x(), location.z()); + if (y == location.y()) { + if (addWater(state, 1)) { + waterChanged = true; + } + hasNaturalWater = true; + } + } + } + } + + if (!hasNaturalWater && config.isNearbyWaterAccepted()) { + for (int i = -4; i <= 4; i++) { + for (int j = -4; j <= 4; j++) { + for (int k : new int[]{0, 1}) { + BlockData block = bukkitWorld.getBlockData(location.x() + i, location.y() + j, location.z() + k); + if (block.getMaterial() == Material.WATER || (block instanceof Waterlogged waterlogged && waterlogged.isWaterlogged())) { + if (addWater(state, 1)) { + waterChanged = true; + } + hasNaturalWater = true; + } + } + } + } + } + + if (!hasNaturalWater) { + int waterToLose = 1; + Fertilizer[] fertilizers = fertilizers(state); + for (Fertilizer fertilizer : fertilizers) { + FertilizerConfig fertilizerConfig = fertilizer.config(); + if (fertilizerConfig != null) { + waterToLose = fertilizerConfig.processWaterToLose(waterToLose); + } + } + if (waterToLose > 0) { + if (addWater(state, -waterToLose)) { + waterChanged = true; + } + } + } + + boolean fertilizerChanged = tickFertilizer(state); + + if (fertilizerChanged || waterChanged) { + updateBlockAppearance(location.toLocation(bukkitWorld), config, hasNaturalWater, fertilizers(state)); + } + } + + public int water(CustomCropsBlockState state) { + Tag tag = state.get("water"); + if (tag == null) { + return 0; + } + return tag.getAsIntTag().map(IntTag::getValue).orElse(0); + } + + public boolean addWater(CustomCropsBlockState state, int water) { + return water(state, water + water(state)); + } + + public boolean addWater(CustomCropsBlockState state, PotConfig config, int water) { + return water(state, config, water + water(state)); + } + + public boolean consumeWater(CustomCropsBlockState state, int water) { + return water(state, water(state) - water); + } + + public boolean consumeWater(CustomCropsBlockState state, PotConfig config, int water) { + return water(state, config, water(state) - water); + } + + public boolean water(CustomCropsBlockState state, int water) { + return water(state, config(state), water); + } + + /** + * Set the water for a pot + * + * @param state the block state + * @param config the pot config + * @param water the amount of water + * @return whether the moisture state has been changed + */ + public boolean water(CustomCropsBlockState state, PotConfig config, int water) { + if (water < 0) water = 0; + int current = Math.min(water, config.storage()); + int previous = water(state); + if (water == previous) return false; + state.set("water", new IntTag("water", current)); + return previous == 0 ^ current == 0; + } + + public PotConfig config(CustomCropsBlockState state) { + return Registries.POT.get(id(state)); + } + + /** + * Get the fertilizers in the pot + * + * @param state the block state + * @return applied fertilizers + */ + @SuppressWarnings("unchecked") + @NotNull + public Fertilizer[] fertilizers(CustomCropsBlockState state) { + Tag fertilizerTag = state.get("fertilizers"); + if (fertilizerTag == null) return new Fertilizer[0]; + List tags = ((ListTag) fertilizerTag.getValue()).getValue(); + Fertilizer[] fertilizers = new Fertilizer[tags.size()]; + for (int i = 0; i < tags.size(); i++) { + CompoundTag tag = tags.get(i); + fertilizers[i] = tagToFertilizer(tag.getValue()); + } + return fertilizers; + } + + /** + * Check if the fertilizer can be applied to this pot + * + * @param state the block state + * @param fertilizer the fertilizer to apply + * @return can be applied or not + */ + public boolean canApplyFertilizer(CustomCropsBlockState state, Fertilizer fertilizer) { + Fertilizer[] fertilizers = fertilizers(state); + boolean hasSameTypeFertilizer = false; + for (Fertilizer applied : fertilizers) { + if (fertilizer.id().equals(applied.id())) { + return true; + } + if (fertilizer.type() == applied.type()) { + return false; + } + } + PotConfig config = config(state); + return config.maxFertilizers() > fertilizers.length; + } + + /** + * Add fertilizer to the pot + * If the pot contains the fertilizer, the times would be reset. + * + * @param state the block state + * @param fertilizer the fertilizer to apply + * @return whether to update pot appearance + */ + @SuppressWarnings("unchecked") + public boolean addFertilizer(CustomCropsBlockState state, Fertilizer fertilizer) { + Tag fertilizerTag = state.get("fertilizers"); + if (fertilizerTag == null) { + fertilizerTag = new ListTag("", TagType.TAG_COMPOUND, new ArrayList<>()); + state.set("fertilizers", fertilizerTag); + } + List tags = ((ListTag) fertilizerTag.getValue()).getValue(); + for (CompoundTag tag : tags) { + CompoundMap map = tag.getValue(); + Fertilizer applied = tagToFertilizer(map); + if (fertilizer.id().equals(applied.id())) { + map.put(new IntTag("times", fertilizer.times())); + return false; + } + if (fertilizer.type() == applied.type()) { + return false; + } + } + PotConfig config = config(state); + if (config.maxFertilizers() <= tags.size()) { + return false; + } + tags.add(new CompoundTag("", fertilizerToTag(fertilizer))); + return true; + } + + @SuppressWarnings("unchecked") + private boolean tickFertilizer(CustomCropsBlockState state) { + // no fertilizers applied + Tag fertilizerTag = state.get("fertilizers"); + if (fertilizerTag == null) { + return false; + } + List tags = ((ListTag) fertilizerTag.getValue()).getValue(); + if (tags.isEmpty()) { + return false; + } + List fertilizerToRemove = new ArrayList<>(); + for (int i = 0; i < tags.size(); i++) { + CompoundMap map = tags.get(i).getValue(); + Fertilizer applied = tagToFertilizer(map); + if (applied.reduceTimes()) { + fertilizerToRemove.add(i); + } else { + tags.get(i).setValue(fertilizerToTag(applied)); + } + } + // no fertilizer is used up + if (fertilizerToRemove.isEmpty()) { + return false; + } + CompoundTag lastEntry = tags.get(tags.size() - 1); + Collections.reverse(fertilizerToRemove); + for (int i : fertilizerToRemove) { + tags.remove(i); + } + // all the fertilizers are used up + if (tags.isEmpty()) { + return true; + } + // if the most recent applied fertilizer is the same + CompoundTag newLastEntry = tags.get(tags.size() - 1); + return lastEntry != newLastEntry; + } + + public void updateBlockAppearance(Location location, CustomCropsBlockState state) { + updateBlockAppearance(location, state, fertilizers(state)); + } + + public void updateBlockAppearance(Location location, CustomCropsBlockState state, Fertilizer[] fertilizers) { + updateBlockAppearance(location, state, water(state) != 0, fertilizers); + } + + public void updateBlockAppearance(Location location, CustomCropsBlockState state, boolean hasWater, Fertilizer[] fertilizers) { + updateBlockAppearance( + location, + config(state), + hasWater, + // always using the latest fertilizer as appearance + fertilizers.length == 0 ? null : fertilizers[fertilizers.length - 1] + ); + } + + public void updateBlockAppearance(Location location, PotConfig config, boolean hasWater, Fertilizer[] fertilizers) { + updateBlockAppearance( + location, + config, + hasWater, + // always using the latest fertilizer as appearance + fertilizers.length == 0 ? null : fertilizers[fertilizers.length - 1] + ); + } + + public void updateBlockAppearance(Location location, PotConfig config, boolean hasWater, @Nullable Fertilizer fertilizer) { + String appearance = config.getPotAppearance(hasWater, fertilizer == null ? null : fertilizer.type()); + if (StringUtils.isCapitalLetter(appearance)) { + Block block = location.getBlock(); + Material type = Material.valueOf(appearance); + if (type == Material.FARMLAND) { + Farmland data = ((Farmland) Material.FARMLAND.createBlockData()); + data.setMoisture(hasWater ? 7 : 0); + block.setBlockData(data, false); + } else { + block.setType(type, false); + } + } else { + BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, ExistenceForm.BLOCK, appearance, FurnitureRotation.NONE); + } + } + + private Fertilizer tagToFertilizer(CompoundMap tag) { + return Fertilizer.builder() + .id(((StringTag) tag.get("id")).getValue()) + .times(((IntTag) tag.get("times")).getValue()) + .build(); + } + + private CompoundMap fertilizerToTag(Fertilizer fertilizer) { + CompoundMap tag = new CompoundMap(); + tag.put(new IntTag("times", fertilizer.times())); + tag.put(new StringTag("id", fertilizer.id())); + return tag; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfig.java new file mode 100644 index 0000000..7ec309e --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfig.java @@ -0,0 +1,103 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.item.FertilizerType; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Set; + +public interface PotConfig { + + String id(); + + int storage(); + + boolean isRainDropAccepted(); + + boolean isNearbyWaterAccepted(); + + WateringMethod[] wateringMethods(); + + Set blocks(); + + boolean isWet(String blockID); + + WaterBar waterBar(); + + int maxFertilizers(); + + String getPotAppearance(boolean watered, FertilizerType type); + + Requirement[] placeRequirements(); + + Requirement[] breakRequirements(); + + Requirement[] useRequirements(); + + Action[] tickActions(); + + Action[] reachLimitActions(); + + Action[] interactActions(); + + Action[] placeActions(); + + Action[] breakActions(); + + Action[] addWaterActions(); + + Action[] fullWaterActions(); + + static Builder builder() { + return new PotConfigImpl.BuilderImpl(); + } + + interface Builder { + + PotConfig build(); + + Builder id(String id); + + Builder storage(int storage); + + Builder isRainDropAccepted(boolean isRainDropAccepted); + + Builder isNearbyWaterAccepted(boolean isNearbyWaterAccepted); + + Builder wateringMethods(WateringMethod[] wateringMethods); + + Builder waterBar(WaterBar waterBar); + + Builder maxFertilizers(int maxFertilizers); + + Builder placeRequirements(Requirement[] requirements); + + Builder breakRequirements(Requirement[] requirements); + + Builder useRequirements(Requirement[] requirements); + + Builder tickActions(Action[] tickActions); + + Builder reachLimitActions(Action[] reachLimitActions); + + Builder interactActions(Action[] interactActions); + + Builder placeActions(Action[] placeActions); + + Builder breakActions(Action[] breakActions); + + Builder addWaterActions(Action[] addWaterActions); + + Builder fullWaterActions(Action[] fullWaterActions); + + Builder basicAppearance(Pair basicAppearance); + + Builder potAppearanceMap(HashMap> potAppearanceMap); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfigImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfigImpl.java new file mode 100644 index 0000000..18b115e --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/PotConfigImpl.java @@ -0,0 +1,339 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.item.FertilizerType; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class PotConfigImpl implements PotConfig { + + private final String id; + private final Pair basicAppearance; + private final HashMap> potAppearanceMap; + private final Set blocks = new HashSet<>(); + private final Set wetBlocks = new HashSet<>(); + private final int storage; + private final boolean isRainDropAccepted; + private final boolean isNearbyWaterAccepted; + private final WateringMethod[] wateringMethods; + private final WaterBar waterBar; + private final int maxFertilizers; + private final Requirement[] placeRequirements; + private final Requirement[] breakRequirements; + private final Requirement[] useRequirements; + private final Action[] tickActions; + private final Action[] reachLimitActions; + private final Action[] interactActions; + private final Action[] placeActions; + private final Action[] breakActions; + private final Action[] addWaterActions; + private final Action[] fullWaterActions; + + public PotConfigImpl( + String id, + Pair basicAppearance, + HashMap> potAppearanceMap, + int storage, + boolean isRainDropAccepted, + boolean isNearbyWaterAccepted, + WateringMethod[] wateringMethods, + WaterBar waterBar, + int maxFertilizers, + Requirement[] placeRequirements, + Requirement[] breakRequirements, + Requirement[] useRequirements, + Action[] tickActions, + Action[] reachLimitActions, + Action[] interactActions, + Action[] placeActions, + Action[] breakActions, + Action[] addWaterActions, + Action[] fullWaterActions + ) { + this.id = id; + this.basicAppearance = basicAppearance; + this.potAppearanceMap = potAppearanceMap; + this.storage = storage; + this.isRainDropAccepted = isRainDropAccepted; + this.isNearbyWaterAccepted = isNearbyWaterAccepted; + this.wateringMethods = wateringMethods; + this.waterBar = waterBar; + this.maxFertilizers = maxFertilizers; + this.placeRequirements = placeRequirements; + this.breakRequirements = breakRequirements; + this.useRequirements = useRequirements; + this.tickActions = tickActions; + this.reachLimitActions = reachLimitActions; + this.interactActions = interactActions; + this.placeActions = placeActions; + this.breakActions = breakActions; + this.addWaterActions = addWaterActions; + this.fullWaterActions = fullWaterActions; + this.blocks.add(basicAppearance.left()); + this.blocks.add(basicAppearance.right()); + this.wetBlocks.add(basicAppearance.right()); + for (Pair pair : potAppearanceMap.values()) { + this.blocks.add(pair.left()); + this.blocks.add(pair.right()); + this.wetBlocks.add(pair.right()); + } + } + + @Override + public String id() { + return id; + } + + @Override + public int storage() { + return storage; + } + + @Override + public boolean isRainDropAccepted() { + return isRainDropAccepted; + } + + @Override + public boolean isNearbyWaterAccepted() { + return isNearbyWaterAccepted; + } + + @Override + public WateringMethod[] wateringMethods() { + return wateringMethods; + } + + @Override + public Set blocks() { + return blocks; + } + + @Override + public boolean isWet(String blockID) { + return wetBlocks.contains(blockID); + } + + @Override + public WaterBar waterBar() { + return waterBar; + } + + @Override + public int maxFertilizers() { + return maxFertilizers; + } + + @Override + public String getPotAppearance(boolean watered, FertilizerType type) { + if (type != null) { + Pair appearance = potAppearanceMap.get(type); + if (appearance != null) { + return watered ? appearance.right() : appearance.left(); + } + } + return watered ? basicAppearance.right() : basicAppearance.left(); + } + + @Override + public Requirement[] placeRequirements() { + return placeRequirements; + } + + @Override + public Requirement[] breakRequirements() { + return breakRequirements; + } + + @Override + public Requirement[] useRequirements() { + return useRequirements; + } + + @Override + public Action[] tickActions() { + return tickActions; + } + + @Override + public Action[] reachLimitActions() { + return reachLimitActions; + } + + @Override + public Action[] interactActions() { + return interactActions; + } + + @Override + public Action[] placeActions() { + return placeActions; + } + + @Override + public Action[] breakActions() { + return breakActions; + } + + @Override + public Action[] addWaterActions() { + return addWaterActions; + } + + @Override + public Action[] fullWaterActions() { + return fullWaterActions; + } + + public static class BuilderImpl implements Builder { + + private String id; + private ExistenceForm existenceForm; + private Pair basicAppearance; + private HashMap> potAppearanceMap; + private int storage; + private boolean isRainDropAccepted; + private boolean isNearbyWaterAccepted; + private WateringMethod[] wateringMethods; + private WaterBar waterBar; + private int maxFertilizers; + private Requirement[] placeRequirements; + private Requirement[] breakRequirements; + private Requirement[] useRequirements; + private Action[] tickActions; + private Action[] reachLimitActions; + private Action[] interactActions; + private Action[] placeActions; + private Action[] breakActions; + private Action[] addWaterActions; + private Action[] fullWaterActions; + + @Override + public PotConfig build() { + return new PotConfigImpl(id, basicAppearance, potAppearanceMap, storage, isRainDropAccepted, isNearbyWaterAccepted, wateringMethods, waterBar, maxFertilizers, placeRequirements, breakRequirements, useRequirements, tickActions, reachLimitActions, interactActions, placeActions, breakActions, addWaterActions, fullWaterActions); + } + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder storage(int storage) { + this.storage = storage; + return this; + } + + @Override + public Builder isRainDropAccepted(boolean isRainDropAccepted) { + this.isRainDropAccepted = isRainDropAccepted; + return this; + } + + @Override + public Builder isNearbyWaterAccepted(boolean isNearbyWaterAccepted) { + this.isNearbyWaterAccepted = isNearbyWaterAccepted; + return this; + } + + @Override + public Builder wateringMethods(WateringMethod[] wateringMethods) { + this.wateringMethods = wateringMethods; + return this; + } + + @Override + public Builder waterBar(WaterBar waterBar) { + this.waterBar = waterBar; + return this; + } + + @Override + public Builder maxFertilizers(int maxFertilizers) { + this.maxFertilizers = maxFertilizers; + return this; + } + + @Override + public Builder placeRequirements(Requirement[] requirements) { + this.placeRequirements = requirements; + return this; + } + + @Override + public Builder breakRequirements(Requirement[] requirements) { + this.breakRequirements = requirements; + return this; + } + + @Override + public Builder useRequirements(Requirement[] requirements) { + this.useRequirements = requirements; + return this; + } + + @Override + public Builder tickActions(Action[] tickActions) { + this.tickActions = tickActions; + return this; + } + + @Override + public Builder reachLimitActions(Action[] reachLimitActions) { + this.reachLimitActions = reachLimitActions; + return this; + } + + @Override + public Builder interactActions(Action[] interactActions) { + this.interactActions = interactActions; + return this; + } + + @Override + public Builder placeActions(Action[] placeActions) { + this.placeActions = placeActions; + return this; + } + + @Override + public Builder breakActions(Action[] breakActions) { + this.breakActions = breakActions; + return this; + } + + @Override + public Builder addWaterActions(Action[] addWaterActions) { + this.addWaterActions = addWaterActions; + return this; + } + + @Override + public Builder fullWaterActions(Action[] fullWaterActions) { + this.fullWaterActions = fullWaterActions; + return this; + } + + @Override + public Builder basicAppearance(Pair basicAppearance) { + this.basicAppearance = basicAppearance; + return this; + } + + @Override + public Builder potAppearanceMap(HashMap> potAppearanceMap) { + this.potAppearanceMap = potAppearanceMap; + return this; + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/ScarecrowBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/ScarecrowBlock.java new file mode 100644 index 0000000..1880824 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/ScarecrowBlock.java @@ -0,0 +1,94 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.BuiltInBlockMechanics; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; +import net.momirealms.customcrops.api.event.*; +import net.momirealms.customcrops.api.util.EventUtils; + +import java.util.Optional; + +public class ScarecrowBlock extends AbstractCustomCropsBlock { + + public ScarecrowBlock() { + super(BuiltInBlockMechanics.SCARECROW.key()); + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + //tickScarecrow(world, location); + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + tickScarecrow(world, location); + } + + private void tickScarecrow(CustomCropsWorld world, Pos3 location) { + if (!ConfigManager.doubleCheck()) return; + String id = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(world.bukkitWorld()), ConfigManager.scarecrowExistenceForm()); + if (ConfigManager.scarecrow().contains(id)) return; + // remove outdated data + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Scarecrow is removed at location[" + world.worldName() + "," + location + "] because the id of the block/furniture is [" + id + "]"); + world.removeBlockState(location); + } + + @Override + public void onInteract(WrappedInteractEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsBlockState state = getOrFixState(world, pos3); + ScarecrowInteractEvent interactEvent = new ScarecrowInteractEvent(event.player(), event.itemInHand(), event.location(), event.relatedID(), state, event.hand()); + EventUtils.fireAndForget(interactEvent); + } + + @Override + public void onBreak(WrappedBreakEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsBlockState state = getOrFixState(world, pos3); + ScarecrowBreakEvent breakEvent = new ScarecrowBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), event.brokenID(), state, event.reason()); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + return; + } + world.removeBlockState(pos3); + } + + @Override + public void onPlace(WrappedPlaceEvent event) { + CustomCropsBlockState state = createBlockState(); + ScarecrowPlaceEvent placeEvent = new ScarecrowPlaceEvent(event.player(), event.location(), event.itemID(), state); + if (EventUtils.fireAndCheckCancel(placeEvent)) { + return; + } + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsWorld world = event.world(); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + } + + public CustomCropsBlockState getOrFixState(CustomCropsWorld world, Pos3 pos3) { + Optional optional = world.getBlockState(pos3); + if (optional.isPresent() && optional.get().type() instanceof ScarecrowBlock) { + return optional.get(); + } + CustomCropsBlockState state = createBlockState(); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at pos3[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + return state; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerBlock.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerBlock.java new file mode 100644 index 0000000..3b6373a --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerBlock.java @@ -0,0 +1,310 @@ +package net.momirealms.customcrops.api.core.block; + +import com.flowpowered.nbt.IntTag; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.*; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +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.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent; +import net.momirealms.customcrops.api.event.SprinklerBreakEvent; +import net.momirealms.customcrops.api.event.SprinklerFillEvent; +import net.momirealms.customcrops.api.event.SprinklerInteractEvent; +import net.momirealms.customcrops.api.event.SprinklerPlaceEvent; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.PlayerUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class SprinklerBlock extends AbstractCustomCropsBlock { + + public SprinklerBlock() { + super(BuiltInBlockMechanics.SPRINKLER.key()); + } + + @Override + public void randomTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (!world.setting().randomTickSprinkler() && canTick(state, world.setting().tickSprinklerInterval())) { + tickSprinkler(state, world, location); + } + } + + @Override + public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + if (world.setting().randomTickSprinkler() && canTick(state, world.setting().tickSprinklerInterval())) { + tickSprinkler(state, world, location); + } + } + + @Override + public void onBreak(WrappedBreakEvent event) { + CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(event.location()); + SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.brokenID()); + if (config == null) { + world.removeBlockState(pos3); + return; + } + + final Player player = event.playerBreaker(); + Context context = Context.player(player); + CustomCropsBlockState state = fixOrGetState(world, pos3, config, event.brokenID()); + if (!RequirementManager.isSatisfied(context, config.breakRequirements())) { + event.setCancelled(true); + return; + } + + SprinklerBreakEvent breakEvent = new SprinklerBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), state, config, event.reason()); + if (EventUtils.fireAndCheckCancel(breakEvent)) { + event.setCancelled(true); + return; + } + + world.removeBlockState(pos3); + ActionManager.trigger(context, config.breakActions()); + } + + @Override + public void onPlace(WrappedPlaceEvent event) { + SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.placedID()); + if (config == null) { + event.setCancelled(true); + return; + } + + final Player player = event.player(); + Context context = Context.player(player); + if (!RequirementManager.isSatisfied(context, config.placeRequirements())) { + event.setCancelled(true); + return; + } + + Pos3 pos3 = Pos3.from(event.location()); + CustomCropsWorld world = event.world(); + if (world.setting().sprinklerPerChunk() >= 0) { + if (world.testChunkLimitation(pos3, this.getClass(), world.setting().sprinklerPerChunk())) { + event.setCancelled(true); + ActionManager.trigger(context, config.reachLimitActions()); + return; + } + } + + CustomCropsBlockState state = createBlockState(); + id(state, config.id()); + water(state, config.threeDItemWithWater().equals(event.placedID()) ? 1 : 0); + + SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, event.item(), event.hand(), event.location(), config, state); + if (EventUtils.fireAndCheckCancel(placeEvent)) { + event.setCancelled(true); + return; + } + + world.addBlockState(pos3, state); + ActionManager.trigger(context, config.placeActions()); + } + + @Override + public void onInteract(WrappedInteractEvent event) { + SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.relatedID()); + if (config == null) { + return; + } + + final Player player = event.player(); + Context context = Context.player(player); + CustomCropsBlockState state = fixOrGetState(event.world(), Pos3.from(event.location()), config, event.relatedID()); + if (!RequirementManager.isSatisfied(context, config.useRequirements())) { + return; + } + + int waterInSprinkler = water(state); + String itemID = event.itemID(); + ItemStack itemInHand = event.itemInHand(); + if (!config.infinite()) { + for (WateringMethod method : config.wateringMethods()) { + if (method.getUsed().equals(itemID) && method.getUsedAmount() <= itemInHand.getAmount()) { + if (method.checkRequirements(context)) { + if (waterInSprinkler >= config.storage()) { + ActionManager.trigger(context, config.fullWaterActions()); + } else { + SprinklerFillEvent waterEvent = new SprinklerFillEvent(player, itemInHand, event.hand(), event.location(), method, state, config); + if (EventUtils.fireAndCheckCancel(waterEvent)) + return; + if (player.getGameMode() != GameMode.CREATIVE) { + itemInHand.setAmount(Math.max(0, itemInHand.getAmount() - method.getUsedAmount())); + if (method.getReturned() != null) { + ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, method.getReturned()); + if (returned != null) { + PlayerUtils.giveItem(player, returned, method.getReturnedAmount()); + } + } + } + method.triggerActions(context); + ActionManager.trigger(context, config.addWaterActions()); + } + } + return; + } + } + } + + SprinklerInteractEvent interactEvent = new SprinklerInteractEvent(player, event.itemInHand(), event.location(), config, state, event.hand()); + if (EventUtils.fireAndCheckCancel(interactEvent)) { + return; + } + + ActionManager.trigger(context, config.interactActions()); + } + + public CustomCropsBlockState fixOrGetState(CustomCropsWorld world, Pos3 pos3, SprinklerConfig sprinklerConfig, String blockID) { + Optional optionalPotState = world.getBlockState(pos3); + if (optionalPotState.isPresent()) { + CustomCropsBlockState potState = optionalPotState.get(); + if (potState.type() instanceof SprinklerBlock sprinklerBlock) { + if (sprinklerBlock.id(potState).equals(sprinklerConfig.id())) { + return potState; + } + } + } + CustomCropsBlockState state = BuiltInBlockMechanics.SPRINKLER.createBlockState(); + id(state, sprinklerConfig.id()); + water(state, blockID.equals(sprinklerConfig.threeDItemWithWater()) ? 1 : 0); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + return state; + } + + private void tickSprinkler(CustomCropsBlockState state, CustomCropsWorld world, Pos3 location) { + SprinklerConfig config = config(state); + if (config == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Sprinkler data is removed at location[" + world.worldName() + "," + location + "] because the sprinkler config[" + id(state) + "] has been removed."); + world.removeBlockState(location); + return; + } + boolean updateState; + if (!config.infinite()) { + int water = water(state); + if (water <= 0) { + return; + } + water(state, --water); + updateState = water == 0; + } else { + updateState = false; + } + + Context context = Context.block(state); + World bukkitWorld = world.bukkitWorld(); + Location bukkitLocation = location.toLocation(bukkitWorld); + + CompletableFuture syncCheck = new CompletableFuture<>(); + + // place/remove entities on main thread + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(() -> { + + if (ConfigManager.doubleCheck()) { + String modelID = BukkitCustomCropsPlugin.getInstance().getItemManager().id(bukkitLocation, config.existenceForm()); + if (modelID == null || !config.modelIDs().contains(modelID)) { + world.removeBlockState(location); + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Sprinkler[" + config.id() + "] is removed at Location[" + world.worldName() + "," + location + "] because the id of the block/furniture is " + modelID); + syncCheck.complete(false); + return; + } + } + + ActionManager.trigger(context, config.workActions()); + if (updateState && !config.threeDItem().equals(config.threeDItemWithWater())) { + updateBlockAppearance(bukkitLocation, config, false); + } + + syncCheck.complete(true); + }, bukkitLocation); + + syncCheck.thenAccept(result -> { + if (result) { + int[][] range = config.range(); + Pos3[] pos3s = new Pos3[range.length * 2]; + for (int i = 0; i < range.length; i++) { + int x = range[i][0]; + int z = range[i][1]; + pos3s[i] = location.add(x, 0, z); + pos3s[i] = location.add(x, -1, z); + } + + for (Pos3 pos3 : pos3s) { + Optional optionalState = world.getBlockState(pos3); + if (optionalState.isPresent()) { + CustomCropsBlockState anotherState = optionalState.get(); + if (anotherState.type() instanceof PotBlock potBlock) { + PotConfig potConfig = potBlock.config(anotherState); + if (config.potWhitelist().contains(potConfig.id())) { + if (potBlock.addWater(anotherState, potConfig, config.sprinklingAmount())) { + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run( + () -> potBlock.updateBlockAppearance( + pos3.toLocation(world.bukkitWorld()), + potConfig, + true, + potBlock.fertilizers(anotherState) + ), + bukkitWorld, + pos3.chunkX(), pos3.chunkZ() + ); + } + } + } + } + } + } + }); + } + + public boolean addWater(CustomCropsBlockState state, int water) { + return water(state, water + water(state)); + } + + public boolean addWater(CustomCropsBlockState state, SprinklerConfig config, int water) { + return water(state, config, water + water(state)); + } + + public int water(CustomCropsBlockState state) { + return state.get("water").getAsIntTag().map(IntTag::getValue).orElse(0); + } + + public boolean water(CustomCropsBlockState state, int water) { + return water(state, config(state), water); + } + + public boolean water(CustomCropsBlockState state, SprinklerConfig config, int water) { + if (water < 0) water = 0; + int current = Math.min(water, config.storage()); + int previous = water(state); + if (water == previous) return false; + state.set("water", new IntTag("water", current)); + return previous == 0 ^ current == 0; + } + + public SprinklerConfig config(CustomCropsBlockState state) { + return Registries.SPRINKLER.get(id(state)); + } + + public void updateBlockAppearance(Location location, SprinklerConfig config, boolean hasWater) { + FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(location, ExistenceForm.ANY); + BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, config.existenceForm(), hasWater ? config.threeDItemWithWater() : config.threeDItem(), rotation); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfig.java new file mode 100644 index 0000000..c9a85b9 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfig.java @@ -0,0 +1,148 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public interface SprinklerConfig { + + String id(); + + int storage(); + + int[][] range(); + + boolean infinite(); + + int sprinklingAmount(); + + @Nullable + String twoDItem(); + + @NotNull + String threeDItem(); + + @NotNull + String threeDItemWithWater(); + + @NotNull + Set potWhitelist(); + + @NotNull + Set modelIDs(); + + @Nullable + WaterBar waterBar(); + + @NotNull + ExistenceForm existenceForm(); + + /** + * Get the requirements for placement + * + * @return requirements for placement + */ + @Nullable + Requirement[] placeRequirements(); + + /** + * Get the requirements for breaking + * + * @return requirements for breaking + */ + @Nullable + Requirement[] breakRequirements(); + + /** + * Get the requirements for using + * + * @return requirements for using + */ + @Nullable + Requirement[] useRequirements(); + + @Nullable + Action[] workActions(); + + @Nullable + Action[] interactActions(); + + @Nullable + Action[] placeActions(); + + @Nullable + Action[] breakActions(); + + @Nullable + Action[] addWaterActions(); + + @Nullable + Action[] reachLimitActions(); + + @Nullable + Action[] fullWaterActions(); + + @NotNull + WateringMethod[] wateringMethods(); + + static Builder builder() { + return new SprinklerConfigImpl.BuilderImpl(); + } + + interface Builder { + + SprinklerConfig build(); + + Builder id(String id); + + Builder existenceForm(ExistenceForm existenceForm); + + Builder storage(int storage); + + Builder range(int[][] range); + + Builder infinite(boolean infinite); + + Builder sprinklingAmount(int sprinklingAmount); + + Builder potWhitelist(Set potWhitelist); + + Builder waterBar(WaterBar waterBar); + + Builder twoDItem(@Nullable String twoDItem); + + Builder threeDItem(String threeDItem); + + Builder threeDItemWithWater(String threeDItemWithWater); + + Builder placeRequirements(Requirement[] placeRequirements); + + Builder breakRequirements(Requirement[] breakRequirements); + + Builder useRequirements(Requirement[] useRequirements); + + Builder workActions(Action[] workActions); + + Builder interactActions(Action[] interactActions); + + Builder addWaterActions(Action[] addWaterActions); + + Builder reachLimitActions(Action[] reachLimitActions); + + Builder placeActions(Action[] placeActions); + + Builder breakActions(Action[] breakActions); + + Builder fullWaterActions(Action[] fullWaterActions); + + Builder wateringMethods(WateringMethod[] wateringMethods); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfigImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfigImpl.java new file mode 100644 index 0000000..3a3a52a --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/SprinklerConfigImpl.java @@ -0,0 +1,375 @@ +package net.momirealms.customcrops.api.core.block; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class SprinklerConfigImpl implements SprinklerConfig { + private final String id; + private final ExistenceForm existenceForm; + private final int storage; + private final int[][] range; + private final boolean infinite; + private final int sprinklingAmount; + private final Set potWhitelist; + private final WaterBar waterBar; + private final String twoDItem; + private final String threeDItem; + private final String threeDItemWithWater; + private final Requirement[] placeRequirements; + private final Requirement[] breakRequirements; + private final Requirement[] useRequirements; + private final Action[] workActions; + private final Action[] interactActions; + private final Action[] reachLimitActions; + private final Action[] addWaterActions; + private final Action[] placeActions; + private final Action[] breakActions; + private final Action[] fullWaterActions; + private final WateringMethod[] wateringMethods; + private final Set modelIDs = new HashSet<>(); + + public SprinklerConfigImpl( + String id, + ExistenceForm existenceForm, + int storage, + int[][] range, + boolean infinite, + int sprinklingAmount, + Set potWhitelist, + WaterBar waterBar, + String twoDItem, + String threeDItem, + String threeDItemWithWater, + Requirement[] placeRequirements, + Requirement[] breakRequirements, + Requirement[] useRequirements, + Action[] workActions, + Action[] interactActions, + Action[] reachLimitActions, + Action[] addWaterActions, + Action[] placeActions, + Action[] breakActions, + Action[] fullWaterActions, + WateringMethod[] wateringMethods + ) { + this.id = id; + this.existenceForm = existenceForm; + this.storage = storage; + this.range = range; + this.infinite = infinite; + this.sprinklingAmount = sprinklingAmount; + this.potWhitelist = potWhitelist; + this.waterBar = waterBar; + this.twoDItem = twoDItem; + this.threeDItem = Objects.requireNonNull(threeDItem); + this.threeDItemWithWater = Objects.requireNonNullElse(threeDItemWithWater, threeDItem); + this.placeRequirements = placeRequirements; + this.breakRequirements = breakRequirements; + this.useRequirements = useRequirements; + this.workActions = workActions; + this.interactActions = interactActions; + this.reachLimitActions = reachLimitActions; + this.addWaterActions = addWaterActions; + this.placeActions = placeActions; + this.breakActions = breakActions; + this.fullWaterActions = fullWaterActions; + this.modelIDs.add(twoDItem); + this.modelIDs.add(threeDItem); + this.modelIDs.add(threeDItemWithWater); + this.wateringMethods = wateringMethods; + } + + @Override + public String id() { + return id; + } + + @Override + public int storage() { + return storage; + } + + @Override + public int[][] range() { + return range; + } + + @Override + public boolean infinite() { + return infinite; + } + + @Override + public int sprinklingAmount() { + return sprinklingAmount; + } + + @Override + public String twoDItem() { + return twoDItem; + } + + @NotNull + @Override + public String threeDItem() { + return threeDItem; + } + + @NotNull + @Override + public String threeDItemWithWater() { + return threeDItemWithWater; + } + + @NotNull + @Override + public Set potWhitelist() { + return potWhitelist; + } + + @NotNull + @Override + public Set modelIDs() { + return modelIDs; + } + + @Override + public WaterBar waterBar() { + return waterBar; + } + + @NotNull + @Override + public ExistenceForm existenceForm() { + return existenceForm; + } + + @Override + public Requirement[] placeRequirements() { + return placeRequirements; + } + + @Override + public Requirement[] breakRequirements() { + return breakRequirements; + } + + @Override + public Requirement[] useRequirements() { + return useRequirements; + } + + @Override + public Action[] workActions() { + return workActions; + } + + @Override + public Action[] interactActions() { + return interactActions; + } + + @Override + public Action[] placeActions() { + return placeActions; + } + + @Override + public Action[] breakActions() { + return breakActions; + } + + @Override + public Action[] addWaterActions() { + return addWaterActions; + } + + @Override + public Action[] reachLimitActions() { + return reachLimitActions; + } + + @Override + public Action[] fullWaterActions() { + return fullWaterActions; + } + + @NotNull + @Override + public WateringMethod[] wateringMethods() { + return wateringMethods == null ? new WateringMethod[0] : wateringMethods; + } + + public static class BuilderImpl implements Builder { + private String id; + private ExistenceForm existenceForm; + private int storage; + private int[][] range; + private boolean infinite; + private int sprinklingAmount; + private Set potWhitelist; + private WaterBar waterBar; + private Requirement[] placeRequirements; + private Requirement[] breakRequirements; + private Requirement[] useRequirements; + private Action[] workActions; + private Action[] interactActions; + private Action[] reachLimitActions; + private Action[] addWaterActions; + private Action[] placeActions; + private Action[] breakActions; + private Action[] fullWaterActions; + private String twoDItem; + private String threeDItem; + private String threeDItemWithWater; + private WateringMethod[] wateringMethods; + + @Override + public SprinklerConfig build() { + return new SprinklerConfigImpl(id, existenceForm, storage, range, infinite, sprinklingAmount, potWhitelist, waterBar, twoDItem, threeDItem, threeDItemWithWater, + placeRequirements, breakRequirements, useRequirements, workActions, interactActions, reachLimitActions, addWaterActions, placeActions, breakActions, fullWaterActions, wateringMethods); + } + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder existenceForm(ExistenceForm existenceForm) { + this.existenceForm = existenceForm; + return this; + } + + @Override + public Builder storage(int storage) { + this.storage = storage; + return this; + } + + @Override + public Builder range(int[][] range) { + this.range = range; + return this; + } + + @Override + public Builder infinite(boolean infinite) { + this.infinite = infinite; + return this; + } + + @Override + public Builder sprinklingAmount(int sprinklingAmount) { + this.sprinklingAmount = sprinklingAmount; + return this; + } + + @Override + public Builder potWhitelist(Set potWhitelist) { + this.potWhitelist = new HashSet<>(potWhitelist); + return this; + } + + @Override + public Builder waterBar(WaterBar waterBar) { + this.waterBar = waterBar; + return this; + } + + @Override + public Builder twoDItem(String twoDItem) { + this.twoDItem = twoDItem; + return this; + } + + @Override + public Builder threeDItem(String threeDItem) { + this.threeDItem = threeDItem; + return this; + } + + @Override + public Builder threeDItemWithWater(String threeDItemWithWater) { + this.threeDItemWithWater = threeDItemWithWater; + return this; + } + + @Override + public Builder placeRequirements(Requirement[] placeRequirements) { + this.placeRequirements = placeRequirements; + return this; + } + + @Override + public Builder breakRequirements(Requirement[] breakRequirements) { + this.breakRequirements = breakRequirements; + return this; + } + + @Override + public Builder useRequirements(Requirement[] useRequirements) { + this.useRequirements = useRequirements; + return this; + } + + @Override + public Builder workActions(Action[] workActions) { + this.workActions = workActions; + return this; + } + + @Override + public Builder interactActions(Action[] interactActions) { + this.interactActions = interactActions; + return this; + } + + @Override + public Builder addWaterActions(Action[] addWaterActions) { + this.addWaterActions = addWaterActions; + return this; + } + + @Override + public Builder reachLimitActions(Action[] reachLimitActions) { + this.reachLimitActions = reachLimitActions; + return this; + } + + @Override + public Builder placeActions(Action[] placeActions) { + this.placeActions = placeActions; + return this; + } + + @Override + public Builder breakActions(Action[] breakActions) { + this.breakActions = breakActions; + return this; + } + + @Override + public Builder fullWaterActions(Action[] fullWaterActions) { + this.fullWaterActions = fullWaterActions; + return this; + } + + @Override + public Builder wateringMethods(WateringMethod[] wateringMethods) { + this.wateringMethods = wateringMethods; + return this; + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/VariationCrop.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/VariationData.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/VariationCrop.java rename to api/src/main/java/net/momirealms/customcrops/api/core/block/VariationData.java index dc5d1b9..dd8f30c 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/VariationCrop.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/VariationData.java @@ -15,31 +15,31 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.item.impl; +package net.momirealms.customcrops.api.core.block; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; +import net.momirealms.customcrops.api.core.ExistenceForm; -public class VariationCrop { +public class VariationData { private final String id; - private final ItemCarrier itemMode; + private final ExistenceForm form; private final double chance; - public VariationCrop(String id, ItemCarrier itemMode, double chance) { + public VariationData(String id, ExistenceForm form, double chance) { this.id = id; - this.itemMode = itemMode; + this.form = form; this.chance = chance; } - public String getItemID() { + public String id() { return id; } - public ItemCarrier getItemCarrier() { - return itemMode; + public ExistenceForm existenceForm() { + return form; } - public double getChance() { + public double chance() { return chance; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractCustomCropsItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractCustomCropsItem.java new file mode 100644 index 0000000..b6c307f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractCustomCropsItem.java @@ -0,0 +1,29 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.core.InteractionResult; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; + +public abstract class AbstractCustomCropsItem implements CustomCropsItem { + + private final Key type; + + public AbstractCustomCropsItem(Key type) { + this.type = type; + } + + @Override + public Key type() { + return type; + } + + @Override + public InteractionResult interactAt(WrappedInteractEvent wrapped) { + return InteractionResult.PASS; + } + + @Override + public void interactAir(WrappedInteractAirEvent event) { + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractFertilizerConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractFertilizerConfig.java new file mode 100644 index 0000000..f0f6088 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/AbstractFertilizerConfig.java @@ -0,0 +1,121 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Objects; +import java.util.Set; + +public abstract class AbstractFertilizerConfig implements FertilizerConfig { + + protected String id; + protected String itemID; + protected String icon; + protected int times; + protected boolean beforePlant; + protected Set whitelistPots; + protected Requirement[] requirements; + protected Action[] beforePlantActions; + protected Action[] useActions; + protected Action[] wrongPotActions; + + public AbstractFertilizerConfig( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions + ) { + this.id = Objects.requireNonNull(id); + this.itemID = Objects.requireNonNull(itemID); + this.beforePlant = beforePlant; + this.times = times; + this.icon = icon; + this.requirements = requirements; + this.whitelistPots = whitelistPots; + this.beforePlantActions = beforePlantActions; + this.useActions = useActions; + this.wrongPotActions = wrongPotActions; + } + + @Override + public Action[] beforePlantActions() { + return beforePlantActions; + } + + @Override + public Action[] useActions() { + return useActions; + } + + @Override + public Action[] wrongPotActions() { + return wrongPotActions; + } + + @Override + public boolean beforePlant() { + return beforePlant; + } + + @Override + public Set whitelistPots() { + return whitelistPots; + } + + @Override + public String icon() { + return icon; + } + + @Override + public int times() { + return times; + } + + @Override + public String id() { + return id; + } + + @Override + public Requirement[] requirements() { + return requirements; + } + + @Override + public String itemID() { + return itemID; + } + + @Override + public int processGainPoints(int previousPoints) { + return previousPoints; + } + + @Override + public int processWaterToLose(int waterToLose) { + return waterToLose; + } + + @Override + public double processVariationChance(double previousChance) { + return previousChance; + } + + @Override + public int processDroppedItemAmount(int amount) { + return amount; + } + + @Override + public double[] overrideQualityRatio() { + return null; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/CustomCropsItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/CustomCropsItem.java new file mode 100644 index 0000000..6358f3f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/CustomCropsItem.java @@ -0,0 +1,15 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.core.InteractionResult; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; + +public interface CustomCropsItem { + + Key type(); + + InteractionResult interactAt(WrappedInteractEvent event); + + void interactAir(WrappedInteractAirEvent event); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/Fertilizer.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/Fertilizer.java new file mode 100644 index 0000000..b685e7a --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/Fertilizer.java @@ -0,0 +1,40 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.core.Registries; +import org.jetbrains.annotations.Nullable; + +public interface Fertilizer { + + String id(); + + int times(); + + boolean reduceTimes(); + + // Flexibility matters more than performance + default FertilizerType type() { + FertilizerConfig config = Registries.FERTILIZER.get(id()); + if (config == null) { + return FertilizerType.INVALID; + } + return config.type(); + } + + @Nullable + default FertilizerConfig config() { + return Registries.FERTILIZER.get(id()); + } + + static Builder builder() { + return new FertilizerImpl.BuilderImpl(); + } + + interface Builder { + + Fertilizer build(); + + Builder id(String id); + + Builder times(int times); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerConfig.java new file mode 100644 index 0000000..e556e57 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerConfig.java @@ -0,0 +1,44 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public interface FertilizerConfig { + + String id(); + + FertilizerType type(); + + boolean beforePlant(); + + String icon(); + + Requirement[] requirements(); + + String itemID(); + + int times(); + + Set whitelistPots(); + + Action[] beforePlantActions(); + + Action[] useActions(); + + Action[] wrongPotActions(); + + int processGainPoints(int previousPoints); + + int processWaterToLose(int waterToLose); + + double processVariationChance(double previousChance); + + int processDroppedItemAmount(int amount); + + @Nullable + double[] overrideQualityRatio(); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerImpl.java new file mode 100644 index 0000000..562d7f8 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerImpl.java @@ -0,0 +1,51 @@ +package net.momirealms.customcrops.api.core.item; + +public class FertilizerImpl implements Fertilizer { + + private final String id; + private int times; + + public FertilizerImpl(String id, int times) { + this.id = id; + this.times = times; + } + + @Override + public String id() { + return id; + } + + @Override + public int times() { + return times; + } + + @Override + public boolean reduceTimes() { + times--; + return times <= 0; + } + + public static class BuilderImpl implements Fertilizer.Builder { + + private String id; + private int times; + + @Override + public Fertilizer build() { + return new FertilizerImpl(id, times); + } + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder times(int times) { + this.times = times; + return this; + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerItem.java new file mode 100644 index 0000000..47724e9 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerItem.java @@ -0,0 +1,113 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.BuiltInBlockMechanics; +import net.momirealms.customcrops.api.core.BuiltInItemMechanics; +import net.momirealms.customcrops.api.core.InteractionResult; +import net.momirealms.customcrops.api.core.Registries; +import net.momirealms.customcrops.api.core.block.CropBlock; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.PotBlock; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.event.FertilizerUseEvent; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; + +public class FertilizerItem extends AbstractCustomCropsItem { + + public FertilizerItem() { + super(BuiltInItemMechanics.FERTILIZER.key()); + } + + @Override + public InteractionResult interactAt(WrappedInteractEvent event) { + FertilizerConfig fertilizerConfig = Registries.FERTILIZER.get(event.itemID()); + if (fertilizerConfig == null) { + return InteractionResult.FAIL; + } + + final Player player = event.player(); + final Context context = Context.player(player); + final CustomCropsWorld world = event.world(); + final ItemStack itemInHand = event.itemInHand(); + String targetBlockID = event.relatedID(); + Location targetLocation = event.location(); + + // if the clicked block is a crop, correct the target block + List cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(event.relatedID()); + if (cropConfigs != null) { + // is a crop + targetLocation = targetLocation.subtract(0,1,0); + targetBlockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetLocation); + } + + // if the clicked block is a pot + PotConfig potConfig = Registries.ITEM_TO_POT.get(targetBlockID); + if (potConfig != null) { + // check pot whitelist + if (!fertilizerConfig.whitelistPots().contains(potConfig.id())) { + ActionManager.trigger(context, fertilizerConfig.wrongPotActions()); + return InteractionResult.FAIL; + } + // check requirements + if (!RequirementManager.isSatisfied(context, fertilizerConfig.requirements())) { + return InteractionResult.FAIL; + } + if (!RequirementManager.isSatisfied(context, potConfig.useRequirements())) { + return InteractionResult.FAIL; + } + // check "before-plant" + if (fertilizerConfig.beforePlant()) { + Location cropLocation = targetLocation.clone().add(0,1,0); + Optional state = world.getBlockState(Pos3.from(cropLocation)); + if (state.isPresent()) { + CustomCropsBlockState blockState = state.get(); + if (blockState.type() instanceof CropBlock) { + ActionManager.trigger(context, fertilizerConfig.beforePlantActions()); + return InteractionResult.FAIL; + } + } + } + + PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic(); + assert potBlock != null; + // fix or get data + Fertilizer fertilizer = Fertilizer.builder() + .times(fertilizerConfig.times()) + .id(fertilizerConfig.id()) + .build(); + CustomCropsBlockState potState = potBlock.fixOrGetState(world, Pos3.from(targetLocation), potConfig, event.relatedID()); + if (!potBlock.canApplyFertilizer(potState,fertilizer)) { + return InteractionResult.FAIL; + } + // trigger event + FertilizerUseEvent useEvent = new FertilizerUseEvent(player, itemInHand, fertilizer, targetLocation, potState, event.hand(), potConfig); + if (EventUtils.fireAndCheckCancel(useEvent)) + return InteractionResult.FAIL; + // add the fertilizer + if (potBlock.addFertilizer(potState, fertilizer)) { + potBlock.updateBlockAppearance(targetLocation, potState, potBlock.fertilizers(potState)); + } + if (player.getGameMode() != GameMode.CREATIVE) { + itemInHand.setAmount(itemInHand.getAmount() - 1); + } + ActionManager.trigger(context, fertilizerConfig.useActions()); + return InteractionResult.SUCCESS; + } + + return InteractionResult.PASS; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerType.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerType.java new file mode 100644 index 0000000..4642652 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/FertilizerType.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for 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.core.item; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.common.util.TriFunction; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Objects; + +public class FertilizerType { + + public static final FertilizerType SPEED_GROW = of("speed_grow", + (manager, id, section) -> { + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + return SpeedGrow.create( + id, section.getString("item"), + section.getInt("times", 14), section.getString("icon", ""), + section.getBoolean("before-plant", false), + new HashSet<>(section.getStringList("pot-whitelist")), + BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true), + pam.parseActions(section.getSection("events.before_plant")), + pam.parseActions(section.getSection("events.use")), + pam.parseActions(section.getSection("events.wrong_pot")), + manager.getIntChancePair(section.getSection("chance")) + ); + } + ); + public static final FertilizerType QUALITY = of("quality", + (manager, id, section) -> { + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + return Quality.create( + id, section.getString("item"), + section.getInt("times", 14), section.getString("icon", ""), + section.getBoolean("before-plant", false), + new HashSet<>(section.getStringList("pot-whitelist")), + BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true), + pam.parseActions(section.getSection("events.before_plant")), + pam.parseActions(section.getSection("events.use")), + pam.parseActions(section.getSection("events.wrong_pot")), + section.getDouble("chance", 1d), + manager.getQualityRatio(section.getString("ratio")) + ); + } + ); + public static final FertilizerType SOIL_RETAIN = of("soil_retain", + (manager, id, section) -> { + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + return SoilRetain.create( + id, section.getString("item"), + section.getInt("times", 14), section.getString("icon", ""), + section.getBoolean("before-plant", false), + new HashSet<>(section.getStringList("pot-whitelist")), + BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true), + pam.parseActions(section.getSection("events.before_plant")), + pam.parseActions(section.getSection("events.use")), + pam.parseActions(section.getSection("events.wrong_pot")), + section.getDouble("chance", 1d) + ); + } + ); + public static final FertilizerType VARIATION = of("variation", + (manager, id, section) -> { + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + return Variation.create( + id, section.getString("item"), + section.getInt("times", 14), section.getString("icon", ""), + section.getBoolean("before-plant", false), + new HashSet<>(section.getStringList("pot-whitelist")), + BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true), + pam.parseActions(section.getSection("events.before_plant")), + pam.parseActions(section.getSection("events.use")), + pam.parseActions(section.getSection("events.wrong_pot")), + section.getBoolean("addOrMultiply", true), + section.getDouble("chance", 0.01d) + ); + } + ); + public static final FertilizerType YIELD_INCREASE = of("yield_increase", + (manager, id, section) -> { + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + return YieldIncrease.create( + id, section.getString("item"), + section.getInt("times", 14), section.getString("icon", ""), + section.getBoolean("before-plant", false), + new HashSet<>(section.getStringList("pot-whitelist")), + BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true), + pam.parseActions(section.getSection("events.before_plant")), + pam.parseActions(section.getSection("events.use")), + pam.parseActions(section.getSection("events.wrong_pot")), + manager.getIntChancePair(section.getSection("chance")) + ); + } + ); + public static final FertilizerType INVALID = of("invalid", + (manager, id, section) -> null + ); + + private final String id; + private final TriFunction argumentConsumer; + + public FertilizerType(String id, TriFunction argumentConsumer) { + this.id = id; + this.argumentConsumer = argumentConsumer; + } + + public String id() { + return id; + } + + public static FertilizerType of(String id, TriFunction argumentConsumer) { + return new FertilizerType(id, argumentConsumer); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FertilizerType that = (FertilizerType) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + public FertilizerConfig parse(ConfigManager manager, String id, Section section) { + return argumentConsumer.apply(manager, id, section); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/Quality.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/Quality.java new file mode 100644 index 0000000..e7ab798 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/Quality.java @@ -0,0 +1,31 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public interface Quality extends FertilizerConfig { + + double chance(); + + double[] ratio(); + + static Quality create( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + double chance, + double[] ratio + ) { + return new QualityImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance, ratio); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/QualityImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/QualityImpl.java new file mode 100644 index 0000000..6370c93 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/QualityImpl.java @@ -0,0 +1,52 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class QualityImpl extends AbstractFertilizerConfig implements Quality { + + private final double chance; + private final double[] ratio; + + protected QualityImpl( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + double chance, + double[] ratio + ) { + super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions); + this.chance = chance; + this.ratio = ratio; + } + + @Override + public double chance() { + return chance; + } + + @Override + public double[] ratio() { + return ratio; + } + + @Override + public FertilizerType type() { + return FertilizerType.QUALITY; + } + + @Override + public double[] overrideQualityRatio() { + return ratio(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SeedItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SeedItem.java new file mode 100644 index 0000000..ea7fc87 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SeedItem.java @@ -0,0 +1,130 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +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.CropBlock; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.CropStageConfig; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.event.CropPlantEvent; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.LocationUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; +import java.util.Map; + +public class SeedItem extends AbstractCustomCropsItem { + + public SeedItem() { + super(BuiltInBlockMechanics.CROP.key()); + } + + @Override + public InteractionResult interactAt(WrappedInteractEvent event) { + // check if it's a pot + PotConfig potConfig = Registries.ITEM_TO_POT.get(event.relatedID()); + if (potConfig == null) return InteractionResult.PASS; + // check if the crop exists + CropConfig cropConfig = Registries.SEED_TO_CROP.get(event.itemID()); + if (cropConfig == null) return InteractionResult.FAIL; + // check the block face + if (event.clickedBlockFace() != BlockFace.UP) + return InteractionResult.PASS; + + final Player player = event.player(); + Context context = Context.player(player); + // check pot whitelist + if (!cropConfig.potWhitelist().contains(potConfig.id())) { + ActionManager.trigger(context, cropConfig.wrongPotActions()); + } + // check plant requirements + if (!RequirementManager.isSatisfied(context, cropConfig.plantRequirements())) { + return InteractionResult.FAIL; + } + // check if the block is empty + if (!suitableForSeed(event.location())) { + return InteractionResult.FAIL; + } + CustomCropsWorld world = event.world(); + Location seedLocation = event.location().add(0, 1, 0); + Pos3 pos3 = Pos3.from(seedLocation); + // check limitation + if (world.setting().cropPerChunk() >= 0) { + if (world.testChunkLimitation(pos3, CropBlock.class, world.setting().cropPerChunk())) { + ActionManager.trigger(context, cropConfig.reachLimitActions()); + return InteractionResult.FAIL; + } + } + final ItemStack itemInHand = event.itemInHand(); + + CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState(); + CropBlock cropBlock = (CropBlock) state.type(); + cropBlock.id(state, cropConfig.id()); + // trigger event + CropPlantEvent plantEvent = new CropPlantEvent(player, itemInHand, event.hand(), seedLocation, cropConfig, state, 0); + if (EventUtils.fireAndCheckCancel(plantEvent)) { + return InteractionResult.FAIL; + } + int point = plantEvent.getPoint(); + int temp = point; + ExistenceForm form = null; + String stageID = null; + while (temp >= 0) { + Map.Entry entry = cropConfig.getFloorStageEntry(temp); + CropStageConfig stageConfig = entry.getValue(); + if (stageConfig.stageID() != null) { + form = stageConfig.existenceForm(); + stageID = stageConfig.stageID(); + break; + } + temp = stageConfig.point() - 1; + } + if (stageID == null || form == null) { + return InteractionResult.FAIL; + } + // reduce item + if (player.getGameMode() != GameMode.CREATIVE) + itemInHand.setAmount(itemInHand.getAmount() - 1); + // place model + BukkitCustomCropsPlugin.getInstance().getItemManager().place(seedLocation, form, stageID, cropConfig.rotation() ? FurnitureRotation.random() : FurnitureRotation.NONE); + cropBlock.point(state, point); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + + // set slot arg + context.arg(ContextKeys.SLOT, event.hand()); + // trigger plant actions + ActionManager.trigger(context, cropConfig.plantActions()); + return InteractionResult.SUCCESS; + } + + private boolean suitableForSeed(Location location) { + Block block = location.getBlock(); + if (block.getType() != Material.AIR) return false; + Location center = LocationUtils.toBlockCenterLocation(location); + Collection entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5); + entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item)); + return entities.isEmpty(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetain.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetain.java new file mode 100644 index 0000000..70f39e5 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetain.java @@ -0,0 +1,28 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public interface SoilRetain extends FertilizerConfig { + + double chance(); + + static SoilRetain create( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + double chance + ) { + return new SoilRetainImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetainImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetainImpl.java new file mode 100644 index 0000000..965d58c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SoilRetainImpl.java @@ -0,0 +1,44 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class SoilRetainImpl extends AbstractFertilizerConfig implements SoilRetain { + + private final double chance; + + public SoilRetainImpl( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + double chance + ) { + super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions); + this.chance = chance; + } + + @Override + public double chance() { + return chance; + } + + @Override + public FertilizerType type() { + return FertilizerType.SOIL_RETAIN; + } + + @Override + public int processWaterToLose(int waterToLose) { + return Math.min(waterToLose, 0); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrow.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrow.java new file mode 100644 index 0000000..141cb6b --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrow.java @@ -0,0 +1,30 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + +public interface SpeedGrow extends FertilizerConfig { + + int pointBonus(); + + static SpeedGrow create( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + List> chances + ) { + return new SpeedGrowImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrowImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrowImpl.java new file mode 100644 index 0000000..0734d2f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SpeedGrowImpl.java @@ -0,0 +1,51 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + +public class SpeedGrowImpl extends AbstractFertilizerConfig implements SpeedGrow { + + private final List> chances; + + public SpeedGrowImpl( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + List> chances + ) { + super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions); + this.chances = chances; + } + + @Override + public int pointBonus() { + for (Pair pair : chances) { + if (Math.random() < pair.left()) { + return pair.right(); + } + } + return 0; + } + + @Override + public FertilizerType type() { + return FertilizerType.SPEED_GROW; + } + + @Override + public int processGainPoints(int previousPoints) { + return pointBonus() + previousPoints; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/SprinklerItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/SprinklerItem.java new file mode 100644 index 0000000..eebd8de --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/SprinklerItem.java @@ -0,0 +1,111 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.core.*; +import net.momirealms.customcrops.api.core.block.SprinklerBlock; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.event.SprinklerPlaceEvent; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.LocationUtils; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; + +public class SprinklerItem extends AbstractCustomCropsItem { + + public SprinklerItem() { + super(BuiltInItemMechanics.SPRINKLER_ITEM.key()); + } + + @Override + public InteractionResult interactAt(WrappedInteractEvent event) { + // should be place on block + if (event.existenceForm() != ExistenceForm.BLOCK) + return InteractionResult.PASS; + SprinklerConfig config = Registries.SPRINKLER.get(event.itemID()); + if (config == null) { + return InteractionResult.FAIL; + } + + Block clicked = event.location().getBlock(); + Location targetLocation; + if (clicked.isReplaceable()) { + targetLocation = event.location(); + } else { + if (event.clickedBlockFace() != BlockFace.UP) + return InteractionResult.PASS; + if (!clicked.isSolid()) + return InteractionResult.PASS; + targetLocation = event.location().clone().add(0,1,0); + if (!suitableForSprinkler(targetLocation)) { + return InteractionResult.PASS; + } + } + + final Player player = event.player(); + final ItemStack itemInHand = event.itemInHand(); + Context context = Context.player(player); + // check requirements + if (!RequirementManager.isSatisfied(context, config.placeRequirements())) { + return InteractionResult.FAIL; + } + + final CustomCropsWorld world = event.world(); + Pos3 pos3 = Pos3.from(targetLocation); + // check limitation + if (world.setting().sprinklerPerChunk() >= 0) { + if (world.testChunkLimitation(pos3, SprinklerBlock.class, world.setting().sprinklerPerChunk())) { + ActionManager.trigger(context, config.reachLimitActions()); + return InteractionResult.FAIL; + } + } + // generate state + CustomCropsBlockState state = BuiltInBlockMechanics.SPRINKLER.createBlockState(); + SprinklerBlock sprinklerBlock = (SprinklerBlock) BuiltInBlockMechanics.SPRINKLER.mechanic(); + sprinklerBlock.id(state, config.id()); + sprinklerBlock.water(state, 0); + // trigger event + SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, itemInHand, event.hand(), targetLocation.clone(), config, state); + if (EventUtils.fireAndCheckCancel(placeEvent)) + return InteractionResult.FAIL; + // clear replaceable block + targetLocation.getBlock().setType(Material.AIR, false); + if (player.getGameMode() != GameMode.CREATIVE) + itemInHand.setAmount(itemInHand.getAmount() - 1); + + // place the sprinkler + BukkitCustomCropsPlugin.getInstance().getItemManager().place(LocationUtils.toSurfaceCenterLocation(targetLocation), config.existenceForm(), config.threeDItem(), FurnitureRotation.NONE); + world.addBlockState(pos3, state).ifPresent(previous -> { + BukkitCustomCropsPlugin.getInstance().debug( + "Overwrite old data with " + state.compoundMap().toString() + + " at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString() + ); + }); + ActionManager.trigger(context, config.placeActions()); + return InteractionResult.SUCCESS; + } + + private boolean suitableForSprinkler(Location location) { + Block block = location.getBlock(); + if (block.getType() != Material.AIR) return false; + Location center = LocationUtils.toBlockCenterLocation(location); + Collection entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5); + entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item)); + return entities.isEmpty(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/Variation.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/Variation.java new file mode 100644 index 0000000..289b86b --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/Variation.java @@ -0,0 +1,31 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public interface Variation extends FertilizerConfig { + + double chanceBonus(); + + boolean addOrMultiply(); + + static Variation create( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + boolean addOrMultiply, + double chance + ) { + return new VariationImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, addOrMultiply, chance); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/VariationImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/VariationImpl.java new file mode 100644 index 0000000..45b4384 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/VariationImpl.java @@ -0,0 +1,56 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.Set; + +public class VariationImpl extends AbstractFertilizerConfig implements Variation { + + private final double chance; + private final boolean addOrMultiply; + + public VariationImpl( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + boolean addOrMultiply, + double chance + ) { + super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions); + this.chance = chance; + this.addOrMultiply = addOrMultiply; + } + + @Override + public double chanceBonus() { + return chance; + } + + @Override + public boolean addOrMultiply() { + return addOrMultiply; + } + + @Override + public FertilizerType type() { + return FertilizerType.VARIATION; + } + + @Override + public double processVariationChance(double previousChance) { + if (addOrMultiply()) { + return previousChance + chanceBonus(); + } else { + return previousChance * chanceBonus(); + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfig.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfig.java new file mode 100644 index 0000000..68ddbef --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfig.java @@ -0,0 +1,108 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.water.FillMethod; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.misc.value.TextValue; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface WateringCanConfig { + + String id(); + + String itemID(); + + int width(); + + int length(); + + int storage(); + + int wateringAmount(); + + boolean dynamicLore(); + + Set whitelistPots(); + + Set whitelistSprinklers(); + + List> lore(); + + WaterBar waterBar(); + + Requirement[] requirements(); + + boolean infinite(); + + Integer appearance(int water); + + Action[] fullActions(); + + Action[] addWaterActions(); + + Action[] consumeWaterActions(); + + Action[] runOutOfWaterActions(); + + Action[] wrongPotActions(); + + Action[] wrongSprinklerActions(); + + FillMethod[] fillMethods(); + + static Builder builder() { + return new WateringCanConfigImpl.BuilderImpl(); + } + + interface Builder { + + WateringCanConfig build(); + + Builder id(String id); + + Builder itemID(String itemID); + + Builder width(int width); + + Builder length(int length); + + Builder storage(int storage); + + Builder wateringAmount(int wateringAmount); + + Builder dynamicLore(boolean dynamicLore); + + Builder potWhitelist(Set whitelistPots); + + Builder sprinklerWhitelist(Set whitelistSprinklers); + + Builder lore(List> lore); + + Builder waterBar(WaterBar waterBar); + + Builder requirements(Requirement[] requirements); + + Builder infinite(boolean infinite); + + Builder appearances(Map appearances); + + Builder fullActions(Action[] fullActions); + + Builder addWaterActions(Action[] addWaterActions); + + Builder consumeWaterActions(Action[] consumeWaterActions); + + Builder runOutOfWaterActions(Action[] runOutOfWaterActions); + + Builder wrongPotActions(Action[] wrongPotActions); + + Builder wrongSprinklerActions(Action[] wrongSprinklerActions); + + Builder fillMethods(FillMethod[] fillMethods); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfigImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfigImpl.java new file mode 100644 index 0000000..1906816 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanConfigImpl.java @@ -0,0 +1,342 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.core.water.FillMethod; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.misc.value.TextValue; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; + +import java.util.*; + +public class WateringCanConfigImpl implements WateringCanConfig { + + private final String id; + private final String itemID; + private final int width; + private final int length; + private final int storage; + private final int wateringAmount; + private final boolean dynamicLore; + private final Set whitelistPots; + private final Set whitelistSprinklers; + private final List> lore; + private final WaterBar waterBar; + private final Requirement[] requirements; + private final boolean infinite; + private final HashMap appearances; + private final Action[] fullActions; + private final Action[] addWaterActions; + private final Action[] consumeWaterActions; + private final Action[] runOutOfWaterActions; + private final Action[] wrongPotActions; + private final Action[] wrongSprinklerActions; + private final FillMethod[] fillMethods; + + public WateringCanConfigImpl( + String id, + String itemID, + int width, + int length, + int storage, + int wateringAmount, + boolean dynamicLore, + Set whitelistPots, + Set whitelistSprinklers, + List> lore, + WaterBar waterBar, + Requirement[] requirements, + boolean infinite, + HashMap appearances, + Action[] fullActions, + Action[] addWaterActions, + Action[] consumeWaterActions, + Action[] runOutOfWaterActions, + Action[] wrongPotActions, + Action[] wrongSprinklerActions, + FillMethod[] fillMethods + ) { + this.id = id; + this.itemID = itemID; + this.width = width; + this.length = length; + this.storage = storage; + this.wateringAmount = wateringAmount; + this.dynamicLore = dynamicLore; + this.whitelistPots = whitelistPots; + this.whitelistSprinklers = whitelistSprinklers; + this.lore = lore; + this.waterBar = waterBar; + this.requirements = requirements; + this.infinite = infinite; + this.appearances = appearances; + this.fullActions = fullActions; + this.addWaterActions = addWaterActions; + this.consumeWaterActions = consumeWaterActions; + this.runOutOfWaterActions = runOutOfWaterActions; + this.wrongPotActions = wrongPotActions; + this.wrongSprinklerActions = wrongSprinklerActions; + this.fillMethods = fillMethods; + } + + @Override + public String id() { + return id; + } + + @Override + public String itemID() { + return itemID; + } + + @Override + public int width() { + return width; + } + + @Override + public int length() { + return length; + } + + @Override + public int storage() { + return storage; + } + + @Override + public int wateringAmount() { + return wateringAmount; + } + + @Override + public boolean dynamicLore() { + return dynamicLore; + } + + @Override + public Set whitelistPots() { + return whitelistPots; + } + + @Override + public Set whitelistSprinklers() { + return whitelistSprinklers; + } + + @Override + public List> lore() { + return lore; + } + + @Override + public WaterBar waterBar() { + return waterBar; + } + + @Override + public Requirement[] requirements() { + return requirements; + } + + @Override + public boolean infinite() { + return infinite; + } + + @Override + public Integer appearance(int water) { + return appearances.get(water); + } + + @Override + public Action[] fullActions() { + return fullActions; + } + + @Override + public Action[] addWaterActions() { + return addWaterActions; + } + + @Override + public Action[] consumeWaterActions() { + return consumeWaterActions; + } + + @Override + public Action[] runOutOfWaterActions() { + return runOutOfWaterActions; + } + + @Override + public Action[] wrongPotActions() { + return wrongPotActions; + } + + @Override + public Action[] wrongSprinklerActions() { + return wrongSprinklerActions; + } + + @Override + public FillMethod[] fillMethods() { + return fillMethods; + } + + static class BuilderImpl implements Builder { + + private String id; + private String itemID; + private int width; + private int length; + private int storage; + private int wateringAmount; + private boolean dynamicLore; + private Set whitelistPots; + private Set whitelistSprinklers; + private List> lore; + private WaterBar waterBar; + private Requirement[] requirements; + private boolean infinite; + private HashMap appearances; + private Action[] fullActions; + private Action[] addWaterActions; + private Action[] consumeWaterActions; + private Action[] runOutOfWaterActions; + private Action[] wrongPotActions; + private Action[] wrongSprinklerActions; + private FillMethod[] fillMethods; + + @Override + public WateringCanConfig build() { + return new WateringCanConfigImpl(id, itemID, width, length, storage, wateringAmount, dynamicLore, whitelistPots, whitelistSprinklers, lore, waterBar, requirements, infinite, appearances, fullActions, addWaterActions, consumeWaterActions, runOutOfWaterActions, wrongPotActions, wrongSprinklerActions, fillMethods); + } + + @Override + public Builder id(String id) { + this.id = id; + return this; + } + + @Override + public Builder itemID(String itemID) { + this.itemID = itemID; + return this; + } + + @Override + public Builder width(int width) { + this.width = width; + return this; + } + + @Override + public Builder length(int length) { + this.length = length; + return this; + } + + @Override + public Builder storage(int storage) { + this.storage = storage; + return this; + } + + @Override + public Builder wateringAmount(int wateringAmount) { + this.wateringAmount = wateringAmount; + return this; + } + + @Override + public Builder dynamicLore(boolean dynamicLore) { + this.dynamicLore = dynamicLore; + return this; + } + + @Override + public Builder potWhitelist(Set whitelistPots) { + this.whitelistPots = new HashSet<>(whitelistPots); + return this; + } + + @Override + public Builder sprinklerWhitelist(Set whitelistSprinklers) { + this.whitelistSprinklers = new HashSet<>(whitelistSprinklers); + return this; + } + + @Override + public Builder lore(List> lore) { + this.lore = new ArrayList<>(lore); + return this; + } + + @Override + public Builder waterBar(WaterBar waterBar) { + this.waterBar = waterBar; + return this; + } + + @Override + public Builder requirements(Requirement[] requirements) { + this.requirements = requirements; + return this; + } + + @Override + public Builder infinite(boolean infinite) { + this.infinite = infinite; + return this; + } + + @Override + public Builder appearances(Map appearances) { + this.appearances = new HashMap<>(appearances); + return this; + } + + @Override + public Builder fullActions(Action[] fullActions) { + this.fullActions = fullActions; + return this; + } + + @Override + public Builder addWaterActions(Action[] addWaterActions) { + this.addWaterActions = addWaterActions; + return this; + } + + @Override + public Builder consumeWaterActions(Action[] consumeWaterActions) { + this.consumeWaterActions = consumeWaterActions; + return this; + } + + @Override + public Builder runOutOfWaterActions(Action[] runOutOfWaterActions) { + this.runOutOfWaterActions = runOutOfWaterActions; + return this; + } + + @Override + public Builder wrongPotActions(Action[] wrongPotActions) { + this.wrongPotActions = wrongPotActions; + return this; + } + + @Override + public Builder wrongSprinklerActions(Action[] wrongSprinklerActions) { + this.wrongSprinklerActions = wrongSprinklerActions; + return this; + } + + @Override + public Builder fillMethods(FillMethod[] fillMethods) { + this.fillMethods = fillMethods; + return this; + } + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanItem.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanItem.java new file mode 100644 index 0000000..96bb2ef --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/WateringCanItem.java @@ -0,0 +1,358 @@ +package net.momirealms.customcrops.api.core.item; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ScoreComponent; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +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.water.FillMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent; +import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent; +import net.momirealms.customcrops.api.event.WateringCanFillEvent; +import net.momirealms.customcrops.api.event.WateringCanWaterPotEvent; +import net.momirealms.customcrops.api.event.WateringCanWaterSprinklerEvent; +import net.momirealms.customcrops.api.misc.value.TextValue; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import net.momirealms.customcrops.common.item.Item; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.FluidCollisionMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class WateringCanItem extends AbstractCustomCropsItem { + + public WateringCanItem() { + super(BuiltInItemMechanics.WATERING_CAN.key()); + } + + public int getCurrentWater(ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR) return 0; + Item wrapped = BukkitCustomCropsPlugin.getInstance().getItemManager().wrap(itemStack); + return (int) wrapped.getTag("CustomCrops", "water").orElse(0); + } + + public void setCurrentWater(ItemStack itemStack, WateringCanConfig config, int water, Context context) { + if (itemStack == null || itemStack.getType() == Material.AIR) return; + Item wrapped = BukkitCustomCropsPlugin.getInstance().getItemManager().wrap(itemStack); + int realWater = Math.min(config.storage(), water); + if (!config.infinite()) { + wrapped.setTag("CustomCrops", "water", realWater); + wrapped.maxDamage().ifPresent(max -> { + if (max <= 0) return; + int damage = (int) (max * (((double) config.storage() - realWater) / config.storage())); + wrapped.damage(damage); + }); + // set appearance + Optional.ofNullable(config.appearance(realWater)).ifPresent(wrapped::customModelData); + } + if (config.dynamicLore()) { + List lore = new ArrayList<>(wrapped.lore().orElse(List.of())); + if (ConfigManager.protectOriginalLore()) { + lore.removeIf(line -> { + Component component = AdventureHelper.jsonToComponent(line); + return component instanceof ScoreComponent scoreComponent + && scoreComponent.objective().equals("water") + && scoreComponent.name().equals("cc"); + }); + } else { + lore.clear(); + } + for (TextValue newLore : config.lore()) { + ScoreComponent.Builder builder = Component.score().name("cc").objective("water"); + builder.append(AdventureHelper.miniMessage(newLore.render(context))); + lore.add(AdventureHelper.componentToJson(builder.build())); + } + wrapped.lore(lore); + } + wrapped.load(); + } + + @Override + public void interactAir(WrappedInteractAirEvent event) { + WateringCanConfig config = Registries.WATERING_CAN.get(event.itemID()); + if (config == null) + return; + + final Player player = event.player();; + Context context = Context.player(player); + // check requirements + if (!RequirementManager.isSatisfied(context, config.requirements())) { + return; + } + // ignore infinite + if (config.infinite()) + return; + // get target block + Block targetBlock = player.getTargetBlockExact(5, FluidCollisionMode.ALWAYS); + if (targetBlock == null) + return; + + final ItemStack itemInHand = event.itemInHand(); + int water = getCurrentWater(itemInHand); + + String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetBlock); + if (targetBlock.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged()) { + blockID = "WATER"; + } + + for (FillMethod method : config.fillMethods()) { + if (method.getID().equals(blockID)) { + if (method.checkRequirements(context)) { + if (water >= config.storage()) { + ActionManager.trigger(context, config.fullActions()); + return; + } + WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, event.hand(), itemInHand, targetBlock.getLocation(), config, method); + if (EventUtils.fireAndCheckCancel(fillEvent)) + return; + setCurrentWater(itemInHand, config, water + method.amountOfWater(), context); + method.triggerActions(context); + ActionManager.trigger(context, config.addWaterActions()); + } + return; + } + } + } + + @Override + public InteractionResult interactAt(WrappedInteractEvent event) { + WateringCanConfig wateringCanConfig = Registries.WATERING_CAN.get(event.itemID()); + if (wateringCanConfig == null) + return InteractionResult.FAIL; + + final Player player = event.player(); + final Context context = Context.player(player); + + // check watering can requirements + if (!RequirementManager.isSatisfied(context, wateringCanConfig.requirements())) { + return InteractionResult.FAIL; + } + + final CustomCropsWorld world = event.world(); + final ItemStack itemInHand = event.itemInHand(); + String targetBlockID = event.relatedID(); + Location targetLocation = event.location(); + BlockFace blockFace = event.clickedBlockFace(); + + int waterInCan = getCurrentWater(itemInHand); + + SprinklerConfig sprinklerConfig = Registries.SPRINKLER.get(targetBlockID); + if (sprinklerConfig != null) { + // ignore infinite sprinkler + if (sprinklerConfig.infinite()) { + return InteractionResult.FAIL; + } + // check requirements + if (!RequirementManager.isSatisfied(context, sprinklerConfig.useRequirements())) { + return InteractionResult.FAIL; + } + // check water + if (waterInCan <= 0 && !wateringCanConfig.infinite()) { + ActionManager.trigger(context, wateringCanConfig.runOutOfWaterActions()); + return InteractionResult.FAIL; + } + // check whitelist + if (!wateringCanConfig.whitelistSprinklers().contains(sprinklerConfig.id())) { + ActionManager.trigger(context, wateringCanConfig.wrongSprinklerActions()); + return InteractionResult.FAIL; + } + + SprinklerBlock sprinklerBlock = (SprinklerBlock) BuiltInBlockMechanics.SPRINKLER.mechanic(); + CustomCropsBlockState sprinklerState = sprinklerBlock.fixOrGetState(world, Pos3.from(targetLocation), sprinklerConfig, targetBlockID); + + // check full + if (sprinklerBlock.water(sprinklerState) >= sprinklerConfig.storage()) { + ActionManager.trigger(context, sprinklerConfig.fullWaterActions()); + return InteractionResult.FAIL; + } + + // trigger event + WateringCanWaterSprinklerEvent waterSprinklerEvent = new WateringCanWaterSprinklerEvent(player, itemInHand, event.hand(), wateringCanConfig, sprinklerConfig, sprinklerState, targetLocation); + if (EventUtils.fireAndCheckCancel(waterSprinklerEvent)) { + return InteractionResult.FAIL; + } + // add water + if (sprinklerBlock.addWater(sprinklerState, sprinklerConfig, wateringCanConfig.wateringAmount())) { + if (!sprinklerConfig.threeDItem().equals(sprinklerConfig.threeDItemWithWater())) { + sprinklerBlock.updateBlockAppearance(targetLocation, sprinklerConfig, true); + } + } + + ActionManager.trigger(context, wateringCanConfig.consumeWaterActions()); + setCurrentWater(itemInHand, wateringCanConfig, waterInCan - 1, context); + return InteractionResult.SUCCESS; + } + + // try filling the watering can + for (FillMethod method : wateringCanConfig.fillMethods()) { + if (method.getID().equals(event.relatedID())) { + if (method.checkRequirements(context)) { + if (waterInCan >= wateringCanConfig.storage()) { + ActionManager.trigger(context, wateringCanConfig.fullActions()); + return InteractionResult.FAIL; + } + WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, event.hand(), itemInHand, targetLocation, wateringCanConfig, method); + if (EventUtils.fireAndCheckCancel(fillEvent)) + return InteractionResult.FAIL; + setCurrentWater(itemInHand, wateringCanConfig, waterInCan + method.amountOfWater(), context); + method.triggerActions(context); + ActionManager.trigger(context, wateringCanConfig.addWaterActions()); + } + return InteractionResult.SUCCESS; + } + } + + // if the clicked block is a crop, correct the target block + List cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(event.relatedID()); + if (cropConfigs != null) { + // is a crop + targetLocation = targetLocation.subtract(0,1,0); + targetBlockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetLocation); + blockFace = BlockFace.UP; + } + + PotConfig potConfig = Registries.ITEM_TO_POT.get(targetBlockID); + if (potConfig != null) { + // need to click the upper face + if (blockFace != BlockFace.UP) + return InteractionResult.PASS; + // check whitelist + if (!wateringCanConfig.whitelistPots().contains(potConfig.id())) { + ActionManager.trigger(context, wateringCanConfig.wrongPotActions()); + return InteractionResult.FAIL; + } + // check water + if (waterInCan <= 0 && !wateringCanConfig.infinite()) { + ActionManager.trigger(context, wateringCanConfig.runOutOfWaterActions()); + return InteractionResult.FAIL; + } + + World bukkitWorld = targetLocation.getWorld(); + ArrayList> pots = potInRange(bukkitWorld, Pos3.from(targetLocation), wateringCanConfig.width(), wateringCanConfig.length(), player.getLocation().getYaw(), potConfig); + + WateringCanWaterPotEvent waterPotEvent = new WateringCanWaterPotEvent(player, itemInHand, event.hand(), wateringCanConfig, potConfig, pots); + if (EventUtils.fireAndCheckCancel(waterPotEvent)) { + return InteractionResult.FAIL; + } + + PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic(); + for (Pair pair : waterPotEvent.getPotWithIDs()) { + CustomCropsBlockState potState = potBlock.fixOrGetState(world,pair.left(), potConfig, pair.right()); + if (potBlock.addWater(potState, potConfig, wateringCanConfig.wateringAmount())) { + Location temp = pair.left().toLocation(bukkitWorld); + potBlock.updateBlockAppearance(temp, potConfig, true, potBlock.fertilizers(potState)); + context.arg(ContextKeys.LOCATION, temp); + ActionManager.trigger(context, potConfig.addWaterActions()); + } + } + + ActionManager.trigger(context, wateringCanConfig.consumeWaterActions()); + setCurrentWater(itemInHand, wateringCanConfig, waterInCan - 1, context); + return InteractionResult.SUCCESS; + } + + return InteractionResult.PASS; + } + + public ArrayList> potInRange(World world, Pos3 pos3, int width, int length, float yaw, PotConfig config) { + ArrayList potPos = new ArrayList<>(); + int extend = (width-1) / 2; + int extra = (width-1) % 2; + switch ((int) ((yaw + 180) / 45)) { + case 0 -> { + // -180 ~ -135 + for (int i = -extend; i <= extend + extra; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(i, 0, -j)); + } + } + } + case 1 -> { + // -135 ~ -90 + for (int i = -extend - extra; i <= extend; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(j, 0, i)); + } + } + } + case 2 -> { + // -90 ~ -45 + for (int i = -extend; i <= extend + extra; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(j, 0, i)); + } + } + } + case 3 -> { + // -45 ~ 0 + for (int i = -extend; i <= extend + extra; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(i, 0, j)); + } + } + } + case 4 -> { + // 0 ~ 45 + for (int i = -extend - extra; i <= extend; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(i, 0, j)); + } + } + } + case 5 -> { + // 45 ~ 90 + for (int i = -extend; i <= extend + extra; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(-j, 0, i)); + } + } + } + case 6 -> { + // 90 ~ 135 + for (int i = -extend - extra; i <= extend; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(-j, 0, i)); + } + } + } + case 7 -> { + // 135 ~ 180 + for (int i = -extend - extra; i <= extend; i++) { + for (int j = 0; j < length; j++) { + potPos.add(pos3.add(i, 0, -j)); + } + } + } + default -> potPos.add(pos3); + } + ItemManager itemManager = BukkitCustomCropsPlugin.getInstance().getItemManager(); + ArrayList> pots = new ArrayList<>(); + for (Pos3 loc : potPos) { + Block block = world.getBlockAt(loc.x(), loc.y(), loc.z()); + String blockID = itemManager.blockID(block); + PotConfig potConfig = Registries.ITEM_TO_POT.get(blockID); + if (potConfig == config) { + pots.add(Pair.of(loc, blockID)); + } + } + return pots; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncrease.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncrease.java new file mode 100644 index 0000000..ed352b5 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncrease.java @@ -0,0 +1,30 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + +public interface YieldIncrease extends FertilizerConfig { + + int amountBonus(); + + static YieldIncrease create( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + List> chances + ) { + return new YieldIncreaseImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncreaseImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncreaseImpl.java new file mode 100644 index 0000000..d7da16b --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/item/YieldIncreaseImpl.java @@ -0,0 +1,51 @@ +package net.momirealms.customcrops.api.core.item; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Set; + +public class YieldIncreaseImpl extends AbstractFertilizerConfig implements YieldIncrease { + + private final List> chances; + + public YieldIncreaseImpl( + String id, + String itemID, + int times, + String icon, + boolean beforePlant, + Set whitelistPots, + Requirement[] requirements, + Action[] beforePlantActions, + Action[] useActions, + Action[] wrongPotActions, + List> chances + ) { + super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions); + this.chances = chances; + } + + @Override + public int amountBonus() { + for (Pair pair : chances) { + if (Math.random() < pair.left()) { + return pair.right(); + } + } + return 0; + } + + @Override + public FertilizerType type() { + return FertilizerType.YIELD_INCREASE; + } + + @Override + public int processDroppedItemAmount(int amount) { + return amount + amountBonus(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/water/AbstractMethod.java b/api/src/main/java/net/momirealms/customcrops/api/core/water/AbstractMethod.java new file mode 100644 index 0000000..e634ffb --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/water/AbstractMethod.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.core.water; + +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import org.bukkit.entity.Player; + +public abstract class AbstractMethod { + + protected int amount; + private final Action[] actions; + private final Requirement[] requirements; + + protected AbstractMethod(int amount, Action[] actions, Requirement[] requirements) { + this.amount = amount; + this.actions = actions; + this.requirements = requirements; + } + + public int amountOfWater() { + return amount; + } + + public void triggerActions(Context context) { + ActionManager.trigger(context, actions); + } + + public boolean checkRequirements(Context context) { + return RequirementManager.isSatisfied(context, requirements); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PositiveFillMethod.java b/api/src/main/java/net/momirealms/customcrops/api/core/water/FillMethod.java similarity index 72% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PositiveFillMethod.java rename to api/src/main/java/net/momirealms/customcrops/api/core/water/FillMethod.java index df621f4..0bc43f1 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PositiveFillMethod.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/water/FillMethod.java @@ -15,16 +15,17 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.item.water; +package net.momirealms.customcrops.api.core.water; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; -public class PositiveFillMethod extends AbstractFillMethod { +public class FillMethod extends AbstractMethod { private final String id; - public PositiveFillMethod(String id, int amount, Action[] actions, Requirement[] requirements) { + public FillMethod(String id, int amount, Action[] actions, Requirement[] requirements) { super(amount, actions, requirements); this.id = id; } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PassiveFillMethod.java b/api/src/main/java/net/momirealms/customcrops/api/core/water/WateringMethod.java similarity index 76% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PassiveFillMethod.java rename to api/src/main/java/net/momirealms/customcrops/api/core/water/WateringMethod.java index adbef45..a549187 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/PassiveFillMethod.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/water/WateringMethod.java @@ -15,20 +15,29 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.item.water; +package net.momirealms.customcrops.api.core.water; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.requirement.Requirement; +import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; -public class PassiveFillMethod extends AbstractFillMethod { +public class WateringMethod extends AbstractMethod { private final String used; private final int usedAmount; private final String returned; private final int returnedAmount; - public PassiveFillMethod(String used, int usedAmount, @Nullable String returned, int returnedAmount, int amount, Action[] actions, Requirement[] requirements) { + public WateringMethod( + String used, + int usedAmount, + @Nullable String returned, + int returnedAmount, + int amount, + Action[] actions, + Requirement[] requirements + ) { super(amount, actions, requirements); this.used = used; this.returned = returned; diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/BlockPos.java similarity index 68% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/BlockPos.java index 10e29bc..c3bf17a 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/BlockPos.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/BlockPos.java @@ -15,9 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world; - -import java.util.Objects; +package net.momirealms.customcrops.api.core.world; public class BlockPos { @@ -31,31 +29,27 @@ public class BlockPos { this.position = ((x & 0xF) << 28) | ((z & 0xF) << 24) | (y & 0xFFFFFF); } - public static BlockPos getByLocation(SimpleLocation location) { - return new BlockPos(location.getX() % 16, location.getY(), location.getZ() % 16); + public static BlockPos fromPos3(Pos3 location) { + return new BlockPos(location.x() % 16, location.y(), location.z() % 16); } - public SimpleLocation getLocation(String world, ChunkPos coordinate) { - return new SimpleLocation(world, coordinate.x() * 16 + getX(), getY(), coordinate.z() * 16 + getZ()); + public Pos3 toPos3(ChunkPos coordinate) { + return new Pos3(coordinate.x() * 16 + x(), y(), coordinate.z() * 16 + z()); } - public int getPosition() { + public int position() { return position; } - public int getX() { + public int x() { return (position >> 28) & 0xF; } - public int getZ() { + public int z() { return (position >> 24) & 0xF; } - public int getSectionID() { - return (int) Math.floor((double) getY() / 16); - } - - public int getY() { + public int y() { int y = position & 0xFFFFFF; if ((y & 0x800000) != 0) { y |= 0xFF000000; @@ -63,6 +57,10 @@ public class BlockPos { return y; } + public int sectionID() { + return (int) Math.floor((double) y() / 16); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -73,15 +71,15 @@ public class BlockPos { @Override public int hashCode() { - return Objects.hash(position); + return Math.abs(position); } @Override public String toString() { return "BlockPos{" + - "x=" + getX() + - "y=" + getY() + - "z=" + getZ() + + "x=" + x() + + "y=" + y() + + "z=" + z() + '}'; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/ChunkPos.java similarity index 77% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/ChunkPos.java index 511e46c..fe43a88 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/ChunkPos.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/ChunkPos.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world; +package net.momirealms.customcrops.api.core.world; import org.bukkit.Chunk; import org.jetbrains.annotations.NotNull; @@ -26,17 +26,21 @@ public record ChunkPos(int x, int z) { return new ChunkPos(x, z); } - public static ChunkPos getByString(String coordinate) { + public static ChunkPos fromString(String coordinate) { String[] split = coordinate.split(",", 2); try { int x = Integer.parseInt(split[0]); int z = Integer.parseInt(split[1]); return new ChunkPos(x, z); + } catch (NumberFormatException e) { + throw new RuntimeException(e); } - catch (NumberFormatException e) { - e.printStackTrace(); - return null; - } + } + + public static ChunkPos fromPos3(Pos3 pos3) { + int chunkX = (int) Math.floor((double) pos3.x() / 16.0); + int chunkZ = (int) Math.floor((double) pos3.z() / 16.0); + return ChunkPos.of(chunkX, chunkZ); } @Override @@ -64,16 +68,16 @@ public record ChunkPos(int x, int z) { } @NotNull - public RegionPos getRegionPos() { + public RegionPos toRegionPos() { return RegionPos.getByChunkPos(this); } @NotNull - public static ChunkPos getByBukkitChunk(@NotNull Chunk chunk) { + public static ChunkPos fromBukkitChunk(@NotNull Chunk chunk) { return new ChunkPos(chunk.getX(), chunk.getZ()); } - public String getAsString() { + public String asString() { return x + "," + z; } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockState.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockState.java new file mode 100644 index 0000000..6b1dec8 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockState.java @@ -0,0 +1,15 @@ +package net.momirealms.customcrops.api.core.world; + +import com.flowpowered.nbt.CompoundMap; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import org.jetbrains.annotations.NotNull; + +public interface CustomCropsBlockState extends DataBlock { + + @NotNull + CustomCropsBlock type(); + + static CustomCropsBlockState create(CustomCropsBlock owner, CompoundMap compoundMap) { + return new CustomCropsBlockStateImpl(owner, compoundMap); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockStateImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockStateImpl.java new file mode 100644 index 0000000..5dd6a2d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsBlockStateImpl.java @@ -0,0 +1,52 @@ +package net.momirealms.customcrops.api.core.world; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.Tag; +import net.momirealms.customcrops.api.core.SynchronizedCompoundMap; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import org.jetbrains.annotations.NotNull; + +public class CustomCropsBlockStateImpl implements CustomCropsBlockState { + + private final SynchronizedCompoundMap compoundMap; + private final CustomCropsBlock owner; + + protected CustomCropsBlockStateImpl(CustomCropsBlock owner, CompoundMap compoundMap) { + this.compoundMap = new SynchronizedCompoundMap(compoundMap); + this.owner = owner; + } + + @NotNull + @Override + public CustomCropsBlock type() { + return owner; + } + + @Override + public Tag set(String key, Tag tag) { + return compoundMap.put(key, tag); + } + + @Override + public Tag get(String key) { + return compoundMap.get(key); + } + + @Override + public Tag remove(String key) { + return compoundMap.remove(key); + } + + @Override + public SynchronizedCompoundMap compoundMap() { + return compoundMap; + } + + @Override + public String toString() { + return "CustomCropsBlock{" + + "Type{" + owner.type().asString() + + "}, " + compoundMap + + '}'; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunk.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunk.java new file mode 100644 index 0000000..9d7894c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunk.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for 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.core.world; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.stream.Stream; + +public interface CustomCropsChunk { + + /** + * Set if the chunk can be force loaded. + * If a chunk is force loaded, no one can unload it unless you set force load to false. + * This can prevent CustomCrops from unloading the chunks on {@link org.bukkit.event.world.ChunkUnloadEvent} + * + * This value will not be persistently stored. Please use {@link org.bukkit.World#setChunkForceLoaded(int, int, boolean)} + * if you want to force a chunk loaded. + * + * @param forceLoad force loaded + */ + void setForceLoaded(boolean forceLoad); + + /** + * Indicates whether the chunk is force loaded + * + * @return force loaded or not + */ + boolean isForceLoaded(); + + /** + * Loads the chunk to cache and participate in the mechanism of the plugin. + * + * @param loadBukkitChunk whether to load Bukkit chunks temporarily if it's not loaded + */ + void load(boolean loadBukkitChunk); + + /** + * Unloads the chunk. Lazy refer to those chunks that will be delayed for unloading. + * Recently unloaded chunks are likely to be loaded again soon. + * + * @param lazy delay unload or not + */ + void unload(boolean lazy); + + /** + * Unloads the chunk if it is a lazy chunk + */ + void unloadLazy(); + + /** + * Indicates whether the chunk is in lazy state + * + * @return lazy or not + */ + boolean isLazy(); + + /** + * Indicates whether the chunk is loaded + * + * @return loaded or not + */ + boolean isLoaded(); + + /** + * Get the world associated with the chunk + * + * @return CustomCrops world + */ + CustomCropsWorld getWorld(); + + /** + * Get the position of the chunk + * + * @return chunk position + */ + ChunkPos chunkPos(); + + /** + * Do second timer + */ + void timer(); + + /** + * Get the unloaded time in seconds + * This value would increase if the chunk is lazy + * + * @return the unloaded time + */ + int unloadedSeconds(); + + /** + * Set the unloaded seconds + * + * @param unloadedSeconds unloadedSeconds + */ + void unloadedSeconds(int unloadedSeconds); + + /** + * Get the last loaded time + * + * @return last loaded time + */ + long lastLoadedTime(); + + /** + * Set the last loaded time to current time + */ + void updateLastLoadedTime(); + + /** + * Get the loaded time in seconds + * + * @return loaded time + */ + int loadedMilliSeconds(); + + /** + * Get block data at a certain location + * + * @param location location + * @return block data + */ + @NotNull + Optional getBlockState(Pos3 location); + + /** + * Remove any block data from a certain location + * + * @param location location + * @return block data + */ + @NotNull + Optional removeBlockState(Pos3 location); + + /** + * Add a custom block data at a certain location + * + * @param block block to add + * @return the previous block data + */ + @NotNull + Optional addBlockState(Pos3 location, CustomCropsBlockState block); + + /** + * Get CustomCrops sections + * + * @return sections + */ + @NotNull + Stream sectionsToSave(); + + /** + * Get section by ID + * + * @param sectionID id + * @return section + */ + @NotNull + Optional getLoadedSection(int sectionID); + + CustomCropsSection getSection(int sectionID); + + Collection sections(); + + Optional removeSection(int sectionID); + + void resetUnloadedSeconds(); + + boolean canPrune(); + + boolean isOfflineTaskNotified(); + + PriorityQueue tickTaskQueue(); + + Set tickedBlocks(); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunkImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunkImpl.java new file mode 100644 index 0000000..77fc3aa --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsChunkImpl.java @@ -0,0 +1,288 @@ +package net.momirealms.customcrops.api.core.world; + +import net.momirealms.customcrops.common.util.RandomUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; + +public class CustomCropsChunkImpl implements CustomCropsChunk { + + private final CustomCropsWorld world; + private final ChunkPos chunkPos; + private final ConcurrentHashMap loadedSections; + private final PriorityQueue queue; + private final Set tickedBlocks; + private long lastLoadedTime; + private int loadedSeconds; + private int unloadedSeconds; + private boolean notified; + private boolean isLoaded; + private boolean forceLoad; + + protected CustomCropsChunkImpl(CustomCropsWorld world, ChunkPos chunkPos) { + this.world = world; + this.chunkPos = chunkPos; + this.loadedSections = new ConcurrentHashMap<>(16); + this.queue = new PriorityQueue<>(); + this.unloadedSeconds = 0; + this.tickedBlocks = Collections.synchronizedSet(new HashSet<>()); + this.updateLastLoadedTime(); + this.notified = true; + this.isLoaded = false; + } + + protected CustomCropsChunkImpl( + CustomCropsWorld world, + ChunkPos chunkPos, + int loadedSeconds, + long lastLoadedTime, + ConcurrentHashMap loadedSections, + PriorityQueue queue, + HashSet tickedBlocks + ) { + this.world = world; + this.chunkPos = chunkPos; + this.loadedSections = loadedSections; + this.lastLoadedTime = lastLoadedTime; + this.loadedSeconds = loadedSeconds; + this.queue = queue; + this.unloadedSeconds = 0; + this.tickedBlocks = Collections.synchronizedSet(tickedBlocks); + } + + @Override + public void setForceLoaded(boolean forceLoad) { + this.forceLoad = forceLoad; + } + + @Override + public boolean isForceLoaded() { + return this.forceLoad; + } + + @Override + public void load(boolean loadBukkitChunk) { + if (!isLoaded()) { + if (((CustomCropsWorldImpl) world).loadChunk(this)) { + this.isLoaded = true; + } + if (loadBukkitChunk && !this.world.bukkitWorld().isChunkLoaded(chunkPos.x(), chunkPos.z())) { + this.world.bukkitWorld().getChunkAt(chunkPos.x(), chunkPos.z()); + } + } + } + + @Override + public void unload(boolean lazy) { + if (isLoaded() && !isForceLoaded()) { + if (((CustomCropsWorldImpl) world).unloadChunk(this, lazy)) { + this.isLoaded = false; + } + } + } + + @Override + public void unloadLazy() { + if (!isLoaded() && isLazy()) { + ((CustomCropsWorldImpl) world).unloadLazyChunk(chunkPos); + } + } + + @Override + public boolean isLazy() { + return ((CustomCropsWorldImpl) world).getLazyChunk(chunkPos) == this; + } + + @Override + public boolean isLoaded() { + return this.isLoaded; + } + + @Override + public CustomCropsWorld getWorld() { + return world; + } + + @Override + public ChunkPos chunkPos() { + return chunkPos; + } + + @Override + public void timer() { + WorldSetting setting = world.setting(); + int interval = setting.minTickUnit(); + this.loadedSeconds++; + // if loadedSeconds reach another recycle, rearrange the tasks + if (this.loadedSeconds >= interval) { + this.loadedSeconds = 0; + this.tickedBlocks.clear(); + this.queue.clear(); + this.arrangeTasks(interval); + } + scheduledTick(); + randomTick(setting.randomTickSpeed()); + } + + private void arrangeTasks(int unit) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (CustomCropsSection section : loadedSections.values()) { + for (Map.Entry entry : section.blockMap().entrySet()) { + this.queue.add(new DelayedTickTask( + random.nextInt(0, unit), + entry.getKey() + )); + this.tickedBlocks.add(entry.getKey()); + } + } + } + + private void scheduledTick() { + while (!queue.isEmpty() && queue.peek().getTime() <= loadedSeconds) { + DelayedTickTask task = queue.poll(); + if (task != null) { + BlockPos pos = task.blockPos(); + CustomCropsSection section = loadedSections.get(pos.sectionID()); + if (section != null) { + Optional block = section.getBlockState(pos); + block.ifPresent(state -> state.type().scheduledTick(state, world, pos.toPos3(chunkPos))); + } + } + } + } + + private void randomTick(int randomTickSpeed) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (CustomCropsSection section : loadedSections.values()) { + int sectionID = section.getSectionID(); + int baseY = sectionID * 16; + for (int i = 0; i < randomTickSpeed; i++) { + int x = random.nextInt(16); + int y = random.nextInt(16) + baseY; + int z = random.nextInt(16); + BlockPos pos = new BlockPos(x,y,z); + Optional block = section.getBlockState(pos); + block.ifPresent(state -> state.type().randomTick(state, world, pos.toPos3(chunkPos))); + } + } + } + + @Override + public int unloadedSeconds() { + return unloadedSeconds; + } + + @Override + public void unloadedSeconds(int unloadedSeconds) { + this.unloadedSeconds = unloadedSeconds; + } + + @Override + public long lastLoadedTime() { + return lastLoadedTime; + } + + @Override + public void updateLastLoadedTime() { + this.lastLoadedTime = System.currentTimeMillis(); + } + + @Override + public int loadedMilliSeconds() { + return (int) (System.currentTimeMillis() - lastLoadedTime); + } + + @NotNull + @Override + public Optional getBlockState(Pos3 location) { + BlockPos pos = BlockPos.fromPos3(location); + return getLoadedSection(pos.sectionID()).flatMap(section -> section.getBlockState(pos)); + } + + @NotNull + @Override + public Optional removeBlockState(Pos3 location) { + BlockPos pos = BlockPos.fromPos3(location); + return getLoadedSection(pos.sectionID()).flatMap(section -> section.removeBlockState(pos)); + } + + @NotNull + @Override + public Optional addBlockState(Pos3 location, CustomCropsBlockState block) { + BlockPos pos = BlockPos.fromPos3(location); + CustomCropsSection section = getSection(pos.sectionID()); + this.arrangeScheduledTickTaskForNewBlock(pos); + return section.addBlockState(pos, block); + } + + @NotNull + @Override + public Stream sectionsToSave() { + return loadedSections.values().stream().filter(section -> !section.canPrune()); + } + + @NotNull + @Override + public Optional getLoadedSection(int sectionID) { + return Optional.ofNullable(loadedSections.get(sectionID)); + } + + @Override + public CustomCropsSection getSection(int sectionID) { + return getLoadedSection(sectionID).orElseGet(() -> { + CustomCropsSection section = new CustomCropsSectionImpl(sectionID); + this.loadedSections.put(sectionID, section); + return section; + }); + } + + @Override + public Collection sections() { + return loadedSections.values(); + } + + @Override + public Optional removeSection(int sectionID) { + return Optional.ofNullable(loadedSections.remove(sectionID)); + } + + @Override + public void resetUnloadedSeconds() { + this.unloadedSeconds = 0; + this.notified = false; + } + + @Override + public boolean canPrune() { + return loadedSections.isEmpty(); + } + + @Override + public boolean isOfflineTaskNotified() { + return notified; + } + + @Override + public PriorityQueue tickTaskQueue() { + return queue; + } + + @Override + public Set tickedBlocks() { + return tickedBlocks; + } + + private void arrangeScheduledTickTaskForNewBlock(BlockPos pos) { + WorldSetting setting = world.setting(); + if (!tickedBlocks.contains(pos)) { + tickedBlocks.add(pos); + int random = RandomUtils.generateRandomInt(0, setting.minTickUnit() - 1); + if (random > loadedSeconds) { + queue.add(new DelayedTickTask(random, pos)); + } + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakBlockWrapper.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegion.java similarity index 58% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakBlockWrapper.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegion.java index 0851a6e..d8f8302 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakBlockWrapper.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegion.java @@ -15,22 +15,32 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.item.function.wrapper; +package net.momirealms.customcrops.api.core.world; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class BreakBlockWrapper extends BreakWrapper { +import java.util.Map; - private final Block brokenBlock; +public interface CustomCropsRegion { - public BreakBlockWrapper(Player player, Block brokenBlock) { - super(player, brokenBlock.getLocation()); - this.brokenBlock = brokenBlock; - } + boolean isLoaded(); - public Block getBrokenBlock() { - return brokenBlock; - } + void unload(); + + void load(); + + @NotNull + CustomCropsWorld getWorld(); + + byte[] getCachedChunkBytes(ChunkPos pos); + + RegionPos regionPos(); + + boolean removeCachedChunk(ChunkPos pos); + + void setCachedChunk(ChunkPos pos, byte[] data); + + Map dataToSave(); + + boolean canPrune(); } - diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegionImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegionImpl.java new file mode 100644 index 0000000..a6ebed2 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsRegionImpl.java @@ -0,0 +1,86 @@ +package net.momirealms.customcrops.api.core.world; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CustomCropsRegionImpl implements CustomCropsRegion { + + private final CustomCropsWorld world; + private final RegionPos regionPos; + private final ConcurrentHashMap cachedChunks; + private boolean isLoaded = false; + + protected CustomCropsRegionImpl(CustomCropsWorld world, RegionPos regionPos) { + this.world = world; + this.cachedChunks = new ConcurrentHashMap<>(); + this.regionPos = regionPos; + } + + protected CustomCropsRegionImpl(CustomCropsWorld world, RegionPos regionPos, ConcurrentHashMap cachedChunks) { + this.world = world; + this.regionPos = regionPos; + this.cachedChunks = cachedChunks; + } + + @Override + public boolean isLoaded() { + return isLoaded; + } + + @Override + public void unload() { + if (this.isLoaded) { + if (((CustomCropsWorldImpl) world).unloadRegion(this)) { + this.isLoaded = false; + } + } + } + + @Override + public void load() { + if (!this.isLoaded) { + if (((CustomCropsWorldImpl) world).loadRegion(this)) { + this.isLoaded = true; + } + } + } + + @NotNull + @Override + public CustomCropsWorld getWorld() { + return this.world; + } + + @Override + public byte[] getCachedChunkBytes(ChunkPos pos) { + return this.cachedChunks.get(pos); + } + + @Override + public RegionPos regionPos() { + return this.regionPos; + } + + @Override + public boolean removeCachedChunk(ChunkPos pos) { + return cachedChunks.remove(pos) != null; + } + + @Override + public void setCachedChunk(ChunkPos pos, byte[] data) { + this.cachedChunks.put(pos, data); + } + + @Override + public Map dataToSave() { + return new HashMap<>(cachedChunks); + } + + @Override + public boolean canPrune() { + return cachedChunks.isEmpty(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSection.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSection.java new file mode 100644 index 0000000..e44d4a4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSection.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.core.world; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public interface CustomCropsSection { + + static CustomCropsSection create(int sectionID) { + return new CustomCropsSectionImpl(sectionID); + } + + static CustomCropsSection restore(int sectionID, ConcurrentHashMap blocks) { + return new CustomCropsSectionImpl(sectionID, blocks); + } + + int getSectionID(); + + @NotNull + Optional getBlockState(BlockPos pos); + + @NotNull + Optional removeBlockState(BlockPos pos); + + @NotNull + Optional addBlockState(BlockPos pos, CustomCropsBlockState block); + + boolean canPrune(); + + CustomCropsBlockState[] blocks(); + + Map blockMap(); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSectionImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSectionImpl.java new file mode 100644 index 0000000..891afa0 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsSectionImpl.java @@ -0,0 +1,61 @@ +package net.momirealms.customcrops.api.core.world; + +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class CustomCropsSectionImpl implements CustomCropsSection { + + private final int sectionID; + private final ConcurrentHashMap blocks; + + protected CustomCropsSectionImpl(int sectionID) { + this.sectionID = sectionID; + this.blocks = new ConcurrentHashMap<>(); + } + + protected CustomCropsSectionImpl(int sectionID, ConcurrentHashMap blocks) { + this.sectionID = sectionID; + this.blocks = blocks; + } + + @Override + public int getSectionID() { + return sectionID; + } + + @NotNull + @Override + public Optional getBlockState(BlockPos pos) { + return Optional.ofNullable(blocks.get(pos)); + } + + @NotNull + @Override + public Optional removeBlockState(BlockPos pos) { + return Optional.ofNullable(blocks.remove(pos)); + } + + @NotNull + @Override + public Optional addBlockState(BlockPos pos, CustomCropsBlockState block) { + return Optional.ofNullable(blocks.put(pos, block)); + } + + @Override + public boolean canPrune() { + return blocks.isEmpty(); + } + + @Override + public CustomCropsBlockState[] blocks() { + return blocks.values().toArray(new CustomCropsBlockState[0]); + } + + @Override + public Map blockMap() { + return blocks; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java new file mode 100644 index 0000000..f4438ec --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorld.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.core.world; + +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.concurrent.ConcurrentHashMap; + +public interface CustomCropsWorld { + + /** + * Create a CustomCrops world + * + * @param world world instance + * @param adaptor the world adaptor + * @return CustomCrops world + * @param world type + */ + static CustomCropsWorld create(W world, WorldAdaptor adaptor) { + return new CustomCropsWorldImpl<>(world, adaptor); + } + + /** + * Create a new CustomCropsChunk associated with this world + * + * @param pos the position of the chunk + * @return the created chunk + */ + default CustomCropsChunk createChunk(ChunkPos pos) { + return new CustomCropsChunkImpl(this, pos); + } + + default CustomCropsChunk restoreChunk( + ChunkPos pos, + int loadedSeconds, + long lastLoadedTime, + ConcurrentHashMap loadedSections, + PriorityQueue queue, + HashSet tickedBlocks + ) { + return new CustomCropsChunkImpl(this, pos, loadedSeconds, lastLoadedTime, loadedSections, queue, tickedBlocks); + } + + default CustomCropsRegion createRegion(RegionPos pos) { + return new CustomCropsRegionImpl(this, pos); + } + + default CustomCropsRegion restoreRegion(RegionPos pos, ConcurrentHashMap cachedChunks) { + return new CustomCropsRegionImpl(this, pos, cachedChunks); + } + + WorldAdaptor adaptor(); + + WorldExtraData extraData(); + + boolean testChunkLimitation(Pos3 pos3, Class clazz, int amount); + + boolean doesChunkHaveBlock(Pos3 pos3, Class clazz); + + int getChunkBlockAmount(Pos3 pos3, Class clazz); + + /** + * Get the state of the block at a certain location + * + * @param location location of the block state + * @return the optional block state + */ + @NotNull + Optional getBlockState(Pos3 location); + + /** + * Remove the block state from a certain location + * + * @param location the location of the block state + * @return the optional removed state + */ + @NotNull + Optional removeBlockState(Pos3 location); + + /** + * Add block state at the certain location + * + * @param location location of the state + * @param block block state to add + * @return the optional previous state + */ + @NotNull + Optional addBlockState(Pos3 location, CustomCropsBlockState block); + + /** + * Save the world to file + */ + void save(); + + /** + * Set if the ticking task is ongoing + * + * @param tick ongoing or not + */ + void setTicking(boolean tick); + + /** + * Get the world associated with this world + * + * @return Bukkit world + */ + W world(); + + World bukkitWorld(); + + String worldName(); + + /** + * Get the settings of the world + * + * @return the setting + */ + @NotNull + WorldSetting setting(); + + /** + * Set the settings of the world + * + * @param setting setting + */ + void setting(WorldSetting setting); + + /* + * Chunks + */ + boolean isChunkLoaded(ChunkPos pos); + + /** + * Get loaded chunk from cache + * + * @param chunkPos the position of the chunk + * @return the optional loaded chunk + */ + @NotNull + Optional getLoadedChunk(ChunkPos chunkPos); + + /** + * Get chunk from cache or file + * + * @param chunkPos the position of the chunk + * @return the optional chunk + */ + @NotNull + Optional getChunk(ChunkPos chunkPos); + + /** + * Get chunk from cache or file, create if not found + * + * @param chunkPos the position of the chunk + * @return the chunk + */ + @NotNull + CustomCropsChunk getOrCreateChunk(ChunkPos chunkPos); + + /** + * Check if a region is loaded + * + * @param regionPos the position of the region + * @return loaded or not + */ + boolean isRegionLoaded(RegionPos regionPos); + + /** + * Get the loaded region, empty if not loaded + * + * @param regionPos position of the region + * @return the optional loaded region + */ + @NotNull + Optional getLoadedRegion(RegionPos regionPos); + + /** + * Get the region from cache or file + * + * @param regionPos position of the region + * @return the optional region + */ + @NotNull + Optional getRegion(RegionPos regionPos); + + /** + * Get the region from cache or file, create if not found + * + * @param regionPos position of the region + * @return the region + */ + @NotNull + CustomCropsRegion getOrCreateRegion(RegionPos regionPos); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java new file mode 100644 index 0000000..1c8744c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/CustomCropsWorldImpl.java @@ -0,0 +1,474 @@ +package net.momirealms.customcrops.api.core.world; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class CustomCropsWorldImpl implements CustomCropsWorld { + + private final ConcurrentHashMap loadedChunks = new ConcurrentHashMap<>(512); + private final ConcurrentHashMap lazyChunks = new ConcurrentHashMap<>(128); + private final ConcurrentHashMap loadedRegions = new ConcurrentHashMap<>(128); + private final WeakReference world; + private final WeakReference bukkitWorld; + private final String worldName; + private long currentMinecraftDay; + private int regionTimer; + private SchedulerTask tickTask; + private WorldSetting setting; + private final WorldAdaptor adaptor; + private final WorldExtraData extraData; + + public CustomCropsWorldImpl(W world, WorldAdaptor adaptor) { + this.world = new WeakReference<>(world); + this.worldName = adaptor.getName(world); + this.bukkitWorld = new WeakReference<>(Bukkit.getWorld(worldName)); + this.regionTimer = 0; + this.adaptor = adaptor; + this.extraData = adaptor.loadExtraData(world); + this.currentMinecraftDay = (int) (adaptor.getWorldFullTime(world) / 24_000); + } + + @Override + public WorldAdaptor adaptor() { + return adaptor; + } + + @NotNull + @Override + public WorldExtraData extraData() { + return extraData; + } + + @Override + public boolean testChunkLimitation(Pos3 pos3, Class clazz, int amount) { + Optional optional = getChunk(pos3.toChunkPos()); + if (optional.isPresent()) { + int i = 0; + CustomCropsChunk chunk = optional.get(); + for (CustomCropsSection section : chunk.sections()) { + for (CustomCropsBlockState state : section.blockMap().values()) { + if (clazz.isAssignableFrom(state.type().getClass())) { + i++; + if (i >= amount) { + return false; + } + } + } + } + return true; + } else { + return false; + } + } + + @Override + public boolean doesChunkHaveBlock(Pos3 pos3, Class clazz) { + Optional optional = getChunk(pos3.toChunkPos()); + if (optional.isPresent()) { + CustomCropsChunk chunk = optional.get(); + for (CustomCropsSection section : chunk.sections()) { + for (CustomCropsBlockState state : section.blockMap().values()) { + if (clazz.isAssignableFrom(state.type().getClass())) { + return true; + } + } + } + } + return false; + } + + @Override + public int getChunkBlockAmount(Pos3 pos3, Class clazz) { + Optional optional = getChunk(pos3.toChunkPos()); + if (optional.isPresent()) { + int i = 0; + CustomCropsChunk chunk = optional.get(); + for (CustomCropsSection section : chunk.sections()) { + for (CustomCropsBlockState state : section.blockMap().values()) { + if (clazz.isAssignableFrom(state.type().getClass())) { + i++; + } + } + } + return i; + } else { + return 0; + } + } + + @NotNull + @Override + public Optional getBlockState(Pos3 location) { + ChunkPos pos = location.toChunkPos(); + Optional chunk = getChunk(pos); + if (chunk.isEmpty()) { + return Optional.empty(); + } else { + CustomCropsChunk customChunk = chunk.get(); + // to let the bukkit system trigger the ChunkUnloadEvent later + customChunk.load(true); + return customChunk.getBlockState(location); + } + } + + @NotNull + @Override + public Optional removeBlockState(Pos3 location) { + ChunkPos pos = location.toChunkPos(); + Optional chunk = getChunk(pos); + if (chunk.isEmpty()) { + return Optional.empty(); + } else { + CustomCropsChunk customChunk = chunk.get(); + // to let the bukkit system trigger the ChunkUnloadEvent later + customChunk.load(true); + return customChunk.removeBlockState(location); + } + } + + @NotNull + @Override + public Optional addBlockState(Pos3 location, CustomCropsBlockState block) { + ChunkPos pos = location.toChunkPos(); + CustomCropsChunk chunk = getOrCreateChunk(pos); + // to let the bukkit system trigger the ChunkUnloadEvent later + chunk.load(true); + return chunk.addBlockState(location, block); + } + + @Override + public void save() { + long time1 = System.currentTimeMillis(); + this.adaptor.saveExtraData(this); + for (CustomCropsChunk chunk : loadedChunks.values()) { + this.adaptor.saveChunk(this, chunk); + } + for (CustomCropsChunk chunk : lazyChunks.values()) { + this.adaptor.saveChunk(this, chunk); + } + for (CustomCropsRegion region : loadedRegions.values()) { + this.adaptor.saveRegion(this, region); + } + long time2 = System.currentTimeMillis(); + BukkitCustomCropsPlugin.getInstance().debug("Took " + (time2-time1) + "ms to save world " + worldName + ". Saved " + (lazyChunks.size() + loadedChunks.size()) + " chunks."); + } + + @Override + public void setTicking(boolean tick) { + if (tick) { + if (this.tickTask == null || this.tickTask.isCancelled()) + this.tickTask = BukkitCustomCropsPlugin.getInstance().getScheduler().asyncRepeating(this::timer, 1, 1, TimeUnit.SECONDS); + } else { + if (this.tickTask != null && !this.tickTask.isCancelled()) + this.tickTask.cancel(); + } + } + + private void timer() { + saveLazyChunks(); + saveLazyRegions(); + if (isANewDay()) { + updateSeasonAndDate(); + } + if (setting().enableScheduler()) { + tickChunks(); + } + } + + private void tickChunks() { + if (VersionHelper.isFolia()) { + SchedulerAdapter scheduler = BukkitCustomCropsPlugin.getInstance().getScheduler(); + for (CustomCropsChunk chunk : loadedChunks.values()) { + scheduler.sync().run(chunk::timer, bukkitWorld(), chunk.chunkPos().x(), chunk.chunkPos().z()); + } + } else { + for (CustomCropsChunk chunk : loadedChunks.values()) { + chunk.timer(); + } + } + } + + private void updateSeasonAndDate() { + int date = extraData().getDate(); + date++; + if (date > setting().seasonDuration()) { + Season season = extraData().getSeason().getNextSeason(); + extraData().setSeason(season); + extraData().setDate(1); + } else { + extraData().setDate(date); + } + } + + private boolean isANewDay() { + long currentDay = bukkitWorld().getFullTime() / 24_000; + if (currentDay != currentMinecraftDay) { + currentMinecraftDay = currentDay; + return true; + } + return false; + } + + private void saveLazyRegions() { + this.regionTimer++; + if (this.regionTimer >= 600) { + this.regionTimer = 0; + ArrayList removed = new ArrayList<>(); + for (Map.Entry entry : loadedRegions.entrySet()) { + if (shouldUnloadRegion(entry.getKey())) { + removed.add(entry.getValue()); + } + } + for (CustomCropsRegion region : removed) { + region.unload(); + } + } + } + + private void saveLazyChunks() { + ArrayList chunksToSave = new ArrayList<>(); + for (Map.Entry lazyEntry : this.lazyChunks.entrySet()) { + CustomCropsChunk chunk = lazyEntry.getValue(); + int sec = chunk.unloadedSeconds() + 1; + if (sec >= 30) { + chunksToSave.add(chunk); + } else { + chunk.unloadedSeconds(sec); + } + } + for (CustomCropsChunk chunk : chunksToSave) { + unloadLazyChunk(chunk.chunkPos()); + } + } + + @Override + public W world() { + return world.get(); + } + + @Override + public World bukkitWorld() { + return bukkitWorld.get(); + } + + @Override + public String worldName() { + return worldName; + } + + @Override + public @NotNull WorldSetting setting() { + return setting; + } + + @Override + public void setting(WorldSetting setting) { + this.setting = setting; + } + + /* + * Chunks + */ + @Nullable + public CustomCropsChunk removeLazyChunk(ChunkPos chunkPos) { + return this.lazyChunks.remove(chunkPos); + } + + @Nullable + public CustomCropsChunk getLazyChunk(ChunkPos chunkPos) { + return this.lazyChunks.get(chunkPos); + } + + @Override + public boolean isChunkLoaded(ChunkPos pos) { + return this.loadedChunks.containsKey(pos); + } + + public boolean loadChunk(CustomCropsChunk chunk) { + Optional previousChunk = getLoadedChunk(chunk.chunkPos()); + if (previousChunk.isPresent()) { + BukkitCustomCropsPlugin.getInstance().debug("Chunk " + chunk.chunkPos() + " already loaded."); + if (previousChunk.get() != chunk) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to load the chunk. There is already a different chunk instance with the same coordinates in the cache. " + chunk.chunkPos()); + return false; + } + return true; + } + this.loadedChunks.put(chunk.chunkPos(), chunk); + this.lazyChunks.remove(chunk.chunkPos()); + return true; + } + + @ApiStatus.Internal + public boolean unloadChunk(CustomCropsChunk chunk, boolean lazy) { + ChunkPos pos = chunk.chunkPos(); + Optional previousChunk = getLoadedChunk(chunk.chunkPos()); + if (previousChunk.isPresent()) { + if (previousChunk.get() != chunk) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to remove the chunk. The provided chunk instance is inconsistent with the one in the cache. " + chunk.chunkPos()); + return false; + } + } else { + return false; + } + this.loadedChunks.remove(chunk.chunkPos()); + chunk.updateLastLoadedTime(); + if (lazy) { + this.lazyChunks.put(pos, chunk); + } else { + this.adaptor.saveChunk(this, chunk); + } + return true; + } + + @ApiStatus.Internal + public boolean unloadChunk(ChunkPos pos, boolean lazy) { + CustomCropsChunk removed = this.loadedChunks.remove(pos); + if (removed != null) { + removed.updateLastLoadedTime(); + if (lazy) { + this.lazyChunks.put(pos, removed); + } else { + this.adaptor.saveChunk(this, removed); + } + return true; + } + return false; + } + + @ApiStatus.Internal + public boolean unloadLazyChunk(ChunkPos pos) { + CustomCropsChunk removed = this.lazyChunks.remove(pos); + if (removed != null) { + this.adaptor.saveChunk(this, removed); + return true; + } + return false; + } + + @NotNull + @Override + public Optional getLoadedChunk(ChunkPos chunkPos) { + return Optional.ofNullable(this.loadedChunks.get(chunkPos)); + } + + @NotNull + @Override + public Optional getChunk(ChunkPos chunkPos) { + return Optional.ofNullable(getLoadedChunk(chunkPos).orElseGet(() -> { + CustomCropsChunk chunk = getLazyChunk(chunkPos); + if (chunk != null) { + return chunk; + } + return this.adaptor.loadChunk(this, chunkPos, false); + })); + } + + @NotNull + @Override + public CustomCropsChunk getOrCreateChunk(ChunkPos chunkPos) { + return Objects.requireNonNull(getLoadedChunk(chunkPos).orElseGet(() -> { + CustomCropsChunk chunk = getLazyChunk(chunkPos); + if (chunk != null) { + return chunk; + } + return this.adaptor.loadChunk(this, chunkPos, true); + })); + } + + /* + * Regions + */ + @Override + public boolean isRegionLoaded(RegionPos pos) { + return this.loadedRegions.containsKey(pos); + } + + @ApiStatus.Internal + public boolean loadRegion(CustomCropsRegion region) { + Optional previousRegion = getLoadedRegion(region.regionPos()); + if (previousRegion.isPresent()) { + BukkitCustomCropsPlugin.getInstance().debug("Region " + region.regionPos() + " already loaded."); + if (previousRegion.get() != region) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to load the region. There is already a different region instance with the same coordinates in the cache. " + region.regionPos()); + return false; + } + return true; + } + this.loadedRegions.put(region.regionPos(), region); + return true; + } + + @NotNull + @Override + public Optional getLoadedRegion(RegionPos regionPos) { + return Optional.ofNullable(loadedRegions.get(regionPos)); + } + + @NotNull + @Override + public Optional getRegion(RegionPos regionPos) { + return Optional.ofNullable(getLoadedRegion(regionPos).orElse(adaptor.loadRegion(this, regionPos, false))); + } + + @NotNull + @Override + public CustomCropsRegion getOrCreateRegion(RegionPos regionPos) { + return Objects.requireNonNull(getLoadedRegion(regionPos).orElse(adaptor.loadRegion(this, regionPos, true))); + } + + private boolean shouldUnloadRegion(RegionPos regionPos) { + for (int chunkX = regionPos.x() * 32; chunkX < regionPos.x() * 32 + 32; chunkX++) { + for (int chunkZ = regionPos.z() * 32; chunkZ < regionPos.z() * 32 + 32; chunkZ++) { + // if a chunk is unloaded, then it should not be in the loaded chunks map + ChunkPos pos = ChunkPos.of(chunkX, chunkZ); + if (isChunkLoaded(pos) || this.lazyChunks.containsKey(pos)) { + return false; + } + } + } + return true; + } + + public boolean unloadRegion(CustomCropsRegion region) { + Optional previousRegion = getLoadedRegion(region.regionPos()); + if (previousRegion.isPresent()) { + if (previousRegion.get() != region) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to remove the region. The provided region instance is inconsistent with the one in the cache. " + region.regionPos()); + return false; + } + } else { + return false; + } + RegionPos regionPos = region.regionPos(); + for (int chunkX = regionPos.x() * 32; chunkX < regionPos.x() * 32 + 32; chunkX++) { + for (int chunkZ = regionPos.z() * 32; chunkZ < regionPos.z() * 32 + 32; chunkZ++) { + ChunkPos pos = ChunkPos.of(chunkX, chunkZ); + if (!unloadLazyChunk(pos)) { + unloadChunk(pos, false); + } + } + } + this.loadedRegions.remove(region.regionPos()); + this.adaptor.saveRegion(this, region); + return true; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SoilRetain.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/DataBlock.java similarity index 65% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SoilRetain.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/DataBlock.java index 0071623..f063ffb 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SoilRetain.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/DataBlock.java @@ -15,16 +15,23 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.item.fertilizer; +package net.momirealms.customcrops.api.core.world; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; +import com.flowpowered.nbt.Tag; +import net.momirealms.customcrops.api.core.SynchronizedCompoundMap; -public interface SoilRetain extends Fertilizer { +public interface DataBlock { + + Tag set(String key, Tag tag); + + Tag get(String key); + + Tag remove(String key); /** - * Get the chance of taking effect + * Get the data map * - * @return chance + * @return data map */ - double getChance(); + SynchronizedCompoundMap compoundMap(); } diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/task/TickTask.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/DelayedTickTask.java similarity index 80% rename from plugin/src/main/java/net/momirealms/customcrops/scheduler/task/TickTask.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/DelayedTickTask.java index 273816e..2526dfd 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/task/TickTask.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/DelayedTickTask.java @@ -15,25 +15,24 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.scheduler.task; +package net.momirealms.customcrops.api.core.world; -import net.momirealms.customcrops.api.mechanic.world.BlockPos; import org.jetbrains.annotations.NotNull; -public class TickTask implements Comparable { +public class DelayedTickTask implements Comparable { private static int taskID; private final int time; private final BlockPos blockPos; private final int id; - public TickTask(int time, BlockPos blockPos) { + public DelayedTickTask(int time, BlockPos blockPos) { this.time = time; this.blockPos = blockPos; this.id = taskID++; } - public BlockPos getChunkPos() { + public BlockPos blockPos() { return blockPos; } @@ -42,7 +41,7 @@ public class TickTask implements Comparable { } @Override - public int compareTo(@NotNull TickTask o) { + public int compareTo(@NotNull DelayedTickTask o) { if (this.time > o.time) { return 1; } else if (this.time < o.time) { diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/Pos3.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/Pos3.java new file mode 100644 index 0000000..4be100f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/Pos3.java @@ -0,0 +1,87 @@ +/* + * 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.core.world; + +import org.bukkit.Location; +import org.bukkit.World; + +public record Pos3(int x, int y, int z) { + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pos3 other = (Pos3) obj; + if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { + return false; + } + if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { + return false; + } + if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.x)); + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.y)); + hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.z)); + return hash; + } + + public static Pos3 from(Location location) { + return new Pos3(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + + public Location toLocation(World world) { + return new Location(world, x, y, z); + } + + public Pos3 add(int x, int y, int z) { + return new Pos3(this.x + x, this.y + y, this.z + z); + } + + public ChunkPos toChunkPos() { + return ChunkPos.fromPos3(this); + } + + public int chunkX() { + return (int) Math.floor((double) this.x() / 16.0); + } + + public int chunkZ() { + return (int) Math.floor((double) this.z() / 16.0); + } + + @Override + public String toString() { + return "Pos3{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/RegionPos.java similarity index 95% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/RegionPos.java index 7b04763..ac0cf69 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/RegionPos.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/RegionPos.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world; +package net.momirealms.customcrops.api.core.world; import org.bukkit.Chunk; import org.jetbrains.annotations.NotNull; @@ -34,8 +34,7 @@ public record RegionPos(int x, int z) { return new RegionPos(x, z); } catch (NumberFormatException e) { - e.printStackTrace(); - return null; + throw new RuntimeException("Invalid coordinate: " + coordinate); } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/Season.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/Season.java new file mode 100644 index 0000000..f6a0398 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/Season.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for 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.core.world; + +import net.momirealms.customcrops.common.locale.MessageConstants; +import net.momirealms.customcrops.common.locale.TranslationManager; + +import java.util.Optional; +import java.util.function.Supplier; + +public enum Season { + + SPRING(() -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.SEASON_SPRING.build().key())).orElse("Spring")), + SUMMER(() -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.SEASON_SUMMER.build().key())).orElse("Summer")), + AUTUMN(() -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.SEASON_AUTUMN.build().key())).orElse("Autumn")), + WINTER(() -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.SEASON_WINTER.build().key())).orElse("Winter")), + DISABLE(() -> Optional.ofNullable(TranslationManager.miniMessageTranslation(MessageConstants.SEASON_DISABLE.build().key())).orElse("Disabled")); + + private final Supplier translationSupplier; + + Season(Supplier translationSupplier) { + this.translationSupplier = translationSupplier; + } + + public String translation() { + return translationSupplier.get(); + } + + public Season getNextSeason() { + return switch (this) { + case SPRING -> SUMMER; + case SUMMER -> AUTUMN; + case AUTUMN -> WINTER; + case WINTER -> SPRING; + default -> DISABLE; + }; + } +} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceFurnitureWrapper.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableChunk.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceFurnitureWrapper.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableChunk.java index 31c5c11..4b9d496 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceFurnitureWrapper.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableChunk.java @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.item.function.wrapper; +package net.momirealms.customcrops.api.core.world; -import org.bukkit.Location; -import org.bukkit.entity.Player; +import java.util.List; -public class PlaceFurnitureWrapper extends PlaceWrapper { +public record SerializableChunk(int x, int z, int loadedSeconds, long lastLoadedTime, + List sections, int[] queuedTasks, int[] ticked) { - public PlaceFurnitureWrapper(Player player, Location location, String id) { - super(player, location, id); + public boolean canPrune() { + return sections.isEmpty(); } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/season/Season.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableSection.java similarity index 72% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/season/Season.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableSection.java index 187e161..5204eb2 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/season/Season.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/SerializableSection.java @@ -15,16 +15,15 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world.season; +package net.momirealms.customcrops.api.core.world; -public enum Season { +import com.flowpowered.nbt.CompoundTag; - SPRING, - SUMMER, - AUTUMN, - WINTER; +import java.util.List; - public Season getNextSeason() { - return Season.values()[(this.ordinal() + 1) % 4]; +public record SerializableSection(int sectionID, List blocks) { + + public boolean canPrune() { + return blocks.isEmpty(); } -} \ No newline at end of file +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldInfoData.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldExtraData.java similarity index 69% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldInfoData.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/WorldExtraData.java index 51c97e1..ecfa0c7 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldInfoData.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldExtraData.java @@ -15,25 +15,43 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world.level; +package net.momirealms.customcrops.api.core.world; import com.google.gson.annotations.SerializedName; -import net.momirealms.customcrops.api.mechanic.world.season.Season; +import org.jetbrains.annotations.Nullable; -public class WorldInfoData { +import java.util.HashMap; + +public class WorldExtraData { @SerializedName("season") private Season season; @SerializedName("date") private int date; + @SerializedName("extra") + private HashMap extra; - public WorldInfoData(Season season, int date) { + public WorldExtraData(Season season, int date) { this.season = season; this.date = date; + this.extra = new HashMap<>(); } - public static WorldInfoData empty() { - return new WorldInfoData(Season.SPRING, 1); + public static WorldExtraData empty() { + return new WorldExtraData(Season.SPRING, 1); + } + + public void addExtraData(String key, Object value) { + this.extra.put(key, value); + } + + public void removeExtraData(String key) { + this.extra.remove(key); + } + + @Nullable + public Object getExtraData(String key) { + return this.extra.get(key); } /** @@ -75,7 +93,7 @@ public class WorldInfoData { @Override public String toString() { - return "WorldInfoData{" + + return "WorldExtraData{" + "season=" + season + ", date=" + date + '}'; diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldManager.java new file mode 100644 index 0000000..3905e5e --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldManager.java @@ -0,0 +1,31 @@ +package net.momirealms.customcrops.api.core.world; + +import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor; +import net.momirealms.customcrops.common.plugin.feature.Reloadable; +import org.bukkit.World; + +import java.util.Optional; +import java.util.Set; + +public interface WorldManager extends Reloadable { + + Season getSeason(World world); + + int getDate(World world); + + CustomCropsWorld loadWorld(World world); + + boolean unloadWorld(World world); + + Optional> getWorld(World world); + + Optional> getWorld(String world); + + boolean isWorldLoaded(World world); + + Set> adaptors(); + + CustomCropsWorld adapt(World world); + + CustomCropsWorld adapt(String world); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSetting.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldSetting.java similarity index 86% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSetting.java rename to api/src/main/java/net/momirealms/customcrops/api/core/world/WorldSetting.java index e9a9b0d..6df176d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSetting.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/WorldSetting.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.world.level; +package net.momirealms.customcrops.api.core.world; public class WorldSetting implements Cloneable { @@ -36,7 +36,6 @@ public class WorldSetting implements Cloneable { private final boolean tickCropRandomly; private final boolean tickPotRandomly; private final boolean tickSprinklerRandomly; - private final boolean scheduledTick; private WorldSetting( boolean enableScheduler, @@ -74,7 +73,6 @@ public class WorldSetting implements Cloneable { this.tickCropRandomly = tickCropRandomly; this.tickPotRandomly = tickPotRandomly; this.tickSprinklerRandomly = tickSprinklerRandomly; - this.scheduledTick = !(tickCropRandomly && tickPotRandomly && tickSprinklerRandomly); } public static WorldSetting of( @@ -117,62 +115,58 @@ public class WorldSetting implements Cloneable { ); } - public boolean isSchedulerEnabled() { + public boolean enableScheduler() { return enableScheduler; } - public int getMinTickUnit() { + public int minTickUnit() { return minTickUnit; } - public int getTickCropInterval() { + public int tickCropInterval() { return tickCropInterval; } - public int getTickPotInterval() { + public int tickPotInterval() { return tickPotInterval; } - public int getTickSprinklerInterval() { + public int tickSprinklerInterval() { return tickSprinklerInterval; } - public boolean isOfflineTick() { + public boolean offlineTick() { return offlineTick; } - public boolean isEnableSeason() { + public boolean enableSeason() { return enableSeason; } - public boolean isAutoSeasonChange() { + public boolean autoSeasonChange() { return autoSeasonChange; } - public int getSeasonDuration() { + public int seasonDuration() { return seasonDuration; } - public int getPotPerChunk() { + public int potPerChunk() { return potPerChunk; } - public int getCropPerChunk() { + public int cropPerChunk() { return cropPerChunk; } - public int getSprinklerPerChunk() { + public int sprinklerPerChunk() { return sprinklerPerChunk; } - public int getRandomTickSpeed() { + public int randomTickSpeed() { return randomTickSpeed; } - public boolean isScheduledTick() { - return scheduledTick; - } - public boolean randomTickCrop() { return tickCropRandomly; } @@ -185,7 +179,7 @@ public class WorldSetting implements Cloneable { return tickPotRandomly; } - public int getMaxOfflineTime() { + public int maxOfflineTime() { return maxOfflineTime; } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/AbstractWorldAdaptor.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/AbstractWorldAdaptor.java new file mode 100644 index 0000000..4934a6f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/AbstractWorldAdaptor.java @@ -0,0 +1,124 @@ +package net.momirealms.customcrops.api.core.world.adaptor; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.IntArrayTag; +import com.flowpowered.nbt.StringTag; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.world.*; +import net.momirealms.customcrops.common.dependency.Dependency; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +public abstract class AbstractWorldAdaptor implements WorldAdaptor { + + public static final int CHUNK_VERSION = 2; + public static final int REGION_VERSION = 1; + + private final Method decompressMethod; + private final Method compressMethod; + + public AbstractWorldAdaptor() { + ClassLoader classLoader = BukkitCustomCropsPlugin.getInstance().getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.ZSTD)); + try { + Class zstd = classLoader.loadClass("com.github.luben.zstd.Zstd"); + decompressMethod = zstd.getMethod("decompress", byte[].class, byte[].class); + compressMethod = zstd.getMethod("compress", byte[].class); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + protected void zstdDecompress(byte[] decompressedData, byte[] compressedData) { + try { + decompressMethod.invoke(null, decompressedData, compressedData); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + //Zstd.decompress(decompressedData, compressedData); + } + + protected byte[] zstdCompress(byte[] data) { + try { + Object result = compressMethod.invoke(null, (Object) data); + return (byte[]) result; + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + //return Zstd.compress(data); + } + + @Override + public int compareTo(@NotNull WorldAdaptor o) { + return Integer.compare(o.priority(), this.priority()); + } + + protected SerializableChunk toSerializableChunk(CustomCropsChunk chunk) { + ChunkPos chunkPos = chunk.chunkPos(); + return new SerializableChunk( + chunkPos.x(), + chunkPos.z(), + chunk.loadedMilliSeconds(), + chunk.lastLoadedTime(), + chunk.sectionsToSave().map(this::toSerializableSection).toList(), + queueToIntArray(chunk.tickTaskQueue()), + tickedBlocksToArray(chunk.tickedBlocks()) + ); + } + + protected SerializableSection toSerializableSection(CustomCropsSection section) { + return new SerializableSection(section.getSectionID(), toCompoundTags(section.blockMap())); + } + + private List toCompoundTags(Map blocks) { + ArrayList tags = new ArrayList<>(blocks.size()); + Map> blockToPosMap = new HashMap<>(); + for (Map.Entry entry : blocks.entrySet()) { + BlockPos coordinate = entry.getKey(); + CustomCropsBlockState block = entry.getValue(); + List coordinates = blockToPosMap.computeIfAbsent(block, k -> new ArrayList<>()); + coordinates.add(coordinate.position()); + } + for (Map.Entry> entry : blockToPosMap.entrySet()) { + tags.add(new CompoundTag("", toCompoundMap(entry.getKey(), entry.getValue()))); + } + return tags; + } + + private CompoundMap toCompoundMap(CustomCropsBlockState block, List pos) { + CompoundMap map = new CompoundMap(); + int[] result = new int[pos.size()]; + for (int i = 0; i < pos.size(); i++) { + result[i] = pos.get(i); + } + map.put(new StringTag("type", block.type().type().asString())); + map.put(new IntArrayTag("pos", result)); + map.put(new CompoundTag("data", block.compoundMap().originalMap())); + return map; + } + + private int[] tickedBlocksToArray(Set set) { + int[] ticked = new int[set.size()]; + int i = 0; + for (BlockPos pos : set) { + ticked[i] = pos.position(); + i++; + } + return ticked; + } + + private int[] queueToIntArray(PriorityQueue queue) { + int size = queue.size() * 2; + int[] tasks = new int[size]; + int i = 0; + for (DelayedTickTask task : queue) { + tasks[i * 2] = task.getTime(); + tasks[i * 2 + 1] = task.blockPos().position(); + i++; + } + return tasks; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/WorldAdaptor.java b/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/WorldAdaptor.java new file mode 100644 index 0000000..8e578c5 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/world/adaptor/WorldAdaptor.java @@ -0,0 +1,45 @@ +package net.momirealms.customcrops.api.core.world.adaptor; + +import net.momirealms.customcrops.api.core.world.*; +import org.jetbrains.annotations.Nullable; + +public interface WorldAdaptor extends Comparable> { + + int BUKKIT_WORLD_PRIORITY = 100; + int SLIME_WORLD_PRIORITY = 200; + + WorldExtraData loadExtraData(W world); + + void saveExtraData(CustomCropsWorld world); + + /** + * Load the region from file or cache + */ + @Nullable + CustomCropsRegion loadRegion(CustomCropsWorld world, RegionPos pos, boolean createIfNotExist); + + /** + * Load the chunk from file or cache + */ + @Nullable + CustomCropsChunk loadChunk(CustomCropsWorld world, ChunkPos pos, boolean createIfNotExist); + + /** + * Unload the region to file or cache + */ + void saveRegion(CustomCropsWorld world, CustomCropsRegion region); + + void saveChunk(CustomCropsWorld world, CustomCropsChunk chunk); + + String getName(W world); + + @Nullable + W getWorld(String worldName); + + CustomCropsWorld adapt(Object world); + + long getWorldFullTime(W world); + + int priority(); + +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedBreakEvent.java new file mode 100644 index 0000000..8870935 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedBreakEvent.java @@ -0,0 +1,103 @@ +package net.momirealms.customcrops.api.core.wrapper; + +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class WrappedBreakEvent { + + private final Entity entity; + private final Block block; + private final Location location; + private final String brokenID; + private final ItemStack itemInHand; + private final String itemID; + private final Cancellable event; + private final CustomCropsWorld world; + private final BreakReason reason; + + public WrappedBreakEvent( + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, + CustomCropsWorld world, + Location location, + String brokenID, + ItemStack itemInHand, + String itemID, + BreakReason reason, + Cancellable event + ) { + this.entity = entityBreaker; + this.block = blockBreaker; + this.location = location; + this.brokenID = brokenID; + this.itemInHand = itemInHand; + this.itemID = itemID; + this.event = event; + this.world = world; + this.reason = reason; + } + + @NotNull + public BreakReason reason() { + return reason; + } + + @NotNull + public CustomCropsWorld world() { + return world; + } + + @NotNull + public Location location() { + return location; + } + + @NotNull + public String brokenID() { + return brokenID; + } + + @Nullable + public ItemStack itemInHand() { + return itemInHand; + } + + @Nullable + public String itemID() { + return itemID; + } + + public boolean isCancelled() { + return event.isCancelled(); + } + + public void setCancelled(boolean cancel) { + event.setCancelled(cancel); + } + + @Nullable + public Player playerBreaker() { + if (entity instanceof Player player) { + return player; + } + return null; + } + + @Nullable + public Entity entityBreaker() { + return entity; + } + + @Nullable + public Block blockBreaker() { + return block; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractAirEvent.java b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractAirEvent.java new file mode 100644 index 0000000..c827c2f --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractAirEvent.java @@ -0,0 +1,43 @@ +package net.momirealms.customcrops.api.core.wrapper; + +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class WrappedInteractAirEvent { + + private final CustomCropsWorld world; + private final ItemStack itemInHand; + private final String itemID; + private final EquipmentSlot hand; + private final Player player; + + public WrappedInteractAirEvent(CustomCropsWorld world, Player player, EquipmentSlot hand, ItemStack itemInHand, String itemID) { + this.world = world; + this.itemInHand = itemInHand; + this.itemID = itemID; + this.hand = hand; + this.player = player; + } + + public CustomCropsWorld world() { + return world; + } + + public ItemStack itemInHand() { + return itemInHand; + } + + public String itemID() { + return itemID; + } + + public EquipmentSlot hand() { + return hand; + } + + public Player player() { + return player; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractEvent.java b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractEvent.java new file mode 100644 index 0000000..c114d3d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedInteractEvent.java @@ -0,0 +1,94 @@ +package net.momirealms.customcrops.api.core.wrapper; + +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import org.bukkit.Location; +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; +import org.jetbrains.annotations.Nullable; + +public class WrappedInteractEvent { + + private final CustomCropsWorld world; + private final String relatedID; + private final ItemStack itemInHand; + private final String itemID; + private final EquipmentSlot hand; + private final BlockFace blockFace; + private final Cancellable event; + private final Location clickedLocation; + private final ExistenceForm existenceForm; + private final Player player; + + public WrappedInteractEvent( + ExistenceForm existenceForm, + Player player, + CustomCropsWorld world, + Location clickedLocation, + String relatedID, + ItemStack itemInHand, + String itemID, + EquipmentSlot hand, + BlockFace blockFace, + Cancellable event + ) { + this.player = player; + this.world = world; + this.clickedLocation = clickedLocation; + this.relatedID = relatedID; + this.itemID = itemID; + this.itemInHand = itemInHand; + this.hand = hand; + this.blockFace = blockFace; + this.event = event; + this.existenceForm = existenceForm; + } + + public CustomCropsWorld world() { + return world; + } + + public boolean isCancelled() { + return event.isCancelled(); + } + + public void setCancelled(boolean cancel) { + event.setCancelled(cancel); + } + + public ExistenceForm existenceForm() { + return existenceForm; + } + + public Location location() { + return clickedLocation; + } + + public String relatedID() { + return relatedID; + } + + public ItemStack itemInHand() { + return itemInHand; + } + + public String itemID() { + return itemID; + } + + public EquipmentSlot hand() { + return hand; + } + + @Nullable + public BlockFace clickedBlockFace() { + return blockFace; + } + + public Player player() { + return player; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedPlaceEvent.java b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedPlaceEvent.java new file mode 100644 index 0000000..612e799 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/core/wrapper/WrappedPlaceEvent.java @@ -0,0 +1,67 @@ +package net.momirealms.customcrops.api.core.wrapper; + +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class WrappedPlaceEvent { + + private final Player player; + private final Location location; + private final String placedID; + private final EquipmentSlot hand; + private final ItemStack item; + private final String itemID; + private final Cancellable event; + private final CustomCropsWorld world; + + public WrappedPlaceEvent(Player player, CustomCropsWorld world, Location location, String placedID, EquipmentSlot hand, ItemStack item, String itemID, Cancellable event) { + this.player = player; + this.location = location; + this.hand = hand; + this.item = item; + this.itemID = itemID; + this.world = world; + this.event = event; + this.placedID = placedID; + } + + public boolean isCancelled() { + return event.isCancelled(); + } + + public void setCancelled(boolean cancel) { + event.setCancelled(cancel); + } + + public String placedID() { + return placedID; + } + + public CustomCropsWorld world() { + return world; + } + + public Player player() { + return player; + } + + public Location location() { + return location; + } + + public EquipmentSlot hand() { + return hand; + } + + public ItemStack item() { + return item; + } + + public String itemID() { + return itemID; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealUseEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealUseEvent.java index 6216a37..17a8c8e 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealUseEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealUseEvent.java @@ -17,41 +17,48 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.BoneMeal; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; +import net.momirealms.customcrops.api.core.block.BoneMeal; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** * An event that triggered when a player interacts a crop with a bone meal */ -public class BoneMealUseEvent extends Event implements Cancellable { +public class BoneMealUseEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; private final BoneMeal boneMeal; - private final WorldCrop crop; + private final CustomCropsBlockState blockState; private final ItemStack itemInHand; - private final Player player; + private final EquipmentSlot equipmentSlot; + private final CropConfig config; public BoneMealUseEvent( @NotNull Player player, @NotNull ItemStack itemInHand, @NotNull Location location, @NotNull BoneMeal boneMeal, - @NotNull WorldCrop crop + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot equipmentSlot, + @NotNull CropConfig config ) { + super(player); this.location = location; - this.crop = crop; + this.blockState = blockState; this.boneMeal = boneMeal; this.itemInHand = itemInHand; - this.player = player; + this.equipmentSlot = equipmentSlot; + this.config = config; } @Override @@ -85,6 +92,11 @@ public class BoneMealUseEvent extends Event implements Cancellable { return location; } + @NotNull + public CropConfig getConfig() { + return config; + } + /** * Get the item in player's hand * If there's nothing in hand, it would return AIR @@ -96,14 +108,14 @@ public class BoneMealUseEvent extends Event implements Cancellable { return itemInHand; } - /** - * Get the crop's data - * - * @return crop data - */ @NotNull - public WorldCrop getCrop() { - return crop; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getEquipmentSlot() { + return equipmentSlot; } /** @@ -115,9 +127,4 @@ public class BoneMealUseEvent extends Event implements Cancellable { public BoneMeal getBoneMeal() { return boneMeal; } - - @NotNull - public Player getPlayer() { - return player; - } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java index 9a2bb76..03b09d2 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/CropBreakEvent.java @@ -17,11 +17,12 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -29,44 +30,86 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * An event that triggered when breaking a crop + * The CropBreakEvent class represents an event that occurs when a crop block is broken + * in the CustomCrops plugin. This event is triggered under various circumstances such + * as a player breaking the crop, trampling, explosions, or specific actions. */ public class CropBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; + private final Entity entityBreaker; + private final Block blockBreaker; private final Location location; - private final WorldCrop worldCrop; - private final Entity entity; - private final Reason reason; + private final CropConfig config; + private final String stageItemID; + private final CustomCropsBlockState blockState; + private final BreakReason reason; + /** + * Constructor for the CropBreakEvent. + * + * @param entityBreaker The entity that caused the crop to break (can be null). + * @param blockBreaker The block that caused the crop to break (can be null). + * @param config The crop configuration associated with the crop. + * @param stageItemID The item ID representing the stage of the crop. + * @param location The location of the crop block that is being broken. + * @param blockState The data of the crop block before it was broken. + * @param reason The reason for the crop break. + */ public CropBreakEvent( - @Nullable Entity entity, + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, + @NotNull CropConfig config, + @NotNull String stageItemID, @NotNull Location location, - @NotNull WorldCrop worldCrop, - @NotNull Reason reason + @NotNull CustomCropsBlockState blockState, + @NotNull BreakReason reason ) { - this.entity = entity; this.location = location; - this.worldCrop = worldCrop; + this.blockState = blockState; this.reason = reason; + this.entityBreaker = entityBreaker; + this.blockBreaker = blockBreaker; + this.config = config; + this.stageItemID = stageItemID; } + /** + * Returns whether the event is cancelled. + * + * @return true if the event is cancelled, false otherwise. + */ @Override public boolean isCancelled() { return cancelled; } + /** + * Sets the cancelled state of the event. + * + * @param cancel true to cancel the event, false otherwise. + */ @Override public void setCancelled(boolean cancel) { this.cancelled = cancel; } + /** + * Gets the list of handlers for this event. + * + * @return the static handler list. + */ @NotNull public static HandlerList getHandlerList() { return handlers; } + /** + * Gets the list of handlers for this event instance. + * + * @return the handler list. + */ @NotNull @Override public HandlerList getHandlers() { @@ -74,19 +117,9 @@ public class CropBreakEvent extends Event implements Cancellable { } /** - * Get the crop's data, it might be null if it's spawned by other plugins in the wild + * Gets the location of the crop block that is being broken. * - * @return crop data - */ - @Nullable - public WorldCrop getWorldCrop() { - return worldCrop; - } - - /** - * Get the crop location - * - * @return location + * @return the location of the broken crop block. */ @NotNull public Location getLocation() { @@ -94,33 +127,62 @@ public class CropBreakEvent extends Event implements Cancellable { } /** - * Get the entity that triggered the event - * This would be null if the crop is broken by respawn anchor - * The breaker can be a TNT, creeper. - * If the pot is a vanilla farmland, it can be trampled by entities + * Gets the block responsible for breaking the crop, if applicable. * - * @return entity + * @return the block that caused the break, or null if not applicable. */ @Nullable - public Entity getEntity() { - return entity; - } - - @Nullable - public Player getPlayer() { - if (entity instanceof Player player) { - return player; - } - return null; + public Block getBlockBreaker() { + return blockBreaker; } /** - * Get the reason + * Gets the entity responsible for breaking the crop, if applicable. * - * @return reason + * @return the entity that caused the break, or null if not applicable. + */ + @Nullable + public Entity getEntityBreaker() { + return entityBreaker; + } + + /** + * Gets the state of the crop block before it was broken. + * + * @return the block state before breakage, or null if not applicable. + */ + @Nullable + public CustomCropsBlockState getBlockState() { + return blockState; + } + + /** + * Gets the item ID representing the stage of the crop. + * + * @return the stage item ID. */ @NotNull - public Reason getReason() { + public String getStageItemID() { + return stageItemID; + } + + /** + * Gets the crop configuration associated with the broken block. + * + * @return the crop configuration. + */ + @NotNull + public CropConfig getCropConfig() { + return config; + } + + /** + * Gets the reason for the crop block breakage. + * + * @return the reason for the break. + */ + @NotNull + public BreakReason getReason() { return reason; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/CropInteractEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/CropInteractEvent.java index 8e55b85..8f89ee3 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/CropInteractEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/CropInteractEvent.java @@ -17,15 +17,16 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * An event that triggered when a player interacts a crop @@ -35,19 +36,28 @@ public class CropInteractEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final WorldCrop crop; + private final CustomCropsBlockState blockState; private final ItemStack itemInHand; + private final EquipmentSlot hand; + private final CropConfig config; + private final String stageItemID; public CropInteractEvent( @NotNull Player who, @NotNull ItemStack itemInHand, @NotNull Location location, - @Nullable WorldCrop crop + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot hand, + @NotNull CropConfig config, + @NotNull String stageItemID ) { super(who); this.location = location; - this.crop = crop; + this.blockState = blockState; this.itemInHand = itemInHand; + this.hand = hand; + this.config = config; + this.stageItemID = stageItemID; } @Override @@ -92,13 +102,23 @@ public class CropInteractEvent extends PlayerEvent implements Cancellable { return itemInHand; } - /** - * Get the crop's data, it might be null if it's spawned by other plugins in the wild - * - * @return crop data - */ - @Nullable - public WorldCrop getCrop() { - return crop; + @NotNull + public CropConfig getCropConfig() { + return config; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public String getStageItemID() { + return stageItemID; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/CropPlantEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/CropPlantEvent.java index 3b04f9d..21d15d6 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/CropPlantEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/CropPlantEvent.java @@ -17,12 +17,14 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.Crop; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -34,22 +36,28 @@ public class CropPlantEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final ItemStack itemInHand; - private final Crop crop; + private final CropConfig config; private final Location location; + private final CustomCropsBlockState blockState; + private final EquipmentSlot hand; private int point; public CropPlantEvent( @NotNull Player who, @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand, @NotNull Location location, - @NotNull Crop crop, + @NotNull CropConfig config, + @NotNull CustomCropsBlockState blockState, int point ) { super(who); this.itemInHand = itemInHand; + this.hand = hand; this.location = location; - this.crop = crop; + this.config = config; this.point = point; + this.blockState = blockState; } @Override @@ -83,14 +91,24 @@ public class CropPlantEvent extends PlayerEvent implements Cancellable { return getHandlerList(); } + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + /** * Get the crop's config * * @return crop */ @NotNull - public Crop getCrop() { - return crop; + public CropConfig getCropConfig() { + return config; } /** diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/CustomCropsReloadEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/CustomCropsReloadEvent.java index 4d939cf..8aa810b 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/CustomCropsReloadEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/CustomCropsReloadEvent.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 @@ -17,7 +17,7 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.CustomCropsPlugin; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; @@ -25,9 +25,9 @@ import org.jetbrains.annotations.NotNull; public class CustomCropsReloadEvent extends Event { private static final HandlerList handlerList = new HandlerList(); - private final CustomCropsPlugin plugin; + private final BukkitCustomCropsPlugin plugin; - public CustomCropsReloadEvent(CustomCropsPlugin plugin) { + public CustomCropsReloadEvent(BukkitCustomCropsPlugin plugin) { this.plugin = plugin; } @@ -41,7 +41,7 @@ public class CustomCropsReloadEvent extends Event { return getHandlerList(); } - public CustomCropsPlugin getPluginInstance() { + public BukkitCustomCropsPlugin getPluginInstance() { return plugin; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/FertilizerUseEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/FertilizerUseEvent.java index 4376d9c..85a276c 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/FertilizerUseEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/FertilizerUseEvent.java @@ -17,13 +17,15 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.item.Fertilizer; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -36,22 +38,28 @@ public class FertilizerUseEvent extends PlayerEvent implements Cancellable { private boolean cancelled; private final ItemStack itemInHand; private final Location location; - private final WorldPot pot; + private final CustomCropsBlockState blockState; private final Fertilizer fertilizer; + private final EquipmentSlot hand; + private final PotConfig config; public FertilizerUseEvent( @NotNull Player player, @NotNull ItemStack itemInHand, @NotNull Fertilizer fertilizer, @NotNull Location location, - @NotNull WorldPot pot + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot hand, + @NotNull PotConfig config ) { super(player); this.cancelled = false; this.itemInHand = itemInHand; this.fertilizer = fertilizer; this.location = location; - this.pot = pot; + this.blockState = blockState; + this.hand = hand; + this.config = config; } @Override @@ -94,14 +102,19 @@ public class FertilizerUseEvent extends PlayerEvent implements Cancellable { return location; } - /** - * Get the pot's data - * - * @return pot data - */ @NotNull - public WorldPot getPot() { - return pot; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public PotConfig getPotConfig() { + return config; } /** diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassBreakEvent.java index 9a5c8bf..b898716 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassBreakEvent.java @@ -17,11 +17,11 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.world.level.WorldGlass; +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -36,20 +36,26 @@ public class GreenhouseGlassBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final Entity entity; - private final Reason reason; - private final WorldGlass glass; + private final Entity entityBreaker; + private final Block blockBreaker; + private final BreakReason reason; + private final CustomCropsBlockState blockState; + private final String glassItemID; public GreenhouseGlassBreakEvent( - @Nullable Entity entity, + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, @NotNull Location location, - @NotNull WorldGlass glass, - @NotNull Reason reason + @NotNull String glassItemID, + @NotNull CustomCropsBlockState blockState, + @NotNull BreakReason reason ) { - this.entity = entity; + this.entityBreaker = entityBreaker; + this.blockBreaker = blockBreaker; this.location = location; this.reason = reason; - this.glass = glass; + this.blockState = blockState; + this.glassItemID = glassItemID; } @Override @@ -84,30 +90,26 @@ public class GreenhouseGlassBreakEvent extends Event implements Cancellable { } @Nullable - public Entity getEntity() { - return entity; + public Entity getEntityBreaker() { + return entityBreaker; } @Nullable - public Player getPlayer() { - if (entity instanceof Player player) { - return player; - } - return null; + public Block getBlockBreaker() { + return blockBreaker; } @NotNull - public Reason getReason() { + public BreakReason getReason() { return reason; } - /** - * Get the glass data - * - * @return glass data - */ @NotNull - public WorldGlass getGlass() { - return glass; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + public String getGlassItemID() { + return glassItemID; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassInteractEvent.java similarity index 57% rename from api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java rename to api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassInteractEvent.java index ed4ed54..df0a46b 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassInteractEvent.java @@ -17,41 +17,43 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.BoneMeal; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; -import org.bukkit.block.Block; +import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** - * An event that triggered when the bone meal is dispensed + * An event that triggered when interacting a sprinkler */ -public class BoneMealDispenseEvent extends Event implements Cancellable { +public class GreenhouseGlassInteractEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final BoneMeal boneMeal; - private final WorldCrop crop; - private final ItemStack boneMealItem; - private final Block dispenser; + private final CustomCropsBlockState blockState; + private final ItemStack itemInHand; + private final String glassItemID; + private final EquipmentSlot hand; - public BoneMealDispenseEvent( - @NotNull Block dispenser, - @NotNull ItemStack boneMealItem, + public GreenhouseGlassInteractEvent( + @NotNull Player who, + @NotNull ItemStack itemInHand, @NotNull Location location, - @NotNull BoneMeal boneMeal, - @NotNull WorldCrop crop + @NotNull String glassItemID, + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot hand ) { + super(who); this.location = location; - this.crop = crop; - this.boneMeal = boneMeal; - this.boneMealItem = boneMealItem; - this.dispenser = dispenser; + this.itemInHand = itemInHand; + this.hand = hand; + this.blockState = blockState; + this.glassItemID = glassItemID; } @Override @@ -76,7 +78,8 @@ public class BoneMealDispenseEvent extends Event implements Cancellable { } /** - * Get the crop location + * Get the sprinkler location + * * @return location */ @NotNull @@ -84,43 +87,28 @@ public class BoneMealDispenseEvent extends Event implements Cancellable { return location; } + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public String getGlassItemID() { + return glassItemID; + } + /** * Get the item in player's hand - * If there's nothing in hand, it would return AIR + * * @return item in hand */ @NotNull - public ItemStack getBoneMealItem() { - return boneMealItem; - } - - /** - * Get the crop's data - * - * @return crop data - */ - @NotNull - public WorldCrop getCrop() { - return crop; - } - - /** - * Get the bone meal's config - * - * @return bone meal config - */ - @NotNull - public BoneMeal getBoneMeal() { - return boneMeal; - } - - /** - * Get the dispenser block - * - * @return dispenser block - */ - @NotNull - public Block getDispenser() { - return dispenser; + public ItemStack getItemInHand() { + return itemInHand; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassPlaceEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassPlaceEvent.java index f161335..a5cfc21 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassPlaceEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/GreenhouseGlassPlaceEvent.java @@ -17,6 +17,7 @@ package net.momirealms.customcrops.api.event; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -32,13 +33,19 @@ public class GreenhouseGlassPlaceEvent extends PlayerEvent implements Cancellabl private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; + private final CustomCropsBlockState blockState; + private final String glassItemID; public GreenhouseGlassPlaceEvent( @NotNull Player who, - @NotNull Location location + @NotNull Location location, + @NotNull String glassItemID, + @NotNull CustomCropsBlockState blockState ) { super(who); this.location = location; + this.glassItemID = glassItemID; + this.blockState = blockState; } @Override @@ -71,4 +78,14 @@ public class GreenhouseGlassPlaceEvent extends PlayerEvent implements Cancellabl public Location getLocation() { return location; } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public String getGlassItemID() { + return glassItemID; + } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/PotBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/PotBreakEvent.java index a5cbb08..d443f4e 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/PotBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/PotBreakEvent.java @@ -17,9 +17,11 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -36,20 +38,26 @@ public class PotBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final WorldPot pot; - private final Entity entity; - private final Reason reason; + private final PotConfig config; + private final CustomCropsBlockState blockState; + private final Entity entityBreaker; + private final Block blockBreaker; + private final BreakReason reason; public PotBreakEvent( - @Nullable Entity entity, + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, @NotNull Location location, - @NotNull WorldPot pot, - @NotNull Reason reason + @NotNull PotConfig config, + @NotNull CustomCropsBlockState blockState, + @NotNull BreakReason reason ) { - this.entity = entity; + this.entityBreaker = entityBreaker; + this.blockBreaker = blockBreaker; this.location = location; - this.pot = pot; + this.blockState = blockState; this.reason = reason; + this.config = config; } @Override @@ -83,32 +91,36 @@ public class PotBreakEvent extends Event implements Cancellable { return location; } - - /** - * Get the pot's data - * - * @return pot - */ - @NotNull - public WorldPot getPot() { - return pot; - } - @Nullable - public Entity getEntity() { - return entity; + public Entity getEntityBreaker() { + return entityBreaker; } @Nullable public Player getPlayer() { - if (entity instanceof Player player) { + if (entityBreaker instanceof Player player) { return player; } return null; } @NotNull - public Reason getReason() { + public BreakReason getReason() { return reason; } + + @NotNull + public PotConfig getPotConfig() { + return config; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @Nullable + public Block getBlockBreaker() { + return blockBreaker; + } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/PotFillEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/PotFillEvent.java index 7a947e7..2b73233 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/PotFillEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/PotFillEvent.java @@ -17,13 +17,15 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -35,22 +37,28 @@ public class PotFillEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final WorldPot pot; + private final PotConfig config; private final ItemStack itemInHand; - private final PassiveFillMethod fillMethod; + private final WateringMethod wateringMethod; + private final CustomCropsBlockState blockState; + private final EquipmentSlot hand; public PotFillEvent( @NotNull Player player, @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand, @NotNull Location location, - @NotNull PassiveFillMethod fillMethod, - @NotNull WorldPot pot + @NotNull WateringMethod wateringMethod, + @NotNull CustomCropsBlockState blockState, + @NotNull PotConfig config ) { super(player); this.location = location; this.itemInHand = itemInHand; - this.pot = pot; - this.fillMethod = fillMethod; + this.wateringMethod = wateringMethod; + this.blockState = blockState; + this.config = config; + this.hand = hand; } @Override @@ -84,17 +92,6 @@ public class PotFillEvent extends PlayerEvent implements Cancellable { return location; } - - /** - * Get the pot's data - * - * @return pot - */ - @NotNull - public WorldPot getPot() { - return pot; - } - /** * Get the item in hand * @@ -105,13 +102,23 @@ public class PotFillEvent extends PlayerEvent implements Cancellable { return itemInHand; } - /** - * Get the passive fill method - * - * @return passive fill method - */ @NotNull - public PassiveFillMethod getFillMethod() { - return fillMethod; + public PotConfig getPotConfig() { + return config; + } + + @NotNull + public WateringMethod getWateringMethod() { + return wateringMethod; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/PotInteractEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/PotInteractEvent.java index f1f5c65..fc59d6c 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/PotInteractEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/PotInteractEvent.java @@ -17,12 +17,14 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -35,18 +37,24 @@ public class PotInteractEvent extends PlayerEvent implements Cancellable { private boolean cancelled; private final ItemStack itemInHand; private final Location location; - private final WorldPot pot; + private final CustomCropsBlockState blockState; + private final PotConfig config; + private final EquipmentSlot hand; public PotInteractEvent( @NotNull Player who, + @NotNull EquipmentSlot hand, @NotNull ItemStack itemInHand, + @NotNull PotConfig config, @NotNull Location location, - @NotNull WorldPot pot + @NotNull CustomCropsBlockState blockState ) { super(who); this.itemInHand = itemInHand; this.location = location; - this.pot = pot; + this.blockState = blockState; + this.config = config; + this.hand = hand; } @Override @@ -81,6 +89,11 @@ public class PotInteractEvent extends PlayerEvent implements Cancellable { return itemInHand; } + @NotNull + public EquipmentSlot getHand() { + return hand; + } + /** * Get the pot location * @@ -97,7 +110,12 @@ public class PotInteractEvent extends PlayerEvent implements Cancellable { * @return pot key */ @NotNull - public WorldPot getPot() { - return pot; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public PotConfig getPotConfig() { + return config; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/PotPlaceEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/PotPlaceEvent.java index 4f8570f..23e2f94 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/PotPlaceEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/PotPlaceEvent.java @@ -17,12 +17,15 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.Pot; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** @@ -33,16 +36,25 @@ public class PotPlaceEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final Pot pot; + private final PotConfig config; + private final CustomCropsBlockState state; + private final ItemStack itemInHand; + private final EquipmentSlot hand; public PotPlaceEvent( @NotNull Player who, @NotNull Location location, - @NotNull Pot pot + @NotNull PotConfig config, + @NotNull CustomCropsBlockState state, + @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand ) { super(who); this.location = location; - this.pot = pot; + this.state = state; + this.itemInHand = itemInHand; + this.hand = hand; + this.config = config; } @Override @@ -76,13 +88,23 @@ public class PotPlaceEvent extends PlayerEvent implements Cancellable { return location; } - /** - * Get the placed pot's config - * - * @return pot - */ @NotNull - public Pot getPot() { - return pot; + public CustomCropsBlockState getState() { + return state; + } + + @NotNull + public ItemStack getItemInHand() { + return itemInHand; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public PotConfig getPotConfig() { + return config; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowBreakEvent.java index d9a2a00..65b8ac1 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowBreakEvent.java @@ -17,11 +17,11 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.world.level.WorldScarecrow; +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -36,20 +36,26 @@ public class ScarecrowBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final Entity entity; - private final Reason reason; - private final WorldScarecrow scarecrow; + private final Entity entityBreaker; + private final Block blockBreaker; + private final BreakReason reason; + private final CustomCropsBlockState blockState; + private final String scarecrowItemID; public ScarecrowBreakEvent( - @Nullable Entity entity, + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, @NotNull Location location, - @NotNull WorldScarecrow scarecrow, - @NotNull Reason reason + @NotNull String scarecrowItemID, + @NotNull CustomCropsBlockState blockState, + @NotNull BreakReason reason ) { this.location = location; + this.entityBreaker = entityBreaker; + this.blockBreaker = blockBreaker; this.reason = reason; - this.entity = entity; - this.scarecrow = scarecrow; + this.blockState = blockState; + this.scarecrowItemID = scarecrowItemID; } @Override @@ -84,25 +90,26 @@ public class ScarecrowBreakEvent extends Event implements Cancellable { } @Nullable - public Entity getEntity() { - return entity; + public Entity getEntityBreaker() { + return entityBreaker; } @Nullable - public Player getPlayer() { - if (entity instanceof Player player) { - return player; - } - return null; + public Block getBlockBreaker() { + return blockBreaker; } @NotNull - public Reason getReason() { + public BreakReason getReason() { return reason; } @NotNull - public WorldScarecrow getScarecrow() { - return scarecrow; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + public String getScarecrowItemID() { + return scarecrowItemID; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowInteractEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowInteractEvent.java new file mode 100644 index 0000000..c84cdde --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowInteractEvent.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for 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.event; + +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An event that triggered when interacting a sprinkler + */ +public class ScarecrowInteractEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Location location; + private final CustomCropsBlockState blockState; + private final ItemStack itemInHand; + private final String scarecrowItemID; + private final EquipmentSlot hand; + + public ScarecrowInteractEvent( + @NotNull Player who, + @NotNull ItemStack itemInHand, + @NotNull Location location, + @NotNull String scarecrowItemID, + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot hand + ) { + super(who); + this.location = location; + this.itemInHand = itemInHand; + this.hand = hand; + this.blockState = blockState; + this.scarecrowItemID = scarecrowItemID; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } + + /** + * Get the sprinkler location + * + * @return location + */ + @NotNull + public Location getLocation() { + return location; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public String getScarecrowItemID() { + return scarecrowItemID; + } + + /** + * Get the item in player's hand + * + * @return item in hand + */ + @NotNull + public ItemStack getItemInHand() { + return itemInHand; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowPlaceEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowPlaceEvent.java index 981aa8b..c55657d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowPlaceEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/ScarecrowPlaceEvent.java @@ -17,6 +17,7 @@ package net.momirealms.customcrops.api.event; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -32,13 +33,19 @@ public class ScarecrowPlaceEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; + private final String scarecrowItemID; + private final CustomCropsBlockState blockState; public ScarecrowPlaceEvent( @NotNull Player who, - @NotNull Location location + @NotNull Location location, + @NotNull String scarecrowItemID, + @NotNull CustomCropsBlockState blockState ) { super(who); this.location = location; + this.blockState = blockState; + this.scarecrowItemID = scarecrowItemID; } @Override @@ -62,6 +69,11 @@ public class ScarecrowPlaceEvent extends PlayerEvent implements Cancellable { return getHandlerList(); } + @NotNull + public String getScarecrowItemID() { + return scarecrowItemID; + } + /** * Get the scarecrow location * @@ -71,4 +83,9 @@ public class ScarecrowPlaceEvent extends PlayerEvent implements Cancellable { public Location getLocation() { return location; } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/SeasonChangeEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/SeasonChangeEvent.java deleted file mode 100644 index 6a67aa1..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/event/SeasonChangeEvent.java +++ /dev/null @@ -1,73 +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.event; - -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import org.bukkit.World; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; - -/** - * An async event triggered when season changes - */ -public class SeasonChangeEvent extends Event { - - private static final HandlerList handlers = new HandlerList(); - private final Season season; - private final World world; - - public SeasonChangeEvent( - @NotNull World world, - @NotNull Season season - ) { - super(true); - this.world = world; - this.season = season; - } - - @NotNull - public static HandlerList getHandlerList() { - return handlers; - } - - @NotNull - @Override - public HandlerList getHandlers() { - return getHandlerList(); - } - - /** - * Get the new season - * - * @return season - */ - @NotNull - public Season getSeason() { - return season; - } - - /** - * Get the world - * - * @return world - */ - public World getWorld() { - return world; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerBreakEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerBreakEvent.java index 34c797c..dfa508d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerBreakEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerBreakEvent.java @@ -17,11 +17,12 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSprinkler; +import net.momirealms.customcrops.api.core.block.BreakReason; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -36,20 +37,26 @@ public class SprinklerBreakEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final WorldSprinkler sprinkler; - private final Entity entity; - private final Reason reason; + private final CustomCropsBlockState blockState; + private final SprinklerConfig config; + private final Entity entityBreaker; + private final Block blockBreaker; + private final BreakReason reason; public SprinklerBreakEvent( - @Nullable Entity entity, + @Nullable Entity entityBreaker, + @Nullable Block blockBreaker, @NotNull Location location, - @NotNull WorldSprinkler sprinkler, - @NotNull Reason reason + @NotNull CustomCropsBlockState blockState, + @NotNull SprinklerConfig config, + @NotNull BreakReason reason ) { - this.entity = entity; + this.entityBreaker = entityBreaker; + this.blockBreaker = blockBreaker; this.location = location; this.reason = reason; - this.sprinkler = sprinkler; + this.config = config; + this.blockState = blockState; } @Override @@ -83,31 +90,28 @@ public class SprinklerBreakEvent extends Event implements Cancellable { return location; } - /** - * Get the sprinkler's data - * - * @return sprinkler - */ - @NotNull - public WorldSprinkler getSprinkler() { - return sprinkler; - } - @Nullable - public Entity getEntity() { - return entity; - } - - @Nullable - public Player getPlayer() { - if (entity instanceof Player player) { - return player; - } - return null; + public Entity getEntityBreaker() { + return entityBreaker; } @NotNull - public Reason getReason() { + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public SprinklerConfig getSprinklerConfig() { + return config; + } + + @Nullable + public Block getBlockBreaker() { + return blockBreaker; + } + + @NotNull + public BreakReason getReason() { return reason; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerFillEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerFillEvent.java index 32a26a2..ae59f85 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerFillEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerFillEvent.java @@ -17,40 +17,48 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSprinkler; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.water.WateringMethod; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; /** - * An event that triggered when a sprinkler is watered by the fill-methods set in each sprinkler's config + * An event that triggered when a pot is watered by the fill-methods set in each sprinkler's config */ public class SprinklerFillEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; - private final ItemStack itemInHand; private final Location location; - private final PassiveFillMethod fillMethod; - private final WorldSprinkler sprinkler; + private final SprinklerConfig config; + private final ItemStack itemInHand; + private final WateringMethod wateringMethod; + private final CustomCropsBlockState blockState; + private final EquipmentSlot hand; public SprinklerFillEvent( @NotNull Player player, @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand, @NotNull Location location, - @NotNull PassiveFillMethod fillMethod, - @NotNull WorldSprinkler sprinkler + @NotNull WateringMethod wateringMethod, + @NotNull CustomCropsBlockState blockState, + @NotNull SprinklerConfig config ) { super(player); - this.itemInHand = itemInHand; this.location = location; - this.fillMethod = fillMethod; - this.sprinkler = sprinkler; + this.itemInHand = itemInHand; + this.wateringMethod = wateringMethod; + this.blockState = blockState; + this.config = config; + this.hand = hand; } @Override @@ -60,7 +68,7 @@ public class SprinklerFillEvent extends PlayerEvent implements Cancellable { @Override public void setCancelled(boolean cancel) { - this.cancelled = cancel; + cancelled = cancel; } @NotNull @@ -75,17 +83,7 @@ public class SprinklerFillEvent extends PlayerEvent implements Cancellable { } /** - * Get the item in player's hand - * - * @return item in hand - */ - @NotNull - public ItemStack getItemInHand() { - return itemInHand; - } - - /** - * Get the sprinkler location + * Get the pot location * * @return location */ @@ -95,17 +93,32 @@ public class SprinklerFillEvent extends PlayerEvent implements Cancellable { } /** - * Get the passive fill method + * Get the item in hand * - * @return passive fill method + * @return item in hand */ @NotNull - public PassiveFillMethod getFillMethod() { - return fillMethod; + public ItemStack getItemInHand() { + return itemInHand; } @NotNull - public WorldSprinkler getSprinkler() { - return sprinkler; + public SprinklerConfig getSprinklerConfig() { + return config; } -} + + @NotNull + public WateringMethod getWateringMethod() { + return wateringMethod; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerInteractEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerInteractEvent.java index d671fe3..7aaf4b2 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerInteractEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerInteractEvent.java @@ -17,12 +17,14 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSprinkler; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -34,19 +36,25 @@ public class SprinklerInteractEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final Location location; - private final WorldSprinkler sprinkler; + private final CustomCropsBlockState blockState; + private final SprinklerConfig config; private final ItemStack itemInHand; + private final EquipmentSlot hand; public SprinklerInteractEvent( @NotNull Player who, @NotNull ItemStack itemInHand, @NotNull Location location, - @NotNull WorldSprinkler sprinkler + @NotNull SprinklerConfig config, + @NotNull CustomCropsBlockState blockState, + @NotNull EquipmentSlot hand ) { super(who); this.location = location; - this.sprinkler = sprinkler; + this.config = config; this.itemInHand = itemInHand; + this.hand = hand; + this.blockState = blockState; } @Override @@ -80,14 +88,19 @@ public class SprinklerInteractEvent extends PlayerEvent implements Cancellable { return location; } - /** - * Get the sprinkler's data - * - * @return sprinkler - */ @NotNull - public WorldSprinkler getSprinkler() { - return sprinkler; + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public SprinklerConfig getSprinklerConfig() { + return config; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; } /** diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerPlaceEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerPlaceEvent.java index d1469d8..e2b1494 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerPlaceEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/SprinklerPlaceEvent.java @@ -17,12 +17,14 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -35,18 +37,24 @@ public class SprinklerPlaceEvent extends PlayerEvent implements Cancellable { private boolean cancelled; private final ItemStack itemInHand; private final Location location; - private final Sprinkler sprinkler; + private final SprinklerConfig config; + private final CustomCropsBlockState blockState; + private final EquipmentSlot hand; public SprinklerPlaceEvent( @NotNull Player who, @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand, @NotNull Location location, - @NotNull Sprinkler sprinkler + @NotNull SprinklerConfig config, + CustomCropsBlockState blockState ) { super(who); this.itemInHand = itemInHand; this.location = location; - this.sprinkler = sprinkler; + this.config = config; + this.hand = hand; + this.blockState = blockState; } @Override @@ -80,6 +88,11 @@ public class SprinklerPlaceEvent extends PlayerEvent implements Cancellable { return itemInHand; } + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + /** * Get the sprinkler location * @@ -90,13 +103,13 @@ public class SprinklerPlaceEvent extends PlayerEvent implements Cancellable { return location; } - /** - * Get the sprinkler's config - * - * @return sprinkler - */ @NotNull - public Sprinkler getSprinkler() { - return sprinkler; + public SprinklerConfig getSprinklerConfig() { + return config; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; } } \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanFillEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanFillEvent.java index 9c4e3a3..05b930a 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanFillEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanFillEvent.java @@ -17,13 +17,14 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.WateringCan; -import net.momirealms.customcrops.api.mechanic.item.water.PositiveFillMethod; +import net.momirealms.customcrops.api.core.item.WateringCanConfig; +import net.momirealms.customcrops.api.core.water.FillMethod; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -35,23 +36,26 @@ public class WateringCanFillEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final ItemStack itemInHand; - private final WateringCan wateringCan; - private final PositiveFillMethod fillMethod; + private final WateringCanConfig config; + private final FillMethod fillMethod; private final Location location; + private final EquipmentSlot hand; public WateringCanFillEvent( @NotNull Player player, + @NotNull EquipmentSlot hand, @NotNull ItemStack itemInHand, @NotNull Location location, - @NotNull WateringCan wateringCan, - @NotNull PositiveFillMethod fillMethod + @NotNull WateringCanConfig config, + @NotNull FillMethod fillMethod ) { super(player); this.cancelled = false; this.itemInHand = itemInHand; - this.wateringCan = wateringCan; + this.config = config; this.location = location; this.fillMethod = fillMethod; + this.hand = hand; } @Override @@ -84,14 +88,9 @@ public class WateringCanFillEvent extends PlayerEvent implements Cancellable { return itemInHand; } - /** - * Get watering can config - * - * @return watering can config - */ @NotNull - public WateringCan getWateringCan() { - return wateringCan; + public WateringCanConfig getConfig() { + return config; } /** @@ -100,10 +99,15 @@ public class WateringCanFillEvent extends PlayerEvent implements Cancellable { * @return positive fill method */ @NotNull - public PositiveFillMethod getFillMethod() { + public FillMethod getFillMethod() { return fillMethod; } + @NotNull + public EquipmentSlot getHand() { + return hand; + } + /** * Get the location * diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterPotEvent.java similarity index 62% rename from api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterEvent.java rename to api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterPotEvent.java index 20efead..e1e45f2 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterEvent.java +++ b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterPotEvent.java @@ -17,43 +17,48 @@ package net.momirealms.customcrops.api.event; -import net.momirealms.customcrops.api.mechanic.item.WateringCan; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import org.bukkit.Location; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.item.WateringCanConfig; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.common.util.Pair; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.Set; +import java.util.List; /** * An event that triggered when player tries to use watering-can to add water to pots/sprinklers */ -public class WateringCanWaterEvent extends PlayerEvent implements Cancellable { +public class WateringCanWaterPotEvent extends PlayerEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); private boolean cancelled; private final ItemStack itemInHand; - private final WateringCan wateringCan; - private final CustomCropsBlock potOrSprinkler; - private final Set location; + private final EquipmentSlot hand; + private final WateringCanConfig wateringCanConfig; + private final PotConfig potConfig; + private final List> potWithIDs; - public WateringCanWaterEvent( + public WateringCanWaterPotEvent( @NotNull Player player, @NotNull ItemStack itemInHand, - @NotNull Set location, - @NotNull WateringCan wateringCan, - @NotNull CustomCropsBlock potOrSprinkler + @NotNull EquipmentSlot hand, + @NotNull WateringCanConfig wateringCanConfig, + @NotNull PotConfig potConfig, + List> potWithIDs ) { super(player); this.cancelled = false; this.itemInHand = itemInHand; - this.wateringCan = wateringCan; - this.location = location; - this.potOrSprinkler = potOrSprinkler; + this.hand = hand; + this.wateringCanConfig = wateringCanConfig; + this.potConfig = potConfig; + this.potWithIDs = potWithIDs; } @Override @@ -86,33 +91,23 @@ public class WateringCanWaterEvent extends PlayerEvent implements Cancellable { return itemInHand; } - /** - * Get the watering can's config - * - * @return watering can config - */ @NotNull - public WateringCan getWateringCan() { - return wateringCan; + public EquipmentSlot getHand() { + return hand; } - /** - * Get the locations that involved in this event - * - * @return locations - */ @NotNull - public Set getLocation() { - return location; + public WateringCanConfig getWateringCanConfig() { + return wateringCanConfig; } - /** - * Get the pot/sprinkler's data - * - * @return data - */ @NotNull - public CustomCropsBlock getPotOrSprinkler() { - return potOrSprinkler; + public PotConfig getPotConfig() { + return potConfig; + } + + @NotNull + public List> getPotWithIDs() { + return potWithIDs; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterSprinklerEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterSprinklerEvent.java new file mode 100644 index 0000000..5d31bbf --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/event/WateringCanWaterSprinklerEvent.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.event; + +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.item.WateringCanConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An event that triggered when player tries to use watering-can to add water to pots/sprinklers + */ +public class WateringCanWaterSprinklerEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final ItemStack itemInHand; + private final EquipmentSlot hand; + private final WateringCanConfig wateringCanConfig; + private final SprinklerConfig sprinklerConfig; + private final CustomCropsBlockState blockState; + private final Location location; + + public WateringCanWaterSprinklerEvent( + @NotNull Player player, + @NotNull ItemStack itemInHand, + @NotNull EquipmentSlot hand, + @NotNull WateringCanConfig wateringCanConfig, + @NotNull SprinklerConfig sprinklerConfig, + @NotNull CustomCropsBlockState blockState, + @NotNull Location location + ) { + super(player); + this.cancelled = false; + this.itemInHand = itemInHand; + this.hand = hand; + this.wateringCanConfig = wateringCanConfig; + this.sprinklerConfig = sprinklerConfig; + this.blockState = blockState; + this.location = location; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + @Override + public @NotNull HandlerList getHandlers() { + return handlers; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Get the watering can item + * + * @return watering can item + */ + @NotNull + public ItemStack getItemInHand() { + return itemInHand; + } + + @NotNull + public EquipmentSlot getHand() { + return hand; + } + + @NotNull + public WateringCanConfig getWateringCanConfig() { + return wateringCanConfig; + } + + @NotNull + public SprinklerConfig getSprinklerConfig() { + return sprinklerConfig; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + @NotNull + public Location getLocation() { + return location; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionFactory.java b/api/src/main/java/net/momirealms/customcrops/api/integration/ExternalProvider.java similarity index 64% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionFactory.java rename to api/src/main/java/net/momirealms/customcrops/api/integration/ExternalProvider.java index 47389e4..67a8688 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionFactory.java +++ b/api/src/main/java/net/momirealms/customcrops/api/integration/ExternalProvider.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,17 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.action; +package net.momirealms.customcrops.api.integration; -public interface ActionFactory { +/** + * The ExternalProvider interface serves as a base interface for various external providers + */ +public interface ExternalProvider { /** - * Build an action by args and chance + * Gets the identification of the external provider. * - * @param args args - * @param chance chance (0-1) - * @return action + * @return The identification string of the external provider. */ - Action build(Object args, double chance); + String identifier(); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/integration/IntegrationManager.java b/api/src/main/java/net/momirealms/customcrops/api/integration/IntegrationManager.java new file mode 100644 index 0000000..6605814 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/integration/IntegrationManager.java @@ -0,0 +1,78 @@ +/* + * 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.integration; + +import net.momirealms.customcrops.common.plugin.feature.Reloadable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface for managing integration providers. + * This allows for the registration and retrieval of various types of providers + * such as Leveler, Enchantment, and Season providers. + */ +public interface IntegrationManager extends Reloadable { + + /** + * Registers a LevelerProvider. + * + * @param levelerProvider the LevelerProvider to register + * @return true if registration is successful, false otherwise + */ + boolean registerLevelerProvider(@NotNull LevelerProvider levelerProvider); + + /** + * Unregisters a LevelerProvider by its ID. + * + * @param id the ID of the LevelerProvider to unregister + * @return true if unregistration is successful, false otherwise + */ + boolean unregisterLevelerProvider(@NotNull String id); + + /** + * Registers a SeasonProvider. + * + * @param seasonProvider the SeasonProvider to register + */ + void registerSeasonProvider(@NotNull SeasonProvider seasonProvider); + + /** + * Retrieves a registered LevelerProvider by its ID. + * + * @param id the ID of the LevelerProvider to retrieve + * @return the LevelerProvider if found, or null if not found + */ + @Nullable + LevelerProvider getLevelerProvider(String id); + + /** + * Registers an ItemProvider. + * + * @param itemProvider the ItemProvider to register + * @return true if registration is successful, false otherwise. + */ + boolean registerItemProvider(@NotNull ItemProvider itemProvider); + + /** + * Unregisters an ItemProvider by its ID. + * + * @param id the ID of the ItemProvider to unregister + * @return true if unregistration is successful, false otherwise. + */ + boolean unregisterItemProvider(@NotNull String id); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/integration/ItemLibrary.java b/api/src/main/java/net/momirealms/customcrops/api/integration/ItemLibrary.java deleted file mode 100644 index 66a49b0..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/integration/ItemLibrary.java +++ /dev/null @@ -1,49 +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.integration; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -public interface ItemLibrary { - - /** - * Get the identification - * for instance "CustomItems" - * - * @return identification - */ - String identification(); - - /** - * Build an item instance for a player - * - * @param player player - * @param id id - * @return item - */ - ItemStack buildItem(Player player, String id); - - /** - * Get an item's id - * - * @param itemStack item - * @return ID - */ - String getItemID(ItemStack itemStack); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/integration/ItemProvider.java b/api/src/main/java/net/momirealms/customcrops/api/integration/ItemProvider.java new file mode 100644 index 0000000..910e5a8 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/integration/ItemProvider.java @@ -0,0 +1,49 @@ +/* + * 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.integration; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface representing a provider for custom items. + * This interface allows for building items for players and retrieving item IDs from item stacks. + */ +public interface ItemProvider extends ExternalProvider { + + /** + * Builds an ItemStack for a player based on a specified item ID. + * + * @param player the player for whom the item is being built. + * @param id the ID of the item to build. + * @return the built ItemStack. + */ + @NotNull + ItemStack buildItem(@NotNull Player player, @NotNull String id); + + /** + * Retrieves the item ID from a given ItemStack. + * + * @param itemStack the ItemStack from which to retrieve the item ID. + * @return the item ID as a string, or null if the item stack does not have an associated ID. + */ + @Nullable + String itemID(@NotNull ItemStack itemStack); +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/integration/LevelInterface.java b/api/src/main/java/net/momirealms/customcrops/api/integration/LevelerProvider.java similarity index 64% rename from api/src/main/java/net/momirealms/customcrops/api/integration/LevelInterface.java rename to api/src/main/java/net/momirealms/customcrops/api/integration/LevelerProvider.java index b347613..b9d1e90 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/integration/LevelInterface.java +++ b/api/src/main/java/net/momirealms/customcrops/api/integration/LevelerProvider.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 @@ -18,8 +18,15 @@ package net.momirealms.customcrops.api.integration; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public interface LevelInterface { +/** + * The LevelerProvider interface defines methods to interact with external leveling + * systems, allowing the management of experience points (XP) and levels for various + * skills or jobs. Implementations of this interface should provide the logic for + * adding XP to players and retrieving their levels in specific skills or jobs. + */ +public interface LevelerProvider extends ExternalProvider { /** * Add exp to a certain skill or job @@ -28,7 +35,7 @@ public interface LevelInterface { * @param target the skill or job, for instance "Fishing" "fisherman" * @param amount the exp amount */ - void addXp(Player player, String target, double amount); + void addXp(@NotNull Player player, @NotNull String target, double amount); /** * Get a player's skill or job's level @@ -37,5 +44,5 @@ public interface LevelInterface { * @param target the skill or job, for instance "Fishing" "fisherman" * @return level */ - int getLevel(Player player, String target); + int getLevel(@NotNull Player player, @NotNull String target); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/integration/SeasonInterface.java b/api/src/main/java/net/momirealms/customcrops/api/integration/SeasonProvider.java similarity index 53% rename from api/src/main/java/net/momirealms/customcrops/api/integration/SeasonInterface.java rename to api/src/main/java/net/momirealms/customcrops/api/integration/SeasonProvider.java index 9522902..a698a47 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/integration/SeasonInterface.java +++ b/api/src/main/java/net/momirealms/customcrops/api/integration/SeasonProvider.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 @@ -17,41 +17,24 @@ package net.momirealms.customcrops.api.integration; -import net.momirealms.customcrops.api.mechanic.world.season.Season; +import net.momirealms.customcrops.api.core.world.Season; import org.bukkit.World; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; -public interface SeasonInterface { +/** + * The SeasonProvider interface defines methods to interact with external seasonal + * systems, allowing the retrieval of the current season for a specific world. + * Implementations of this interface should provide the logic for determining the + * season based on the world context. + */ +public interface SeasonProvider extends ExternalProvider { /** * Get a world's season * * @param world world - * @return spring, summer, autumn, winter or null + * @return spring, summer, autumn, winter or disabled */ - @Nullable Season getSeason(World world); - - /** - * Get a world's date - * - * @param world world - * @return date - */ - int getDate(World world); - - /** - * Set a world's season - * - * @param world world - * @param season season - */ - void setSeason(World world, Season season); - - /** - * Set a world's date - * - * @param world world - * @param date date - */ - void setDate(World world, int date); + @NotNull + Season getSeason(@NotNull World world); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/ActionManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/ActionManager.java deleted file mode 100644 index 47d4b34..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/ActionManager.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionFactory; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import org.bukkit.configuration.ConfigurationSection; - -import java.util.HashMap; - -public interface ActionManager extends Reloadable { - - /** - * Register a custom action type - * - * @param type type - * @param actionFactory action factory - * @return success or not - */ - boolean registerAction(String type, ActionFactory actionFactory); - - /** - * Unregister an action type by id - * - * @param type type - * @return success or not - */ - boolean unregisterAction(String type); - - /** - * Build an action instance with Bukkit configs - * - * @param section bukkit config - * @return action - */ - Action getAction(ConfigurationSection section); - - /** - * If an action type exists - * - * @param type type - * @return exist or not - */ - default boolean hasAction(String type) { - return getActionFactory(type) != null; - } - - /** - * Build an action map with Bukkit configs - * - * @param section bukkit config - * @return action map - */ - HashMap getActionMap(ConfigurationSection section); - - /** - * Build actions with Bukkit configs - * - * @param section bukkit config - * @return actions - */ - Action[] getActions(ConfigurationSection section); - - /** - * Get an action factory by type - * - * @param type type - * @return action factory - */ - ActionFactory getActionFactory(String type); - - /** - * Trigger actions - * - * @param state state - * @param actions actions - */ - static void triggerActions(State state, Action... actions) { - if (actions != null) - for (Action action : actions) - action.trigger(state); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/AdventureManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/AdventureManager.java deleted file mode 100644 index fce5c77..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/AdventureManager.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.manager; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import net.momirealms.customcrops.api.common.Initable; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public abstract class AdventureManager implements Initable { - - private static AdventureManager instance; - - public AdventureManager() { - instance = this; - } - - public static AdventureManager getInstance() { - return instance; - } - - public abstract void sendMessage(CommandSender sender, String s); - - public abstract void sendMessageWithPrefix(CommandSender sender, String text); - - public abstract void sendConsoleMessage(String text); - - public abstract void sendPlayerMessage(Player player, String text); - - public abstract void sendActionbar(Player player, String text); - - public abstract void sendSound(Player player, Sound.Source source, Key key, float pitch, float volume); - - public abstract void sendSound(Player player, Sound sound); - - public abstract Component getComponentFromMiniMessage(String text); - - public abstract String legacyToMiniMessage(String legacy); - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public abstract boolean isColorCode(char c); - - public abstract void sendTitle(Player player, String title, String subTitle, int fadeIn, int stay, int fadeOut); - - public abstract int rgbaToDecimal(String rgba); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/ConditionManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/ConditionManager.java deleted file mode 100644 index 91ce5a1..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/ConditionManager.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.mechanic.condition.Condition; -import net.momirealms.customcrops.api.mechanic.condition.ConditionFactory; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import org.bukkit.configuration.ConfigurationSection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface ConditionManager extends Reloadable { - - /** - * Register a custom condition type - * - * @param type type - * @param conditionFactory condition factory - * @return success or not - */ - boolean registerCondition(String type, ConditionFactory conditionFactory); - - /** - * Unregister a condition type by id - * - * @param type type - * @return success or not - */ - boolean unregisterCondition(String type); - - /** - * If a condition type exists - * - * @param type type - * @return exist or not - */ - default boolean hasCondition(String type) { - return getConditionFactory(type) != null; - } - - /** - * Build conditions with Bukkit configs - * - * @param section bukkit config - * @return conditions - */ - @NotNull - Condition[] getConditions(ConfigurationSection section); - - /** - * Build a condition instance with Bukkit configs - * - * @param section bukkit config - * @return condition - */ - Condition getCondition(ConfigurationSection section); - - /** - * Build a condition instance with Bukkit configs - * - * @return condition - */ - Condition getCondition(String key, Object args); - - /** - * Get a condition factory by type - * - * @param type type - * @return condition factory - */ - @Nullable ConditionFactory getConditionFactory(String type); - - /** - * Are conditions met for a custom crops block - * - * @param block block - * @param offline is the check for offline ticks - * @param conditions conditions - * @return meet or not - */ - static boolean isConditionMet(CustomCropsBlock block, boolean offline, Condition... conditions) { - if (conditions == null) return true; - for (Condition condition : conditions) { - if (!condition.isConditionMet(block, offline)) { - return false; - } - } - return true; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/ConfigManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/ConfigManager.java deleted file mode 100644 index 7820679..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/ConfigManager.java +++ /dev/null @@ -1,185 +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.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import org.bukkit.World; - -public abstract class ConfigManager implements Reloadable { - - private static ConfigManager instance; - - public ConfigManager() { - instance = this; - } - - public static ConfigManager getInstance() { - return instance; - } - - public static boolean legacyColorSupport() { - return instance.hasLegacyColorSupport(); - } - - public static int maximumPoolSize() { - return instance.getMaximumPoolSize(); - } - - public static int corePoolSize() { - return instance.getCorePoolSize(); - } - - public static int keepAliveTime() { - return instance.getKeepAliveTime(); - } - - public static boolean debug() { - return instance.getDebugMode(); - } - - public static boolean protectLore() { - return instance.isProtectLore(); - } - - public static String[] itemDetectionOrder() { - return instance.getItemDetectionOrder(); - } - - public static String lang() { - return instance.getLang(); - } - - public static boolean metrics() { - return instance.hasMetrics(); - } - - public static boolean checkUpdate() { - return instance.hasCheckUpdate(); - } - - public static double[] defaultQualityRatio() { - return instance.getDefaultQualityRatio(); - } - - public static boolean preventTrampling() { - return instance.isPreventTrampling(); - } - - public static boolean disableMoisture() { - return instance.isDisableMoisture(); - } - - public static boolean syncSeasons() { - return instance.isSyncSeasons(); - } - - public static boolean enableGreenhouse() { - return instance.isGreenhouseEnabled(); - } - - public static World referenceWorld() { - return instance.getReferenceWorld(); - } - - public static int greenhouseRange() { - return instance.getGreenhouseRange(); - } - - public static int scarecrowRange() { - return instance.getScarecrowRange(); - } - - public static String greenhouseID() { - return instance.getGreenhouseID(); - } - - public static boolean enableScarecrow() { - return instance.isScarecrowEnabled(); - } - - public static String scarecrowID() { - return instance.getScarecrowID(); - } - - public static boolean convertWorldOnLoad() { - return instance.isConvertWorldOnLoad(); - } - - public static boolean scarecrowProtectChunk() { - return instance.doesScarecrowProtectChunk(); - } - - public static ItemCarrier scarecrowItemCarrier() { - return instance.getScarecrowItemCarrier(); - } - - public static ItemCarrier glassItemCarrier() { - return instance.getGlassItemCarrier(); - } - - public abstract boolean isConvertWorldOnLoad(); - - public abstract double[] getDefaultQualityRatio(); - - public abstract String getLang(); - - public abstract boolean getDebugMode(); - - public abstract boolean hasLegacyColorSupport(); - - public abstract int getMaximumPoolSize(); - - public abstract int getKeepAliveTime(); - - public abstract int getCorePoolSize(); - - public abstract boolean isProtectLore(); - - public abstract String[] getItemDetectionOrder(); - - public abstract boolean hasMetrics(); - - public abstract boolean hasCheckUpdate(); - - public abstract boolean isDisableMoisture(); - - public abstract boolean isPreventTrampling(); - - public abstract boolean isGreenhouseEnabled(); - - public abstract String getGreenhouseID(); - - public abstract int getGreenhouseRange(); - - public abstract boolean isScarecrowEnabled(); - - public abstract String getScarecrowID(); - - public abstract int getScarecrowRange(); - - public abstract boolean isSyncSeasons(); - - public abstract boolean doesScarecrowProtectChunk(); - - public abstract ItemCarrier getScarecrowItemCarrier(); - - public abstract ItemCarrier getGlassItemCarrier(); - - public abstract World getReferenceWorld(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/IntegrationManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/IntegrationManager.java deleted file mode 100644 index aa2e90f..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/IntegrationManager.java +++ /dev/null @@ -1,58 +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.manager; - -import net.momirealms.customcrops.api.common.Initable; -import net.momirealms.customcrops.api.integration.LevelInterface; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import org.jetbrains.annotations.Nullable; - -public interface IntegrationManager extends Initable { - - /** - * Registers a level plugin with the specified name. - * - * @param plugin The name of the level plugin. - * @param level The implementation of the LevelInterface. - * @return true if the registration was successful, false if the plugin name is already registered. - */ - boolean registerLevelPlugin(String plugin, LevelInterface level); - - /** - * Unregisters a level plugin with the specified name. - * - * @param plugin The name of the level plugin to unregister. - * @return true if the unregistration was successful, false if the plugin name is not found. - */ - boolean unregisterLevelPlugin(String plugin); - - /** - * Get the LevelInterface provided by a plugin. - * - * @param plugin The name of the plugin providing the LevelInterface. - * @return The LevelInterface provided by the specified plugin, or null if the plugin is not registered. - */ - @Nullable LevelInterface getLevelPlugin(String plugin); - - /** - * Get the SeasonInterface provided by a plugin. - * - * @return the season interface - */ - SeasonInterface getSeasonInterface(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/ItemManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/ItemManager.java deleted file mode 100644 index 4056afe..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/ItemManager.java +++ /dev/null @@ -1,413 +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.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.integration.ItemLibrary; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; - -public interface ItemManager extends Reloadable { - - /** - * Register an item library - * - * @param itemLibrary item library - * @return success or not - */ - boolean registerItemLibrary(@NotNull ItemLibrary itemLibrary); - - /** - * Unregister an item library by identification - * - * @param identification identification - * @return success or not - */ - boolean unregisterItemLibrary(String identification); - - /** - * Get item's id by ItemStack - * ItemsAdder: namespace:id - * Oraxen: id - * Vanilla: CAPITAL_ID - * Other item libraries: LibraryID:ItemID - * - * @param itemStack item - * @return ID - */ - String getItemID(ItemStack itemStack); - - /** - * Get item by ID - * ItemsAdder: namespace:id - * Oraxen: id - * Vanilla: CAPITAL_ID - * Other item libraries: LibraryID:ItemID - * - * @param player player - * @param id id - * @return item - */ - ItemStack getItemStack(Player player, String id); - - /** - * Place an item at a certain location - * - * @param location location - * @param carrier carrier - * @param id id - */ - void placeItem(Location location, ItemCarrier carrier, String id); - - /** - * Place an item at a certain location - * - * @param location location - * @param carrier carrier - * @param id id - * @param rotation rotation - */ - void placeItem(Location location, ItemCarrier carrier, String id, CRotation rotation); - - /** - * Remove any block/entity from a certain location - * - * @param location location - * @return the rotation of the removed entity - */ - CRotation removeAnythingAt(Location location); - - /** - * Get the rotation of the removed entity - * - * @param location location - * @return rotation - */ - CRotation getRotation(Location location); - - /** - * Get watering can config by ID - * - * @param id id - * @return watering can config - */ - @Nullable - WateringCan getWateringCanByID(@NotNull String id); - - /** - * Get watering can config by item ID - * - * @param id item ID - * @return watering can config - */ - @Nullable - WateringCan getWateringCanByItemID(@NotNull String id); - - /** - * Get watering can config by itemStack - * - * @param itemStack itemStack - * @return watering can config - */ - @Nullable - WateringCan getWateringCanByItemStack(@NotNull ItemStack itemStack); - - /** - * Get sprinkler config by ID - * - * @param id id - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerByID(@NotNull String id); - - /** - * Get sprinkler config by 3D item ID - * - * @param id 3D item ID - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerBy3DItemID(@NotNull String id); - - /** - * Get sprinkler config by 2D item ID - * - * @param id 2D item ID - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerBy2DItemID(@NotNull String id); - - /** - * Get sprinkler config by entity - * - * @param entity entity - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerByEntity(@NotNull Entity entity); - - /** - * Get sprinkler config by block - * - * @param block block - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerByBlock(@NotNull Block block); - - /** - * Get sprinkler config by 2D itemStack - * - * @param itemStack 2D itemStack - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerBy2DItemStack(@NotNull ItemStack itemStack); - - /** - * Get sprinkler config by 3D itemStack - * - * @param itemStack 3D itemStack - * @return sprinkler config - */ - @Nullable - Sprinkler getSprinklerBy3DItemStack(@NotNull ItemStack itemStack); - - /** - * Get pot config by ID - * - * @param id id - * @return pot config - */ - @Nullable - Pot getPotByID(@NotNull String id); - - /** - * Get pot config by block ID - * - * @param id block ID - * @return pot config - */ - @Nullable - Pot getPotByBlockID(@NotNull String id); - - /** - * Get pot config by block - * - * @param block block - * @return pot config - */ - @Nullable - Pot getPotByBlock(@NotNull Block block); - - /** - * Get pot config by block itemStack - * - * @param itemStack itemStack - * @return pot config - */ - @Nullable - Pot getPotByItemStack(@NotNull ItemStack itemStack); - - /** - * Get fertilizer config by ID - * - * @param id id - * @return fertilizer config - */ - @Nullable - Fertilizer getFertilizerByID(String id); - - /** - * Get fertilizer config by item ID - * - * @param id item id - * @return fertilizer config - */ - @Nullable - Fertilizer getFertilizerByItemID(String id); - - /** - * Get fertilizer config by itemStack - * - * @param itemStack itemStack - * @return fertilizer config - */ - @Nullable - Fertilizer getFertilizerByItemStack(@NotNull ItemStack itemStack); - - /** - * Get crop config by ID - * - * @param id id - * @return crop config - */ - @Nullable - Crop getCropByID(String id); - - /** - * Get crop config by seed ID - * - * @param id seed ID - * @return crop config - */ - @Nullable - Crop getCropBySeedID(String id); - - /** - * Get crop config by seed itemStack - * - * @param itemStack seed itemStack - * @return crop config - */ - @Nullable - Crop getCropBySeedItemStack(ItemStack itemStack); - - /** - * Get crop config by stage item ID - * - * @param id stage item ID - * @return crop config - */ - @Nullable - Crop getCropByStageID(String id); - - /** - * Get crop config by entity - * - * @param entity entity - * @return crop config - */ - @Nullable - Crop getCropByEntity(Entity entity); - - /** - * Get crop config by block - * - * @param block block - * @return crop config - */ - @Nullable - Crop getCropByBlock(Block block); - - /** - * Get crop stage config by stage ID - * - * @param id stage ID - * @return crop stage config - */ - @Nullable - Crop.Stage getCropStageByStageID(String id); - - /** - * Update a pot's block state - * - * @param location location - * @param pot pot config - * @param hasWater has water or not - * @param fertilizer fertilizer - */ - void updatePotState(Location location, Pot pot, boolean hasWater, Fertilizer fertilizer); - - /** - * Get the pots that can be watered with a watering can - * - * @param baseLocation the clicked pot's location - * @param width width of the working range - * @param length length of the working range - * @param yaw player's yaw - * @param potID pot's ID - * @return the pots that can be watered - */ - @NotNull - Collection getPotInRange(Location baseLocation, int width, int length, float yaw, String potID); - - void handlePlayerInteractBlock( - Player player, - Block clickedBlock, - BlockFace clickedFace, - Cancellable event - ); - - void handlePlayerInteractAir( - Player player, - Cancellable event - ); - - void handlePlayerBreakBlock( - Player player, - Block brokenBlock, - String blockID, - Cancellable event - ); - - void handlePlayerInteractFurniture( - Player player, - Location location, - String id, - Entity baseEntity, - Cancellable event - ); - - void handlePlayerPlaceFurniture( - Player player, - Location location, - String id, - Cancellable event - ); - - void handlePlayerBreakFurniture( - Player player, - Location location, - String id, - Cancellable event - ); - - void handlePlayerPlaceBlock( - Player player, - Block block, - String blockID, - Cancellable event - ); - - void handleEntityTramplingBlock( - Entity entity, - Block block, - Cancellable event - ); - - void handleExplosion( - Entity entity, - List blocks, - Cancellable event - ); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/MessageManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/MessageManager.java deleted file mode 100644 index 7fc653b..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/MessageManager.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.manager; - -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import org.jetbrains.annotations.Nullable; - -public abstract class MessageManager { - - private static MessageManager instance; - - public MessageManager() { - instance = this; - } - - public static MessageManager getInstance() { - return instance; - } - - public static String seasonTranslation(@Nullable Season season) { - return instance.getSeasonTranslation(season); - } - - public static String reloadMessage() { - return instance.getReload(); - } - - public static String prefix() { - return instance.getPrefix(); - } - - protected abstract String getPrefix(); - - protected abstract String getReload(); - - protected abstract String getSeasonTranslation(Season season); - - public abstract void reload(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/PlaceholderManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/PlaceholderManager.java deleted file mode 100644 index a720b18..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/PlaceholderManager.java +++ /dev/null @@ -1,47 +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.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import org.bukkit.entity.Player; - -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -public abstract class PlaceholderManager implements Reloadable { - - public static final Pattern pattern = Pattern.compile("\\{[^{}]+}"); - private static PlaceholderManager instance; - - public PlaceholderManager() { - instance = this; - } - - public static PlaceholderManager getInstance() { - return instance; - } - - public abstract String parse(Player player, String text, Map vars); - - public abstract List parse(Player player, List text, Map vars); - - public abstract List detectPlaceholders(String text); - - public abstract String setPlaceholders(Player player, String text); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/RequirementManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/RequirementManager.java deleted file mode 100644 index b4f6efb..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/RequirementManager.java +++ /dev/null @@ -1,110 +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.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.RequirementFactory; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import org.bukkit.configuration.ConfigurationSection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface RequirementManager extends Reloadable { - - /** - * Register a custom requirement type - * - * @param type type - * @param requirementFactory requirement factory - * @return success or not - */ - boolean registerRequirement(String type, RequirementFactory requirementFactory); - - /** - * Unregister a custom requirement by type - * - * @param type type - * @return success or not - */ - boolean unregisterRequirement(String type); - - /** - * Build requirements with Bukkit configs - * - * @param section bukkit config - * @param advanced check "not-met-actions" or not - * @return requirements - */ - @Nullable - Requirement[] getRequirements(ConfigurationSection section, boolean advanced); - - /** - * If a requirement type exists - * - * @param type type - * @return exist or not - */ - default boolean hasRequirement(String type) { - return getRequirementFactory(type) != null; - } - - /** - * Build a requirement instance with Bukkit configs - * - * @param section bukkit config - * @param advanced check "not-met-actions" or not - * @return requirement - */ - @NotNull - Requirement getRequirement(ConfigurationSection section, boolean advanced); - - /** - * Build a requirement instance with Bukkit configs - * - * @return requirement - */ - @NotNull - Requirement getRequirement(String type, Object value); - - /** - * Get a requirement factory by type - * - * @param type type - * @return requirement factory - */ - @Nullable - RequirementFactory getRequirementFactory(String type); - - /** - * Are requirements met for a player - * - * @param state state - * @param requirements requirements - * @return meet or not - */ - static boolean isRequirementMet(State state, Requirement... requirements) { - if (requirements == null) return true; - for (Requirement requirement : requirements) { - if (!requirement.isStateMet(state)) { - return false; - } - } - return true; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/VersionManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/VersionManager.java deleted file mode 100644 index 3e874a6..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/VersionManager.java +++ /dev/null @@ -1,91 +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.manager; - -import java.util.concurrent.CompletionStage; - -public abstract class VersionManager { - - private static VersionManager instance; - - public VersionManager() { - instance = this; - } - - public static VersionManager getInstance() { - return instance; - } - - public static boolean isHigherThan1_19_R3() { - return instance.isVersionNewerThan1_19_R3(); - } - - public static boolean isHigherThan1_19_R2() { - return instance.isVersionNewerThan1_19_R2(); - } - - public static boolean isHigherThan1_18() { - return instance.isVersionNewerThan1_18(); - } - - public static boolean isHigherThan1_19() { - return instance.isVersionNewerThan1_19(); - } - - public static boolean isHigherThan1_20() { - return instance.isVersionNewerThan1_20(); - } - - public static boolean isHigherThan1_20_R2() { - return instance.isVersionNewerThan1_20_R2(); - } - - public abstract boolean isVersionNewerThan1_20_R2(); - - public abstract boolean hasRegionScheduler(); - - public static boolean folia() { - return instance.hasRegionScheduler(); - } - - public abstract String getPluginVersion(); - - public static String pluginVersion() { - return instance.getPluginVersion(); - } - - public static boolean spigot() { - return instance.isSpigot(); - } - - public abstract boolean isSpigot(); - - public abstract boolean isVersionNewerThan1_19_R3(); - - public abstract boolean isVersionNewerThan1_19(); - - public abstract boolean isVersionNewerThan1_19_R2(); - - public abstract boolean isVersionNewerThan1_20(); - - public abstract boolean isVersionNewerThan1_18(); - - public abstract boolean isMojmap(); - - public abstract CompletionStage checkUpdate(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java b/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java deleted file mode 100644 index edff0c5..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/WorldManager.java +++ /dev/null @@ -1,420 +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.manager; - -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.world.AbstractWorldAdaptor; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.*; -import org.bukkit.Chunk; -import org.bukkit.World; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Optional; - -public interface WorldManager extends Reloadable { - - /** - * Load a specified world and convert it into a CustomCrops world - * This method ignores the whitelist and blacklist - * If there already exists one, it would not create a new instance but return the created one - * - * @param world world - */ - @NotNull - CustomCropsWorld loadWorld(@NotNull World world); - - /** - * Unload a specified world and save it to file - * This method ignores the whitelist and blacklist - * - * @param world world - */ - boolean unloadWorld(@NotNull World world); - - /** - * Check if the world has CustomCrops mechanisms - * - * @param world world - * @return has or not - */ - boolean isMechanicEnabled(@NotNull World world); - - /** - * Get all the worlds loaded in CustomCrops - * - * @return worlds - */ - @NotNull - Collection getWorldNames(); - - /** - * Get all the worlds loaded in CustomCrops - * - * @return worlds - */ - @NotNull - Collection getBukkitWorlds(); - - /** - * Get all the worlds loaded in CustomCrops - * - * @return worlds - */ - @NotNull - Collection getCustomCropsWorlds(); - - /** - * Get CustomCrops world by name - * - * @param name name - * @return CustomCrops world - */ - @NotNull - Optional getCustomCropsWorld(@NotNull String name); - - /** - * Get CustomCrops world by Bukkit world - * - * @param world world - * @return CustomCrops world - */ - @NotNull - Optional getCustomCropsWorld(@NotNull World world); - - /** - * Get sprinkler at a certain location - * - * @param location location - * @return sprinkler - */ - @NotNull - Optional getSprinklerAt(@NotNull SimpleLocation location); - - /** - * Get pot at a certain location - * - * @param location location - * @return pot - */ - @NotNull - Optional getPotAt(@NotNull SimpleLocation location); - - /** - * Get crop at a certain location - * - * @param location location - * @return crop - */ - @NotNull - Optional getCropAt(@NotNull SimpleLocation location); - - /** - * Get greenhouse glass at a certain location - * - * @param location location - * @return greenhouse glass - */ - @NotNull Optional getGlassAt(@NotNull SimpleLocation location); - - /** - * Get scarecrow at a certain location - * - * @param location location - * @return scarecrow - */ - @NotNull Optional getScarecrowAt(@NotNull SimpleLocation location); - - /** - * Get any CustomCrops block at a certain location - * The block can be crop, sprinkler and etc. - * - * @param location location - * @return CustomCrops block - */ - Optional getBlockAt(SimpleLocation location); - - /** - * Create crop data - * - * @param location location - * @param crop crop config - * @param point initial point - * @return the crop data - */ - WorldCrop createCropData(SimpleLocation location, Crop crop, int point); - - /** - * Create crop data - * - * @param location location - * @param crop crop config - * @return the crop data - */ - default WorldCrop createCropData(SimpleLocation location, Crop crop) { - return createCropData(location, crop, 0); - } - - /** - * Create sprinkler data - * - * @param location location - * @param sprinkler sprinkler config - * @param water initial water - * @return the sprinkler data - */ - WorldSprinkler createSprinklerData(SimpleLocation location, Sprinkler sprinkler, int water); - - /** - * Create sprinkler data - * - * @param location location - * @param sprinkler sprinkler config - * @return the sprinkler data - */ - default WorldSprinkler createSprinklerData(SimpleLocation location, Sprinkler sprinkler) { - return createSprinklerData(location, sprinkler, 0); - } - - /** - * Create pot data - * - * @param location location - * @param pot pot config - * @param water initial water - * @param fertilizer fertilizer config - * @param fertilizerTimes the remaining usages of the fertilizer - * @return the pot data - */ - WorldPot createPotData(SimpleLocation location, Pot pot, int water, @Nullable Fertilizer fertilizer, int fertilizerTimes); - - /** - * Create pot data - * - * @param location location - * @param pot pot config - * @return the pot data - */ - default WorldPot createPotData(SimpleLocation location, Pot pot) { - return createPotData(location, pot, 0, null, 0); - } - - /** - * Create Greenhouse glass data - * - * @param location location - * @return the greenhouse glass data - */ - WorldGlass createGreenhouseGlassData(SimpleLocation location); - - /** - * Create scarecrow data - * - * @param location location - * @return the scarecrow data - */ - WorldScarecrow createScarecrowData(SimpleLocation location); - - /** - * Add water to the sprinkler - * This method would create new sprinkler data if the sprinkler data not exists in that place - * This method would also update the sprinkler's model if it has models according to the water amount - * - * @param sprinkler sprinkler config - * @param location location - * @param amount amount of water - */ - void addWaterToSprinkler(@NotNull Sprinkler sprinkler, @NotNull SimpleLocation location, int amount); - - /** - * Add fertilizer to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it has appearance variations for different fertilizers - * - * @param pot pot config - * @param fertilizer fertilizer config - * @param location location - */ - void addFertilizerToPot(@NotNull Pot pot, @NotNull Fertilizer fertilizer, @NotNull SimpleLocation location); - - /** - * Add water to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it's dry - * - * @param pot pot config - * @param amount amount of water - * @param location location - */ - void addWaterToPot(@NotNull Pot pot, int amount, @NotNull SimpleLocation location); - - /** - * Add points to a crop - * This method would do nothing if the crop data not exists in that place - * This method would change the stage of the crop and trigger the actions - * - * @param crop crop config - * @param points points to add - * @param location location - */ - void addPointToCrop(@NotNull Crop crop, int points, @NotNull SimpleLocation location); - - /** - * Add greenhouse glass data - * - * @param glass glass data - * @param location location - */ - void addGlassAt(@NotNull WorldGlass glass, @NotNull SimpleLocation location); - - /** - * Add pot data - * - * @param pot pot data - * @param location location - */ - void addPotAt(@NotNull WorldPot pot, @NotNull SimpleLocation location); - - /** - * Add sprinkler data - * - * @param sprinkler sprinkler data - * @param location location - */ - void addSprinklerAt(@NotNull WorldSprinkler sprinkler, @NotNull SimpleLocation location); - - /** - * Add crop data - * - * @param crop crop data - * @param location location - */ - void addCropAt(@NotNull WorldCrop crop, @NotNull SimpleLocation location); - - /** - * Add scarecrow data - * - * @param scarecrow scarecrow data - * @param location location - */ - void addScarecrowAt(@NotNull WorldScarecrow scarecrow, @NotNull SimpleLocation location); - - /** - * Remove sprinkler data from a certain location - * - * @param location location - */ - @Nullable - WorldSprinkler removeSprinklerAt(@NotNull SimpleLocation location); - - /** - * Remove pot data from a certain location - * - * @param location location - */ - @Nullable - WorldPot removePotAt(@NotNull SimpleLocation location); - - /** - * Remove crop data from a certain location - * - * @param location location - */ - @Nullable - WorldCrop removeCropAt(@NotNull SimpleLocation location); - - /** - * Remove greenhouse glass data from a certain location - * - * @param location location - */ - @Nullable - WorldGlass removeGlassAt(@NotNull SimpleLocation location); - - /** - * Remove scarecrow data from a certain location - * - * @param location location - */ - @Nullable - WorldScarecrow removeScarecrowAt(@NotNull SimpleLocation location); - - /** - * If a certain type of item reached the limitation - * - * @param location location - * @param itemType the type of the item - * @return reached or not - */ - boolean isReachLimit(SimpleLocation location, ItemType itemType); - - /** - * Handle the load of a chunk - * It's recommended to call world.getChunkAt(x,z), otherwise you have to manually control the load/unload process - * - * @param bukkitChunk chunk - */ - void handleChunkLoad(Chunk bukkitChunk); - - /** - * Handle the unload of a chunk - * - * @param bukkitChunk chunk - */ - void handleChunkUnload(Chunk bukkitChunk); - - /** - * Save a chunk to region (from memory to memory) - * - * @param chunk the chunk to save - */ - void saveChunkToCachedRegion(CustomCropsChunk chunk); - - /** - * Save a region to file (from memory to disk) - * - * @param region the region to save - */ - void saveRegionToFile(CustomCropsRegion region); - - /** - * Remove any block data from a certain location - * - * @param location location - * @return block data - */ - CustomCropsBlock removeAnythingAt(SimpleLocation location); - - /** - * Get the world adaptor - * - * @return the world adaptor - */ - AbstractWorldAdaptor getWorldAdaptor(); - - /** - * Save a world's season and date - * - * @param customCropsWorld the world to save - */ - void saveInfoData(CustomCropsWorld customCropsWorld); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/Action.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/Action.java deleted file mode 100644 index fde774e..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/Action.java +++ /dev/null @@ -1,30 +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.mechanic.action; - -import net.momirealms.customcrops.api.mechanic.requirement.State; - -public interface Action { - - /** - * Trigger the action - * - * @param state the state of the player - */ - void trigger(State state); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionExpansion.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionExpansion.java deleted file mode 100644 index 9f9f9a3..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionExpansion.java +++ /dev/null @@ -1,49 +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.mechanic.action; - -public abstract class ActionExpansion { - - /** - * Get the version number - * - * @return version - */ - public abstract String getVersion(); - - /** - * Get the author - * - * @return author - */ - public abstract String getAuthor(); - - /** - * Get the type of the action - * - * @return the type of the action - */ - public abstract String getActionType(); - - /** - * Get the action factory - * - * @return the action factory - */ - public abstract ActionFactory getActionFactory(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionTrigger.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionTrigger.java deleted file mode 100644 index 4ae87a6..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/action/ActionTrigger.java +++ /dev/null @@ -1,36 +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.mechanic.action; - -public enum ActionTrigger { - - BREAK, - PLACE, - GROW, - ADD_WATER, - NO_WATER, - CONSUME_WATER, - FULL, - WORK, - USE, - WRONG_POT, - WRONG_SPRINKLER, - BEFORE_PLANT, - REACH_LIMIT, - INTERACT, PLANT, RIPE, -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Condition.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Condition.java deleted file mode 100644 index 1f38f85..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Condition.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.condition; - -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; - -public interface Condition { - - /** - * If the block meets the conditions - * - * @param block block - * @param offline offline tick - * @return met or not - */ - boolean isConditionMet(CustomCropsBlock block, boolean offline); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionExpansion.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionExpansion.java deleted file mode 100644 index 0ecde8a..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionExpansion.java +++ /dev/null @@ -1,49 +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.mechanic.condition; - -public abstract class ConditionExpansion { - - /** - * Get the version number - * - * @return version - */ - public abstract String getVersion(); - - /** - * Get the author - * - * @return author - */ - public abstract String getAuthor(); - - /** - * Get the type of the condition - * - * @return the type of the condition - */ - public abstract String getConditionType(); - - /** - * Get the condition factory - * - * @return the condition factory - */ - public abstract ConditionFactory getConditionFactory(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionFactory.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionFactory.java deleted file mode 100644 index 97beece..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/ConditionFactory.java +++ /dev/null @@ -1,29 +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.mechanic.condition; - -public interface ConditionFactory { - - /** - * Build a condition with the args - * - * @param args args - * @return condition - */ - Condition build(Object args); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Conditions.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Conditions.java deleted file mode 100644 index aa38313..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/Conditions.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.momirealms.customcrops.api.mechanic.condition; - -public class Conditions { - - private final Condition[] conditions; - - public Conditions(Condition[] conditions) { - this.conditions = conditions; - } - - /** - * Get a list of conditions - * - * @return conditions - */ - public Condition[] getConditions() { - return conditions; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/DeathConditions.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/DeathConditions.java deleted file mode 100644 index 7dcc29e..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/condition/DeathConditions.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.momirealms.customcrops.api.mechanic.condition; - -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import org.jetbrains.annotations.Nullable; - -public class DeathConditions extends Conditions { - - private final String deathItem; - private final ItemCarrier itemCarrier; - private final int deathDelay; - - public DeathConditions(Condition[] conditions, String deathItem, ItemCarrier itemCarrier, int deathDelay) { - super(conditions); - this.deathItem = deathItem; - this.itemCarrier = itemCarrier; - this.deathDelay = deathDelay; - } - - /** - * Get the item to replace, null if the crop would be removed - * - * @return the item to replace - */ - @Nullable - public String getDeathItem() { - return deathItem; - } - - /** - * Get the item carrier of the item to replace - * - * @return item carrier - */ - public ItemCarrier getItemCarrier() { - return itemCarrier; - } - - /** - * Get the delay in ticks - * - * @return delay - */ - public int getDeathDelay() { - return deathDelay; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Crop.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Crop.java deleted file mode 100644 index 4a942f1..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Crop.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item; - -import net.momirealms.customcrops.api.common.item.KeyItem; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.condition.Conditions; -import net.momirealms.customcrops.api.mechanic.condition.DeathConditions; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.HashSet; - -public interface Crop extends KeyItem { - - /** - * Get the id of the seed - * - * @return seed ID - */ - String getSeedItemID(); - - /** - * Get the max points to grow - * - * @return max points - */ - int getMaxPoints(); - - /** - * Get the requirements for planting - * - * @return requirements for planting - */ - Requirement[] getPlantRequirements(); - - /** - * Get the requirements for breaking - * - * @return requirements for breaking - */ - Requirement[] getBreakRequirements(); - - /** - * Get the requirements for interactions - * - * @return requirements for interactions - */ - Requirement[] getInteractRequirements(); - - /** - * Get the conditions to grow - * - * @return conditions to grow - */ - Conditions getGrowConditions(); - - /** - * Get the conditions of death - * - * @return conditions of death - */ - DeathConditions[] getDeathConditions(); - - /** - * Get the available bone meals - * - * @return bone meals - */ - BoneMeal[] getBoneMeals(); - - /** - * If the crop has rotations - */ - boolean hasRotation(); - - /** - * Trigger actions - * - * @param trigger action trigger - * @param state player state - */ - void trigger(ActionTrigger trigger, State state); - - /** - * Get the stage config by point - * - * @param point point - * @return stage config - */ - @Nullable - Stage getStageByPoint(int point); - - /** - * Get the stage item ID by point - * This is always NotNull if the point is no lower than 0 - * - * @param point point - * @return the stage item ID - */ - @NotNull - String getStageItemByPoint(int point); - - /** - * Get stage config by stage item ID - * - * @param id item id - * @return stage config - */ - @Nullable - Stage getStageByItemID(String id); - - /** - * Get all the stages - * - * @return stages - */ - Collection getStages(); - - /** - * Get the pots to plant - * - * @return whitelisted pots - */ - HashSet getPotWhitelist(); - - /** - * Get the carrier of this crop - * - * @return carrier of this crop - */ - ItemCarrier getItemCarrier(); - - interface Stage { - - /** - * Get the crop config - * - * @return crop config - */ - Crop getCrop(); - - /** - * Get the offset of the hologram - * - * @return offset - */ - double getHologramOffset(); - - /** - * Get the stage item ID - * This can be null if this point doesn't have any state change - * - * @return stage item ID - */ - @Nullable - String getStageID(); - - /** - * Get the point of this stage - * - * @return point - */ - int getPoint(); - - /** - * Trigger actions - * - * @param trigger action trigger - * @param state player state - */ - void trigger(ActionTrigger trigger, State state); - - /** - * Get the requirements for interactions - * - * @return requirements for interactions - */ - Requirement[] getInteractRequirements(); - - /** - * Get the requirements for breaking - * - * @return requirements for breaking - */ - Requirement[] getBreakRequirements(); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Fertilizer.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Fertilizer.java deleted file mode 100644 index 17d50c7..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Fertilizer.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item; - -import net.momirealms.customcrops.api.common.item.EventItem; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; - -import java.util.HashSet; - -public interface Fertilizer extends EventItem { - - /** - * Get the key - * - * @return key - */ - String getKey(); - - /** - * Get the item ID - * - * @return item ID - */ - String getItemID(); - - /** - * Get the max times of usage - * - * @return the max times of usage - */ - int getTimes(); - - /** - * Get the type of the fertilizer - * - * @return the type of the fertilizer - */ - FertilizerType getFertilizerType(); - - /** - * Get the pot whitelist - * - * @return pot whitelist - */ - HashSet getPotWhitelist(); - - /** - * If the fertilizer can only be used before planting - */ - boolean isBeforePlant(); - - /** - * Get the image of the fertilizer - * - * @return icon - */ - String getIcon(); - - /** - * Get the requirements for this fertilizer - * - * @return requirements - */ - Requirement[] getRequirements(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/FertilizerType.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/FertilizerType.java deleted file mode 100644 index 2e6898c..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/FertilizerType.java +++ /dev/null @@ -1,26 +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.mechanic.item; - -public enum FertilizerType { - SPEED_GROW, - QUALITY, - SOIL_RETAIN, - VARIATION, - YIELD_INCREASE -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemCarrier.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemCarrier.java deleted file mode 100644 index 449d710..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemCarrier.java +++ /dev/null @@ -1,27 +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.mechanic.item; - -public enum ItemCarrier { - NOTE_BLOCK, - MUSHROOM, - CHORUS, - TRIPWIRE, - ITEM_FRAME, - ITEM_DISPLAY -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemType.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemType.java deleted file mode 100644 index b28b3e8..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/ItemType.java +++ /dev/null @@ -1,26 +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.mechanic.item; - -public enum ItemType { - CROP, - POT, - SPRINKLER, - GREENHOUSE, - SCARECROW -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java deleted file mode 100644 index 2a1628b..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Pot.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item; - -import net.momirealms.customcrops.api.common.item.KeyItem; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; - -import java.util.HashSet; - -public interface Pot extends KeyItem { - - /** - * Get max water storage - * - * @return water storage - */ - int getStorage(); - - /** - * Get the key - * - * @return key - */ - String getKey(); - - /** - * Get the blocks that belong to this pot - * - * @return blocks - */ - HashSet getPotBlocks(); - - /** - * Get the methods to fill this pot - * - * @return methods - */ - PassiveFillMethod[] getPassiveFillMethods(); - - /** - * Get the dry state - * - * @return dry state item ID - */ - String getDryItem(); - - /** - * Get the wet state - * - * @return wet state item ID - */ - String getWetItem(); - - /** - * Get the requirements for placement - * - * @return requirements for placement - */ - Requirement[] getPlaceRequirements(); - - /** - * Get the requirements for breaking - * - * @return requirements for breaking - */ - Requirement[] getBreakRequirements(); - - /** - * Get the requirements for using - * - * @return requirements for using - */ - Requirement[] getUseRequirements(); - - /** - * Trigger actions - * - * @param trigger action trigger - * @param state player state - */ - void trigger(ActionTrigger trigger, State state); - - /** - * Get the water bar images - * - * @return water bar images - */ - WaterBar getWaterBar(); - - /** - * Does the pot absorb raindrop - */ - boolean isRainDropAccepted(); - - /** - * Does nearby water make the pot wet - */ - boolean isNearbyWaterAccepted(); - - /** - * Get the block ID by water and fertilizers - * - * @param water water - * @param type the type of the fertilizer - * @return block item ID - */ - String getBlockState(boolean water, FertilizerType type); - - /** - * Is the pot a vanilla block - */ - boolean isVanillaBlock(); - - /** - * Is the id a wet pot - */ - boolean isWetPot(String id); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Scarecrow.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Scarecrow.java deleted file mode 100644 index e9667aa..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Scarecrow.java +++ /dev/null @@ -1,30 +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.mechanic.item; - -import net.momirealms.customcrops.api.common.item.KeyItem; - -public interface Scarecrow extends KeyItem { - - /** - * Get the item ID - * - * @return item ID - */ - String getItemID(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Sprinkler.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Sprinkler.java deleted file mode 100644 index f34148a..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/Sprinkler.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item; - -import net.momirealms.customcrops.api.common.item.KeyItem; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; - -import java.util.HashSet; - -public interface Sprinkler extends KeyItem { - - /** - * Get the 2D item ID - * - * @return 2D item ID - */ - String get2DItemID(); - - /** - * Get the 3D item ID - * - * @return 3D item ID - */ - String get3DItemID(); - - /** - * Get the 3D item ID (With water inside) - * - * @return 3D item ID (With water inside) - */ - String get3DItemWithWater(); - - /** - * Get the max storage of water - * - * @return max storage of water - */ - int getStorage(); - - /** - * Get the working range - * - * @return working range - */ - int getRange(); - - /** - * Is water infinite - */ - boolean isInfinite(); - - /** - * Get the amount of water to add to the pot during sprinkling - * - * @return amount of water to add to the pot during sprinkling - */ - int getWater(); - - /** - * Get the pots that receive the water - * - * @return whitelisted pots - */ - HashSet getPotWhitelist(); - - /** - * Get the carrier of the pot - * - * @return carrier of the pot - */ - ItemCarrier getItemCarrier(); - - /** - * Get methods to fill the sprinkler - * - * @return methods to fill the sprinkler - */ - PassiveFillMethod[] getPassiveFillMethods(); - - /** - * Get the requirements for placement - * - * @return requirements for placement - */ - Requirement[] getPlaceRequirements(); - - /** - * Get the requirements for breaking - * - * @return requirements for breaking - */ - Requirement[] getBreakRequirements(); - - /** - * Get the requirements for using - * - * @return requirements for using - */ - Requirement[] getUseRequirements(); - - /** - * Trigger actions - * - * @param trigger action trigger - * @param state player state - */ - void trigger(ActionTrigger trigger, State state); - - /** - * Get the water bar images - * - * @return water bar images - */ - WaterBar getWaterBar(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/WateringCan.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/WateringCan.java deleted file mode 100644 index fccad50..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/WateringCan.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item; - -import net.momirealms.customcrops.api.common.item.KeyItem; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -public interface WateringCan extends KeyItem { - - /** - * Get the ID of the item - * - * @return item ID - */ - String getItemID(); - - /** - * Get the width of the effective range - * - * @return width - */ - int getWidth(); - - /** - * Get the length of the effective range - * - * @return length - */ - int getLength(); - - /** - * Get the storage of water - * - * @return storage of water - */ - int getStorage(); - - /** - * Get the amount of water to add in one try - */ - int getWater(); - - /** - * If the watering can has dynamic lore - */ - boolean hasDynamicLore(); - - /** - * Update a watering can's data - * - * @param player player - * @param itemStack watering can item - * @param water the amount of water - * @param args the placeholders - */ - void updateItem(Player player, ItemStack itemStack, int water, Map args); - - /** - * Get the current water - * - * @param itemStack watering can item - * @return current water - */ - int getCurrentWater(ItemStack itemStack); - - /** - * Get the pots that receive water from this watering can - * - * @return whitelisted pots - */ - HashSet getPotWhitelist(); - - /** - * Get the sprinklers that receive water from this watering can - * - * @return whitelisted sprinklers - */ - HashSet getSprinklerWhitelist(); - - /** - * Get the dynamic lores - * - * @return dynamic lores - */ - List getLore(); - - /** - * Get the water bar images - * - * @return water bar images - */ - @Nullable WaterBar getWaterBar(); - - /** - * Get the requirements for using this watering can - * - * @return requirements - */ - Requirement[] getRequirements(); - - /** - * If the water is infinite - */ - boolean isInfinite(); - - /** - * Trigger actions - * - * @param trigger action trigger - * @param state player state - */ - void trigger(ActionTrigger trigger, State state); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/AbstractCustomListener.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/AbstractCustomListener.java deleted file mode 100644 index e841c99..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/AbstractCustomListener.java +++ /dev/null @@ -1,349 +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.mechanic.item.custom; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.event.BoneMealDispenseEvent; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.ItemManager; -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import net.momirealms.customcrops.api.mechanic.world.level.WorldGlass; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.util.EventUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.Dispenser; -import org.bukkit.entity.Entity; -import org.bukkit.entity.FallingBlock; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.block.*; -import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.entity.ItemSpawnEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerItemDamageEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.List; -import java.util.Optional; - -public abstract class AbstractCustomListener implements Listener { - - protected ItemManager itemManager; - private final HashSet CUSTOM_MATERIAL = new HashSet<>(); - - public AbstractCustomListener(ItemManager itemManager) { - this.itemManager = itemManager; - this.CUSTOM_MATERIAL.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, - Material.AIR - ) - ); - if (VersionManager.isHigherThan1_19()) { - this.CUSTOM_MATERIAL.add( - Material.MANGROVE_LEAVES - ); - } - if (VersionManager.isHigherThan1_20()) { - this.CUSTOM_MATERIAL.add( - Material.CHERRY_LEAVES - ); - } - } - - @EventHandler (ignoreCancelled = true) - public void onBlockFalling(EntityChangeBlockEvent event) { - if (event.getEntity() instanceof FallingBlock fallingBlock) { - final Block block = event.getBlock(); - final Location location = block.getLocation(); - Optional customCropsBlock = CustomCropsPlugin.get().getWorldManager().getBlockAt(SimpleLocation.of(location)); - if (customCropsBlock.isPresent()) { - event.setCancelled(true); - block.getWorld().dropItemNaturally(block.getLocation(), new ItemStack(fallingBlock.getBlockData().getMaterial())); - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onInteractBlock(PlayerInteractEvent event) { - if (event.getHand() != EquipmentSlot.HAND) - return; - if (event.getAction() != Action.RIGHT_CLICK_BLOCK) - return; - - Player player = event.getPlayer(); - this.itemManager.handlePlayerInteractBlock( - player, - event.getClickedBlock(), - event.getBlockFace(), - event - ); - } - - @EventHandler - public void onInteractAir(PlayerInteractEvent event) { - if (event.getHand() != EquipmentSlot.HAND) - return; - if (event.getAction() != Action.RIGHT_CLICK_AIR) - return; - - Player player = event.getPlayer(); - this.itemManager.handlePlayerInteractAir( - player, - event - ); - } - - @EventHandler (ignoreCancelled = true, priority = EventPriority.LOW) - public void onBreakBlock(BlockBreakEvent event) { - Player player = event.getPlayer(); - Block block = event.getBlock(); - Material type = block.getType(); - // custom block should be handled by other plugins' events - if (CUSTOM_MATERIAL.contains(type)) - return; - this.itemManager.handlePlayerBreakBlock( - player, - block, - type.name(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceBlock(BlockPlaceEvent event) { - final Block block = event.getBlock(); - final Location location = block.getLocation(); - if (CUSTOM_MATERIAL.contains(block.getType())) { - return; - } - Optional customCropsBlock = CustomCropsPlugin.get().getWorldManager().getBlockAt(SimpleLocation.of(location)); - if (customCropsBlock.isPresent()) { - CustomCropsPlugin.get().getWorldManager().removeAnythingAt(SimpleLocation.of(location)); - } - this.onPlaceBlock( - event.getPlayer(), - block, - event.getBlockPlaced().getType().name(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onItemSpawn(ItemSpawnEvent event) { - Item item = event.getEntity(); - ItemStack itemStack = item.getItemStack(); - String itemID = this.itemManager.getItemID(itemStack); - Crop.Stage stage = this.itemManager.getCropStageByStageID(itemID); - if (stage != null) { - event.setCancelled(true); - return; - } - - Sprinkler sprinkler = this.itemManager.getSprinklerBy3DItemID(itemID); - if (sprinkler != null) { - ItemStack newItem = this.itemManager.getItemStack(null, sprinkler.get2DItemID()); - if (newItem != null && newItem.getType() != Material.AIR) { - newItem.setAmount(itemStack.getAmount()); - item.setItemStack(newItem); - } - return; - } - - Pot pot = this.itemManager.getPotByBlockID(itemID); - if (pot != null) { - ItemStack newItem = this.itemManager.getItemStack(null, pot.getDryItem()); - if (newItem != null && newItem.getType() != Material.AIR) { - newItem.setAmount(itemStack.getAmount()); - item.setItemStack(newItem); - } - return; - } - } - - @EventHandler (ignoreCancelled = true) - public void onBlockChange(BlockFadeEvent event) { - Block block = event.getBlock(); - if (block.getType() == Material.FARMLAND) { - SimpleLocation above = SimpleLocation.of(block.getLocation()).add(0,1,0); - if (CustomCropsPlugin.get().getWorldManager().getBlockAt(above).isPresent()) { - event.setCancelled(true); - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onTrampling(EntityChangeBlockEvent event) { - Block block = event.getBlock(); - if (block.getType() == Material.FARMLAND && event.getTo() == Material.DIRT) { - if (ConfigManager.preventTrampling()) { - event.setCancelled(true); - return; - } - itemManager.handleEntityTramplingBlock(event.getEntity(), block, event); - } - } - - @EventHandler (ignoreCancelled = true) - public void onMoistureChange(MoistureChangeEvent event) { - if (ConfigManager.disableMoisture()) - event.setCancelled(true); - } - - @EventHandler (ignoreCancelled = true) - public void onPistonExtend(BlockPistonExtendEvent event) { - WorldManager manager = CustomCropsPlugin.get().getWorldManager(); - for (Block block : event.getBlocks()) { - if (manager.getBlockAt(SimpleLocation.of(block.getLocation())).isPresent()) { - event.setCancelled(true); - return; - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onPistonRetract(BlockPistonRetractEvent event) { - WorldManager manager = CustomCropsPlugin.get().getWorldManager(); - for (Block block : event.getBlocks()) { - if (manager.getBlockAt(SimpleLocation.of(block.getLocation())).isPresent()) { - event.setCancelled(true); - return; - } - } - } - - @EventHandler (ignoreCancelled = true) - public void onItemDamage(PlayerItemDamageEvent event) { - ItemStack itemStack = event.getItem(); - WateringCan wateringCan = this.itemManager.getWateringCanByItemStack(itemStack); - if (wateringCan != null) { - event.setCancelled(true); - } - } - - @EventHandler (ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onExplosion(EntityExplodeEvent event) { - this.itemManager.handleExplosion(event.getEntity(), event.blockList(), event); - } - - @EventHandler (ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onExplosion(BlockExplodeEvent event) { - this.itemManager.handleExplosion(null, event.blockList(), event); - } - - @EventHandler (ignoreCancelled = true) - public void onDispenser(BlockDispenseEvent event) { - Block block = event.getBlock(); - if (block.getBlockData() instanceof org.bukkit.block.data.type.Dispenser directional) { - Block relative = block.getRelative(directional.getFacing()); - Location location = relative.getLocation(); - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional worldCropOptional = CustomCropsPlugin.get().getWorldManager().getCropAt(simpleLocation); - if (worldCropOptional.isPresent()) { - WorldCrop crop = worldCropOptional.get(); - Crop config = crop.getConfig(); - ItemStack itemStack = event.getItem(); - String itemID = itemManager.getItemID(itemStack); - if (crop.getPoint() < config.getMaxPoints()) { - for (BoneMeal boneMeal : config.getBoneMeals()) { - if (boneMeal.getItem().equals(itemID)) { - if (!boneMeal.isDispenserAllowed()) { - return; - } - // fire the event - if (EventUtils.fireAndCheckCancel(new BoneMealDispenseEvent(block, itemStack, location, boneMeal, crop))) { - event.setCancelled(true); - return; - } - if (block.getState() instanceof Dispenser dispenser) { - event.setCancelled(true); - Inventory inventory = dispenser.getInventory(); - for (ItemStack storage : inventory.getStorageContents()) { - if (storage == null) continue; - String id = itemManager.getItemID(storage); - if (id.equals(itemID)) { - storage.setAmount(storage.getAmount() - 1); - boneMeal.trigger(new State(null, itemStack, location)); - CustomCropsPlugin.get().getWorldManager().addPointToCrop(config, boneMeal.getPoint(), simpleLocation); - } - } - } - return; - } - } - } - } - } - } - - public void onPlaceBlock(Player player, Block block, String blockID, Cancellable event) { - if (player == null) return; - this.itemManager.handlePlayerPlaceBlock(player, block, blockID, event); - } - - public void onBreakFurniture(Player player, Location location, String id, Cancellable event) { - if (player == null) return; - this.itemManager.handlePlayerBreakFurniture(player, location, id, event); - } - - public void onPlaceFurniture(Player player, Location location, String id, Cancellable event) { - if (player == null) return; - this.itemManager.handlePlayerPlaceFurniture(player, location, id, event); - } - - public void onInteractFurniture(Player player, Location location, String id, @Nullable Entity baseEntity, Cancellable event) { - if (player == null) return; - this.itemManager.handlePlayerInteractFurniture(player, location, id, baseEntity, event); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/CustomProvider.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/CustomProvider.java deleted file mode 100644 index c6029ea..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/custom/CustomProvider.java +++ /dev/null @@ -1,132 +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.mechanic.item.custom; - -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import net.momirealms.customcrops.api.util.DisplayEntityUtils; -import net.momirealms.customcrops.api.util.LocationUtils; -import net.momirealms.customcrops.api.util.StringUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.*; -import org.bukkit.inventory.ItemStack; - -import java.util.Collection; - -public interface CustomProvider { - - boolean removeBlock(Location location); - - void placeCustomBlock(Location location, String id); - - default void placeBlock(Location location, String id) { - if (StringUtils.isCapitalLetter(id)) { - location.getBlock().setType(Material.valueOf(id)); - } else { - placeCustomBlock(location, id); - } - } - - Entity placeFurniture(Location location, String id); - - void removeFurniture(Entity entity); - - String getBlockID(Block block); - - String getItemID(ItemStack itemInHand); - - ItemStack getItemStack(String id); - - String getEntityID(Entity entity); - - boolean isFurniture(Entity entity); - - default boolean isAir(Location location) { - Block block = location.getBlock(); - if (block.getType() != Material.AIR) - return false; - Location center = LocationUtils.toCenterLocation(location); - Collection entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5); - entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item)); - return entities.isEmpty(); - } - - default CRotation removeAnythingAt(Location location) { - removeBlock(location); - Collection entities = location.getWorld().getNearbyEntities(LocationUtils.toCenterLocation(location), 0.5,0.51,0.5); - entities.removeIf(entity -> { - EntityType type = entity.getType(); - return type != EntityType.ITEM_FRAME - && (!VersionManager.isHigherThan1_19_R3() || type != EntityType.ITEM_DISPLAY); - }); - if (entities.isEmpty()) return CRotation.NONE; - CRotation previousCRotation; - Entity first = entities.stream().findFirst().get(); - if (first instanceof ItemFrame itemFrame) { - previousCRotation = CRotation.getByRotation(itemFrame.getRotation()); - } else if (VersionManager.isHigherThan1_19_R3()) { - previousCRotation = DisplayEntityUtils.getRotation(first); - } else { - previousCRotation = CRotation.NONE; - } - for (Entity entity : entities) { - removeFurniture(entity); - } - return previousCRotation; - } - - default String getSomethingAt(Location location) { - Block block = location.getBlock(); - if (block.getType() != Material.AIR) { - return getBlockID(block); - } else { - Collection entities = location.getWorld().getNearbyEntities(location.toCenterLocation(), 0.5,0.51,0.5); - for (Entity entity : entities) { - if (isFurniture(entity)) { - return getEntityID(entity); - } - } - } - return "AIR"; - } - - default CRotation getRotation(Location location) { - if (location.getBlock().getType() == Material.AIR) { - Collection entities = location.getWorld().getNearbyEntities(LocationUtils.toCenterLocation(location), 0.5,0.51,0.5); - entities.removeIf(entity -> { - EntityType type = entity.getType(); - return type != EntityType.ITEM_FRAME - && (!VersionManager.isHigherThan1_19_R3() || type != EntityType.ITEM_DISPLAY); - }); - if (entities.isEmpty()) return CRotation.NONE; - CRotation rotation; - Entity first = entities.stream().findFirst().get(); - if (first instanceof ItemFrame itemFrame) { - rotation = CRotation.getByRotation(itemFrame.getRotation()); - } else if (VersionManager.isHigherThan1_19_R3()) { - rotation = DisplayEntityUtils.getRotation(first); - } else { - rotation = CRotation.NONE; - } - return rotation; - } - return CRotation.NONE; - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SpeedGrow.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SpeedGrow.java deleted file mode 100644 index 28a20b1..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/SpeedGrow.java +++ /dev/null @@ -1,30 +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.mechanic.item.fertilizer; - -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; - -public interface SpeedGrow extends Fertilizer { - - /** - * Get the extra points to gain - * - * @return points - */ - int getPointBonus(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/YieldIncrease.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/YieldIncrease.java deleted file mode 100644 index fbdfc51..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/YieldIncrease.java +++ /dev/null @@ -1,30 +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.mechanic.item.fertilizer; - -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; - -public interface YieldIncrease extends Fertilizer { - - /** - * Get the extra amount to drop - * - * @return amount bonus - */ - int getAmountBonus(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/AbstractFillMethod.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/AbstractFillMethod.java deleted file mode 100644 index 6855f91..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/water/AbstractFillMethod.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.item.water; - -import net.momirealms.customcrops.api.manager.ActionManager; -import net.momirealms.customcrops.api.manager.RequirementManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; - -public abstract class AbstractFillMethod { - - protected int amount; - private final Action[] actions; - private final Requirement[] requirements; - - protected AbstractFillMethod(int amount, Action[] actions, Requirement[] requirements) { - this.amount = amount; - this.actions = actions; - this.requirements = requirements; - } - - /** - * Get the amount of water to add - * - * @return amount - */ - public int getAmount() { - return amount; - } - - /** - * Trigger actions related to this fill methods - * - * @param state state - */ - public void trigger(State state) { - ActionManager.triggerActions(state, actions); - } - - /** - * If player meet the requirements for this fill method - * - * @param state state - * @return meet or not - */ - public boolean canFill(State state) { - return RequirementManager.isRequirementMet(state, requirements); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/CRotation.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/CRotation.java deleted file mode 100644 index 1e1fc4c..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/CRotation.java +++ /dev/null @@ -1,58 +0,0 @@ -package net.momirealms.customcrops.api.mechanic.misc; - -import org.bukkit.Rotation; - -public enum CRotation { - - NONE(0f), - RANDOM(0f), - EAST(-90f), - SOUTH(0f), - WEST(90f), - NORTH(180f); - - private final float yaw; - - CRotation(float yaw) { - this.yaw = yaw; - } - - public float getYaw() { - return yaw; - } - - public static CRotation getByRotation(Rotation rotation) { - switch (rotation) { - default -> { - return CRotation.NONE; - } - case CLOCKWISE -> { - return CRotation.WEST; - } - case COUNTER_CLOCKWISE -> { - return CRotation.EAST; - } - case FLIPPED -> { - return CRotation.NORTH; - } - } - } - - public static CRotation getByYaw(float yaw) { - yaw = (Math.abs(yaw + 180) % 360); - switch ((int) (yaw/90)) { - case 1 -> { - return CRotation.WEST; - } - case 2 -> { - return CRotation.NORTH; - } - case 3 -> { - return CRotation.EAST; - } - default -> { - return CRotation.SOUTH; - } - } - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/MatchRule.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/MatchRule.java deleted file mode 100644 index 8258d72..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/MatchRule.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.momirealms.customcrops.api.mechanic.misc; - -public enum MatchRule { - - BLACKLIST, - WHITELIST, - REGEX -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Reason.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Reason.java deleted file mode 100644 index eae2386..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Reason.java +++ /dev/null @@ -1,29 +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.mechanic.misc; - -public enum Reason { - // Player break - BREAK, - // Trampling - TRAMPLE, - // Block explosion and entity explosion - EXPLODE, - // Action "break" - ACTION -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Value.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Value.java deleted file mode 100644 index 48e1802..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/Value.java +++ /dev/null @@ -1,31 +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.mechanic.misc; - -import org.bukkit.entity.Player; - -public interface Value { - - /** - * Get double value - * - * @param player player - * @return the value - */ - double get(Player player); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/Requirement.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/Requirement.java deleted file mode 100644 index e92876c..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/Requirement.java +++ /dev/null @@ -1,29 +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.mechanic.requirement; - -public interface Requirement { - - /** - * Does player meet the requirement - * - * @param state state of player - * @return met or not - */ - boolean isStateMet(State state); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementFactory.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementFactory.java deleted file mode 100644 index 4fa5eec..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementFactory.java +++ /dev/null @@ -1,45 +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.mechanic.requirement; - -import net.momirealms.customcrops.api.mechanic.action.Action; - -import java.util.List; - -public interface RequirementFactory { - - /** - * Build a requirement - * - * @param args args - * @param notMetActions actions to perform if the requirement is not met - * @param advanced whether to trigger the notMetActions or not - * @return requirement - */ - Requirement build(Object args, List notMetActions, boolean advanced); - - /** - * Build a requirement - * - * @param args args - * @return requirement - */ - default Requirement build(Object args) { - return build(args, null, false); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/State.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/State.java deleted file mode 100644 index 630a49a..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/State.java +++ /dev/null @@ -1,73 +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.mechanic.requirement; - -import net.momirealms.customcrops.api.util.LocationUtils; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.Map; - -public class State { - - private final Player player; - private final ItemStack itemInHand; - private final Location location; - private final HashMap args; - - public State(Player player, ItemStack itemInHand, @NotNull Location location) { - this.player = player; - this.itemInHand = itemInHand; - this.location = LocationUtils.toBlockLocation(location); - this.args = new HashMap<>(); - if (player != null) { - setArg("{player}", player.getName()); - } - setArg("{x}", String.valueOf(location.getBlockX())); - setArg("{y}", String.valueOf(location.getBlockY())); - setArg("{z}", String.valueOf(location.getBlockZ())); - setArg("{world}", location.getWorld().getName()); - } - - public Player getPlayer() { - return player; - } - - public ItemStack getItemInHand() { - return itemInHand; - } - - public Location getLocation() { - return location; - } - - public Map getArgs() { - return args; - } - - public void setArg(String key, String value) { - args.put(key, value); - } - - public String getArg(String key) { - return args.get(key); - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java deleted file mode 100644 index 8827d2d..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/AbstractWorldAdaptor.java +++ /dev/null @@ -1,49 +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.mechanic.world; - -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import org.bukkit.event.Listener; - -public abstract class AbstractWorldAdaptor implements Listener { - - public static final int chunkVersion = 1; - public static final int regionVersion = 1; - protected WorldManager worldManager; - - public AbstractWorldAdaptor(WorldManager worldManager) { - this.worldManager = worldManager; - } - - public abstract void unload(CustomCropsWorld customCropsWorld); - - public abstract void init(CustomCropsWorld customCropsWorld); - - public abstract void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos); - - public abstract void unloadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos); - - public abstract void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk); - - public abstract void saveRegion(CustomCropsRegion customCropsRegion); - - public abstract void saveInfoData(CustomCropsWorld customCropsWorld); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/CustomCropsBlock.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/CustomCropsBlock.java deleted file mode 100644 index 1aae7f8..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/CustomCropsBlock.java +++ /dev/null @@ -1,31 +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.mechanic.world; - -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.world.level.DataBlock; - -public interface CustomCropsBlock extends DataBlock, Tickable { - - /** - * Get the type of the item - * - * @return type of the item - */ - ItemType getType(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java deleted file mode 100644 index 14c623d..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/SimpleLocation.java +++ /dev/null @@ -1,147 +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.mechanic.world; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; - -public class SimpleLocation { - - private int x; - private int y; - private int z; - private final String worldName; - - public SimpleLocation(String worldName, int x, int y, int z){ - this.worldName = worldName; - this.x = x; - this.y = y; - this.z = z; - } - - public int getX() { - return x; - } - - public int getZ() { - return z; - } - - public int getY() { - return y; - } - - public String getWorldName() { - return worldName; - } - - public ChunkPos getChunkPos() { - return new ChunkPos((int) Math.floor((double) getX() / 16), (int) Math.floor((double) getZ() / 16)); - } - - public SimpleLocation add(int x, int y, int z) { - this.x += x; - this.y += y; - this.z += z; - return this; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final SimpleLocation other = (SimpleLocation) obj; - if (!Objects.equals(worldName, other.getWorldName())) { - return false; - } - if (this.x != other.x) { - return false; - } - if (this.y != other.y) { - return false; - } - if (this.z != other.z) { - return false; - } - return true; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 7 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); - hash = 13 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); - return hash; - } - - public Location getBukkitLocation() { - World world = Bukkit.getWorld(worldName); - if (world == null) return null; - return new Location(world, x, y, z); - } - - @Nullable - public World getBukkitWorld() { - return Bukkit.getWorld(worldName); - } - - public static SimpleLocation getByString(String location) { - String[] loc = location.split(",", 4); - return new SimpleLocation(loc[0], Integer.parseInt(loc[1]), Integer.parseInt(loc[2]), Integer.parseInt(loc[3])); - } - - public static SimpleLocation of(Location location) { - return new SimpleLocation(location.getWorld().getName(), location.getBlockX(), location.getBlockY(), location.getBlockZ()); - } - - @Override - public String toString() { - return "SimpleLocation{" + - "x=" + x + - ", y=" + y + - ", z=" + z + - ", worldName='" + worldName + '\'' + - '}'; - } - - public boolean isNear(SimpleLocation simpleLocation, int distance) { - if (Math.abs(simpleLocation.x - this.x) > distance) { - return false; - } - if (Math.abs(simpleLocation.z - this.z) > distance) { - return false; - } - if (Math.abs(simpleLocation.y - this.y) > distance) { - return false; - } - return true; - } - - public SimpleLocation copy() { - return new SimpleLocation(worldName, x, y, z); - } -} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/Tickable.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/Tickable.java deleted file mode 100644 index 031d045..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/Tickable.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.momirealms.customcrops.api.mechanic.world; - -public interface Tickable { - - /** - * Tick - * - * @param interval interval - * @param offline offline tick - */ - void tick(int interval, boolean offline); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/AbstractCustomCropsBlock.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/AbstractCustomCropsBlock.java deleted file mode 100644 index 1e94433..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/AbstractCustomCropsBlock.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.world.level; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.IntTag; -import com.flowpowered.nbt.Tag; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.SynchronizedCompoundMap; - -public class AbstractCustomCropsBlock implements DataBlock { - - private final SimpleLocation location; - private final SynchronizedCompoundMap compoundMap; - - public AbstractCustomCropsBlock(SimpleLocation location, CompoundMap compoundMap) { - this.compoundMap = new SynchronizedCompoundMap(compoundMap); - this.location = location; - } - - /** - * Set data by key - * - * @param key key - * @param tag data tag - */ - @Override - public void setData(String key, Tag tag) { - compoundMap.put(key, tag); - } - - /** - * Get data tag by key - * - * @param key key - * @return data tag - */ - @Override - public Tag getData(String key) { - return compoundMap.get(key); - } - - /** - * Get the data map - * - * @return data map - */ - @Override - public SynchronizedCompoundMap getCompoundMap() { - return compoundMap; - } - - /** - * Get the location of the block - * - * @return location - */ - @Override - public SimpleLocation getLocation() { - return location; - } - - /** - * interval is customized - * You can perform tick logics if the block is ticked for some times - * - * @param interval interval - * @return can be ticked or not - */ - public boolean canTick(int interval) { - if (interval <= 0) { - return false; - } - if (interval == 1) { - return true; - } - Tag tag = getData("tick"); - int tick = 0; - if (tag != null) { - tick = tag.getAsIntTag().map(IntTag::getValue).orElse(0); - } - if (++tick >= interval) { - setData("tick", new IntTag("tick", 0)); - return true; - } else { - setData("tick", new IntTag("tick", tick)); - } - return false; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java deleted file mode 100644 index 19acd36..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsChunk.java +++ /dev/null @@ -1,344 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import org.jetbrains.annotations.Nullable; - -import java.util.Optional; - -public interface CustomCropsChunk { - - /** - * Calculate the unload time and perform compensation - * This method can only be called in a loaded chunk - */ - void notifyOfflineUpdates(); - - /** - * Get the world associated with the chunk - * - * @return CustomCrops world - */ - CustomCropsWorld getCustomCropsWorld(); - - /** - * Get the region associated with the chunk - * - * @return CustomCrops region - */ - CustomCropsRegion getCustomCropsRegion(); - - /** - * Get the position of the chunk - * - * @return chunk position - */ - ChunkPos getChunkPos(); - - /** - * Do second timer - */ - void secondTimer(); - - /** - * Get the unloaded time in seconds - * This value would increase if the chunk is lazy - * - * @return the unloaded time - */ - int getUnloadedSeconds(); - - /** - * Set the unloaded seconds - * - * @param unloadedSeconds unloadedSeconds - */ - void setUnloadedSeconds(int unloadedSeconds); - - /** - * Get the last loaded time - * - * @return last loaded time - */ - long getLastLoadedTime(); - - /** - * Set the last loaded time to latest - */ - void updateLastLoadedTime(); - - /** - * Get the loaded time in seconds - * - * @return loaded time - */ - int getLoadedSeconds(); - - /** - * Get crop at a certain location - * - * @param location location - * @return crop data - */ - Optional getCropAt(SimpleLocation location); - - /** - * Get sprinkler at a certain location - * - * @param location location - * @return sprinkler data - */ - Optional getSprinklerAt(SimpleLocation location); - - /** - * Get pot at a certain location - * - * @param location location - * @return pot data - */ - Optional getPotAt(SimpleLocation location); - - /** - * Get greenhouse glass at a certain location - * - * @param location location - * @return greenhouse glass data - */ - Optional getGlassAt(SimpleLocation location); - - /** - * Get scarecrow at a certain location - * - * @param location location - * @return scarecrow data - */ - Optional getScarecrowAt(SimpleLocation location); - - /** - * Get block data at a certain location - * - * @param location location - * @return block data - */ - Optional getBlockAt(SimpleLocation location); - - /** - * Add water to the sprinkler - * This method would create new sprinkler data if the sprinkler data not exists in that place - * This method would also update the sprinkler's model if it has models according to the water amount - * - * @param sprinkler sprinkler config - * @param amount amount of water - * @param location location - */ - void addWaterToSprinkler(Sprinkler sprinkler, int amount, SimpleLocation location); - - /** - * Add fertilizer to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it has appearance variations for different fertilizers - * - * @param pot pot config - * @param fertilizer fertilizer config - * @param location location - */ - void addFertilizerToPot(Pot pot, Fertilizer fertilizer, SimpleLocation location); - - /** - * Add water to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it's dry - * - * @param pot pot config - * @param amount amount of water - * @param location location - */ - void addWaterToPot(Pot pot, int amount, SimpleLocation location); - - /** - * Add points to a crop - * This method would do nothing if the crop data not exists in that place - * This method would change the stage of the crop and trigger the actions - * - * @param crop crop config - * @param points points to add - * @param location location - */ - void addPointToCrop(Crop crop, int points, SimpleLocation location); - - /** - * Remove sprinkler data from a certain location - * - * @param location location - */ - @Nullable - WorldSprinkler removeSprinklerAt(SimpleLocation location); - - /** - * Remove pot data from a certain location - * - * @param location location - */ - @Nullable - WorldPot removePotAt(SimpleLocation location); - - /** - * Remove crop data from a certain location - * - * @param location location - */ - @Nullable - WorldCrop removeCropAt(SimpleLocation location); - - /** - * Remove greenhouse glass data from a certain location - * - * @param location location - */ - @Nullable - WorldGlass removeGlassAt(SimpleLocation location); - - /** - * Remove scarecrow data from a certain location - * - * @param location location - */ - @Nullable - WorldScarecrow removeScarecrowAt(SimpleLocation location); - - /** - * Remove any block data from a certain location - * - * @param location location - * @return block data - */ - @Nullable - CustomCropsBlock removeBlockAt(SimpleLocation location); - - /** - * Add a custom block data at a certain location - * - * @param block block data - * @param location location - * @return the previous block data - */ - @Nullable - CustomCropsBlock addBlockAt(CustomCropsBlock block, SimpleLocation location); - - /** - * Get the amount of crops in this chunk - * - * @return the amount of crops - */ - int getCropAmount(); - - /** - * Get the amount of pots in this chunk - * - * @return the amount of pots - */ - int getPotAmount(); - - /** - * Get the amount of sprinklers in this chunk - * - * @return the amount of sprinklers - */ - int getSprinklerAmount(); - - /** - * Add pot data - * - * @param pot pot data - * @param location location - */ - void addPotAt(WorldPot pot, SimpleLocation location); - - /** - * Add sprinkler data - * - * @param sprinkler sprinkler data - * @param location location - */ - void addSprinklerAt(WorldSprinkler sprinkler, SimpleLocation location); - - /** - * Add crop data - * - * @param crop crop data - * @param location location - */ - void addCropAt(WorldCrop crop, SimpleLocation location); - - /** - * Add greenhouse glass data - * - * @param glass glass data - * @param location location - */ - void addGlassAt(WorldGlass glass, SimpleLocation location); - - /** - * Add scarecrow data - * - * @param scarecrow scarecrow data - * @param location location - */ - void addScarecrowAt(WorldScarecrow scarecrow, SimpleLocation location); - - /** - * If this chunk has scarecrow - * - * @return has or not - */ - boolean hasScarecrow(); - - /** - * Get CustomCrops sections - * - * @return sections - */ - CustomCropsSection[] getSections(); - - /** - * Get section by ID - * - * @param sectionID id - * @return section - */ - @Nullable - CustomCropsSection getSection(int sectionID); - - void resetUnloadedSeconds(); - - /** - * If the chunk can be pruned - * - * @return can be pruned or not - */ - boolean canPrune(); - - boolean isOfflineTaskNotified(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java deleted file mode 100644 index 9852347..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsRegion.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.RegionPos; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public interface CustomCropsRegion { - - /** - * Get the CustomCrops world associated with the region - * - * @return CustomCrops world - */ - CustomCropsWorld getCustomCropsWorld(); - - /** - * Get the cached chunk - * - * @param pos chunk position - * @return cached chunk in bytes - */ - byte @Nullable [] getChunkBytes(ChunkPos pos); - - /** - * Get the position of the region - * - * @return the position of the region - */ - RegionPos getRegionPos(); - - /** - * Remove a chunk by the position - * - * @param pos the position of the chunk - */ - void removeChunk(ChunkPos pos); - - /** - * Put a chunk's data to cache - * - * @param pos the position of the chunk - * @param data the serialized data - */ - void saveChunk(ChunkPos pos, byte[] data); - - /** - * Get the data to save - * - * @return the data to save - */ - Map getRegionDataToSave(); - - /** - * If the region can be pruned or not - */ - boolean canPrune(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsSection.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsSection.java deleted file mode 100644 index d2fff01..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsSection.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.world.BlockPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public interface CustomCropsSection { - - /** - * Get the section ID - * - * @return section ID - */ - int getSectionID(); - - /** - * Get block at a certain position - * - * @param pos block position - * @return the block - */ - @Nullable - CustomCropsBlock getBlockAt(BlockPos pos); - - /** - * Remove a block by a certain position - * - * @param pos block position - * @return the removed block - */ - @Nullable - CustomCropsBlock removeBlockAt(BlockPos pos); - - /** - * Add block at a certain position - * - * @param pos block position - * @param block the new block - * @return the previous block - */ - @Nullable - CustomCropsBlock addBlockAt(BlockPos pos, CustomCropsBlock block); - - /** - * If the section can be pruned or not - */ - boolean canPrune(); - - /** - * Get the blocks in this section - * - * @return blocks - */ - CustomCropsBlock[] getBlocks(); - - /** - * Get the block map - * - * @return block map - */ - Map getBlockMap(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java deleted file mode 100644 index 25e1804..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/CustomCropsWorld.java +++ /dev/null @@ -1,412 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.RegionPos; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import org.bukkit.World; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.Optional; - -public interface CustomCropsWorld { - - /** - * Save all the data - */ - void save(); - - /** - * Start the tick system - */ - void startTick(); - - /** - * Stop the tick system - */ - void cancelTick(); - - /** - * Check if a region is loaded - * - * @param regionPos region position - * @return loaded or not - */ - boolean isRegionLoaded(RegionPos regionPos); - - /** - * Removed lazy chunks (Delayed unloading chunks) - * - * @param chunkPos chunk position - * @return the removed lazy chunk - */ - CustomCropsChunk removeLazyChunkAt(ChunkPos chunkPos); - - /** - * Get the world's settings - * - * @return world settings - */ - WorldSetting getWorldSetting(); - - /** - * Set the world's settings - * - * @param setting settings - */ - void setWorldSetting(WorldSetting setting); - - /** - * Get all the chunks - * - * @return chunks - */ - Collection getChunkStorage(); - - /** - * Get bukkit world - * This would be null if the world is unloaded - * - * @return bukkit world - */ - @Nullable - World getWorld(); - - /** - * Get the world's name - * - * @return world's name - */ - @NotNull - String getWorldName(); - - /** - * Check if the chunk is loaded - * The chunk would only be loaded when it has CustomCrops data and being loaded by minecraft chunk system - * - * @param chunkPos chunk position - * @return loaded or not - */ - boolean isChunkLoaded(ChunkPos chunkPos); - - /** - * Create CustomCrops chunk at a certain chunk position - * This method can only be called if that chunk is loaded - * Otherwise it would fail - * - * @param chunkPos chunk position - * @return the generated CustomCrops chunk - */ - Optional getOrCreateLoadedChunkAt(ChunkPos chunkPos); - - /** - * Get the loaded CustomCrops chunk at a certain chunk position - * - * @param chunkPos chunk position - * @return CustomCrops chunk - */ - Optional getLoadedChunkAt(ChunkPos chunkPos); - - /** - * Get the loaded CustomCrops region at a certain region position - * - * @param regionPos region position - * @return CustomCrops region - */ - Optional getLoadedRegionAt(RegionPos regionPos); - - /** - * Load a CustomCrops region - * You don't need to worry about the unloading since CustomCrops would unload the region - * if it's unused - * - * @param region region - */ - void loadRegion(CustomCropsRegion region); - - /** - * Load a CustomCrops chunk - * It's unsafe to call this method. Please use world.getChunkAt instead - * - * @param chunk chunk - */ - void loadChunk(CustomCropsChunk chunk); - - /** - * Unload a CustomCrops chunk - * It's unsafe to call this method - * - * @param chunkPos chunk position - */ - void unloadChunk(ChunkPos chunkPos); - - /** - * Delete a chunk's data - * - * @param chunkPos chunk position - */ - void deleteChunk(ChunkPos chunkPos); - - /** - * Set the season and date of the world - * - * @param infoData info data - */ - void setInfoData(WorldInfoData infoData); - - /** - * Get the season and date - * This might return the wrong value if sync-seasons is enabled - * - * @return info data - */ - WorldInfoData getInfoData(); - - /** - * Get the date of the world - * This would return the reference world's date if sync-seasons is enabled - * - * @return date - */ - int getDate(); - - /** - * Get the season of the world - * This would return the reference world's season if sync-seasons is enabled - * - * @return date - */ - @Nullable - Season getSeason(); - - /** - * Get sprinkler at a certain location - * - * @param location location - * @return sprinkler data - */ - Optional getSprinklerAt(SimpleLocation location); - - /** - * Get pot at a certain location - * - * @param location location - * @return pot data - */ - Optional getPotAt(SimpleLocation location); - - /** - * Get crop at a certain location - * - * @param location location - * @return crop data - */ - Optional getCropAt(SimpleLocation location); - - /** - * Get greenhouse glass at a certain location - * - * @param location location - * @return greenhouse glass data - */ - Optional getGlassAt(SimpleLocation location); - - /** - * Get scarecrow at a certain location - * - * @param location location - * @return scarecrow data - */ - Optional getScarecrowAt(SimpleLocation location); - - /** - * Get block data at a certain location - * - * @param location location - * @return block data - */ - Optional getBlockAt(SimpleLocation location); - - /** - * Add water to the sprinkler - * This method would create new sprinkler data if the sprinkler data not exists in that place - * This method would also update the sprinkler's model if it has models according to the water amount - * - * @param sprinkler sprinkler config - * @param amount amount of water - * @param location location - */ - void addWaterToSprinkler(Sprinkler sprinkler, int amount, SimpleLocation location); - - /** - * Add fertilizer to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it has appearance variations for different fertilizers - * - * @param pot pot config - * @param fertilizer fertilizer config - * @param location location - */ - void addFertilizerToPot(Pot pot, Fertilizer fertilizer, SimpleLocation location); - - /** - * Add water to the pot - * This method would create new pot data if the pot data not exists in that place - * This method would update the pot's block state if it's dry - * - * @param pot pot config - * @param amount amount of water - * @param location location - */ - void addWaterToPot(Pot pot, int amount, SimpleLocation location); - - /** - * Add points to a crop - * This method would do nothing if the crop data not exists in that place - * This method would change the stage of the crop and trigger the actions - * - * @param crop crop config - * @param points points to add - * @param location location - */ - void addPointToCrop(Crop crop, int points, SimpleLocation location); - - /** - * Remove sprinkler data from a certain location - * - * @param location location - */ - @Nullable - WorldSprinkler removeSprinklerAt(SimpleLocation location); - - /** - * Remove pot data from a certain location - * - * @param location location - */ - @Nullable - WorldPot removePotAt(SimpleLocation location); - - /** - * Remove crop data from a certain location - * - * @param location location - */ - @Nullable - WorldCrop removeCropAt(SimpleLocation location); - - /** - * Remove greenhouse glass data from a certain location - * - * @param location location - */ - @Nullable - WorldGlass removeGlassAt(SimpleLocation location); - - /** - * Remove scarecrow data from a certain location - * - * @param location location - */ - @Nullable - WorldScarecrow removeScarecrowAt(SimpleLocation location); - - /** - * Remove any block data from a certain location - * - * @param location location - * @return block data - */ - @Nullable - CustomCropsBlock removeAnythingAt(SimpleLocation location); - - boolean doesChunkHaveScarecrow(SimpleLocation location); - - /** - * If the amount of pot reaches the limitation - * - * @param location location - * @return reach or not - */ - boolean isPotReachLimit(SimpleLocation location); - - /** - * If the amount of crop reaches the limitation - * - * @param location location - * @return reach or not - */ - boolean isCropReachLimit(SimpleLocation location); - - /** - * If the amount of sprinkler reaches the limitation - * - * @param location location - * @return reach or not - */ - boolean isSprinklerReachLimit(SimpleLocation location); - - /** - * Add pot data - * - * @param pot pot data - * @param location location - */ - void addPotAt(WorldPot pot, SimpleLocation location); - - /** - * Add sprinkler data - * - * @param sprinkler sprinkler data - * @param location location - */ - void addSprinklerAt(WorldSprinkler sprinkler, SimpleLocation location); - - /** - * Add crop data - * - * @param crop crop data - * @param location location - */ - void addCropAt(WorldCrop crop, SimpleLocation location); - - /** - * Add greenhouse glass data - * - * @param glass glass data - * @param location location - */ - void addGlassAt(WorldGlass glass, SimpleLocation location); - - /** - * Add scarecrow data - * - * @param scarecrow scarecrow data - * @param location location - */ - void addScarecrowAt(WorldScarecrow scarecrow, SimpleLocation location); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/DataBlock.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/DataBlock.java deleted file mode 100644 index 0236184..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/DataBlock.java +++ /dev/null @@ -1,55 +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.mechanic.world.level; - -import com.flowpowered.nbt.Tag; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.SynchronizedCompoundMap; - -public interface DataBlock { - - /** - * Set data by key - * - * @param key key - * @param tag data tag - */ - void setData(String key, Tag tag); - - /** - * Get data tag by key - * - * @param key key - * @return data tag - */ - Tag getData(String key); - - /** - * Get the data map - * - * @return data map - */ - SynchronizedCompoundMap getCompoundMap(); - - /** - * Get the location of the block - * - * @return location - */ - SimpleLocation getLocation(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldCrop.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldCrop.java deleted file mode 100644 index e56daee..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldCrop.java +++ /dev/null @@ -1,52 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; - -public interface WorldCrop extends CustomCropsBlock { - - /** - * Get the key of the crop - * - * @return key - */ - String getKey(); - - /** - * Get the point - * - * @return point - */ - int getPoint(); - - /** - * Set the point - * - * @param point point - */ - void setPoint(int point); - - /** - * Get the config - * - * @return crop config - */ - Crop getConfig(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldGlass.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldGlass.java deleted file mode 100644 index b4b74ce..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldGlass.java +++ /dev/null @@ -1,23 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; - -public interface WorldGlass extends CustomCropsBlock { -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldPot.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldPot.java deleted file mode 100644 index 7cd7a80..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldPot.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface WorldPot extends CustomCropsBlock { - - /** - * Get the key of the pot - * - * @return key - */ - String getKey(); - - /** - * Get the amount of water - * - * @return amount of water - */ - int getWater(); - - /** - * Set the amount of water - * - * @param water water - */ - void setWater(int water); - - /** - * Get the fertilizer config - * - * @return fertilizer config - */ - @Nullable - Fertilizer getFertilizer(); - - /** - * Set the fertilizer - * - * @param fertilizer fertilizer - */ - void setFertilizer(@NotNull Fertilizer fertilizer); - - /** - * Remove the fertilizer - */ - void removeFertilizer(); - - /** - * Get the remaining usages of the fertilizer - * - * @return remaining usages of the fertilizer - */ - int getFertilizerTimes(); - - /** - * Set the remaining usages of the fertilizer - * - * @param times the remaining usages of the fertilizer - */ - void setFertilizerTimes(int times); - - /** - * Get the pot config - * - * @return pot config - */ - Pot getConfig(); - - /** - * Tick the rainwater and nearby water - */ - void tickWater(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldScarecrow.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldScarecrow.java deleted file mode 100644 index a6725af..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldScarecrow.java +++ /dev/null @@ -1,23 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; - -public interface WorldScarecrow extends CustomCropsBlock { -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSprinkler.java b/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSprinkler.java deleted file mode 100644 index 9cab54d..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/world/level/WorldSprinkler.java +++ /dev/null @@ -1,52 +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.mechanic.world.level; - -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; - -public interface WorldSprinkler extends CustomCropsBlock { - - /** - * Get the amount of water - * - * @return amount of water - */ - int getWater(); - - /** - * Set the amount of water - * - * @param water amount of water - */ - void setWater(int water); - - /** - * Get the key of the sprinkler - * - * @return key - */ - String getKey(); - - /** - * Get the sprinkler config - * - * @return sprinkler config - */ - Sprinkler getConfig(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/image/WaterBar.java b/api/src/main/java/net/momirealms/customcrops/api/misc/WaterBar.java similarity index 95% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/image/WaterBar.java rename to api/src/main/java/net/momirealms/customcrops/api/misc/WaterBar.java index c1879d6..87339d6 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/misc/image/WaterBar.java +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/WaterBar.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.misc.image; +package net.momirealms.customcrops.api.misc; public record WaterBar(String left, String empty, String full, String right) { diff --git a/api/src/main/java/net/momirealms/customcrops/api/manager/CoolDownManager.java b/api/src/main/java/net/momirealms/customcrops/api/misc/cooldown/CoolDownManager.java similarity index 54% rename from api/src/main/java/net/momirealms/customcrops/api/manager/CoolDownManager.java rename to api/src/main/java/net/momirealms/customcrops/api/misc/cooldown/CoolDownManager.java index 92396f6..5d23652 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/manager/CoolDownManager.java +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/cooldown/CoolDownManager.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,30 +15,44 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.manager; +package net.momirealms.customcrops.api.misc.cooldown; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Reloadable; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.common.plugin.feature.Reloadable; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +/** + * Manages cooldowns for various actions or events. + * Keeps track of cooldown times for different keys associated with player UUIDs. + */ public class CoolDownManager implements Listener, Reloadable { private final ConcurrentHashMap dataMap; - private final CustomCropsPlugin plugin; + private final BukkitCustomCropsPlugin plugin; - public CoolDownManager(CustomCropsPlugin plugin) { + public CoolDownManager(BukkitCustomCropsPlugin plugin) { this.dataMap = new ConcurrentHashMap<>(); this.plugin = plugin; } + /** + * Checks if a player is currently in cooldown for a specific key. + * + * @param uuid The UUID of the player. + * @param key The key associated with the cooldown. + * @param time The cooldown time in milliseconds. + * @return True if the player is in cooldown, false otherwise. + */ public boolean isCoolDown(UUID uuid, String key, long time) { Data data = this.dataMap.computeIfAbsent(uuid, k -> new Data()); return data.isCoolDown(key, time); @@ -46,7 +60,7 @@ public class CoolDownManager implements Listener, Reloadable { @Override public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); } @Override @@ -60,6 +74,11 @@ public class CoolDownManager implements Listener, Reloadable { this.dataMap.clear(); } + /** + * Event handler for when a player quits the game. Removes their cooldown data. + * + * @param event The PlayerQuitEvent triggered when a player quits. + */ @EventHandler public void onQuit(PlayerQuitEvent event) { dataMap.remove(event.getPlayer().getUniqueId()); @@ -67,20 +86,27 @@ public class CoolDownManager implements Listener, Reloadable { public static class Data { - private final HashMap coolDownMap; + private final Map coolDownMap; public Data() { - this.coolDownMap = new HashMap<>(); + this.coolDownMap = Collections.synchronizedMap(new HashMap<>()); } - public synchronized boolean isCoolDown(String key, long delay) { + /** + * Checks if the player is in cooldown for a specific key. + * + * @param key The key associated with the cooldown. + * @param delay The cooldown delay in milliseconds. + * @return True if the player is in cooldown, false otherwise. + */ + public boolean isCoolDown(String key, long delay) { long time = System.currentTimeMillis(); long last = coolDownMap.getOrDefault(key, time - delay); if (last + delay > time) { - return true; + return true; // Player is in cooldown } else { coolDownMap.put(key, time); - return false; + return false; // Player is not in cooldown } } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/BukkitPlaceholderManager.java b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/BukkitPlaceholderManager.java new file mode 100644 index 0000000..9fae019 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/BukkitPlaceholderManager.java @@ -0,0 +1,145 @@ +/* + * 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.misc.placeholder; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.common.util.RandomUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +/** + * This class handles the registration and parsing of custom placeholders, + * and integrates with PlaceholderAPI if available. + */ +public class BukkitPlaceholderManager implements PlaceholderManager { + + private final BukkitCustomCropsPlugin plugin; + private boolean hasPapi; + private final HashMap, String>> customPlaceholderMap; + private static BukkitPlaceholderManager instance; + + /** + * Constructs a new BukkitPlaceholderManager instance. + * + * @param plugin the instance of {@link BukkitCustomCropsPlugin} + */ + public BukkitPlaceholderManager(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + this.customPlaceholderMap = new HashMap<>(); + instance = this; + } + + /** + * Loads the placeholder manager, checking for PlaceholderAPI and registering default placeholders. + */ + @Override + public void load() { + this.hasPapi = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + this.customPlaceholderMap.put("{random}", (p, map) -> String.valueOf(RandomUtils.generateRandomDouble(0, 1))); + } + + /** + * Unloads the placeholder manager, clearing all registered placeholders. + */ + @Override + public void unload() { + this.hasPapi = false; + this.customPlaceholderMap.clear(); + } + + /** + * Gets the singleton instance of BukkitPlaceholderManager. + * + * @return the singleton instance + */ + public static BukkitPlaceholderManager getInstance() { + return instance; + } + + @Override + public boolean registerCustomPlaceholder(String placeholder, String original) { + if (this.customPlaceholderMap.containsKey(placeholder)) return false; + this.customPlaceholderMap.put(placeholder, (p, map) -> PlaceholderAPIUtils.parse(p, parse(p, original, map))); + return true; + } + + @Override + public boolean registerCustomPlaceholder(String placeholder, BiFunction, String> provider) { + if (this.customPlaceholderMap.containsKey(placeholder)) return false; + this.customPlaceholderMap.put(placeholder, provider); + return true; + } + + @Override + public List resolvePlaceholders(String text) { + List placeholders = new ArrayList<>(); + Matcher matcher = PATTERN.matcher(text); + while (matcher.find()) placeholders.add(matcher.group()); + return placeholders; + } + + private String setPlaceholders(OfflinePlayer player, String text) { + return hasPapi ? PlaceholderAPIUtils.parse(player, text) : text; + } + + @Override + public String parseSingle(@Nullable OfflinePlayer player, String placeholder, Map replacements) { + String result = null; + if (replacements != null) + result = replacements.get(placeholder); + if (result != null) + return result; + String custom = Optional.ofNullable(customPlaceholderMap.get(placeholder)).map(supplier -> supplier.apply(player, replacements)).orElse(null); + if (custom == null) + return placeholder; + return setPlaceholders(player, custom); + } + + @Override + public String parse(@Nullable OfflinePlayer player, String text, Map replacements) { + var list = resolvePlaceholders(text); + for (String papi : list) { + String replacer = null; + if (replacements != null) { + replacer = replacements.get(papi); + } + if (replacer == null) { + String custom = Optional.ofNullable(customPlaceholderMap.get(papi)).map(supplier -> supplier.apply(player, replacements)).orElse(null); + if (custom != null) + replacer = setPlaceholders(player, parse(player, custom, replacements)); + } + if (replacer != null) { + text = text.replace(papi, replacer); + } + } + return text; + } + + @Override + public List parse(@Nullable OfflinePlayer player, List list, Map replacements) { + return list.stream() + .map(s -> parse(player, s, replacements)) + .collect(Collectors.toList()); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderAPIUtils.java b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderAPIUtils.java new file mode 100644 index 0000000..4fdfeaf --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderAPIUtils.java @@ -0,0 +1,51 @@ +/* + * 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.misc.placeholder; + +import me.clip.placeholderapi.PlaceholderAPI; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +/** + * Utility class for interacting with the PlaceholderAPI. + * Provides methods to parse placeholders in strings for both online and offline players. + */ +public class PlaceholderAPIUtils { + + /** + * Parses placeholders in the provided text for an online player. + * + * @param player The online player for whom the placeholders should be parsed. + * @param text The text containing placeholders to be parsed. + * @return The text with parsed placeholders. + */ + public static String parse(Player player, String text) { + return PlaceholderAPI.setPlaceholders(player, text); + } + + /** + * Parses placeholders in the provided text for an offline player. + * + * @param player The offline player for whom the placeholders should be parsed. + * @param text The text containing placeholders to be parsed. + * @return The text with parsed placeholders. + */ + public static String parse(OfflinePlayer player, String text) { + return PlaceholderAPI.setPlaceholders(player, text); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderManager.java b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderManager.java new file mode 100644 index 0000000..275d35d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/placeholder/PlaceholderManager.java @@ -0,0 +1,88 @@ +/* + * 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.misc.placeholder; + +import net.momirealms.customcrops.common.plugin.feature.Reloadable; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.regex.Pattern; + +public interface PlaceholderManager extends Reloadable { + + Pattern PATTERN = Pattern.compile("\\{[^{}]+}"); + + /** + * Registers a custom placeholder. + * + * @param placeholder the placeholder to be registered + * @param original the original placeholder string for instance {@code %test_placeholder%} + * @return true if the placeholder was successfully registered, false otherwise + */ + boolean registerCustomPlaceholder(String placeholder, String original); + + /** + * Registers a custom placeholder. + * + * @param placeholder the placeholder to be registered + * @param provider the value provider + * @return true if the placeholder was successfully registered, false otherwise + */ + boolean registerCustomPlaceholder(String placeholder, BiFunction, String> provider); + + /** + * Resolves all placeholders within a given text. + * + * @param text the text to resolve placeholders in. + * @return a list of found placeholders. + */ + List resolvePlaceholders(String text); + + /** + * Parses a single placeholder for the specified player, using the provided replacements. + * + * @param player the player for whom the placeholder is being parsed + * @param placeholder the placeholder to be parsed + * @param replacements a map of replacements to be used + * @return the parsed placeholder value + */ + String parseSingle(@Nullable OfflinePlayer player, String placeholder, Map replacements); + + /** + * Parses placeholders in the given text for the specified player, using the provided replacements. + * + * @param player the player for whom placeholders are being parsed + * @param text the text containing placeholders + * @param replacements a map of replacements to be used + * @return the text with placeholders replaced + */ + String parse(@Nullable OfflinePlayer player, String text, Map replacements); + + /** + * Parses placeholders in the given list of texts for the specified player, using the provided replacements. + * + * @param player the player for whom placeholders are being parsed + * @param list the list of texts containing placeholders + * @param replacements a map of replacements to be used + * @return the list of texts with placeholders replaced + */ + List parse(@Nullable OfflinePlayer player, List list, Map replacements); +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/DynamicText.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/DynamicText.java new file mode 100644 index 0000000..816df34 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/DynamicText.java @@ -0,0 +1,76 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.misc.placeholder.BukkitPlaceholderManager; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DynamicText { + + private final Player owner; + private String originalValue; + private String latestValue; + private String[] placeholders; + + public DynamicText(Player owner, String rawValue) { + this.owner = owner; + analyze(rawValue); + } + + private void analyze(String value) { + // Analyze the provided text to find and replace placeholders with '%s'. + // Store the original value, placeholders, and the initial latest value. + List placeholdersOwner = new ArrayList<>(BukkitPlaceholderManager.getInstance().resolvePlaceholders(value)); + String origin = value; + for (String placeholder : placeholdersOwner) { + origin = origin.replace(placeholder, "%s"); + } + originalValue = origin; + placeholders = placeholdersOwner.toArray(new String[0]); + latestValue = originalValue; + } + + public String getLatestValue() { + return latestValue; + } + + public boolean update(Map placeholders) { + String string = originalValue; + if (this.placeholders.length != 0) { + BukkitPlaceholderManager bukkitPlaceholderManager = BukkitPlaceholderManager.getInstance(); + if ("%s".equals(originalValue)) { + string = bukkitPlaceholderManager.parseSingle(owner, this.placeholders[0], placeholders); + } else { + Object[] values = new String[this.placeholders.length]; + for (int i = 0; i < this.placeholders.length; i++) { + values[i] = bukkitPlaceholderManager.parseSingle(owner, this.placeholders[i], placeholders); + } + string = String.format(originalValue, values); + } + } + if (!latestValue.equals(string)) { + latestValue = string; + return true; + } + return false; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/ExpressionMathValueImpl.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/ExpressionMathValueImpl.java new file mode 100644 index 0000000..56bb5f0 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/ExpressionMathValueImpl.java @@ -0,0 +1,41 @@ +/* + * 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.misc.value; + + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.common.helper.ExpressionHelper; + +public class ExpressionMathValueImpl implements MathValue { + + private final TextValue raw; + + public ExpressionMathValueImpl(String raw) { + this.raw = TextValue.auto(raw); + } + + @Override + public double evaluate(Context context) { + return ExpressionHelper.evaluate(raw.render(context)); + } + + @Override + public double evaluate(Context context, boolean parseRawPlaceholders) { + return ExpressionHelper.evaluate(raw.render(context, parseRawPlaceholders)); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/MathValue.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/MathValue.java new file mode 100644 index 0000000..f31b859 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/MathValue.java @@ -0,0 +1,123 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.context.Context; + +/** + * The MathValue interface represents a mathematical value that can be evaluated + * within a specific context. This interface allows for the evaluation of mathematical + * expressions or plain numerical values in the context of custom fishing mechanics. + * + * @param the type of the holder object for the context + */ +public interface MathValue { + + /** + * Evaluates the mathematical value within the given context. + * + * @param context the context in which the value is evaluated + * @return the evaluated value as a double + */ + double evaluate(Context context); + + /** + * Evaluates the mathematical value within the given context. + * + * @param context the context in which the value is evaluated + * @param parseRawPlaceholders whether to parse raw placeholders for instance %xxx% + * @return the evaluated value as a double + */ + default double evaluate(Context context, boolean parseRawPlaceholders) { + return evaluate(context); + } + + /** + * Creates a MathValue based on a mathematical expression. + * + * @param expression the mathematical expression to evaluate + * @param the type of the holder object for the context + * @return a MathValue instance representing the given expression + */ + static MathValue expression(String expression) { + return new ExpressionMathValueImpl<>(expression); + } + + /** + * Creates a MathValue based on a plain numerical value. + * + * @param value the numerical value to represent + * @param the type of the holder object for the context + * @return a MathValue instance representing the given plain value + */ + static MathValue plain(double value) { + return new PlainMathValueImpl<>(value); + } + + /** + * Creates a MathValue based on a range of values. + * + * @param value the ranged value to represent + * @param the type of the holder object for the context + * @return a MathValue instance representing the given ranged value + */ + static MathValue rangedDouble(String value) { + return new RangedDoubleValueImpl<>(value); + } + + /** + * Creates a MathValue based on a range of values. + * + * @param value the ranged value to represent + * @param the type of the holder object for the context + * @return a MathValue instance representing the given ranged value + */ + static MathValue rangedInt(String value) { + return new RangedIntValueImpl<>(value); + } + + /** + * Automatically creates a MathValue based on the given object. + * If the object is a String, it is treated as a mathematical expression. + * If the object is a numerical type (Double, Integer, Long, Float), it is treated as a plain value. + * + * @param o the object to evaluate and create a MathValue from + * @param the type of the holder object for the context + * @return a MathValue instance representing the given object, either as an expression or a plain value + * @throws IllegalArgumentException if the object type is not supported + */ + static MathValue auto(Object o) { + return auto(o, false); + } + + static MathValue auto(Object o, boolean intFirst) { + if (o instanceof String s) { + if (s.contains("~")) { + return intFirst ? (s.contains(".") ? rangedDouble(s) : rangedInt(s)) : rangedDouble(s); + } + try { + return plain(Double.parseDouble(s)); + } catch (NumberFormatException e) { + return expression(s); + } + } else if (o instanceof Number n) { + return plain(n.doubleValue()); + } + throw new IllegalArgumentException("Unsupported type: " + o.getClass()); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlaceholderTextValueImpl.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlaceholderTextValueImpl.java new file mode 100644 index 0000000..df6fd97 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlaceholderTextValueImpl.java @@ -0,0 +1,42 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.misc.placeholder.BukkitPlaceholderManager; +import org.bukkit.OfflinePlayer; + +import java.util.Map; + +public class PlaceholderTextValueImpl implements TextValue { + + private final String raw; + + public PlaceholderTextValueImpl(String raw) { + this.raw = raw; + } + + @Override + public String render(Context context) { + Map replacements = context.placeholderMap(); + String text; + if (context.holder() instanceof OfflinePlayer player) text = BukkitPlaceholderManager.getInstance().parse(player, raw, replacements); + else text = BukkitPlaceholderManager.getInstance().parse(null, raw, replacements); + return text; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/PlainValue.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainMathValueImpl.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/PlainValue.java rename to api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainMathValueImpl.java index 5a56f70..e4885e5 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/PlainValue.java +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainMathValueImpl.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,21 +15,20 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.misc.value; +package net.momirealms.customcrops.api.misc.value; -import net.momirealms.customcrops.api.mechanic.misc.Value; -import org.bukkit.entity.Player; +import net.momirealms.customcrops.api.context.Context; -public class PlainValue implements Value { +public class PlainMathValueImpl implements MathValue { private final double value; - public PlainValue(double value) { + public PlainMathValueImpl(double value) { this.value = value; } @Override - public double get(Player player) { + public double evaluate(Context context) { return value; } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/EmptyAction.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainTextValueImpl.java similarity index 64% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/action/EmptyAction.java rename to api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainTextValueImpl.java index 811f8b0..28bdafc 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/EmptyAction.java +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/PlainTextValueImpl.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,20 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.action; +package net.momirealms.customcrops.api.misc.value; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.requirement.State; +import net.momirealms.customcrops.api.context.Context; -public class EmptyAction implements Action { +public class PlainTextValueImpl implements TextValue { - public static EmptyAction instance = new EmptyAction(); + private final String raw; + + public PlainTextValueImpl(String raw) { + this.raw = raw; + } @Override - public void trigger(State state) { + public String render(Context context) { + return raw; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedDoubleValueImpl.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedDoubleValueImpl.java new file mode 100644 index 0000000..142d649 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedDoubleValueImpl.java @@ -0,0 +1,41 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.common.util.RandomUtils; + +public class RangedDoubleValueImpl implements MathValue { + + private final MathValue min; + private final MathValue max; + + public RangedDoubleValueImpl(String value) { + String[] split = value.split("~"); + if (split.length != 2) { + throw new IllegalArgumentException("Correct ranged format `a~b`"); + } + this.min = MathValue.auto(split[0]); + this.max = MathValue.auto(split[1]); + } + + @Override + public double evaluate(Context context) { + return RandomUtils.generateRandomDouble(min.evaluate(context), max.evaluate(context)); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedIntValueImpl.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedIntValueImpl.java new file mode 100644 index 0000000..7d16b38 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/RangedIntValueImpl.java @@ -0,0 +1,41 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.common.util.RandomUtils; + +public class RangedIntValueImpl implements MathValue { + + private final MathValue min; + private final MathValue max; + + public RangedIntValueImpl(String value) { + String[] split = value.split("~"); + if (split.length != 2) { + throw new IllegalArgumentException("Correct ranged format `a~b`"); + } + this.min = MathValue.auto(split[0]); + this.max = MathValue.auto(split[1]); + } + + @Override + public double evaluate(Context context) { + return RandomUtils.generateRandomInt((int) min.evaluate(context), (int) max.evaluate(context)); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/misc/value/TextValue.java b/api/src/main/java/net/momirealms/customcrops/api/misc/value/TextValue.java new file mode 100644 index 0000000..3366bc4 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/misc/value/TextValue.java @@ -0,0 +1,96 @@ +/* + * 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.misc.value; + +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.misc.placeholder.PlaceholderAPIUtils; +import org.bukkit.OfflinePlayer; + +import java.util.regex.Pattern; + +/** + * The TextValue interface represents a text value that can be rendered + * within a specific context. This interface allows for the rendering of + * placeholder-based or plain text values in the context of custom fishing mechanics. + * + * @param the type of the holder object for the context + */ +public interface TextValue { + + Pattern pattern = Pattern.compile("\\{[^{}]+}"); + + /** + * Renders the text value within the given context. + * + * @param context the context in which the text value is rendered + * @return the rendered text as a String + */ + String render(Context context); + + /** + * Renders the text value within the given context. + * + * @param context the context in which the text value is rendered + * @param parseRawPlaceholders whether to parse raw placeholders for instance %xxx% + * @return the rendered text as a String + */ + default String render(Context context, boolean parseRawPlaceholders) { + if (!parseRawPlaceholders || !(context.holder() instanceof OfflinePlayer player)) return render(context); + return PlaceholderAPIUtils.parse(player, render(context)); + } + + /** + * Creates a TextValue based on a placeholder text. + * Placeholders can be dynamically replaced with context-specific values. + * + * @param text the placeholder text to render + * @param the type of the holder object for the context + * @return a TextValue instance representing the given placeholder text + */ + static TextValue placeholder(String text) { + return new PlaceholderTextValueImpl<>(text); + } + + /** + * Creates a TextValue based on plain text. + * + * @param text the plain text to render + * @param the type of the holder object for the context + * @return a TextValue instance representing the given plain text + */ + static TextValue plain(String text) { + return new PlainTextValueImpl<>(text); + } + + /** + * Automatically creates a TextValue based on the given argument. + * If the argument contains placeholders (detected by a regex pattern), + * a PlaceholderTextValueImpl instance is created. Otherwise, a PlainTextValueImpl + * instance is created. + * + * @param arg the text to evaluate and create a TextValue from + * @param the type of the holder object for the context + * @return a TextValue instance representing the given text, either as a placeholder or plain text + */ + static TextValue auto(String arg) { + if (pattern.matcher(arg).find()) + return placeholder(arg); + else + return plain(arg); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/requirement/AbstractRequirementManager.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/AbstractRequirementManager.java new file mode 100644 index 0000000..4e6b727 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/AbstractRequirementManager.java @@ -0,0 +1,1118 @@ +package net.momirealms.customcrops.api.requirement; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.ContextKeys; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.block.CrowAttack; +import net.momirealms.customcrops.api.core.block.GreenhouseBlock; +import net.momirealms.customcrops.api.core.block.PotBlock; +import net.momirealms.customcrops.api.core.block.ScarecrowBlock; +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.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.api.core.world.Season; +import net.momirealms.customcrops.api.misc.value.MathValue; +import net.momirealms.customcrops.api.misc.value.TextValue; +import net.momirealms.customcrops.api.util.MoonPhase; +import net.momirealms.customcrops.common.util.ClassUtils; +import net.momirealms.customcrops.common.util.ListUtils; +import net.momirealms.customcrops.common.util.Pair; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Farmland; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +import static java.util.Objects.requireNonNull; + +@SuppressWarnings("DuplicatedCode") +public abstract class AbstractRequirementManager implements RequirementManager { + + private final HashMap> requirementFactoryMap = new HashMap<>(); + private static final String EXPANSION_FOLDER = "expansions/requirement"; + protected final BukkitCustomCropsPlugin plugin; + protected Class tClass; + + public AbstractRequirementManager(BukkitCustomCropsPlugin plugin, Class tClass) { + this.plugin = plugin; + this.tClass = tClass; + this.registerBuiltInRequirements(); + } + + protected void registerBuiltInRequirements() { + this.registerEnvironmentRequirement(); + this.registerTimeRequirement(); + this.registerYRequirement(); + this.registerAndRequirement(); + this.registerOrRequirement(); + this.registerMoonPhaseRequirement(); + this.registerRandomRequirement(); + this.registerBiomeRequirement(); + this.registerSeasonRequirement(); + this.registerWorldRequirement(); + this.registerDateRequirement(); + this.registerWeatherRequirement(); + this.registerPAPIRequirement(); + this.registerLightRequirement(); + this.registerTemperatureRequirement(); + this.registerFertilizerRequirement(); + this.registerPotRequirement(); + this.registerCrowAttackRequirement(); + this.registerWaterRequirement(); + } + + @Override + public boolean registerRequirement(@NotNull RequirementFactory requirementFactory, @NotNull String... types) { + for (String type : types) { + if (this.requirementFactoryMap.containsKey(type)) return false; + } + for (String type : types) { + this.requirementFactoryMap.put(type, requirementFactory); + } + return true; + } + + @Override + public boolean unregisterRequirement(@NotNull String type) { + return this.requirementFactoryMap.remove(type) != null; + } + + @Override + public boolean hasRequirement(@NotNull String type) { + return requirementFactoryMap.containsKey(type); + } + + @Nullable + @Override + public RequirementFactory getRequirementFactory(@NotNull String type) { + return requirementFactoryMap.get(type); + } + + @NotNull + @SuppressWarnings("unchecked") + @Override + public Requirement[] parseRequirements(Section section, boolean runActions) { + List> requirements = new ArrayList<>(); + if (section != null) + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + String typeOrName = entry.getKey(); + if (hasRequirement(typeOrName)) { + requirements.add(parseRequirement(typeOrName, entry.getValue())); + } else { + Section inner = section.getSection(typeOrName); + if (inner != null) { + requirements.add(parseRequirement(inner, runActions)); + } else { + plugin.getPluginLogger().warn("Section " + typeOrName + " is misconfigured"); + } + } + } + return requirements.toArray(new Requirement[0]); + } + + @NotNull + @Override + public Requirement parseRequirement(@NotNull Section section, boolean runActions) { + List> actionList = new ArrayList<>(); + if (runActions && section.contains("not-met-actions")) { + Action[] actions = plugin.getActionManager(tClass).parseActions(requireNonNull(section.getSection("not-met-actions"))); + actionList.addAll(List.of(actions)); + } + String type = section.getString("type"); + if (type == null) { + plugin.getPluginLogger().warn("No requirement type found at " + section.getRouteAsString()); + return Requirement.empty(); + } + var factory = getRequirementFactory(type); + if (factory == null) { + plugin.getPluginLogger().warn("Requirement type: " + type + " not exists"); + return Requirement.empty(); + } + return factory.process(section.get("value"), actionList, runActions); + } + + @NotNull + @Override + public Requirement parseRequirement(@NotNull String type, @NotNull Object value) { + RequirementFactory factory = getRequirementFactory(type); + if (factory == null) { + plugin.getPluginLogger().warn("Requirement type: " + type + " doesn't exist."); + return Requirement.empty(); + } + return factory.process(value); + } + + @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> expansionClass = (Class>) ClassUtils.findClass(expansionJar, RequirementExpansion.class, tClass); + classes.add(expansionClass); + } catch (IOException | ClassNotFoundException e) { + plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e); + } + } + } + try { + for (Class> expansionClass : classes) { + RequirementExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); + unregisterRequirement(expansion.getRequirementType()); + registerRequirement(expansion.getRequirementFactory(), expansion.getRequirementType()); + plugin.getPluginLogger().info("Loaded requirement expansion: " + expansion.getRequirementType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor()); + } + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | + NoSuchMethodException e) { + plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e); + } + } + + protected void registerEnvironmentRequirement() { + registerRequirement((args, actions, advanced) -> { + List environments = ListUtils.toList(args); + return context -> { + Location location = context.arg(ContextKeys.LOCATION); + if (location == null) return false; + var name = location.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); + if (environments.contains(name)) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "environment"); + registerRequirement((args, actions, advanced) -> { + List environments = ListUtils.toList(args); + return context -> { + Location location = context.arg(ContextKeys.LOCATION); + if (location == null) return false; + var name = location.getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); + if (!environments.contains(name)) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "!environment"); + } + + protected void registerTimeRequirement() { + registerRequirement((args, actions, runActions) -> { + List list = ListUtils.toList(args); + List> timePairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long time = location.getWorld().getTime(); + for (Pair pair : timePairs) + if (time >= pair.left() && time <= pair.right()) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "time"); + } + + protected void registerYRequirement() { + registerRequirement((args, actions, runActions) -> { + List list = ListUtils.toList(args); + List> posPairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Double.parseDouble(split[0]), Double.parseDouble(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + double y = location.getY(); + for (Pair pair : posPairs) + if (y >= pair.left() && y <= pair.right()) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "ypos"); + } + + protected void registerOrRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + Requirement[] requirements = parseRequirements(section, runActions); + return context -> { + for (Requirement requirement : requirements) + if (requirement.isSatisfied(context)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at || requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "||"); + } + + protected void registerAndRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + Requirement[] requirements = parseRequirements(section, runActions); + return context -> { + outer: { + for (Requirement requirement : requirements) + if (!requirement.isSatisfied(context)) + break outer; + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at && requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "&&"); + } + + protected void registerRandomRequirement() { + registerRequirement((args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (Math.random() < value.evaluate(context, true)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "random"); + } + + protected void registerBiomeRequirement() { + registerRequirement((args, actions, runActions) -> { + HashSet biomes = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(location); + if (biomes.contains(currentBiome)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "biome"); + registerRequirement((args, actions, runActions) -> { + HashSet biomes = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(location); + if (!biomes.contains(currentBiome)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "!biome"); + } + + protected void registerMoonPhaseRequirement() { + registerRequirement((args, actions, runActions) -> { + HashSet moonPhases = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long days = location.getWorld().getFullTime() / 24_000; + if (moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "moon-phase"); + registerRequirement((args, actions, runActions) -> { + HashSet moonPhases = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + long days = location.getWorld().getFullTime() / 24_000; + if (!moonPhases.contains(MoonPhase.getPhase(days).name().toLowerCase(Locale.ENGLISH))) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "!moon-phase"); + } + + protected void registerWorldRequirement() { + registerRequirement((args, actions, runActions) -> { + HashSet worlds = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + if (worlds.contains(location.getWorld().getName())) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "world"); + registerRequirement((args, actions, runActions) -> { + HashSet worlds = new HashSet<>(ListUtils.toList(args)); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + if (!worlds.contains(location.getWorld().getName())) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "!world"); + } + + protected void registerWeatherRequirement() { + registerRequirement((args, actions, runActions) -> { + HashSet weathers = new HashSet<>(ListUtils.toList(args)); + return context -> { + String currentWeather; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + World world = location.getWorld(); + if (world.isClearWeather()) currentWeather = "clear"; + else if (world.isThundering()) currentWeather = "thunder"; + else currentWeather = "rain"; + if (weathers.contains(currentWeather)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "weather"); + } + + protected void registerDateRequirement() { + registerRequirement((args, actions, runActions) -> { + HashSet dates = new HashSet<>(ListUtils.toList(args)); + return context -> { + Calendar calendar = Calendar.getInstance(); + String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE); + if (dates.contains(current)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "date"); + } + + protected void registerSeasonRequirement() { + registerRequirement((args, actions, runActions) -> { + Set seasons = new HashSet<>(ListUtils.toList(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Season season = plugin.getWorldManager().getSeason(location.getWorld()); + if (season == Season.DISABLE) return true; + if (seasons.contains(season.name())) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "season"); + registerRequirement((args, actions, runActions) -> { + Set seasons = new HashSet<>(ListUtils.toList(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Season season = plugin.getWorldManager().getSeason(location.getWorld()); + if (!seasons.contains(season.name())) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "!season"); + registerRequirement((args, actions, runActions) -> { + Set seasons = new HashSet<>(ListUtils.toList(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Season season = plugin.getWorldManager().getSeason(location.getWorld()); + if (season == Season.DISABLE || seasons.contains(season.name())) { + return true; + } + if (ConfigManager.enableGreenhouse()) { + Pos3 pos3 = Pos3.from(location); + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isPresent()) { + CustomCropsWorld cropsWorld = world.get(); + for (int i = 1, range = ConfigManager.greenhouseRange(); i <= range; i++) { + Optional optionalState = cropsWorld.getBlockState(pos3.add(0,i,0)); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof GreenhouseBlock) { + return true; + } + } + } + } + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "suitable-season", "suitable_season"); + registerRequirement((args, actions, runActions) -> { + Set seasons = new HashSet<>(ListUtils.toList(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Season season = plugin.getWorldManager().getSeason(location.getWorld()); + if (seasons.contains(season.name())) { + if (ConfigManager.enableGreenhouse()) { + Pos3 pos3 = Pos3.from(location); + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isPresent()) { + CustomCropsWorld cropsWorld = world.get(); + for (int i = 1, range = ConfigManager.greenhouseRange(); i <= range; i++) { + Optional optionalState = cropsWorld.getBlockState(pos3.add(0,i,0)); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof GreenhouseBlock) { + if (runActions) ActionManager.trigger(context, actions); + return false; + } + } + } + } + } + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "unsuitable-season", "unsuitable_season"); + } + + protected void registerPAPIRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) < v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at < requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "<"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) <= v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at <= requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "<="); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) != v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at != requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!="); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) == v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at == requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "=="); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) >= v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at >= requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, ">="); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + MathValue v1 = MathValue.auto(section.get("value1")); + MathValue v2 = MathValue.auto(section.get("value2")); + return context -> { + if (v1.evaluate(context, true) > v2.evaluate(context, true)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at > requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, ">"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("papi", "")); + String v2 = section.getString("regex", ""); + return context -> { + if (v1.render(context, true).matches(v2)) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at regex requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "regex"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context, true).startsWith(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at startsWith requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "startsWith"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context, true).startsWith(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !startsWith requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!startsWith"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context, true).endsWith(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at endsWith requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "endsWith"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context, true).endsWith(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !endsWith requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!endsWith"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (v1.render(context, true).contains(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at contains requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "contains"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context, true).contains(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !contains requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!contains"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue papi = TextValue.auto(section.getString("papi", "")); + List values = ListUtils.toList(section.get("values")); + return context -> { + if (values.contains(papi.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at in-list requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "in-list"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue papi = TextValue.auto(section.getString("papi", "")); + List values = ListUtils.toList(section.get("values")); + return context -> { + if (!values.contains(papi.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !in-list requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!in-list"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + + return context -> { + if (v1.render(context, true).equals(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at equals requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "equals"); + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + TextValue v1 = TextValue.auto(section.getString("value1", "")); + TextValue v2 = TextValue.auto(section.getString("value2", "")); + return context -> { + if (!v1.render(context, true).equals(v2.render(context, true))) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at !equals requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "!equals"); + } + + protected void registerFertilizerRequirement() { + registerRequirement((args, actions, advanced) -> { + if (args instanceof Section section) { + boolean has = section.getBoolean("has"); + int y = section.getInt("y", 0); + HashSet types = new HashSet<>(ListUtils.toList(section.get("type")).stream().map(str -> str.toUpperCase(Locale.ENGLISH)).toList()); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0,y,0); + Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld()); + if (optionalWorld.isEmpty()) { + return false; + } + Pos3 pos3 = Pos3.from(location); + CustomCropsWorld world = optionalWorld.get(); + Optional optionalState = world.getBlockState(pos3); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof PotBlock potBlock) { + Fertilizer[] fertilizers = potBlock.fertilizers(optionalState.get()); + if (fertilizers.length == 0) { + if (!has && types.isEmpty()) { + return true; + } + } else { + if (has) { + if (types.isEmpty()) { + return true; + } + for (Fertilizer fertilizer : fertilizers) { + FertilizerConfig config = fertilizer.config(); + if (config != null) { + if (types.contains(config.type().id())) { + return true; + } + } + } + } else { + outer: { + for (Fertilizer fertilizer : fertilizers) { + FertilizerConfig config = fertilizer.config(); + if (config != null) { + if (types.contains(config.type().id())) { + break outer; + } + } + } + return true; + } + } + } + } + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at fertilizer-type requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "fertilizer_type", "fertilizer-type"); + registerRequirement((args, actions, advanced) -> { + if (args instanceof Section section) { + boolean has = section.getBoolean("has"); + int y = section.getInt("y", 0); + HashSet keys = new HashSet<>(ListUtils.toList(section.get("key"))); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0,y,0); + Pos3 pos3 = Pos3.from(location); + Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld()); + if (optionalWorld.isEmpty()) { + return false; + } + CustomCropsWorld world = optionalWorld.get(); + Optional optionalState = world.getBlockState(pos3); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof PotBlock potBlock) { + Fertilizer[] fertilizers = potBlock.fertilizers(optionalState.get()); + if (fertilizers.length == 0) { + if (!has && keys.isEmpty()) { + return true; + } + } else { + if (has) { + if (keys.isEmpty()) { + return true; + } + for (Fertilizer fertilizer : fertilizers) { + if (keys.contains(fertilizer.id())) { + return true; + } + } + } else { + outer: { + for (Fertilizer fertilizer : fertilizers) { + if (keys.contains(fertilizer.id())) { + break outer; + } + } + return true; + } + } + } + } + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at fertilizer requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "fertilizer"); + } + + protected void registerLightRequirement() { + registerRequirement((args, actions, advanced) -> { + List list = ListUtils.toList(args); + List> lightPairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int temp = location.getBlock().getLightLevel(); + for (Pair pair : lightPairs) + if (temp >= pair.left() && temp <= pair.right()) + return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "light"); + registerRequirement((args, actions, advanced) -> { + List list = ListUtils.toList(args); + List> lightPairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int temp = location.getBlock().getLightFromSky(); + for (Pair pair : lightPairs) + if (temp >= pair.left() && temp <= pair.right()) + return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "natural-light"); + registerRequirement((args, actions, advanced) -> { + int value = (int) args; + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int light = location.getBlock().getLightFromSky(); + if (light > value) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "skylight_more_than", "skylight-more-than"); + registerRequirement((args, actions, advanced) -> { + int value = (int) args; + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int light = location.getBlock().getLightFromSky(); + if (light < value) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "skylight_less_than", "skylight-less-than"); + registerRequirement((args, actions, advanced) -> { + int value = (int) args; + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int light = location.getBlock().getLightLevel(); + if (light > value) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "light_more_than", "light-more-than"); + registerRequirement((args, actions, advanced) -> { + int value = (int) args; + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + int light = location.getBlock().getLightLevel(); + if (light < value) return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "light_less_than", "light-less-than"); + } + + protected void registerTemperatureRequirement() { + registerRequirement((args, actions, advanced) -> { + List list = ListUtils.toList(args); + List> temperaturePairs = list.stream().map(line -> { + String[] split = line.split("~"); + return new Pair<>(Integer.parseInt(split[0]), Integer.parseInt(split[1])); + }).toList(); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + double temp = location.getWorld().getTemperature(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + for (Pair pair : temperaturePairs) + if (temp >= pair.left() && temp <= pair.right()) + return true; + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "temperature"); + } + + private void registerPotRequirement() { + registerRequirement((args, actions, advanced) -> { + if (args instanceof Section section) { + int y = section.getInt("y", 0); + HashSet ids = new HashSet<>(ListUtils.toList(section.get("id"))); + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0,y,0); + Pos3 pos3 = Pos3.from(location); + Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld()); + if (optionalWorld.isEmpty()) { + return false; + } + CustomCropsWorld world = optionalWorld.get(); + Optional optionalState = world.getBlockState(pos3); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof PotBlock potBlock) { + if (ids.contains(potBlock.id(optionalState.get()))) { + return true; + } + } + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at pot requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "pot"); + } + + private void registerCrowAttackRequirement() { + registerRequirement((args, actions, advanced) -> { + if (args instanceof Section section) { + String flyModel = section.getString("fly-model"); + String standModel = section.getString("stand-model"); + double chance = section.getDouble("chance"); + return (context) -> { + if (Math.random() > chance) return false; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Pos3 pos3 = Pos3.from(location); + if (ConfigManager.enableScarecrow()) { + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isEmpty()) return false; + CustomCropsWorld customCropsWorld = world.get(); + if (!ConfigManager.scarecrowProtectChunk()) { + int range = ConfigManager.scarecrowRange(); + for (int i = -range; i <= range; i++) { + for (int j = -range; j <= range; j++) { + for (int k : new int[]{0,-1,1}) { + Optional optionalState = customCropsWorld.getBlockState(pos3.add(i, k, j)); + if (optionalState.isPresent() && optionalState.get().type() instanceof ScarecrowBlock) { + if (advanced) ActionManager.trigger(context, actions); + return false; + } + } + } + } + } else { + if (customCropsWorld.doesChunkHaveBlock(pos3, ScarecrowBlock.class)) { + if (advanced) ActionManager.trigger(context, actions); + return false; + } + } + } + if (!Optional.ofNullable(context.arg(ContextKeys.OFFLINE)).orElse(false)) + new CrowAttack( + location, + plugin.getItemManager().build(null, flyModel), + plugin.getItemManager().build(null, standModel) + ).start(); + return true; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at crow-attack requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "crow_attack", "crow-attack"); + } + + protected void registerWaterRequirement() { + registerRequirement((args, actions, advanced) -> { + int value; + int y; + if (args instanceof Integer integer) { + y = -1; + value = integer; + } else if (args instanceof Section section) { + y = section.getInt("y"); + value = section.getInt("value"); + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at water-more-than requirement which is expected be `Section`"); + return Requirement.empty(); + } + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0, y, 0); + Pos3 pos3 = Pos3.from(location); + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isEmpty()) return false; + CustomCropsWorld customCropsWorld = world.get(); + Optional state = customCropsWorld.getBlockState(pos3); + if (state.isPresent() && state.get().type() instanceof PotBlock potBlock) { + int water = potBlock.water(state.get()); + if (water > value) return true; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "water-more-than", "water_more_than"); + registerRequirement((args, actions, advanced) -> { + int value; + int y; + if (args instanceof Integer integer) { + y = -1; + value = integer; + } else if (args instanceof Section section) { + y = section.getInt("y"); + value = section.getInt("value"); + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at water-less-than requirement which is expected be `Section`"); + return Requirement.empty(); + } + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0, y, 0); + Pos3 pos3 = Pos3.from(location); + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isEmpty()) return false; + CustomCropsWorld customCropsWorld = world.get(); + Optional state = customCropsWorld.getBlockState(pos3); + if (state.isPresent() && state.get().type() instanceof PotBlock potBlock) { + int water = potBlock.water(state.get()); + if (water < value) return true; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "water-less-than", "water_less_than"); + registerRequirement((args, actions, advanced) -> { + int value; + int y; + if (args instanceof Integer integer) { + y = -1; + value = integer; + } else if (args instanceof Section section) { + y = section.getInt("y"); + value = section.getInt("value"); + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at water-less-than requirement which is expected be `Section`"); + return Requirement.empty(); + } + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0, y, 0); + Block block = location.getBlock(); + if (block.getBlockData() instanceof Farmland farmland) { + return farmland.getMoisture() > value; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "moisture-more-than", "moisture_more_than"); + registerRequirement((args, actions, advanced) -> { + int value; + int y; + if (args instanceof Integer integer) { + y = -1; + value = integer; + } else if (args instanceof Section section) { + y = section.getInt("y"); + value = section.getInt("value"); + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at water-less-than requirement which is expected be `Section`"); + return Requirement.empty(); + } + return context -> { + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0, y, 0); + Block block = location.getBlock(); + if (block.getBlockData() instanceof Farmland farmland) { + return farmland.getMoisture() < value; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "moisture-less-than", "moisture_less_than"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/EmptyRequirement.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/EmptyRequirement.java similarity index 60% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/EmptyRequirement.java rename to api/src/main/java/net/momirealms/customcrops/api/requirement/EmptyRequirement.java index 6d66041..90323bf 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/EmptyRequirement.java +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/EmptyRequirement.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.requirement; +package net.momirealms.customcrops.api.requirement; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.State; +import net.momirealms.customcrops.api.context.Context; -public class EmptyRequirement implements Requirement { +/** + * Represents an empty requirement that always returns true when checking conditions. + */ +public class EmptyRequirement implements Requirement { - public static EmptyRequirement instance = new EmptyRequirement(); + public static EmptyRequirement instance() { + return new EmptyRequirement<>(); + } @Override - public boolean isStateMet(State state) { + public boolean isSatisfied(Context context) { return true; } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/requirement/Requirement.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/Requirement.java new file mode 100644 index 0000000..bb94890 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/Requirement.java @@ -0,0 +1,41 @@ +/* + * 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.requirement; + +import net.momirealms.customcrops.api.context.Context; + +/** + * Interface representing a requirement that must be met. + * This can be used to define conditions that need to be satisfied within a given context. + * + * @param the type parameter for the context + */ +public interface Requirement { + + /** + * Evaluates whether the requirement is met within the given context. + * + * @param context the context in which the requirement is evaluated + * @return true if the requirement is met, false otherwise + */ + boolean isSatisfied(Context context); + + static Requirement empty() { + return EmptyRequirement.instance(); + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementExpansion.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementExpansion.java similarity index 53% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementExpansion.java rename to api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementExpansion.java index 0b70b6a..8e84c6b 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/requirement/RequirementExpansion.java +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementExpansion.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,35 +15,39 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.requirement; +package net.momirealms.customcrops.api.requirement; -public abstract class RequirementExpansion { +/** + * An abstract class representing a requirement expansion + * Requirement expansions are used to define custom requirements for various functionalities. + */ +public abstract class RequirementExpansion { /** - * Get the version number + * Get the version of this requirement expansion. * - * @return version + * @return The version of the expansion. */ public abstract String getVersion(); /** - * Get the author + * Get the author of this requirement expansion. * - * @return author + * @return The author of the expansion. */ public abstract String getAuthor(); /** - * Get the type of the requirement + * Get the type of requirement provided by this expansion. * - * @return the type of the requirement + * @return The type of requirement. */ public abstract String getRequirementType(); /** - * Get the requirement factory + * Get the factory for creating requirements defined by this expansion. * - * @return the requirement factory + * @return The requirement factory. */ - public abstract RequirementFactory getRequirementFactory(); + public abstract RequirementFactory getRequirementFactory(); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementFactory.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementFactory.java new file mode 100644 index 0000000..9059623 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementFactory.java @@ -0,0 +1,50 @@ +/* + * 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.requirement; + +import net.momirealms.customcrops.api.action.Action; + +import java.util.List; + +/** + * Interface representing a factory for creating requirements. + * + * @param the type of object that the requirement will operate on + */ +public interface RequirementFactory { + + /** + * Build a requirement with the given arguments, not satisfied actions, and check run actions flag. + * + * @param args The arguments used to build the requirement. + * @param notSatisfiedActions Actions to be triggered when the requirement is not met (can be null). + * @param runActions Flag indicating whether to run the action if the requirement is not met. + * @return The built requirement. + */ + Requirement process(Object args, List> notSatisfiedActions, boolean runActions); + + /** + * Build a requirement with the given arguments. + * + * @param args The arguments used to build the requirement. + * @return The built requirement. + */ + default Requirement process(Object args) { + return process(args, List.of(), false); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementManager.java b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementManager.java new file mode 100644 index 0000000..9bd8e43 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/requirement/RequirementManager.java @@ -0,0 +1,133 @@ +/* + * 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.requirement; + +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.List; + +/** + * The RequirementManager interface manages custom requirement types and provides methods for handling requirements. + * + * @param the type of the context in which the requirements are evaluated. + */ +public interface RequirementManager extends Reloadable { + + /** + * Registers a custom requirement type with its corresponding factory. + * + * @param requirementFactory The factory responsible for creating instances of the requirement. + * @param alias The type identifier of the requirement. + * @return True if registration was successful, false if the type is already registered. + */ + boolean registerRequirement(@NotNull RequirementFactory requirementFactory, @NotNull String... alias); + + /** + * Unregisters a custom requirement type. + * + * @param type The type identifier of the requirement to unregister. + * @return True if unregistration was successful, false if the type is not registered. + */ + boolean unregisterRequirement(@NotNull String type); + + /** + * Checks if a requirement type is registered. + * + * @param type The type identifier of the requirement. + * @return True if the requirement type is registered, otherwise false. + */ + boolean hasRequirement(@NotNull String type); + + /** + * Retrieves a RequirementFactory based on the specified requirement type. + * + * @param type The requirement type for which to retrieve a factory. + * @return A RequirementFactory for the specified type, or null if no factory is found. + */ + @Nullable + RequirementFactory getRequirementFactory(@NotNull String type); + + /** + * Retrieves an array of requirements based on a configuration section. + * + * @param section The configuration section containing requirement definitions. + * @param runActions A flag indicating whether to use advanced requirements. + * @return An array of Requirement objects based on the configuration section. + */ + @NotNull + Requirement[] parseRequirements(Section section, boolean runActions); + + /** + * Retrieves a Requirement object based on a configuration section and advanced flag. + * + * @param section The configuration section containing requirement definitions. + * @param runActions A flag indicating whether to use advanced requirements. + * @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid. + */ + @NotNull + Requirement parseRequirement(@NotNull Section section, boolean runActions); + + /** + * Gets a requirement based on the provided type and value. + * If a valid RequirementFactory is found for the type, it is used to create the requirement. + * + * @param type The type representing the requirement type. + * @param value The value associated with the requirement. + * @return A Requirement instance based on the type and value, or an EmptyRequirement if the type is invalid. + */ + @NotNull + Requirement parseRequirement(@NotNull String type, @NotNull Object value); + + /** + * Checks if all requirements in the provided array are satisfied within the given context. + * + * @param context The context in which the requirements are evaluated. + * @param requirements An array of requirements to check. + * @return True if all requirements are satisfied, otherwise false. + */ + static boolean isSatisfied(Context context, @Nullable Requirement[] requirements) { + if (requirements == null) return true; + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied(context)) { + return false; + } + } + return true; + } + + /** + * Checks if all requirements in the provided array are satisfied within the given context. + * + * @param context The context in which the requirements are evaluated. + * @param requirements A list of requirements to check. + * @return True if all requirements are satisfied, otherwise false. + */ + static boolean isSatisfied(Context context, @Nullable List> requirements) { + if (requirements == null) return true; + for (Requirement requirement : requirements) { + if (!requirement.isSatisfied(context)) { + return false; + } + } + return true; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/scheduler/CancellableTask.java b/api/src/main/java/net/momirealms/customcrops/api/scheduler/CancellableTask.java deleted file mode 100644 index d968878..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/scheduler/CancellableTask.java +++ /dev/null @@ -1,33 +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.scheduler; - -public interface CancellableTask { - - /** - * Cancel the task - */ - void cancel(); - - /** - * Get if the task is cancelled or not - * - * @return cancelled or not - */ - boolean isCancelled(); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/scheduler/Scheduler.java b/api/src/main/java/net/momirealms/customcrops/api/scheduler/Scheduler.java deleted file mode 100644 index 01ba2f4..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/scheduler/Scheduler.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.scheduler; - -import org.bukkit.Location; -import org.bukkit.World; - -import java.util.concurrent.TimeUnit; - -public interface Scheduler { - - /** - * Runs a task synchronously on the main server thread or region thread. - * - * @param runnable The task to run. - * @param location The location associated with the task. - */ - void runTaskSync(Runnable runnable, Location location); - - /** - * Runs a task synchronously on the main server thread or region thread. - * - * @param runnable The task to run. - * @param world world - * @param x chunk X - * @param z chunk Z - */ - void runTaskSync(Runnable runnable, World world, int x, int z); - - /** - * Runs a task synchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the first execution. - * @param periodTicks The period between subsequent executions in ticks. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks); - - /** - * Runs a task asynchronously with a specified delay. - * - * @param runnable The task to run. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit); - - /** - * Runs a task asynchronously. - * - * @param runnable The task to run. - */ - void runTaskAsync(Runnable runnable); - - /** - * Runs a task synchronously with a specified delay. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delay The delay before the task execution. - * @param timeUnit The time unit for the delay. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit); - - /** - * Runs a task synchronously with a specified delay in ticks. - * - * @param runnable The task to run. - * @param location The location associated with the task. - * @param delayTicks The delay in ticks before the task execution. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks); - - /** - * Runs a task asynchronously with a specified delay and period. - * - * @param runnable The task to run. - * @param delay The delay before the first execution. - * @param period The period between subsequent executions. - * @param timeUnit The time unit for the delay and period. - * @return A CancellableTask for managing the scheduled task. - */ - CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit); -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/DisplayEntityUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/DisplayEntityUtils.java deleted file mode 100644 index fcef960..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/util/DisplayEntityUtils.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.momirealms.customcrops.api.util; - -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import org.bukkit.entity.Entity; -import org.bukkit.entity.ItemDisplay; - -public class DisplayEntityUtils { - - public static CRotation getRotation(Entity entity) { - if (entity instanceof ItemDisplay itemDisplay) { - return CRotation.getByYaw(itemDisplay.getLocation().getYaw()); - } - return CRotation.NONE; - } -} diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/EventUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/EventUtils.java index 038de4d..723a175 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/util/EventUtils.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/EventUtils.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 @@ -21,12 +21,28 @@ import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; +/** + * Utility class for handling events. + */ public class EventUtils { + /** + * Fires an event and does not wait for any result. + * + * @param event the {@link Event} to be fired + */ public static void fireAndForget(Event event) { Bukkit.getPluginManager().callEvent(event); } + /** + * Fires an event and checks if it is cancelled. + * This method only accepts events that implement {@link Cancellable}. + * + * @param event the {@link Event} to be fired + * @return true if the event is cancelled, false otherwise + * @throws IllegalArgumentException if the event is not cancellable + */ public static boolean fireAndCheckCancel(Event event) { if (!(event instanceof Cancellable cancellable)) throw new IllegalArgumentException("Only cancellable events are allowed here"); diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/FakeCancellable.java b/api/src/main/java/net/momirealms/customcrops/api/util/FakeCancellable.java new file mode 100644 index 0000000..3202bc7 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/util/FakeCancellable.java @@ -0,0 +1,18 @@ +package net.momirealms.customcrops.api.util; + +import org.bukkit.event.Cancellable; + +public class FakeCancellable implements Cancellable { + + private boolean cancelled; + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/InventoryUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/InventoryUtils.java new file mode 100644 index 0000000..1e8f2eb --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/util/InventoryUtils.java @@ -0,0 +1,108 @@ +/* + * 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.util; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Utility class for working with Bukkit Inventories and item stacks. + */ +public class InventoryUtils { + + private InventoryUtils() { + } + + /** + * Serialize an array of ItemStacks to a Base64-encoded string. + * + * @param contents The ItemStack array to serialize. + * @return The Base64-encoded string representing the serialized ItemStacks. + */ + public static @NotNull String stacksToBase64(ItemStack[] contents) { + if (contents == null || contents.length == 0) { + return ""; + } + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream); + dataOutput.writeInt(contents.length); + for (ItemStack itemStack : contents) { + dataOutput.writeObject(itemStack); + } + dataOutput.close(); + byte[] byteArr = outputStream.toByteArray(); + outputStream.close(); + return Base64Coder.encodeLines(byteArr); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * Deserialize an ItemStack array from a Base64-encoded string. + * + * @param base64 The Base64-encoded string representing the serialized ItemStacks. + * @return An array of ItemStacks deserialized from the input string. + */ + @Nullable + public static ItemStack[] getInventoryItems(String base64) { + ItemStack[] itemStacks = null; + if (base64 == null || base64.isEmpty()) return new ItemStack[]{}; + ByteArrayInputStream inputStream; + try { + inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64)); + } catch (IllegalArgumentException ignored) { + return new ItemStack[]{}; + } + BukkitObjectInputStream dataInput = null; + try { + dataInput = new BukkitObjectInputStream(inputStream); + itemStacks = new ItemStack[dataInput.readInt()]; + } catch (IOException ioException) { + ioException.printStackTrace(); + } + if (itemStacks == null) return new ItemStack[]{}; + for (int i = 0; i < itemStacks.length; i++) { + try { + itemStacks[i] = (ItemStack) dataInput.readObject(); + } catch (IOException | ClassNotFoundException | NullPointerException e) { + try { + dataInput.close(); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + return null; + } + } + try { + dataInput.close(); + } catch (IOException ignored) { + } + return itemStacks; + } +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/LocationUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/LocationUtils.java index 41c2879..158029a 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/util/LocationUtils.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/LocationUtils.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 @@ -53,11 +53,20 @@ public class LocationUtils { } @NotNull - public static Location toCenterLocation(Location location) { + public static Location toBlockCenterLocation(Location location) { Location centerLoc = location.clone(); centerLoc.setX(location.getBlockX() + 0.5); centerLoc.setY(location.getBlockY() + 0.5); centerLoc.setZ(location.getBlockZ() + 0.5); return centerLoc; } + + @NotNull + public static Location toSurfaceCenterLocation(Location location) { + Location centerLoc = location.clone(); + centerLoc.setX(location.getBlockX() + 0.5); + centerLoc.setZ(location.getBlockZ() + 0.5); + centerLoc.setY(location.getBlockY()); + return centerLoc; + } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/LogUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/LogUtils.java deleted file mode 100644 index 1f03d49..0000000 --- a/api/src/main/java/net/momirealms/customcrops/api/util/LogUtils.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.util; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import org.jetbrains.annotations.NotNull; - -import java.util.logging.Level; - -/** - * Utility class for logging messages with various log levels. - */ -public final class LogUtils { - - private LogUtils() {} - - /** - * Log an informational message. - * - * @param message The message to log. - */ - public static void info(@NotNull String message) { - CustomCropsPlugin.getInstance().getLogger().info(message); - } - - /** - * Log a warning message. - * - * @param message The message to log. - */ - public static void warn(@NotNull String message) { - CustomCropsPlugin.getInstance().getLogger().warning(message); - } - - /** - * Log a severe error message. - * - * @param message The message to log. - */ - public static void severe(@NotNull String message) { - CustomCropsPlugin.getInstance().getLogger().severe(message); - } - - /** - * Log a warning message with a throwable exception. - * - * @param message The message to log. - * @param throwable The throwable exception to log. - */ - public static void warn(@NotNull String message, Throwable throwable) { - CustomCropsPlugin.getInstance().getLogger().log(Level.WARNING, message, throwable); - } - - /** - * Log a severe error message with a throwable exception. - * - * @param message The message to log. - * @param throwable The throwable exception to log. - */ - public static void severe(@NotNull String message, Throwable throwable) { - CustomCropsPlugin.getInstance().getLogger().log(Level.SEVERE, message, throwable); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakFurnitureWrapper.java b/api/src/main/java/net/momirealms/customcrops/api/util/MoonPhase.java similarity index 51% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakFurnitureWrapper.java rename to api/src/main/java/net/momirealms/customcrops/api/util/MoonPhase.java index c705fca..b0d07b0 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakFurnitureWrapper.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/MoonPhase.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,30 +15,41 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.item.function.wrapper; +package net.momirealms.customcrops.api.util; -import org.bukkit.Location; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -public class BreakFurnitureWrapper extends BreakWrapper { +import java.util.HashMap; +import java.util.Map; - private final Location location; - private final String id; +public enum MoonPhase { - public BreakFurnitureWrapper(Player player, Location location, String id) { - super(player, location); - this.location = location; - this.id = id; + FULL_MOON(0L), + WANING_GIBBOUS(1L), + LAST_QUARTER(2L), + WANING_CRESCENT(3L), + NEW_MOON(4L), + WAXING_CRESCENT(5L), + FIRST_QUARTER(6L), + WAXING_GIBBOUS(7L); + + private final long day; + + MoonPhase(long day) { + this.day = day; + } + + private static final Map BY_DAY = new HashMap<>(); + + static { + for (MoonPhase phase : values()) { + BY_DAY.put(phase.day, phase); + } } @NotNull - public Location getLocation() { - return location; - } - - @NotNull - public String getID() { - return id; + public static MoonPhase getPhase(long day) { + return BY_DAY.get(day % 8L); } } + diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/ParticleUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/ParticleUtils.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customcrops/util/ParticleUtils.java rename to api/src/main/java/net/momirealms/customcrops/api/util/ParticleUtils.java index 5162f5b..8481ca6 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/util/ParticleUtils.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/ParticleUtils.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.util; +package net.momirealms.customcrops.api.util; import org.bukkit.Particle; diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/PlayerUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/PlayerUtils.java new file mode 100644 index 0000000..8a4b32d --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/util/PlayerUtils.java @@ -0,0 +1,158 @@ +/* + * 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.util; + +import net.momirealms.customcrops.common.util.RandomUtils; +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +public class PlayerUtils { + + public static void dropItem(@NotNull Player player, @NotNull ItemStack itemStack, boolean retainOwnership, boolean noPickUpDelay, boolean throwRandomly) { + requireNonNull(player, "player"); + requireNonNull(itemStack, "itemStack"); + Location location = player.getLocation().clone(); + Item item = player.getWorld().dropItem(player.getEyeLocation().clone().subtract(new Vector(0,0.3,0)), itemStack); + item.setPickupDelay(noPickUpDelay ? 0 : 40); + item.setOwner(player.getUniqueId()); + if (retainOwnership) { + item.setThrower(player.getUniqueId()); + } + if (throwRandomly) { + double d1 = RandomUtils.generateRandomDouble(0,1) * 0.5f; + double d2 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2); + item.setVelocity(new Vector(-Math.sin(d2) * d1, 0.2f, Math.cos(d2) * d1)); + } else { + double d1 = Math.sin(location.getPitch() * (Math.PI/180)); + double d2 = RandomUtils.generateRandomDouble(0, 0.02); + double d3 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2); + Vector vector = location.getDirection().multiply(0.3).setY(-d1 * 0.3 + 0.1 + (RandomUtils.generateRandomDouble(0,1) - RandomUtils.generateRandomDouble(0,1)) * 0.1); + vector.add(new Vector(Math.cos(d3) * d2, 0, Math.sin(d3) * d2)); + item.setVelocity(vector); + } + } + + public static int putItemsToInventory(Inventory inventory, ItemStack itemStack, int amount) { + ItemMeta meta = itemStack.getItemMeta(); + int maxStackSize = itemStack.getMaxStackSize(); + for (ItemStack other : inventory.getStorageContents()) { + if (other != null) { + if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { + if (other.getAmount() < maxStackSize) { + int delta = maxStackSize - other.getAmount(); + if (amount > delta) { + other.setAmount(maxStackSize); + amount -= delta; + } else { + other.setAmount(amount + other.getAmount()); + return 0; + } + } + } + } + } + + if (amount > 0) { + for (ItemStack other : inventory.getStorageContents()) { + if (other == null) { + if (amount > maxStackSize) { + amount -= maxStackSize; + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + inventory.addItem(cloned); + } else { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(amount); + inventory.addItem(cloned); + return 0; + } + } + } + } + + return amount; + } + + public static int giveItem(Player player, ItemStack itemStack, int amount) { + PlayerInventory inventory = player.getInventory(); + ItemMeta meta = itemStack.getItemMeta(); + int maxStackSize = itemStack.getMaxStackSize(); + if (amount > maxStackSize * 100) { + amount = maxStackSize * 100; + } + int actualAmount = amount; + for (ItemStack other : inventory.getStorageContents()) { + if (other != null) { + if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { + if (other.getAmount() < maxStackSize) { + int delta = maxStackSize - other.getAmount(); + if (amount > delta) { + other.setAmount(maxStackSize); + amount -= delta; + } else { + other.setAmount(amount + other.getAmount()); + return actualAmount; + } + } + } + } + } + if (amount > 0) { + for (ItemStack other : inventory.getStorageContents()) { + if (other == null) { + if (amount > maxStackSize) { + amount -= maxStackSize; + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + inventory.addItem(cloned); + } else { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(amount); + inventory.addItem(cloned); + return actualAmount; + } + } + } + } + + if (amount > 0) { + for (int i = 0; i < amount / maxStackSize; i++) { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(maxStackSize); + player.getWorld().dropItem(player.getLocation(), cloned); + } + int left = amount % maxStackSize; + if (left != 0) { + ItemStack cloned = itemStack.clone(); + cloned.setAmount(left); + player.getWorld().dropItem(player.getLocation(), cloned); + } + } + + return actualAmount; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/PluginUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/PluginUtils.java new file mode 100644 index 0000000..982e7ca --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/util/PluginUtils.java @@ -0,0 +1,20 @@ +package net.momirealms.customcrops.api.util; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +public class PluginUtils { + + public static boolean isEnabled(String name) { + return Bukkit.getPluginManager().isPluginEnabled(name); + } + + @SuppressWarnings("deprecation") + public static String getPluginVersion(String name) { + Plugin plugin = Bukkit.getPluginManager().getPlugin(name); + if (plugin != null) { + return plugin.getDescription().getVersion(); + } + return ""; + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/util/StringUtils.java b/api/src/main/java/net/momirealms/customcrops/api/util/StringUtils.java index 65e1687..860ac8f 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/util/StringUtils.java +++ b/api/src/main/java/net/momirealms/customcrops/api/util/StringUtils.java @@ -12,4 +12,14 @@ public class StringUtils { return true; } + public static String toLowerCase(String input) { + char[] chars = input.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c >= 'A' && c <= 'Z') { + chars[i] = (char) (c + 32); + } + } + return new String(chars); + } } diff --git a/build.gradle.kts b/build.gradle.kts index 1c3a241..d72e8d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,81 +1,49 @@ +import java.io.ByteArrayOutputStream + plugins { - id("org.gradle.java") - id("application") - id("org.gradle.maven-publish") - id("io.github.goooler.shadow") version "8.1.7" + id("java") } -allprojects { - - project.group = "net.momirealms" - project.version = "3.5.11" - - apply() - apply(plugin = "java") - apply(plugin = "application") - apply(plugin = "io.github.goooler.shadow") - apply(plugin = "org.gradle.maven-publish") - - application { - mainClass.set("") - } - - repositories { - mavenCentral() - maven("https://maven.aliyun.com/repository/public/") - maven("https://betonquest.org/nexus/repository/betonquest/") - maven("https://maven.enginehub.org/repo/") - maven("https://oss.sonatype.org/content/groups/public/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - maven("https://repo.codemc.org/repository/maven-public/") - maven("https://jitpack.io") - maven("https://repo.papermc.io/repository/maven-public/") - maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") - maven("https://repo.dmulloy2.net/repository/public/") - maven("https://mvn.lumine.io/repository/maven-public/") - maven("https://repo.bg-software.com/repository/api/") - maven("https://repo.infernalsuite.com/repository/maven-snapshots/") - maven("https://repo.rapture.pw/repository/maven-releases/") - maven("https://nexus.phoenixdevt.fr/repository/maven-public/") - maven("https://r.irepo.space/maven/") - maven("https://repo.auxilor.io/repository/maven-public/") - maven("https://nexus.betonquest.org/repository/betonquest/") - maven("https://repo.infernalsuite.com/repository/maven-releases/") - maven("https://repo.rapture.pw/repository/maven-releases/") - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - maven("https://repo.xenondevs.xyz/releases/") - maven("https://repo.oraxen.com/snapshots/") - } -} +val git : String = versionBanner() +val builder : String = builder() +ext["git_version"] = git +ext["builder"] = builder subprojects { + + apply(plugin = "java") + apply(plugin = "java-library") + tasks.processResources { - val props = mapOf("version" to version) - inputs.properties(props) filteringCharset = "UTF-8" - filesMatching("*plugin.yml") { - expand(props) - } - } - tasks.shadowJar { - if (arrayListOf("plugin", "api").contains(project.name)) { - destinationDirectory.set(file("$rootDir/target")) + filesMatching(arrayListOf("custom-crops.properties")) { + expand(rootProject.properties) } - archiveClassifier.set("") - archiveFileName.set("CustomCrops-" + project.name + "-" + project.version + ".jar") - } - if ("api" == project.name) { - publishing { - publications { - create("mavenJava") { - groupId = "net.momirealms" - artifactId = "CustomCrops" - version = rootProject.version.toString() - artifact(tasks.shadowJar) - } - } + filesMatching(arrayListOf("*.yml", "*/*.yml")) { + expand( + Pair("project_version", rootProject.properties["project_version"]), + Pair("config_version", rootProject.properties["config_version"]) + ) } } +} + +fun versionBanner(): String { + val os = ByteArrayOutputStream() + project.exec { + commandLine = "git rev-parse --short=8 HEAD".split(" ") + standardOutput = os + } + return String(os.toByteArray()).trim() +} + +fun builder(): String { + val os = ByteArrayOutputStream() + project.exec { + commandLine = "git config user.name".split(" ") + standardOutput = os + } + return String(os.toByteArray()).trim() } \ No newline at end of file diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..cd6333b --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,39 @@ +repositories { + mavenCentral() + maven("https://jitpack.io/") // rtag +} + +dependencies { + compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") { + exclude(module = "adventure-bom") + exclude(module = "checker-qual") + exclude(module = "annotations") + } + compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") + compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}") + compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + compileOnly("org.jetbrains:annotations:${rootProject.properties["jetbrains_annotations_version"]}") + compileOnly("org.slf4j:slf4j-api:${rootProject.properties["slf4j_version"]}") + compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.properties["log4j_version"]}") + compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") + compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") + compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}") + compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}") + compileOnly("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}") + compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}") + compileOnly("com.google.guava:guava:${rootProject.properties["guava_version"]}") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customcrops/common/annotation/DoNotUse.java b/common/src/main/java/net/momirealms/customcrops/common/annotation/DoNotUse.java new file mode 100644 index 0000000..ef22d08 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/annotation/DoNotUse.java @@ -0,0 +1,11 @@ +package net.momirealms.customcrops.common.annotation; + +import org.jetbrains.annotations.ApiStatus; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +@ApiStatus.Internal +@Target({ElementType.FIELD}) +public @interface DoNotUse { +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandFeature.java b/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandFeature.java new file mode 100644 index 0000000..e2ab75e --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandFeature.java @@ -0,0 +1,85 @@ +/* + * 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.common.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customcrops.common.sender.SenderFactory; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; + +public abstract class AbstractCommandFeature implements CommandFeature { + + protected final CustomCropsCommandManager commandManager; + protected CommandConfig commandConfig; + + public AbstractCommandFeature(CustomCropsCommandManager commandManager) { + this.commandManager = commandManager; + } + + protected abstract SenderFactory getSenderFactory(); + + public abstract Command.Builder assembleCommand(CommandManager manager, Command.Builder builder); + + @Override + @SuppressWarnings("unchecked") + public Command registerCommand(CommandManager manager, Command.Builder builder) { + Command command = (Command) assembleCommand(manager, builder).build(); + manager.command(command); + return command; + } + + @Override + public void registerRelatedFunctions() { + // empty + } + + @Override + public void unregisterRelatedFunctions() { + // empty + } + + @Override + @SuppressWarnings("unchecked") + public void handleFeedback(CommandContext context, TranslatableComponent.Builder key, Component... args) { + if (context.flags().hasFlag("silent")) { + return; + } + commandManager.handleCommandFeedback((C) context.sender(), key, args); + } + + @Override + public void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args) { + commandManager.handleCommandFeedback(sender, key, args); + } + + @Override + public CustomCropsCommandManager getCustomCropsCommandManager() { + return commandManager; + } + + @Override + public CommandConfig getCommandConfig() { + return commandConfig; + } + + public void setCommandConfig(CommandConfig commandConfig) { + this.commandConfig = commandConfig; + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandManager.java b/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandManager.java new file mode 100644 index 0000000..03327e5 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/AbstractCommandManager.java @@ -0,0 +1,179 @@ +/* + * 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.common.command; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customcrops.common.locale.CustomCropsCaptionFormatter; +import net.momirealms.customcrops.common.locale.CustomCropsCaptionProvider; +import net.momirealms.customcrops.common.locale.TranslationManager; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; +import net.momirealms.customcrops.common.sender.Sender; +import net.momirealms.customcrops.common.util.ArrayUtils; +import net.momirealms.customcrops.common.util.TriConsumer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.caption.Caption; +import org.incendo.cloud.caption.StandardCaptionKeys; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.exception.*; +import org.incendo.cloud.exception.handling.ExceptionContext; +import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +public abstract class AbstractCommandManager implements CustomCropsCommandManager { + + protected final HashSet> registeredRootCommandComponents = new HashSet<>(); + protected final HashSet> registeredFeatures = new HashSet<>(); + protected final CommandManager commandManager; + protected final CustomCropsPlugin plugin; + private final CustomCropsCaptionFormatter captionFormatter = new CustomCropsCaptionFormatter(); + private final MinecraftExceptionHandler.Decorator decorator = (formatter, ctx, msg) -> msg; + + private TriConsumer feedbackConsumer; + + public AbstractCommandManager(CustomCropsPlugin plugin, CommandManager commandManager) { + this.commandManager = commandManager; + this.plugin = plugin; + this.inject(); + this.feedbackConsumer = defaultFeedbackConsumer(); + } + + @Override + public void setFeedbackConsumer(@NotNull TriConsumer feedbackConsumer) { + this.feedbackConsumer = feedbackConsumer; + } + + @Override + public TriConsumer defaultFeedbackConsumer() { + return ((sender, node, component) -> { + wrapSender(sender).sendMessage( + component, true + ); + }); + } + + protected abstract Sender wrapSender(C c); + + private void inject() { + getCommandManager().captionRegistry().registerProvider(new CustomCropsCaptionProvider<>()); + injectExceptionHandler(InvalidSyntaxException.class, MinecraftExceptionHandler.createDefaultInvalidSyntaxHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SYNTAX); + injectExceptionHandler(InvalidCommandSenderException.class, MinecraftExceptionHandler.createDefaultInvalidSenderHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SENDER); + injectExceptionHandler(NoPermissionException.class, MinecraftExceptionHandler.createDefaultNoPermissionHandler(), StandardCaptionKeys.EXCEPTION_NO_PERMISSION); + injectExceptionHandler(ArgumentParseException.class, MinecraftExceptionHandler.createDefaultArgumentParsingHandler(), StandardCaptionKeys.EXCEPTION_INVALID_ARGUMENT); + injectExceptionHandler(CommandExecutionException.class, MinecraftExceptionHandler.createDefaultCommandExecutionHandler(), StandardCaptionKeys.EXCEPTION_UNEXPECTED); + } + + private void injectExceptionHandler(Class type, MinecraftExceptionHandler.MessageFactory factory, Caption key) { + getCommandManager().exceptionController().registerHandler(type, ctx -> { + final @Nullable ComponentLike message = factory.message(captionFormatter, (ExceptionContext) ctx); + if (message != null) { + handleCommandFeedback(ctx.context().sender(), key.key(), decorator.decorate(captionFormatter, ctx, message.asComponent()).asComponent()); + } + }); + } + + @Override + public CommandConfig getCommandConfig(YamlDocument document, String featureID) { + Section section = document.getSection(featureID); + if (section == null) return null; + return new CommandConfig.Builder() + .permission(section.getString("permission")) + .usages(section.getStringList("usage")) + .enable(section.getBoolean("enable", false)) + .build(); + } + + @Override + public Collection> buildCommandBuilders(CommandConfig config) { + ArrayList> list = new ArrayList<>(); + for (String usage : config.getUsages()) { + if (!usage.startsWith("/")) continue; + String command = usage.substring(1).trim(); + String[] split = command.split(" "); + Command.Builder builder = new CommandBuilder.BasicCommandBuilder<>(getCommandManager(), split[0]) + .setCommandNode(ArrayUtils.subArray(split, 1)) + .setPermission(config.getPermission()) + .getBuiltCommandBuilder(); + list.add(builder); + } + return list; + } + + @Override + public void registerFeature(CommandFeature feature, CommandConfig config) { + if (!config.isEnable()) throw new RuntimeException("Registering a disabled command feature is not allowed"); + for (Command.Builder builder : buildCommandBuilders(config)) { + Command command = feature.registerCommand(commandManager, builder); + this.registeredRootCommandComponents.add(command.rootComponent()); + } + feature.registerRelatedFunctions(); + this.registeredFeatures.add(feature); + ((AbstractCommandFeature) feature).setCommandConfig(config); + } + + @Override + public void registerDefaultFeatures() { + YamlDocument document = plugin.getConfigManager().loadConfig(commandsFile); + try { + document.save(new File(plugin.getDataDirectory().toFile(), "commands.yml")); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.getFeatures().values().forEach(feature -> { + CommandConfig config = getCommandConfig(document, feature.getFeatureID()); + if (config.isEnable()) { + registerFeature(feature, config); + } + }); + } + + @Override + public void unregisterFeatures() { + this.registeredRootCommandComponents.forEach(component -> this.commandManager.commandRegistrationHandler().unregisterRootCommand(component)); + this.registeredRootCommandComponents.clear(); + this.registeredFeatures.forEach(CommandFeature::unregisterRelatedFunctions); + this.registeredFeatures.clear(); + } + + @Override + public CommandManager getCommandManager() { + return commandManager; + } + + @Override + public void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args) { + TranslatableComponent component = key.arguments(args).build(); + this.feedbackConsumer.accept(sender, component.key(), TranslationManager.render(component)); + } + + @Override + public void handleCommandFeedback(C sender, String node, Component component) { + this.feedbackConsumer.accept(sender, node, component); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/CommandBuilder.java b/common/src/main/java/net/momirealms/customcrops/common/command/CommandBuilder.java new file mode 100644 index 0000000..024021d --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/CommandBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.common.command; + +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public interface CommandBuilder { + + CommandBuilder setPermission(String permission); + + CommandBuilder setCommandNode(String... subNodes); + + Command.Builder getBuiltCommandBuilder(); + + class BasicCommandBuilder implements CommandBuilder { + + private Command.Builder commandBuilder; + + public BasicCommandBuilder(CommandManager commandManager, String rootNode) { + this.commandBuilder = commandManager.commandBuilder(rootNode); + } + + @Override + public CommandBuilder setPermission(String permission) { + this.commandBuilder = this.commandBuilder.permission(permission); + return this; + } + + @Override + public CommandBuilder setCommandNode(String... subNodes) { + for (String sub : subNodes) { + this.commandBuilder = this.commandBuilder.literal(sub); + } + return this; + } + + @Override + public Command.Builder getBuiltCommandBuilder() { + return commandBuilder; + } + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/CommandConfig.java b/common/src/main/java/net/momirealms/customcrops/common/command/CommandConfig.java new file mode 100644 index 0000000..76916f0 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/CommandConfig.java @@ -0,0 +1,77 @@ +/* + * 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.common.command; + +import java.util.ArrayList; +import java.util.List; + +public class CommandConfig { + + private boolean enable = false; + private List usages = new ArrayList<>(); + private String permission = null; + + private CommandConfig() { + } + + public CommandConfig(boolean enable, List usages, String permission) { + this.enable = enable; + this.usages = usages; + this.permission = permission; + } + + public boolean isEnable() { + return enable; + } + + public List getUsages() { + return usages; + } + + public String getPermission() { + return permission; + } + + public static class Builder { + + private final CommandConfig config; + + public Builder() { + this.config = new CommandConfig<>(); + } + + public Builder usages(List usages) { + config.usages = usages; + return this; + } + + public Builder permission(String permission) { + config.permission = permission; + return this; + } + + public Builder enable(boolean enable) { + config.enable = enable; + return this; + } + + public CommandConfig build() { + return config; + } + } +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/CommandFeature.java b/common/src/main/java/net/momirealms/customcrops/common/command/CommandFeature.java new file mode 100644 index 0000000..065cef2 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/CommandFeature.java @@ -0,0 +1,43 @@ +/* + * 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.common.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; + +public interface CommandFeature { + + Command registerCommand(CommandManager cloudCommandManager, Command.Builder builder); + + String getFeatureID(); + + void registerRelatedFunctions(); + + void unregisterRelatedFunctions(); + + void handleFeedback(CommandContext context, TranslatableComponent.Builder key, Component... args); + + void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args); + + CustomCropsCommandManager getCustomCropsCommandManager(); + + CommandConfig getCommandConfig(); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/command/CustomCropsCommandManager.java b/common/src/main/java/net/momirealms/customcrops/common/command/CustomCropsCommandManager.java new file mode 100644 index 0000000..b231727 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/command/CustomCropsCommandManager.java @@ -0,0 +1,56 @@ +/* + * 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.common.command; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.util.Index; +import net.momirealms.customcrops.common.util.TriConsumer; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +public interface CustomCropsCommandManager { + + String commandsFile = "commands.yml"; + + void unregisterFeatures(); + + void registerFeature(CommandFeature feature, CommandConfig config); + + void registerDefaultFeatures(); + + Index> getFeatures(); + + void setFeedbackConsumer(@NotNull TriConsumer feedbackConsumer); + + TriConsumer defaultFeedbackConsumer(); + + CommandConfig getCommandConfig(YamlDocument document, String featureID); + + Collection> buildCommandBuilders(CommandConfig config); + + CommandManager getCommandManager(); + + void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args); + + void handleCommandFeedback(C sender, String node, Component component); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/config/ConfigLoader.java b/common/src/main/java/net/momirealms/customcrops/common/config/ConfigLoader.java new file mode 100644 index 0000000..e834e72 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/config/ConfigLoader.java @@ -0,0 +1,69 @@ +/* + * 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.common.config; + +import dev.dejvokep.boostedyaml.YamlDocument; + +import java.io.File; + +/** + * Interface for loading and managing configuration files. + */ +public interface ConfigLoader { + + /** + * Loads a YAML configuration file from the specified file path. + * + * @param filePath the path to the configuration file + * @return the loaded {@link YamlDocument} + */ + YamlDocument loadConfig(String filePath); + + /** + * Loads a YAML configuration file from the specified file path with a custom route separator. + * + * @param filePath the path to the configuration file + * @param routeSeparator the custom route separator character + * @return the loaded {@link YamlDocument} + */ + YamlDocument loadConfig(String filePath, char routeSeparator); + + /** + * Loads a YAML data file. + * + * @param file the {@link File} object representing the data file + * @return the loaded {@link YamlDocument} + */ + YamlDocument loadData(File file); + + /** + * Loads a YAML data file with a custom route separator. + * + * @param file the {@link File} object representing the data file + * @param routeSeparator the custom route separator character + * @return the loaded {@link YamlDocument} + */ + YamlDocument loadData(File file, char routeSeparator); + + /** + * Saves a resource file from the plugin's jar to the specified file path. + * + * @param filePath the path where the resource file will be saved + */ + void saveResource(String filePath); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/dependency/Dependency.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/Dependency.java new file mode 100644 index 0000000..a55794b --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/Dependency.java @@ -0,0 +1,268 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.common.dependency; + +import net.momirealms.customcrops.common.dependency.relocation.Relocation; +import net.momirealms.customcrops.common.plugin.CustomCropsProperties; +import org.jetbrains.annotations.Nullable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * The dependencies used by CustomCrops. + */ +public enum Dependency { + + ASM( + "org.ow2.asm", + "asm", + "maven", + "asm" + ), + ASM_COMMONS( + "org.ow2.asm", + "asm-commons", + "maven", + "asm-commons" + ), + JAR_RELOCATOR( + "me.lucko", + "jar-relocator", + "maven", + "jar-relocator" + ), + CLOUD_CORE( + "org{}incendo", + "cloud-core", + "maven", + "cloud-core", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_BRIGADIER( + "org{}incendo", + "cloud-brigadier", + "maven", + "cloud-brigadier", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_SERVICES( + "org{}incendo", + "cloud-services", + "maven", + "cloud-services", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_BUKKIT( + "org{}incendo", + "cloud-bukkit", + "maven", + "cloud-bukkit", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_PAPER( + "org{}incendo", + "cloud-paper", + "maven", + "cloud-paper", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + CLOUD_MINECRAFT_EXTRAS( + "org{}incendo", + "cloud-minecraft-extras", + "maven", + "cloud-minecraft-extras", + Relocation.of("cloud", "org{}incendo{}cloud"), + Relocation.of("adventure", "net{}kyori{}adventure"), + Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + GEANTY_REF( + "io{}leangen{}geantyref", + "geantyref", + "maven", + "geantyref", + Relocation.of("geantyref", "io{}leangen{}geantyref") + ), + BOOSTED_YAML( + "dev{}dejvokep", + "boosted-yaml", + "maven", + "boosted-yaml", + Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") + ), + BSTATS_BASE( + "org{}bstats", + "bstats-base", + "maven", + "bstats-base", + Relocation.of("bstats", "org{}bstats") + ), + BSTATS_BUKKIT( + "org{}bstats", + "bstats-bukkit", + "maven", + "bstats-bukkit", + Relocation.of("bstats", "org{}bstats") + ) { + @Override + public String getVersion() { + return Dependency.BSTATS_BASE.getVersion(); + } + }, + GSON( + "com.google.code.gson", + "gson", + "maven", + "gson" + ), + CAFFEINE( + "com{}github{}ben-manes{}caffeine", + "caffeine", + "maven", + "caffeine", + Relocation.of("caffeine", "com{}github{}benmanes{}caffeine") + ), + EXP4J( + "net{}objecthunter", + "exp4j", + "maven", + "exp4j", + Relocation.of("exp4j", "net{}objecthunter{}exp4j") + ), + SLF4J_SIMPLE( + "org.slf4j", + "slf4j-simple", + "maven", + "slf4j_simple" + ) { + @Override + public String getVersion() { + return Dependency.SLF4J_API.getVersion(); + } + }, + SLF4J_API( + "org.slf4j", + "slf4j-api", + "maven", + "slf4j" + ), + ZSTD( + "com.github.luben", + "zstd-jni", + "maven", + "zstd-jni" + ); + + private final List relocations; + private final String repo; + private final String groupId; + private String rawArtifactId; + private String customArtifactID; + + private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; + + Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID) { + this(groupId, rawArtifactId, repo, customArtifactID, new Relocation[0]); + } + + Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID, Relocation... relocations) { + this.rawArtifactId = rawArtifactId; + this.groupId = groupId; + this.relocations = new ArrayList<>(Arrays.stream(relocations).toList()); + this.repo = repo; + this.customArtifactID = customArtifactID; + } + + public Dependency setCustomArtifactID(String customArtifactID) { + this.customArtifactID = customArtifactID; + return this; + } + + public Dependency setRawArtifactID(String artifactId) { + this.rawArtifactId = artifactId; + return this; + } + + public String getVersion() { + return CustomCropsProperties.getValue(customArtifactID); + } + + private static String rewriteEscaping(String s) { + return s.replace("{}", "."); + } + + public String getFileName(String classifier) { + String name = customArtifactID.toLowerCase(Locale.ROOT).replace('_', '-'); + String extra = classifier == null || classifier.isEmpty() + ? "" + : "-" + classifier; + return name + "-" + this.getVersion() + extra + ".jar"; + } + + String getMavenRepoPath() { + return String.format(MAVEN_FORMAT, + rewriteEscaping(groupId).replace(".", "/"), + rewriteEscaping(rawArtifactId), + getVersion(), + rewriteEscaping(rawArtifactId), + getVersion() + ); + } + + public List getRelocations() { + return this.relocations; + } + + /** + * Creates a {@link MessageDigest} suitable for computing the checksums + * of dependencies. + * + * @return the digest + */ + public static MessageDigest createDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + @Nullable + public String getRepo() { + return repo; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyDownloadException.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyDownloadException.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyDownloadException.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyDownloadException.java index 515e589..16bc647 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyDownloadException.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyDownloadException.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies; +package net.momirealms.customcrops.common.dependency; /** * Exception thrown if a dependency cannot be downloaded. diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManager.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManager.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManager.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManager.java index 93c7a77..0b8045e 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManager.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManager.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies; +package net.momirealms.customcrops.common.dependency; import java.util.Collection; import java.util.Set; diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManagerImpl.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManagerImpl.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManagerImpl.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManagerImpl.java index dc8ee83..cbf177a 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyManagerImpl.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyManagerImpl.java @@ -23,18 +23,15 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies; +package net.momirealms.customcrops.common.dependency; -import com.google.common.collect.ImmutableSet; -import net.momirealms.customcrops.CustomCropsPluginImpl; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.libraries.classpath.ClassPathAppender; -import net.momirealms.customcrops.libraries.dependencies.classloader.IsolatedClassLoader; -import net.momirealms.customcrops.libraries.dependencies.relocation.Relocation; -import net.momirealms.customcrops.libraries.dependencies.relocation.RelocationHandler; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import net.momirealms.customcrops.common.dependency.classloader.IsolatedClassLoader; +import net.momirealms.customcrops.common.dependency.relocation.Relocation; +import net.momirealms.customcrops.common.dependency.relocation.RelocationHandler; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; +import net.momirealms.customcrops.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customcrops.common.util.FileUtils; -import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -42,6 +39,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; /** * Loads and manages runtime dependencies for the plugin. @@ -57,20 +55,24 @@ public class DependencyManagerImpl implements DependencyManager { /** A map of dependencies which have already been loaded. */ private final EnumMap loaded = new EnumMap<>(Dependency.class); /** A map of isolated classloaders which have been created. */ - private final Map, IsolatedClassLoader> loaders = new HashMap<>(); + private final Map, IsolatedClassLoader> loaders = new HashMap<>(); /** Cached relocation handler instance. */ - private @MonotonicNonNull RelocationHandler relocationHandler = null; + private final RelocationHandler relocationHandler; + private final Executor loadingExecutor; + private final CustomCropsPlugin plugin; - public DependencyManagerImpl(CustomCropsPluginImpl plugin, ClassPathAppender classPathAppender) { + public DependencyManagerImpl(CustomCropsPlugin plugin) { + this.plugin = plugin; this.registry = new DependencyRegistry(); this.cacheDirectory = setupCacheDirectory(plugin); - this.classPathAppender = classPathAppender; + this.classPathAppender = plugin.getClassPathAppender(); + this.loadingExecutor = plugin.getScheduler().async(); this.relocationHandler = new RelocationHandler(this); } @Override public ClassLoader obtainClassLoaderWith(Set dependencies) { - ImmutableSet set = ImmutableSet.copyOf(dependencies); + Set set = new HashSet<>(dependencies); for (Dependency dependency : dependencies) { if (!this.loaded.containsKey(dependency)) { @@ -111,13 +113,15 @@ public class DependencyManagerImpl implements DependencyManager { continue; } - try { - loadDependency(dependency); - } catch (Throwable e) { - new RuntimeException("Unable to load dependency " + dependency.name(), e).printStackTrace(); - } finally { - latch.countDown(); - } + this.loadingExecutor.execute(() -> { + try { + loadDependency(dependency); + } catch (Throwable e) { + this.plugin.getPluginLogger().warn("Unable to load dependency " + dependency.name(), e); + } finally { + latch.countDown(); + } + }); } try { @@ -142,7 +146,8 @@ public class DependencyManagerImpl implements DependencyManager { } private Path downloadDependency(Dependency dependency) throws DependencyDownloadException { - Path file = this.cacheDirectory.resolve(dependency.getFileName(null)); + String fileName = dependency.getFileName(null); + Path file = this.cacheDirectory.resolve(fileName); // if the file already exists, don't attempt to re-download it. if (Files.exists(file)) { @@ -150,30 +155,19 @@ public class DependencyManagerImpl implements DependencyManager { } DependencyDownloadException lastError = null; - String fileName = dependency.getFileName(null); String forceRepo = dependency.getRepo(); - if (forceRepo == null) { - // attempt to download the dependency from each repo in order. - for (DependencyRepository repo : DependencyRepository.values()) { + List repository = DependencyRepository.getByID(forceRepo); + if (!repository.isEmpty()) { + int i = 0; + while (i < repository.size()) { try { - LogUtils.info("Downloading dependency(" + fileName + ") from " + repo.getUrl() + dependency.getMavenRepoPath()); - repo.download(dependency, file); - LogUtils.info("Successfully downloaded " + fileName); - return file; - } catch (DependencyDownloadException e) { - lastError = e; - } - } - } else { - DependencyRepository repository = DependencyRepository.getByID(forceRepo); - if (repository != null) { - try { - LogUtils.info("Downloading dependency(" + fileName + ") from " + repository.getUrl() + dependency.getMavenRepoPath()); - repository.download(dependency, file); - LogUtils.info("Successfully downloaded " + fileName); + plugin.getPluginLogger().info("Downloading dependency(" + fileName + ")[" + repository.get(i).getUrl() + dependency.getMavenRepoPath() + "]"); + repository.get(i).download(dependency, file); + plugin.getPluginLogger().info("Successfully downloaded " + fileName); return file; } catch (DependencyDownloadException e) { lastError = e; + i++; } } } @@ -193,16 +187,21 @@ public class DependencyManagerImpl implements DependencyManager { return remappedFile; } - LogUtils.info("Remapping " + dependency.getFileName(null)); + plugin.getPluginLogger().info("Remapping " + dependency.getFileName(null)); relocationHandler.remap(normalFile, remappedFile, rules); - LogUtils.info("Successfully remapped " + dependency.getFileName(null)); + plugin.getPluginLogger().info("Successfully remapped " + dependency.getFileName(null)); return remappedFile; } - private static Path setupCacheDirectory(CustomCropsPluginImpl plugin) { - File folder = new File(plugin.getDataFolder(), "libs"); - folder.mkdirs(); - return folder.toPath(); + private static Path setupCacheDirectory(CustomCropsPlugin plugin) { + Path cacheDirectory = plugin.getDataDirectory().resolve("libs"); + try { + FileUtils.createDirectoriesIfNotExists(cacheDirectory); + } catch (IOException e) { + throw new RuntimeException("Unable to create libs directory", e); + } + + return cacheDirectory; } @Override @@ -222,8 +221,7 @@ public class DependencyManagerImpl implements DependencyManager { } if (firstEx != null) { - firstEx.printStackTrace(); + plugin.getPluginLogger().severe(firstEx.getMessage(), firstEx); } } - } diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRegistry.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRegistry.java similarity index 93% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRegistry.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRegistry.java index e2714fc..8c13401 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRegistry.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRegistry.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies; +package net.momirealms.customcrops.common.dependency; import com.google.gson.JsonElement; @@ -36,7 +36,7 @@ public class DependencyRegistry { return switch (dependency) { // all used within 'isolated' classloaders, and are therefore not // relocated. - case ASM, ASM_COMMONS, JAR_RELOCATOR, H2_DRIVER, SQLITE_DRIVER -> false; + case ASM, ASM_COMMONS, JAR_RELOCATOR, ZSTD -> false; default -> true; }; } diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRepository.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRepository.java similarity index 86% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRepository.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRepository.java index d41347f..667768f 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/DependencyRepository.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/DependencyRepository.java @@ -23,9 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies; - -import com.google.common.io.ByteStreams; +package net.momirealms.customcrops.common.dependency; import java.io.IOException; import java.io.InputStream; @@ -33,6 +31,10 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; /** * Represents a repository which contains {@link Dependency}s. @@ -54,15 +56,7 @@ public enum DependencyRepository { /** * Maven Central Mirror */ - MAVEN_CENTRAL_MIRROR("aliyun", "https://maven.aliyun.com/repository/public/"), - /** - * Code MC - */ - CODE_MC("codemc", "https://repo.codemc.io/repository/maven-public/"), - /** - * Jitpack - */ - JITPACK("jitpack", "https://jitpack.io/"); + MAVEN_CENTRAL_MIRROR("maven", "https://maven.aliyun.com/repository/public/"); private final String url; private final String id; @@ -76,13 +70,18 @@ public enum DependencyRepository { return url; } - public static DependencyRepository getByID(String id) { + public static List getByID(String id) { + ArrayList repositories = new ArrayList<>(); for (DependencyRepository repository : values()) { if (id.equals(repository.id)) { - return repository; + repositories.add(repository); } } - return null; + // 中国大陆优先使用阿里云镜像 + if (Locale.getDefault() == Locale.SIMPLIFIED_CHINESE) { + Collections.reverse(repositories); + } + return repositories; } /** @@ -108,7 +107,7 @@ public enum DependencyRepository { try { URLConnection connection = openConnection(dependency); try (InputStream in = connection.getInputStream()) { - byte[] bytes = ByteStreams.toByteArray(in); + byte[] bytes = in.readAllBytes(); if (bytes.length == 0) { throw new DependencyDownloadException("Empty stream"); } diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/classloader/IsolatedClassLoader.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/classloader/IsolatedClassLoader.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/classloader/IsolatedClassLoader.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/classloader/IsolatedClassLoader.java index ad4c962..09e7a74 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/classloader/IsolatedClassLoader.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/classloader/IsolatedClassLoader.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies.classloader; +package net.momirealms.customcrops.common.dependency.classloader; import java.net.URL; import java.net.URLClassLoader; diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/Relocation.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/Relocation.java similarity index 97% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/Relocation.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/Relocation.java index 3578cc0..b1e10bc 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/Relocation.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/Relocation.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies.relocation; +package net.momirealms.customcrops.common.dependency.relocation; import java.util.Objects; diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHandler.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHandler.java similarity index 89% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHandler.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHandler.java index 2320e85..02298ea 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHandler.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHandler.java @@ -23,11 +23,11 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies.relocation; +package net.momirealms.customcrops.common.dependency.relocation; -import net.momirealms.customcrops.libraries.dependencies.Dependency; -import net.momirealms.customcrops.libraries.dependencies.DependencyManager; -import net.momirealms.customcrops.libraries.dependencies.classloader.IsolatedClassLoader; +import net.momirealms.customcrops.common.dependency.Dependency; +import net.momirealms.customcrops.common.dependency.DependencyManager; +import net.momirealms.customcrops.common.dependency.classloader.IsolatedClassLoader; import java.io.File; import java.io.IOException; @@ -66,8 +66,8 @@ public class RelocationHandler { this.jarRelocatorRunMethod.setAccessible(true); } catch (Exception e) { try { - if (classLoader instanceof IsolatedClassLoader) { - ((IsolatedClassLoader) classLoader).close(); + if (classLoader instanceof IsolatedClassLoader isolatedClassLoader) { + isolatedClassLoader.close(); } } catch (IOException ex) { e.addSuppressed(ex); @@ -87,5 +87,4 @@ public class RelocationHandler { Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings); this.jarRelocatorRunMethod.invoke(relocator); } - } diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHelper.java b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHelper.java similarity index 95% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHelper.java rename to common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHelper.java index c5e64ea..fb2cf12 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/relocation/RelocationHelper.java +++ b/common/src/main/java/net/momirealms/customcrops/common/dependency/relocation/RelocationHelper.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.dependencies.relocation; +package net.momirealms.customcrops.common.dependency.relocation; public final class RelocationHelper { @@ -32,6 +32,5 @@ public final class RelocationHelper { public static final String OKHTTP3_STRING = String.valueOf(new char[]{'o', 'k', 'h', 't', 't', 'p', '3'}); private RelocationHelper() { - } } diff --git a/common/src/main/java/net/momirealms/customcrops/common/helper/AdventureHelper.java b/common/src/main/java/net/momirealms/customcrops/common/helper/AdventureHelper.java new file mode 100644 index 0000000..bfa728e --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/helper/AdventureHelper.java @@ -0,0 +1,285 @@ +/* + * 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.common.helper; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.title.Title; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * Helper class for handling Adventure components and related functionalities. + */ +public class AdventureHelper { + + private final MiniMessage miniMessage; + private final MiniMessage miniMessageStrict; + private final GsonComponentSerializer gsonComponentSerializer; + private final Cache miniMessageToJsonCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); + public static boolean legacySupport = false; + + private AdventureHelper() { + this.miniMessage = MiniMessage.builder().build(); + this.miniMessageStrict = MiniMessage.builder().strict(true).build(); + this.gsonComponentSerializer = GsonComponentSerializer.builder().build(); + } + + private static class SingletonHolder { + private static final AdventureHelper INSTANCE = new AdventureHelper(); + } + + /** + * Retrieves the singleton instance of AdventureHelper. + * + * @return the singleton instance + */ + public static AdventureHelper getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Converts a MiniMessage string to a Component. + * + * @param text the MiniMessage string + * @return the resulting Component + */ + public static Component miniMessage(String text) { + if (legacySupport) { + return getMiniMessage().deserialize(legacyToMiniMessage(text)); + } else { + return getMiniMessage().deserialize(text); + } + } + + /** + * Retrieves the MiniMessage instance. + * + * @return the MiniMessage instance + */ + public static MiniMessage getMiniMessage() { + return getInstance().miniMessage; + } + + /** + * Retrieves the GsonComponentSerializer instance. + * + * @return the GsonComponentSerializer instance + */ + public static GsonComponentSerializer getGson() { + return getInstance().gsonComponentSerializer; + } + + /** + * Converts a MiniMessage string to a JSON string. + * + * @param miniMessage the MiniMessage string + * @return the JSON string representation + */ + public static String miniMessageToJson(String miniMessage) { + AdventureHelper instance = getInstance(); + return instance.miniMessageToJsonCache.get(miniMessage, (text) -> instance.gsonComponentSerializer.serialize(miniMessage(text))); + } + + /** + * Sends a title to an audience. + * + * @param audience the audience to send the title to + * @param title the title component + * @param subtitle the subtitle component + * @param fadeIn the fade-in duration in ticks + * @param stay the stay duration in ticks + * @param fadeOut the fade-out duration in ticks + */ + public static void sendTitle(Audience audience, Component title, Component subtitle, int fadeIn, int stay, int fadeOut) { + audience.showTitle(Title.title(title, subtitle, Title.Times.times(Duration.ofMillis(fadeIn * 50L), Duration.ofMillis(stay * 50L), Duration.ofMillis(fadeOut * 50L)))); + } + + /** + * Sends an action bar message to an audience. + * + * @param audience the audience to send the action bar message to + * @param actionBar the action bar component + */ + public static void sendActionBar(Audience audience, Component actionBar) { + audience.sendActionBar(actionBar); + } + + /** + * Sends a message to an audience. + * + * @param audience the audience to send the message to + * @param message the message component + */ + public static void sendMessage(Audience audience, Component message) { + audience.sendMessage(message); + } + + /** + * Plays a sound for an audience. + * + * @param audience the audience to play the sound for + * @param sound the sound to play + */ + public static void playSound(Audience audience, Sound sound) { + audience.playSound(sound); + } + + /** + * Surrounds text with a MiniMessage font tag. + * + * @param text the text to surround + * @param font the font as a {@link Key} + * @return the text surrounded by the MiniMessage font tag + */ + public static String surroundWithMiniMessageFont(String text, Key font) { + return "" + text + ""; + } + + /** + * Surrounds text with a MiniMessage font tag. + * + * @param text the text to surround + * @param font the font as a {@link String} + * @return the text surrounded by the MiniMessage font tag + */ + public static String surroundWithMiniMessageFont(String text, String font) { + return "" + text + ""; + } + + /** + * Converts a JSON string to a MiniMessage string. + * + * @param json the JSON string + * @return the MiniMessage string representation + */ + public static String jsonToMiniMessage(String json) { + return getInstance().miniMessageStrict.serialize(getInstance().gsonComponentSerializer.deserialize(json)); + } + + /** + * Converts a JSON string to a Component. + * + * @param json the JSON string + * @return the resulting Component + */ + public static Component jsonToComponent(String json) { + return getInstance().gsonComponentSerializer.deserialize(json); + } + + /** + * Converts a Component to a JSON string. + * + * @param component the Component to convert + * @return the JSON string representation + */ + public static String componentToJson(Component component) { + return getGson().serialize(component); + } + + /** + * Checks if a character is a legacy color code. + * + * @param c the character to check + * @return true if the character is a color code, false otherwise + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isLegacyColorCode(char c) { + return c == '§' || c == '&'; + } + + /** + * Converts a legacy color code string to a MiniMessage string. + * + * @param legacy the legacy color code string + * @return the MiniMessage string representation + */ + public static String legacyToMiniMessage(String legacy) { + StringBuilder stringBuilder = new StringBuilder(); + char[] chars = legacy.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (!isLegacyColorCode(chars[i])) { + stringBuilder.append(chars[i]); + continue; + } + if (i + 1 >= chars.length) { + stringBuilder.append(chars[i]); + continue; + } + switch (chars[i+1]) { + case '0' -> stringBuilder.append(""); + case '1' -> stringBuilder.append(""); + case '2' -> stringBuilder.append(""); + case '3' -> stringBuilder.append(""); + case '4' -> stringBuilder.append(""); + case '5' -> stringBuilder.append(""); + case '6' -> stringBuilder.append(""); + case '7' -> stringBuilder.append(""); + case '8' -> stringBuilder.append(""); + case '9' -> stringBuilder.append(""); + case 'a' -> stringBuilder.append(""); + case 'b' -> stringBuilder.append(""); + case 'c' -> stringBuilder.append(""); + case 'd' -> stringBuilder.append(""); + case 'e' -> stringBuilder.append(""); + case 'f' -> stringBuilder.append(""); + case 'r' -> stringBuilder.append(""); + case 'l' -> stringBuilder.append(""); + case 'm' -> stringBuilder.append(""); + case 'o' -> stringBuilder.append(""); + case 'n' -> stringBuilder.append(""); + case 'k' -> stringBuilder.append(""); + case 'x' -> { + if (i + 13 >= chars.length + || !isLegacyColorCode(chars[i+2]) + || !isLegacyColorCode(chars[i+4]) + || !isLegacyColorCode(chars[i+6]) + || !isLegacyColorCode(chars[i+8]) + || !isLegacyColorCode(chars[i+10]) + || !isLegacyColorCode(chars[i+12])) { + stringBuilder.append(chars[i]); + continue; + } + stringBuilder + .append("<#") + .append(chars[i+3]) + .append(chars[i+5]) + .append(chars[i+7]) + .append(chars[i+9]) + .append(chars[i+11]) + .append(chars[i+13]) + .append(">"); + i += 12; + } + default -> { + stringBuilder.append(chars[i]); + continue; + } + } + i++; + } + return stringBuilder.toString(); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/QualityCrop.java b/common/src/main/java/net/momirealms/customcrops/common/helper/ExpressionHelper.java similarity index 55% rename from api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/QualityCrop.java rename to common/src/main/java/net/momirealms/customcrops/common/helper/ExpressionHelper.java index 53004bf..38b7a1a 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/mechanic/item/fertilizer/QualityCrop.java +++ b/common/src/main/java/net/momirealms/customcrops/common/helper/ExpressionHelper.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,23 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.mechanic.item.fertilizer; +package net.momirealms.customcrops.common.helper; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; +import net.objecthunter.exp4j.ExpressionBuilder; -public interface QualityCrop extends Fertilizer { +/** + * Helper class for evaluating mathematical expressions. + */ +public class ExpressionHelper { /** - * Get the chance of taking effect + * Evaluates a mathematical expression provided as a string. * - * @return chance + * @param expression the mathematical expression to evaluate + * @return the result of the evaluation as a double */ - double getChance(); - - /** - * Get the modified quality ratio - * - * @return the modified quality ratio - */ - double[] getRatio(); + public static double evaluate(String expression) { + return new ExpressionBuilder(expression).build().evaluate(); + } } diff --git a/common/src/main/java/net/momirealms/customcrops/common/helper/GsonHelper.java b/common/src/main/java/net/momirealms/customcrops/common/helper/GsonHelper.java new file mode 100644 index 0000000..c870103 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/helper/GsonHelper.java @@ -0,0 +1,59 @@ +/* + * 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.common.helper; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Helper class for managing Gson instances. + */ +public class GsonHelper { + + private final Gson gson; + + public GsonHelper() { + this.gson = new GsonBuilder() + .create(); + } + + /** + * Retrieves the Gson instance. + * + * @return the Gson instance + */ + public Gson getGson() { + return gson; + } + + /** + * Retrieves the singleton Gson instance from GsonHelper. + * + * @return the singleton Gson instance + */ + public static Gson get() { + return SingletonHolder.INSTANCE.getGson(); + } + + /** + * Static inner class for holding the singleton instance of GsonHelper. + */ + private static class SingletonHolder { + private static final GsonHelper INSTANCE = new GsonHelper(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/VersionManagerImpl.java b/common/src/main/java/net/momirealms/customcrops/common/helper/VersionHelper.java similarity index 64% rename from plugin/src/main/java/net/momirealms/customcrops/manager/VersionManagerImpl.java rename to common/src/main/java/net/momirealms/customcrops/common/helper/VersionHelper.java index 47b1b96..3bfb8b9 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/VersionManagerImpl.java +++ b/common/src/main/java/net/momirealms/customcrops/common/helper/VersionHelper.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,11 +15,9 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.manager; +package net.momirealms.customcrops.common.helper; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.util.LogUtils; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.io.BufferedReader; import java.io.InputStream; @@ -27,96 +25,17 @@ import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; -public class VersionManagerImpl extends VersionManager { +/** + * This class implements the VersionManager interface and is responsible for managing version-related information. + */ +public class VersionHelper { - private final CustomCropsPlugin plugin; - private final String pluginVersion; - private boolean foliaScheduler; - private final boolean isSpigot; - private boolean isMojmap; - - private final float mcVersion; - - @SuppressWarnings("deprecation") - public VersionManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.isSpigot = plugin.getServer().getName().equals("CraftBukkit"); - this.pluginVersion = plugin.getDescription().getVersion(); - - String[] split = plugin.getServerVersion().split("\\."); - this.mcVersion = Float.parseFloat(split[1] + "." + (split.length >= 3 ? split[2] : "0")); - - try { - Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); - this.foliaScheduler = true; - } catch (ClassNotFoundException ignored) { - this.foliaScheduler = false; - } - - // Check if the server is Mojmap - try { - Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket"); - this.isMojmap = true; - } catch (ClassNotFoundException ignored) { - - } - } - - @Override - public boolean hasRegionScheduler() { - return foliaScheduler; - } - - @Override - public String getPluginVersion() { - return pluginVersion; - } - - @Override - public boolean isSpigot() { - return isSpigot; - } - - @Override - public boolean isVersionNewerThan1_19_R3() { - return mcVersion >= 19.4; - } - - @Override - public boolean isVersionNewerThan1_20_R2() { - return mcVersion >= 20.2; - } - - @Override - public boolean isVersionNewerThan1_19() { - return mcVersion >= 19; - } - - @Override - public boolean isVersionNewerThan1_19_R2() { - return mcVersion >= 19.3; - } - - @Override - public boolean isVersionNewerThan1_20() { - return mcVersion >= 20; - } - - @Override - public boolean isVersionNewerThan1_18() { - return mcVersion >= 18; - } - - @Override - public boolean isMojmap() { - return isMojmap; - } - - @Override - public CompletableFuture checkUpdate() { + // Method to asynchronously check for plugin updates + public static final Function> UPDATE_CHECKER = (plugin) -> { CompletableFuture updateFuture = new CompletableFuture<>(); - plugin.getScheduler().runTaskAsync(() -> { + plugin.getScheduler().async().execute(() -> { try { URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2625&key=version"); URLConnection conn = url.openConnection(); @@ -124,7 +43,7 @@ public class VersionManagerImpl extends VersionManager { conn.setReadTimeout(60000); InputStream inputStream = conn.getInputStream(); String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine(); - String current = plugin.getVersionManager().getPluginVersion(); + String current = plugin.getPluginVersion(); inputStream.close(); if (!compareVer(newest, current)) { updateFuture.complete(false); @@ -132,14 +51,75 @@ public class VersionManagerImpl extends VersionManager { } updateFuture.complete(true); } catch (Exception exception) { - LogUtils.warn("Error occurred when checking update.", exception); + plugin.getPluginLogger().warn("Error occurred when checking update.", exception); updateFuture.complete(false); } }); return updateFuture; + }; + + private static float version; + private static boolean mojmap; + private static boolean folia; + + public static void init(String serverVersion) { + String[] split = serverVersion.split("\\."); + version = Float.parseFloat(split[1] + "." + (split.length == 3 ? split[2] : "0")); + checkMojMap(); + checkFolia(); } - private boolean compareVer(String newV, String currentV) { + private static void checkMojMap() { + // Check if the server is Mojmap + try { + Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket"); + mojmap = true; + } catch (ClassNotFoundException ignored) { + } + } + + private static void checkFolia() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + } catch (ClassNotFoundException ignored) { + } + } + + public static boolean isVersionNewerThan1_18() { + return version >= 18; + } + + public static boolean isVersionNewerThan1_19() { + return version >= 19; + } + + public static boolean isVersionNewerThan1_19_4() { + return version >= 19.4; + } + + public static boolean isVersionNewerThan1_19_3() { + return version >= 19.3; + } + + public static boolean isVersionNewerThan1_20() { + return version >= 20.0; + } + + public static boolean isVersionNewerThan1_20_5() { + return version >= 20.5; + } + + public static boolean isFolia() { + return folia; + } + + public static boolean isMojmap() { + return mojmap; + } + + // Method to compare two version strings + private static boolean compareVer(String newV, String currentV) { if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) { return false; } @@ -183,4 +163,4 @@ public class VersionManagerImpl extends VersionManager { } return newVS.length > currentVS.length; } -} +} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/AbstractItem.java b/common/src/main/java/net/momirealms/customcrops/common/item/AbstractItem.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/AbstractItem.java rename to common/src/main/java/net/momirealms/customcrops/common/item/AbstractItem.java index 4deb2c0..a3ee1ea 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/AbstractItem.java +++ b/common/src/main/java/net/momirealms/customcrops/common/item/AbstractItem.java @@ -1,6 +1,23 @@ -package net.momirealms.customcrops.mechanic.item.factory; +/* + * 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 . + */ -import net.momirealms.customcrops.api.CustomCropsPlugin; +package net.momirealms.customcrops.common.item; + +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.util.List; import java.util.Optional; @@ -17,17 +34,6 @@ public class AbstractItem implements Item { this.item = item; } - @Override - public Item customModelData(Integer data) { - factory.customModelData(item, data); - return this; - } - - @Override - public Optional customModelData() { - return factory.customModelData(item); - } - @Override public Item damage(Integer data) { factory.damage(item, data); @@ -50,6 +56,17 @@ public class AbstractItem implements Item { return factory.maxDamage(item); } + @Override + public Item customModelData(Integer data) { + factory.customModelData(item, data); + return this; + } + + @Override + public Optional customModelData() { + return factory.customModelData(item); + } + @Override public Item lore(List lore) { factory.lore(item, lore); @@ -61,6 +78,12 @@ public class AbstractItem implements Item { return factory.lore(item); } + + @Override + public boolean unbreakable() { + return factory.unbreakable(item); + } + @Override public Optional getTag(Object... path) { return factory.getTag(item, path); @@ -101,4 +124,8 @@ public class AbstractItem implements Item { public void update() { factory.update(item); } + + public R getRTagItem() { + return item; + } } diff --git a/common/src/main/java/net/momirealms/customcrops/common/item/ComponentKeys.java b/common/src/main/java/net/momirealms/customcrops/common/item/ComponentKeys.java new file mode 100644 index 0000000..f6f5d99 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/item/ComponentKeys.java @@ -0,0 +1,32 @@ +/* + * 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.common.item; + +import net.kyori.adventure.key.Key; + +public class ComponentKeys { + + public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString(); + public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString(); + public static final String LORE = Key.key("minecraft", "lore").asString(); + public static final String DAMAGE = Key.key("minecraft", "damage").asString(); + public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString(); + public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString(); + public static final String ENCHANTMENTS = Key.key("minecraft", "enchantments").asString(); + public static final String STORED_ENCHANTMENTS = Key.key("minecraft", "stored_enchantments").asString(); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/item/Item.java b/common/src/main/java/net/momirealms/customcrops/common/item/Item.java new file mode 100644 index 0000000..19b2d85 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/item/Item.java @@ -0,0 +1,157 @@ +/* + * 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.common.item; + +import java.util.List; +import java.util.Optional; + +/** + * Interface representing an item. + * This interface provides methods for managing item properties such as custom model data, + * damage, display name, lore, enchantments, and tags. + * + * @param the type of the item implementation + */ +public interface Item { + + /** + * Sets the custom model data for the item. + * + * @param data the custom model data to set + * @return the current {@link Item} instance for method chaining + */ + Item customModelData(Integer data); + + /** + * Retrieves the custom model data of the item. + * + * @return an {@link Optional} containing the custom model data, or empty if not set + */ + Optional customModelData(); + + /** + * Sets the damage value for the item. + * + * @param data the damage value to set + * @return the current {@link Item} instance for method chaining + */ + Item damage(Integer data); + + /** + * Retrieves the damage value of the item. + * + * @return an {@link Optional} containing the damage value, or empty if not set + */ + Optional damage(); + + /** + * Sets the maximum damage value for the item. + * + * @param data the maximum damage value to set + * @return the current {@link Item} instance for method chaining + */ + Item maxDamage(Integer data); + + /** + * Retrieves the maximum damage value of the item. + * + * @return an {@link Optional} containing the maximum damage value, or empty if not set + */ + Optional maxDamage(); + + /** + * Sets the lore for the item. + * + * @param lore the lore to set + * @return the current {@link Item} instance for method chaining + */ + Item lore(List lore); + + /** + * Retrieves the lore of the item. + * + * @return an {@link Optional} containing the lore, or empty if not set + */ + Optional> lore(); + + /** + * Checks if the item is unbreakable. + * + * @return true if the item is unbreakable, false otherwise + */ + boolean unbreakable(); + + /** + * Retrieves the tag value at the specified path. + * + * @param path the path to the tag value + * @return an {@link Optional} containing the tag value, or empty if not found + */ + Optional getTag(Object... path); + + /** + * Sets the tag value at the specified path. + * + * @param value the value to set + * @param path the path to the tag value + * @return the current {@link Item} instance for method chaining + */ + Item setTag(Object value, Object... path); + + /** + * Checks if the item has a tag value at the specified path. + * + * @param path the path to the tag value + * @return true if the tag value exists, false otherwise + */ + boolean hasTag(Object... path); + + /** + * Removes the tag value at the specified path. + * + * @param path the path to the tag value + * @return true if the tag was removed, false otherwise + */ + boolean removeTag(Object... path); + + /** + * Retrieves the underlying item implementation. + * + * @return the item implementation of type {@link I} + */ + I getItem(); + + /** + * Loads changes to the item. + * + * @return the loaded item implementation of type {@link I} + */ + I load(); + + /** + * Loads the changes and gets a copy of the item. + * + * @return a copy of the loaded item implementation of type {@link I} + */ + I loadCopy(); + + /** + * Loads the {@link I}'s changes to the {@link Item} instance. + */ + void update(); +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ItemFactory.java b/common/src/main/java/net/momirealms/customcrops/common/item/ItemFactory.java similarity index 55% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ItemFactory.java rename to common/src/main/java/net/momirealms/customcrops/common/item/ItemFactory.java index 72c62c3..22773a3 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ItemFactory.java +++ b/common/src/main/java/net/momirealms/customcrops/common/item/ItemFactory.java @@ -1,6 +1,23 @@ -package net.momirealms.customcrops.mechanic.item.factory; +/* + * 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 . + */ -import net.momirealms.customcrops.api.CustomCropsPlugin; +package net.momirealms.customcrops.common.item; + +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.util.List; import java.util.Objects; @@ -35,19 +52,21 @@ public abstract class ItemFactory

{ protected abstract I loadCopy(R item); - protected abstract Optional customModelData(R item); - protected abstract void customModelData(R item, Integer data); + protected abstract Optional customModelData(R item); + protected abstract Optional> lore(R item); protected abstract void lore(R item, List lore); - protected abstract Optional maxDamage(R item); - - protected abstract void maxDamage(R item, Integer data); - protected abstract Optional damage(R item); - protected abstract void damage(R item, Integer data); + protected abstract void damage(R item, Integer damage); + + protected abstract Optional maxDamage(R item); + + protected abstract void maxDamage(R item, Integer damage); + + protected abstract boolean unbreakable(R item); } diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionFormatter.java b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionFormatter.java new file mode 100644 index 0000000..49d198c --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionFormatter.java @@ -0,0 +1,35 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.caption.Caption; +import org.incendo.cloud.caption.CaptionVariable; +import org.incendo.cloud.minecraft.extras.caption.ComponentCaptionFormatter; + +import java.util.List; + +public class CustomCropsCaptionFormatter implements ComponentCaptionFormatter { + + @Override + public @NonNull Component formatCaption(@NonNull Caption captionKey, @NonNull C recipient, @NonNull String caption, @NonNull List<@NonNull CaptionVariable> variables) { + Component component = ComponentCaptionFormatter.translatable().formatCaption(captionKey, recipient, caption, variables); + return TranslationManager.render(component); + } +} diff --git a/api/src/main/java/net/momirealms/customcrops/api/common/Initable.java b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionKeys.java similarity index 56% rename from api/src/main/java/net/momirealms/customcrops/api/common/Initable.java rename to common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionKeys.java index 15ff32b..7cc8d7d 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/common/Initable.java +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionKeys.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,13 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.common; +package net.momirealms.customcrops.common.locale; -public interface Initable { +import org.incendo.cloud.caption.Caption; - /** - * init - */ - void init(); +public final class CustomCropsCaptionKeys { - /** - * disable - */ - void disable(); + public static final Caption ARGUMENT_PARSE_FAILURE_TIME = Caption.of("argument.parse.failure.time"); + public static final Caption ARGUMENT_PARSE_FAILURE_URL = Caption.of("argument.parse.failure.url"); + public static final Caption ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR = Caption.of("argument.parse.failure.namedtextcolor"); } diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionProvider.java b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionProvider.java new file mode 100644 index 0000000..4be1009 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/CustomCropsCaptionProvider.java @@ -0,0 +1,37 @@ +/* + * 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.common.locale; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.caption.CaptionProvider; +import org.incendo.cloud.caption.DelegatingCaptionProvider; + +public final class CustomCropsCaptionProvider extends DelegatingCaptionProvider { + + private static final CaptionProvider PROVIDER = CaptionProvider.constantProvider() + .putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_URL, "") + .putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_TIME, "") + .putCaption(CustomCropsCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR, "") + .build(); + + @SuppressWarnings("unchecked") + @Override + public @NonNull CaptionProvider delegate() { + return (CaptionProvider) PROVIDER; + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/MessageConstants.java b/common/src/main/java/net/momirealms/customcrops/common/locale/MessageConstants.java new file mode 100644 index 0000000..7305293 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/MessageConstants.java @@ -0,0 +1,32 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; + +public interface MessageConstants { + + TranslatableComponent.Builder COMMAND_RELOAD_SUCCESS = Component.translatable().key("command.reload.success"); + TranslatableComponent.Builder SEASON_SPRING = Component.translatable().key("season.spring"); + TranslatableComponent.Builder SEASON_SUMMER = Component.translatable().key("season.summer"); + TranslatableComponent.Builder SEASON_AUTUMN = Component.translatable().key("season.autumn"); + TranslatableComponent.Builder SEASON_WINTER = Component.translatable().key("season.winter"); + TranslatableComponent.Builder SEASON_DISABLE = Component.translatable().key("season.disable"); + +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistry.java b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistry.java new file mode 100644 index 0000000..00072df --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistry.java @@ -0,0 +1,73 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.translation.Translator; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +public interface MiniMessageTranslationRegistry extends Translator { + + static @NotNull MiniMessageTranslationRegistry create(final Key name, final MiniMessage miniMessage) { + return new MiniMessageTranslationRegistryImpl(requireNonNull(name, "name"), requireNonNull(miniMessage, "MiniMessage")); + } + + void register(@NotNull String key, @NotNull Locale locale, @NotNull String format); + + void unregister(@NotNull String key); + + boolean contains(@NotNull String key); + + String miniMessageTranslation(@NotNull String key, @NotNull Locale locale); + + void defaultLocale(@NotNull Locale defaultLocale); + + default void registerAll(final @NotNull Locale locale, final @NotNull Map bundle) { + this.registerAll(locale, bundle.keySet(), bundle::get); + } + + default void registerAll(final @NotNull Locale locale, final @NotNull Set keys, final Function function) { + IllegalArgumentException firstError = null; + int errorCount = 0; + for (final String key : keys) { + try { + this.register(key, locale, function.apply(key)); + } catch (final IllegalArgumentException e) { + if (firstError == null) { + firstError = e; + } + errorCount++; + } + } + if (firstError != null) { + if (errorCount == 1) { + throw firstError; + } else if (errorCount > 1) { + throw new IllegalArgumentException(String.format("Invalid key (and %d more)", errorCount - 1), firstError); + } + } + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistryImpl.java b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistryImpl.java new file mode 100644 index 0000000..6f24359 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslationRegistryImpl.java @@ -0,0 +1,235 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.internal.Internals; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.util.TriState; +import net.kyori.examination.Examinable; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +public class MiniMessageTranslationRegistryImpl implements Examinable, MiniMessageTranslationRegistry { + private final Key name; + private final Map translations = new ConcurrentHashMap<>(); + private Locale defaultLocale = Locale.US; + private final MiniMessage miniMessage; + + MiniMessageTranslationRegistryImpl(final Key name, final MiniMessage miniMessage) { + this.name = name; + this.miniMessage = miniMessage; + } + + @Override + public void register(final @NotNull String key, final @NotNull Locale locale, final @NotNull String format) { + this.translations.computeIfAbsent(key, Translation::new).register(locale, format); + } + + @Override + public void unregister(final @NotNull String key) { + this.translations.remove(key); + } + + @Override + public boolean contains(final @NotNull String key) { + return this.translations.containsKey(key); + } + + @Override + public @NotNull Key name() { + return name; + } + + @Override + public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) { + // No need to implement this method + return null; + } + + @Override + public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { + Translation translation = translations.get(component.key()); + if (translation == null) { + return null; + } + String miniMessageString = translation.translate(locale); + if (miniMessageString == null) { + return null; + } + if (miniMessageString.isEmpty()) { + return Component.empty(); + } + final Component resultingComponent; + if (component.arguments().isEmpty()) { + resultingComponent = this.miniMessage.deserialize(miniMessageString); + } else { + resultingComponent = this.miniMessage.deserialize(miniMessageString, new ArgumentTag(component.arguments())); + } + if (component.children().isEmpty()) { + return resultingComponent; + } else { + return resultingComponent.children(component.children()); + } + } + + @Override + public String miniMessageTranslation(@NotNull String key, @NotNull Locale locale) { + Translation translation = translations.get(key); + if (translation == null) { + return null; + } + return translation.translate(locale); + } + + @Override + public @NotNull TriState hasAnyTranslations() { + if (!this.translations.isEmpty()) { + return TriState.TRUE; + } + return TriState.FALSE; + } + + @Override + public void defaultLocale(final @NotNull Locale defaultLocale) { + this.defaultLocale = requireNonNull(defaultLocale, "defaultLocale"); + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("translations", this.translations)); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof MiniMessageTranslationRegistryImpl that)) return false; + return this.name.equals(that.name) + && this.translations.equals(that.translations) + && this.defaultLocale.equals(that.defaultLocale); + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.translations, this.defaultLocale); + } + + @Override + public String toString() { + return Internals.toString(this); + } + + public static class ArgumentTag implements TagResolver { + private static final String NAME = "argument"; + private static final String NAME_1 = "arg"; + + private final List argumentComponents; + + public ArgumentTag(final @NotNull List argumentComponents) { + this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents"); + } + + @Override + public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException { + final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments)); + + if (index < 0 || index >= argumentComponents.size()) { + throw ctx.newException("Invalid argument number", arguments); + } + + return Tag.inserting(argumentComponents.get(index)); + } + + @Override + public boolean has(final @NotNull String name) { + return name.equals(NAME) || name.equals(NAME_1); + } + } + + final class Translation implements Examinable { + private final String key; + private final Map formats; + + Translation(final @NotNull String key) { + this.key = requireNonNull(key, "translation key"); + this.formats = new ConcurrentHashMap<>(); + } + + void register(final @NotNull Locale locale, final @NotNull String format) { + if (this.formats.putIfAbsent(requireNonNull(locale, "locale"), requireNonNull(format, "message format")) != null) { + throw new IllegalArgumentException(String.format("Translation already exists: %s for %s", this.key, locale)); + } + } + + @Nullable String translate(final @NotNull Locale locale) { + String format = this.formats.get(requireNonNull(locale, "locale")); + if (format == null) { + format = this.formats.get(new Locale(locale.getLanguage())); // try without country + if (format == null) { + format = this.formats.get(MiniMessageTranslationRegistryImpl.this.defaultLocale); // try local default locale + } + } + return format; + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("key", this.key), + ExaminableProperty.of("formats", this.formats) + ); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof Translation that)) return false; + return this.key.equals(that.key) && + this.formats.equals(that.formats); + } + + @Override + public int hashCode() { + return Objects.hash(this.key, this.formats); + } + + @Override + public String toString() { + return Internals.toString(this); + } + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslator.java b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslator.java new file mode 100644 index 0000000..6106ccf --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslator.java @@ -0,0 +1,47 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.translation.Translator; +import net.kyori.examination.Examinable; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; + +public interface MiniMessageTranslator extends Translator, Examinable { + + static @NotNull MiniMessageTranslator translator() { + return MiniMessageTranslatorImpl.INSTANCE; + } + + static @NotNull TranslatableComponentRenderer renderer() { + return MiniMessageTranslatorImpl.INSTANCE.renderer; + } + + static @NotNull Component render(final @NotNull Component component, final @NotNull Locale locale) { + return renderer().render(component, locale); + } + + @NotNull Iterable sources(); + + boolean addSource(final @NotNull Translator source); + + boolean removeSource(final @NotNull Translator source); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslatorImpl.java b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslatorImpl.java new file mode 100644 index 0000000..e566f5d --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/MiniMessageTranslatorImpl.java @@ -0,0 +1,92 @@ +/* + * 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.common.locale; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; +import net.kyori.adventure.translation.Translator; +import net.kyori.adventure.util.TriState; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +public class MiniMessageTranslatorImpl implements MiniMessageTranslator { + + private static final Key NAME = Key.key("customcrops", "main"); + static final MiniMessageTranslatorImpl INSTANCE = new MiniMessageTranslatorImpl(); + final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(this); + private final Set sources = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + @Override + public @NotNull Key name() { + return NAME; + } + + @Override + public @NotNull TriState hasAnyTranslations() { + if (!this.sources.isEmpty()) { + return TriState.TRUE; + } + return TriState.FALSE; + } + + @Override + public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) { + // No need to implement this method + return null; + } + + @Override + public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { + for (final Translator source : this.sources) { + final Component translation = source.translate(component, locale); + if (translation != null) return translation; + } + return null; + } + + @Override + public @NotNull Iterable sources() { + return Collections.unmodifiableSet(this.sources); + } + + @Override + public boolean addSource(final @NotNull Translator source) { + if (source == this) throw new IllegalArgumentException("MiniMessageTranslationSource"); + return this.sources.add(source); + } + + @Override + public boolean removeSource(final @NotNull Translator source) { + return this.sources.remove(source); + } + + @Override + public @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("sources", this.sources)); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/locale/TranslationManager.java b/common/src/main/java/net/momirealms/customcrops/common/locale/TranslationManager.java new file mode 100644 index 0000000..f385ae6 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/locale/TranslationManager.java @@ -0,0 +1,201 @@ +/* + * 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.common.locale; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.translation.Translator; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; +import net.momirealms.customcrops.common.util.Pair; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TranslationManager { + + public static final Locale DEFAULT_LOCALE = Locale.ENGLISH; + private static final List locales = List.of("en", "zh_cn"); + private static TranslationManager instance; + private static Locale FORCE_LOCALE = null; + + private final CustomCropsPlugin plugin; + private final Set installed = ConcurrentHashMap.newKeySet(); + private MiniMessageTranslationRegistry registry; + private final Path translationsDirectory; + + public TranslationManager(CustomCropsPlugin plugin) { + this.plugin = plugin; + this.translationsDirectory = this.plugin.getConfigDirectory().resolve("translations"); + instance = this; + } + + public static void forceLocale(Locale locale) { + FORCE_LOCALE = locale; + } + + public void reload() { + // remove any previous registry + if (this.registry != null) { + MiniMessageTranslator.translator().removeSource(this.registry); + this.installed.clear(); + } + for (String lang : locales) { + this.plugin.getConfigManager().saveResource("translations/" + lang + ".yml"); + } + + this.registry = MiniMessageTranslationRegistry.create(Key.key("customcrops", "main"), AdventureHelper.getMiniMessage()); + this.registry.defaultLocale(DEFAULT_LOCALE); + this.loadFromFileSystem(this.translationsDirectory, false); + MiniMessageTranslator.translator().addSource(this.registry); + } + + public static String miniMessageTranslation(String key) { + return miniMessageTranslation(key, null); + } + + public static String miniMessageTranslation(String key, @Nullable Locale locale) { + if (FORCE_LOCALE != null) { + return instance.registry.miniMessageTranslation(key, FORCE_LOCALE); + } + if (locale == null) { + locale = Locale.getDefault(); + if (locale == null) { + locale = DEFAULT_LOCALE; + } + } + return instance.registry.miniMessageTranslation(key, locale); + } + + public static Component render(Component component) { + return render(component, null); + } + + public static Component render(Component component, @Nullable Locale locale) { + if (FORCE_LOCALE != null) { + return MiniMessageTranslator.render(component, FORCE_LOCALE); + } + if (locale == null) { + locale = Locale.getDefault(); + if (locale == null) { + locale = DEFAULT_LOCALE; + } + } + return MiniMessageTranslator.render(component, locale); + } + + public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) { + List translationFiles; + try (Stream stream = Files.list(directory)) { + translationFiles = stream.filter(TranslationManager::isTranslationFile).collect(Collectors.toList()); + } catch (IOException e) { + translationFiles = Collections.emptyList(); + } + + if (translationFiles.isEmpty()) { + return; + } + + Map> loaded = new HashMap<>(); + for (Path translationFile : translationFiles) { + try { + Pair> result = loadTranslationFile(translationFile); + loaded.put(result.left(), result.right()); + } catch (Exception e) { + if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) { + this.plugin.getPluginLogger().warn("Error loading locale file: " + translationFile.getFileName(), e); + } + } + } + + // try registering the locale without a country code - if we don't already have a registration for that + loaded.forEach((locale, bundle) -> { + Locale localeWithoutCountry = new Locale(locale.getLanguage()); + if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) { + try { + this.registry.registerAll(localeWithoutCountry, bundle); + } catch (IllegalArgumentException e) { + // ignore + } + } + }); + + Locale localLocale = Locale.getDefault(); + if (!this.installed.contains(localLocale)) { + plugin.getPluginLogger().warn(localLocale.toString().toLowerCase(Locale.ENGLISH) + ".yml not exists, using en.yml as default locale."); + } + } + + public static boolean isTranslationFile(Path path) { + return path.getFileName().toString().endsWith(".yml"); + } + + private static boolean isAdventureDuplicatesException(Exception e) { + return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists")); + } + + @SuppressWarnings("unchecked") + private Pair> loadTranslationFile(Path translationFile) { + String fileName = translationFile.getFileName().toString(); + String localeString = fileName.substring(0, fileName.length() - ".yml".length()); + Locale locale = parseLocale(localeString); + + if (locale == null) { + throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register."); + } + + Map bundle = new HashMap<>(); + YamlDocument document = plugin.getConfigManager().loadConfig("translations" + "\\" + translationFile.getFileName(), '@'); + try { + document.save(new File(plugin.getDataDirectory().toFile(), "translations" + "\\" + translationFile.getFileName())); + } catch (IOException e) { + throw new IllegalStateException("Could not update translation file: " + translationFile.getFileName(), e); + } + Map map = document.getStringRouteMappedValues(false); + map.remove("config-version"); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof List list) { + List strList = (List) list; + StringJoiner stringJoiner = new StringJoiner(""); + for (String str : strList) { + stringJoiner.add(str); + } + bundle.put(entry.getKey(), stringJoiner.toString()); + } else if (entry.getValue() instanceof String str) { + bundle.put(entry.getKey(), str); + } + } + + this.registry.registerAll(locale, bundle); + this.installed.add(locale); + + return Pair.of(locale, bundle); + } + + public static @Nullable Locale parseLocale(@Nullable String locale) { + return locale == null || locale.isEmpty() ? null : Translator.parseLocale(locale); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsPlugin.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsPlugin.java new file mode 100644 index 0000000..77eeb24 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsPlugin.java @@ -0,0 +1,138 @@ +/* + * 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.common.plugin; + +import net.momirealms.customcrops.common.config.ConfigLoader; +import net.momirealms.customcrops.common.dependency.DependencyManager; +import net.momirealms.customcrops.common.locale.TranslationManager; +import net.momirealms.customcrops.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customcrops.common.plugin.logging.PluginLogger; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter; + +import java.io.InputStream; +import java.nio.file.Path; + +/** + * Interface representing the main CustomCrops plugin. + */ +public interface CustomCropsPlugin { + + /** + * Retrieves an input stream for a resource file within the plugin. + * + * @param filePath the path to the resource file + * @return an {@link InputStream} for the resource file + */ + InputStream getResourceStream(String filePath); + + /** + * Retrieves the plugin logger. + * + * @return the {@link PluginLogger} instance + */ + PluginLogger getPluginLogger(); + + /** + * Retrieves the class path appender. + * + * @return the {@link ClassPathAppender} instance + */ + ClassPathAppender getClassPathAppender(); + + /** + * Retrieves the scheduler adapter. + * + * @return the {@link SchedulerAdapter} instance + */ + SchedulerAdapter getScheduler(); + + /** + * Retrieves the data directory path. + * + * @return the {@link Path} to the data directory + */ + Path getDataDirectory(); + + /** + * Retrieves the configuration directory path. + * By default, this is the same as the data directory. + * + * @return the {@link Path} to the configuration directory + */ + default Path getConfigDirectory() { + return getDataDirectory(); + } + + /** + * Retrieves the dependency manager. + * + * @return the {@link DependencyManager} instance + */ + DependencyManager getDependencyManager(); + + /** + * Retrieves the translation manager. + * + * @return the {@link TranslationManager} instance + */ + TranslationManager getTranslationManager(); + + /** + * Retrieves the configuration manager. + * + * @return the {@link ConfigLoader} instance + */ + ConfigLoader getConfigManager(); + + /** + * Retrieves the server version. + * + * @return the server version as a string + */ + String getServerVersion(); + + /** + * Retrieves the plugin version. + * + * @return the plugin version as a string + */ + String getPluginVersion(); + + /** + * Loads the plugin. + * This method is called during the plugin's loading phase. + */ + void load(); + + /** + * Enables the plugin. + * This method is called during the plugin's enabling phase. + */ + void enable(); + + /** + * Disables the plugin. + * This method is called during the plugin's disabling phase. + */ + void disable(); + + /** + * Reloads the plugin. + */ + void reload(); +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsProperties.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsProperties.java new file mode 100644 index 0000000..a0d0bde --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/CustomCropsProperties.java @@ -0,0 +1,61 @@ +/* + * 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.common.plugin; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class CustomCropsProperties { + + private final HashMap propertyMap; + + private CustomCropsProperties(HashMap propertyMap) { + this.propertyMap = propertyMap; + } + + public static String getValue(String key) { + if (!SingletonHolder.INSTANCE.propertyMap.containsKey(key)) { + throw new RuntimeException("Unknown key: " + key); + } + return SingletonHolder.INSTANCE.propertyMap.get(key); + } + + private static class SingletonHolder { + + private static final CustomCropsProperties INSTANCE = getInstance(); + + private static CustomCropsProperties getInstance() { + try (InputStream inputStream = CustomCropsProperties.class.getClassLoader().getResourceAsStream("custom-crops.properties")) { + HashMap versionMap = new HashMap<>(); + Properties properties = new Properties(); + properties.load(inputStream); + for (Map.Entry entry : properties.entrySet()) { + if (entry.getKey() instanceof String key && entry.getValue() instanceof String value) { + versionMap.put(key, value); + } + } + return new CustomCropsProperties(versionMap); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ClassPathAppender.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ClassPathAppender.java similarity index 96% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ClassPathAppender.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ClassPathAppender.java index d63541f..42abf37 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ClassPathAppender.java @@ -23,7 +23,7 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.classpath; +package net.momirealms.customcrops.common.plugin.classpath; import java.nio.file.Path; diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ReflectionClassPathAppender.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ReflectionClassPathAppender.java similarity index 88% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ReflectionClassPathAppender.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ReflectionClassPathAppender.java index 101c50e..052f6d8 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/ReflectionClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/ReflectionClassPathAppender.java @@ -23,7 +23,9 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.classpath; +package net.momirealms.customcrops.common.plugin.classpath; + +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.net.MalformedURLException; import java.net.URLClassLoader; @@ -40,6 +42,10 @@ public class ReflectionClassPathAppender implements ClassPathAppender { } } + public ReflectionClassPathAppender(CustomCropsPlugin plugin) throws IllegalStateException { + this(plugin.getClass().getClassLoader()); + } + @Override public void addJarToClasspath(Path file) { try { diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/URLClassLoaderAccess.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/URLClassLoaderAccess.java similarity index 95% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/URLClassLoaderAccess.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/URLClassLoaderAccess.java index 1a151d4..dbc41c6 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/URLClassLoaderAccess.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/classpath/URLClassLoaderAccess.java @@ -23,9 +23,9 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.classpath; +package net.momirealms.customcrops.common.plugin.classpath; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -66,7 +66,7 @@ public abstract class URLClassLoaderAccess { * * @param url the URL to add */ - public abstract void addURL(@NonNull URL url); + public abstract void addURL(@NotNull URL url); private static void throwError(Throwable cause) throws UnsupportedOperationException { throw new UnsupportedOperationException("CustomCrops is unable to inject into the plugin URLClassLoader.\n" + @@ -100,7 +100,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { try { ADD_URL_METHOD.invoke(super.classLoader, url); } catch (ReflectiveOperationException e) { @@ -162,7 +162,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { if (this.unopenedURLs == null || this.pathURLs == null) { URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs")); } @@ -182,7 +182,7 @@ public abstract class URLClassLoaderAccess { } @Override - public void addURL(@NonNull URL url) { + public void addURL(@NotNull URL url) { URLClassLoaderAccess.throwError(null); } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/common/Reloadable.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/feature/Reloadable.java similarity index 76% rename from api/src/main/java/net/momirealms/customcrops/api/common/Reloadable.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/feature/Reloadable.java index c19bc59..485d7b2 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/common/Reloadable.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/feature/Reloadable.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,26 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.common; +package net.momirealms.customcrops.common.plugin.feature; -public interface Reloadable extends Initable { - - @Override - default void init() { - load(); - } - - void load(); - - void unload(); - - @Override - default void disable() { - unload(); - } +public interface Reloadable { default void reload() { unload(); load(); } + + default void unload() { + } + + default void load() { + } + + default void disable() { + unload(); + } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/JarInJarClassPathAppender.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/JavaPluginLogger.java similarity index 54% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/JarInJarClassPathAppender.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/logging/JavaPluginLogger.java index 13b2d40..559d934 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/classpath/JarInJarClassPathAppender.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/JavaPluginLogger.java @@ -23,40 +23,40 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.classpath; +package net.momirealms.customcrops.common.plugin.logging; -import net.momirealms.customcrops.libraries.loader.JarInJarClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; -import java.io.IOException; -import java.net.MalformedURLException; -import java.nio.file.Path; +public class JavaPluginLogger implements PluginLogger { + private final Logger logger; -public class JarInJarClassPathAppender implements ClassPathAppender { - private final JarInJarClassLoader classLoader; - - public JarInJarClassPathAppender(ClassLoader classLoader) { - if (!(classLoader instanceof JarInJarClassLoader)) { - throw new IllegalArgumentException("Loader is not a JarInJarClassLoader: " + classLoader.getClass().getName()); - } - this.classLoader = (JarInJarClassLoader) classLoader; + public JavaPluginLogger(Logger logger) { + this.logger = logger; } @Override - public void addJarToClasspath(Path file) { - try { - this.classLoader.addJarToClasspath(file.toUri().toURL()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + public void info(String s) { + this.logger.info(s); } @Override - public void close() { - this.classLoader.deleteJarResource(); - try { - this.classLoader.close(); - } catch (IOException e) { - e.printStackTrace(); - } + public void warn(String s) { + this.logger.warning(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.log(Level.WARNING, s, t); + } + + @Override + public void severe(String s) { + this.logger.severe(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.log(Level.SEVERE, s, t); } } diff --git a/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/Log4jPluginLogger.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/Log4jPluginLogger.java new file mode 100644 index 0000000..8bf8efe --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/Log4jPluginLogger.java @@ -0,0 +1,61 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.common.plugin.logging; + +import org.apache.logging.log4j.Logger; + +public class Log4jPluginLogger implements PluginLogger { + private final Logger logger; + + public Log4jPluginLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void info(String s) { + this.logger.info(s); + } + + @Override + public void warn(String s) { + this.logger.warn(s); + } + + @Override + public void warn(String s, Throwable t) { + this.logger.warn(s, t); + } + + @Override + public void severe(String s) { + this.logger.error(s); + } + + @Override + public void severe(String s, Throwable t) { + this.logger.error(s, t); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/loader/LoadingException.java b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/PluginLogger.java similarity index 71% rename from plugin/src/main/java/net/momirealms/customcrops/libraries/loader/LoadingException.java rename to common/src/main/java/net/momirealms/customcrops/common/plugin/logging/PluginLogger.java index 2d5e925..0c5f292 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/loader/LoadingException.java +++ b/common/src/main/java/net/momirealms/customcrops/common/plugin/logging/PluginLogger.java @@ -23,19 +23,24 @@ * SOFTWARE. */ -package net.momirealms.customcrops.libraries.loader; +package net.momirealms.customcrops.common.plugin.logging; /** - * Runtime exception used if there is a problem during loading + * Represents the logger instance being used by CustomCrops on the platform. + * + *

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

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

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

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

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

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

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

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

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

implements AutoCloseable { + private final P plugin; + + public SenderFactory(P plugin) { + this.plugin = plugin; + } + + protected P getPlugin() { + return this.plugin; + } + + protected abstract UUID getUniqueId(T sender); + + protected abstract String getName(T sender); + + public abstract Audience getAudience(T sender); + + protected abstract void sendMessage(T sender, Component message); + + protected abstract Tristate getPermissionValue(T sender, String node); + + protected abstract boolean hasPermission(T sender, String node); + + protected abstract void performCommand(T sender, String command); + + protected abstract boolean isConsole(T sender); + + protected boolean consoleHasAllPermissions() { + return true; + } + + public final Sender wrap(T sender) { + Objects.requireNonNull(sender, "sender"); + return new AbstractSender<>(this.plugin, this, sender); + } + + @Override + public void close() { + + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/ArrayUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/ArrayUtils.java new file mode 100644 index 0000000..da7798a --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/ArrayUtils.java @@ -0,0 +1,102 @@ +/* + * 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.common.util; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utility class for handling operations with arrays. + */ +public class ArrayUtils { + + private ArrayUtils() {} + + /** + * Creates a subarray from the specified array starting from the given index. + * + * @param array the original array + * @param index the starting index for the subarray + * @param the type of the elements in the array + * @return the subarray starting from the given index + * @throws IllegalArgumentException if the index is less than 0 + */ + public static T[] subArray(T[] array, int index) { + if (index < 0) { + throw new IllegalArgumentException("Index should be a value no lower than 0"); + } + if (array.length <= index) { + @SuppressWarnings("unchecked") + T[] emptyArray = (T[]) Array.newInstance(array.getClass().getComponentType(), 0); + return emptyArray; + } + @SuppressWarnings("unchecked") + T[] subArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - index); + System.arraycopy(array, index, subArray, 0, array.length - index); + return subArray; + } + + /** + * Splits the specified array into a list of subarrays, each with the specified chunk size. + * + * @param array the original array + * @param chunkSize the size of each chunk + * @param the type of the elements in the array + * @return a list of subarrays + */ + public static List splitArray(T[] array, int chunkSize) { + List result = new ArrayList<>(); + for (int i = 0; i < array.length; i += chunkSize) { + int end = Math.min(array.length, i + chunkSize); + @SuppressWarnings("unchecked") + T[] chunk = (T[]) Array.newInstance(array.getClass().getComponentType(), end - i); + System.arraycopy(array, i, chunk, 0, end - i); + result.add(chunk); + } + return result; + } + + /** + * Appends an element to the specified array. + * + * @param array the original array + * @param element the element to append + * @param the type of the elements in the array + * @return a new array with the appended element + */ + public static T[] appendElementToArray(T[] array, T element) { + T[] newArray = Arrays.copyOf(array, array.length + 1); + newArray[array.length] = element; + return newArray; + } + + /** + * Splits a string value into an array of substrings based on comma separation. + * The input string is expected to be in the format "[value1, value2, ...]". + * + * @param value the string value to split + * @return an array of substrings + */ + public static String[] splitValue(String value) { + return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']')) + .replaceAll("\\s", "") + .split(","); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/ClassUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/ClassUtils.java similarity index 50% rename from plugin/src/main/java/net/momirealms/customcrops/util/ClassUtils.java rename to common/src/main/java/net/momirealms/customcrops/common/util/ClassUtils.java index b558e6b..9737c5c 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/util/ClassUtils.java +++ b/common/src/main/java/net/momirealms/customcrops/common/util/ClassUtils.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,13 +15,15 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.util; +package net.momirealms.customcrops.common.util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; @@ -29,58 +31,58 @@ import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +/** + * Utility class for handling classes. + */ public class ClassUtils { private ClassUtils() {} - /** - * Attempts to find a class within a JAR file that extends or implements a given class or interface. - * - * @param file The JAR file in which to search for the class. - * @param clazz The base class or interface to match against. - * @param The type of the base class or interface. - * @return A Class object representing the found class, or null if not found. - * @throws IOException If there is an issue reading the JAR file. - * @throws ClassNotFoundException If the specified class cannot be found. - */ @Nullable - public static Class findClass( + public static Class findClass( @NotNull File file, - @NotNull Class clazz + @NotNull Class clazz, + @NotNull Class type ) throws IOException, ClassNotFoundException { if (!file.exists()) { return null; } - URL jar = file.toURI().toURL(); - URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); - List matches = new ArrayList<>(); + URL jarUrl = file.toURI().toURL(); List> classes = new ArrayList<>(); - try (JarInputStream stream = new JarInputStream(jar.openStream())) { + try (URLClassLoader loader = new URLClassLoader(new URL[]{jarUrl}, clazz.getClassLoader()); + JarInputStream jarStream = new JarInputStream(jarUrl.openStream())) { + JarEntry entry; - while ((entry = stream.getNextJarEntry()) != null) { - final String name = entry.getName(); + while ((entry = jarStream.getNextJarEntry()) != null) { + String name = entry.getName(); if (!name.endsWith(".class")) { continue; } - matches.add(name.substring(0, name.lastIndexOf('.')).replace('/', '.')); - } - for (String match : matches) { + String className = name.substring(0, name.lastIndexOf('.')).replace('/', '.'); + try { - Class loaded = loader.loadClass(match); - if (clazz.isAssignableFrom(loaded)) { - classes.add(loaded.asSubclass(clazz)); + Class loadedClass = loader.loadClass(className); + if (clazz.isAssignableFrom(loadedClass)) { + Type superclassType = loadedClass.getGenericSuperclass(); + if (superclassType instanceof ParameterizedType parameterizedType) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length > 0 && typeArguments[0].equals(type)) { + classes.add(loadedClass.asSubclass(clazz)); + } + } } - } catch (NoClassDefFoundError ignored) { + } catch (ClassNotFoundException | NoClassDefFoundError ignored) { } } } + if (classes.isEmpty()) { - loader.close(); return null; } + return classes.get(0); } } diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/CompletableFutures.java b/common/src/main/java/net/momirealms/customcrops/common/util/CompletableFutures.java new file mode 100644 index 0000000..7e6b99c --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/CompletableFutures.java @@ -0,0 +1,73 @@ +/* + * 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.common.util; + +import com.google.common.collect.ImmutableList; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collector; +import java.util.stream.Stream; + +/** + * Utility class for handling operations with {@link CompletableFuture}. + */ +public class CompletableFutures { + + private CompletableFutures() {} + + /** + * A collector for collecting a stream of CompletableFuture instances into a single CompletableFuture that completes + * when all of the input CompletableFutures complete. + * + * @param The type of CompletableFuture. + * @return A collector for CompletableFuture instances. + */ + public static > Collector, CompletableFuture> collector() { + return Collector.of( + ImmutableList.Builder::new, + ImmutableList.Builder::add, + (l, r) -> l.addAll(r.build()), + builder -> allOf(builder.build()) + ); + } + + /** + * Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input + * CompletableFutures complete. + * + * @param futures A stream of CompletableFuture instances. + * @return A CompletableFuture that completes when all input CompletableFutures complete. + */ + public static CompletableFuture allOf(Stream> futures) { + CompletableFuture[] arr = futures.toArray(CompletableFuture[]::new); + return CompletableFuture.allOf(arr); + } + + /** + * Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input + * CompletableFutures complete. + * + * @param futures A collection of CompletableFuture instances. + * @return A CompletableFuture that completes when all input CompletableFutures complete. + */ + public static CompletableFuture allOf(Collection> futures) { + CompletableFuture[] arr = futures.toArray(new CompletableFuture[0]); + return CompletableFuture.allOf(arr); + } +} \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/Either.java b/common/src/main/java/net/momirealms/customcrops/common/util/Either.java new file mode 100644 index 0000000..bb1809d --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/Either.java @@ -0,0 +1,113 @@ +/* + * 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.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; + +/** + * Interface representing a value that can be either of two types, primary or fallback. + * Provides methods to create instances of either type and to map between them. + * + * @param the type of the primary value + * @param the type of the fallback value + */ +public interface Either { + + /** + * Creates an {@link Either} instance with a primary value. + * + * @param value the primary value + * @param the type of the primary value + * @param the type of the fallback value + * @return an {@link Either} instance with the primary value + */ + static @NotNull Either ofPrimary(final @NotNull U value) { + return EitherImpl.of(requireNonNull(value, "value"), null); + } + + /** + * Creates an {@link Either} instance with a fallback value. + * + * @param value the fallback value + * @param the type of the primary value + * @param the type of the fallback value + * @return an {@link Either} instance with the fallback value + */ + static @NotNull Either ofFallback(final @NotNull V value) { + return EitherImpl.of(null, requireNonNull(value, "value")); + } + + /** + * Retrieves the primary value, if present. + * + * @return an {@link Optional} containing the primary value, or empty if not present + */ + @NotNull + Optional primary(); + + /** + * Retrieves the fallback value, if present. + * + * @return an {@link Optional} containing the fallback value, or empty if not present + */ + @NotNull + Optional fallback(); + + /** + * Retrieves the primary value, or maps the fallback value to the primary type if the primary is not present. + * + * @param mapFallback a function to map the fallback value to the primary type + * @return the primary value, or the mapped fallback value if the primary is not present + */ + default @Nullable U primaryOrMapFallback(final @NotNull Function mapFallback) { + return this.primary().orElseGet(() -> mapFallback.apply(this.fallback().get())); + } + + /** + * Retrieves the fallback value, or maps the primary value to the fallback type if the fallback is not present. + * + * @param mapPrimary a function to map the primary value to the fallback type + * @return the fallback value, or the mapped primary value if the fallback is not present + */ + default @Nullable V fallbackOrMapPrimary(final @NotNull Function mapPrimary) { + return this.fallback().orElseGet(() -> mapPrimary.apply(this.primary().get())); + } + + /** + * Maps either the primary or fallback value to a new type. + * + * @param mapPrimary a function to map the primary value + * @param mapFallback a function to map the fallback value + * @param the type of the result + * @return the mapped result + */ + default @NotNull R mapEither( + final @NotNull Function mapPrimary, + final @NotNull Function mapFallback + ) { + return this.primary() + .map(mapPrimary) + .orElseGet(() -> this.fallback().map(mapFallback).get()); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/EitherImpl.java b/common/src/main/java/net/momirealms/customcrops/common/util/EitherImpl.java new file mode 100644 index 0000000..8e2c7f7 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/EitherImpl.java @@ -0,0 +1,130 @@ +/* + * 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.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +final class EitherImpl implements Either { + private final @Nullable U primary; + private final @Nullable V fallback; + + private EitherImpl(Optional primary, Optional fallback) { + this.primary = primary.orElse(null); + this.fallback = fallback.orElse(null); + } + + private EitherImpl(@Nullable U primary, @Nullable V fallback) { + this.primary = primary; + this.fallback = fallback; + } + + private EitherImpl( + EitherImpl original, + @Nullable U primary, + @Nullable V fallback + ) { + this.primary = primary; + this.fallback = fallback; + } + + @Override + public @NotNull Optional primary() { + return Optional.ofNullable(primary); + } + + @Override + public @NotNull Optional fallback() { + return Optional.ofNullable(fallback); + } + + public final EitherImpl withPrimary(@Nullable U value) { + @Nullable U newValue = value; + if (this.primary == newValue) return this; + return new EitherImpl<>(this, newValue, this.fallback); + } + + public EitherImpl withPrimary(Optional optional) { + @Nullable U value = optional.orElse(null); + if (this.primary == value) return this; + return new EitherImpl<>(this, value, this.fallback); + } + + public EitherImpl withFallback(@Nullable V value) { + @Nullable V newValue = value; + if (this.fallback == newValue) return this; + return new EitherImpl<>(this, this.primary, newValue); + } + + public EitherImpl withFallback(Optional optional) { + @Nullable V value = optional.orElse(null); + if (this.fallback == value) return this; + return new EitherImpl<>(this, this.primary, value); + } + + @Override + public boolean equals(@Nullable Object another) { + if (this == another) return true; + return another instanceof EitherImpl + && equalTo((EitherImpl) another); + } + + private boolean equalTo(EitherImpl another) { + return Objects.equals(primary, another.primary) + && Objects.equals(fallback, another.fallback); + } + + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + Objects.hashCode(primary); + h += (h << 5) + Objects.hashCode(fallback); + return h; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Either{"); + if (primary != null) { + builder.append("primary=").append(primary); + } + if (fallback != null) { + if (builder.length() > 7) builder.append(", "); + builder.append("fallback=").append(fallback); + } + return builder.append("}").toString(); + } + + public static EitherImpl of(Optional primary, Optional fallback) { + return new EitherImpl<>(primary, fallback); + } + + public static EitherImpl of(@Nullable U primary, @Nullable V fallback) { + return new EitherImpl<>(primary, fallback); + } + + public static EitherImpl copyOf(Either instance) { + if (instance instanceof EitherImpl) { + return (EitherImpl) instance; + } + return EitherImpl.of(instance.primary(), instance.fallback()); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/FileUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/FileUtils.java new file mode 100644 index 0000000..96dd570 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/FileUtils.java @@ -0,0 +1,112 @@ +/* + * 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.common.util; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Utility class for handling file and directory operations. + */ +public class FileUtils { + + private FileUtils() {} + + /** + * Creates a file if it does not already exist. + * + * @param path the path to the file + * @return the path to the file + * @throws IOException if an I/O error occurs + */ + public static Path createFileIfNotExists(Path path) throws IOException { + if (!Files.exists(path)) { + Files.createFile(path); + } + return path; + } + + /** + * Creates a directory if it does not already exist. + * + * @param path the path to the directory + * @return the path to the directory + * @throws IOException if an I/O error occurs + */ + public static Path createDirectoryIfNotExists(Path path) throws IOException { + if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { + return path; + } + + try { + Files.createDirectory(path); + } catch (FileAlreadyExistsException e) { + // ignore + } + + return path; + } + + /** + * Creates directories if they do not already exist. + * + * @param path the path to the directories + * @return the path to the directories + * @throws IOException if an I/O error occurs + */ + public static Path createDirectoriesIfNotExists(Path path) throws IOException { + if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) { + return path; + } + + try { + Files.createDirectories(path); + } catch (FileAlreadyExistsException e) { + // ignore + } + + return path; + } + + /** + * Deletes a directory and all its contents. + * + * @param path the path to the directory + * @throws IOException if an I/O error occurs + */ + public static void deleteDirectory(Path path) throws IOException { + if (!Files.exists(path) || !Files.isDirectory(path)) { + return; + } + + try (DirectoryStream contents = Files.newDirectoryStream(path)) { + for (Path file : contents) { + if (Files.isDirectory(file)) { + deleteDirectory(file); + } else { + Files.delete(file); + } + } + } + + Files.delete(path); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/Key.java b/common/src/main/java/net/momirealms/customcrops/common/util/Key.java new file mode 100644 index 0000000..2835d74 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/Key.java @@ -0,0 +1,56 @@ +package net.momirealms.customcrops.common.util; + +public class Key { + + private final String namespace; + private final String value; + + public Key(String namespace, String value) { + this.namespace = namespace; + this.value = value; + } + + public static Key key(String namespace, String value) { + return new Key(namespace, value); + } + + public static Key key(String key) { + int index = key.indexOf(":"); + String namespace = index >= 1 ? key.substring(0, index) : "minecraft"; + String value = index >= 0 ? key.substring(index + 1) : key; + return key(namespace, value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Key key = (Key) o; + return namespace.equals(key.namespace) && value.equals(key.value); + } + + @Override + public int hashCode() { + return asString().hashCode(); + } + + public String asString() { + return namespace + ":" + value; + } + + public String namespace() { + return namespace; + } + + public String value() { + return value; + } + + @Override + public String toString() { + return "Key{" + + "namespace='" + namespace + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/ListUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/ListUtils.java new file mode 100644 index 0000000..7c31884 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/ListUtils.java @@ -0,0 +1,49 @@ +/* + * 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.common.util; + +import java.util.List; + +/** + * Utility class for handling operations related to lists. + */ +public class ListUtils { + + private ListUtils() { + } + + /** + * Converts an object to a list of strings. + * If the object is a string, it returns a list containing the string. + * If the object is a list, it casts and returns the list as a list of strings. + * + * @param obj the object to convert + * @return the resulting list of strings + * @throws IllegalArgumentException if the object cannot be converted to a list of strings + */ + @SuppressWarnings("unchecked") + public static List toList(final Object obj) { + if (obj instanceof String s) { + return List.of(s); + } else if (obj instanceof List list) { + return (List) list; + } else { + return List.of(); + } + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/Pair.java b/common/src/main/java/net/momirealms/customcrops/common/util/Pair.java new file mode 100644 index 0000000..40530a1 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/Pair.java @@ -0,0 +1,90 @@ +/* + * 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.common.util; + +/** + * A generic class representing a pair of values. + * This class provides methods to create and access pairs of values. + * + * @param the type of the left value + * @param the type of the right value + */ +public class Pair { + private L left; + private R right; + + /** + * Constructs a new {@link Pair} with the specified left and right values. + * + * @param left the left value + * @param right the right value + */ + public Pair(L left, R right) { + this.left = left; + this.right = right; + } + + /** + * Returns the left value. + * + * @return the left value + */ + public L left() { + return left; + } + + /** + * Sets the left value. + * + * @param left the new left value + */ + public void left(L left) { + this.left = left; + } + + /** + * Returns the right value. + * + * @return the right value + */ + public R right() { + return right; + } + + /** + * Sets the right value. + * + * @param right the new right value + */ + public void right(R right) { + this.right = right; + } + + /** + * Creates a new {@link Pair} with the specified left and right values. + * + * @param left the left value + * @param right the right value + * @param the type of the left value + * @param the type of the right value + * @return a new {@link Pair} with the specified values + */ + public static Pair of(final L left, final R right) { + return new Pair<>(left, right); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/RandomUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/RandomUtils.java new file mode 100644 index 0000000..9b78447 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/RandomUtils.java @@ -0,0 +1,140 @@ +/* + * 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.common.util; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Utility class for generating random values. + */ +public class RandomUtils { + + private final Random random; + + private RandomUtils() { + random = ThreadLocalRandom.current(); + } + + /** + * Static inner class to hold the singleton instance of RandomUtils. + */ + private static class SingletonHolder { + private static final RandomUtils INSTANCE = new RandomUtils(); + } + + /** + * Retrieves the singleton instance of RandomUtils. + * + * @return the singleton instance + */ + private static RandomUtils getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Generates a random integer between the specified minimum and maximum values (inclusive). + * + * @param min the minimum value + * @param max the maximum value + * @return a random integer between min and max (inclusive) + */ + public static int generateRandomInt(int min, int max) { + return getInstance().random.nextInt(max - min + 1) + min; + } + + /** + * Generates a random double between the specified minimum and maximum values (inclusive). + * + * @param min the minimum value + * @param max the maximum value + * @return a random double between min and max (inclusive) + */ + public static double generateRandomDouble(double min, double max) { + return min + (max - min) * getInstance().random.nextDouble(); + } + + /** + * Generates a random float between the specified minimum and maximum values (inclusive). + * + * @param min the minimum value + * @param max the maximum value + * @return a random float between min and max (inclusive) + */ + public static float generateRandomFloat(float min, float max) { + return min + (max - min) * getInstance().random.nextFloat(); + } + + /** + * Generates a random boolean value. + * + * @return a random boolean value + */ + public static boolean generateRandomBoolean() { + return getInstance().random.nextBoolean(); + } + + /** + * Selects a random element from the specified array. + * + * @param array the array to select a random element from + * @param the type of the elements in the array + * @return a random element from the array + */ + public static T getRandomElementFromArray(T[] array) { + int index = getInstance().random.nextInt(array.length); + return array[index]; + } + + /** + * Generates a random value based on a triangular distribution. + * + * @param mode the mode (peak) of the distribution + * @param deviation the deviation from the mode + * @return a random value based on a triangular distribution + */ + public static double triangle(double mode, double deviation) { + return mode + deviation * (generateRandomDouble(0,1) - generateRandomDouble(0,1)); + } + + /** + * Selects a specified number of random elements from the given array. + * + * @param array the array to select random elements from + * @param count the number of random elements to select + * @param the type of the elements in the array + * @return an array containing the selected random elements + * @throws IllegalArgumentException if the count is greater than the array length + */ + public static T[] getRandomElementsFromArray(T[] array, int count) { + if (count > array.length) { + throw new IllegalArgumentException("Count cannot be greater than array length"); + } + + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[count]; + + for (int i = 0; i < count; i++) { + int index = getInstance().random.nextInt(array.length - i); + result[i] = array[index]; + array[index] = array[array.length - i - 1]; + } + + return result; + } +} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionResult.java b/common/src/main/java/net/momirealms/customcrops/common/util/TriConsumer.java similarity index 78% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionResult.java rename to common/src/main/java/net/momirealms/customcrops/common/util/TriConsumer.java index a2036f5..3a722d9 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/FunctionResult.java +++ b/common/src/main/java/net/momirealms/customcrops/common/util/TriConsumer.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,11 +15,8 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.item.function; +package net.momirealms.customcrops.common.util; -public enum FunctionResult { - - PASS, - RETURN, - CANCEL_EVENT_AND_RETURN -} +public interface TriConsumer { + void accept(K k, V v, S s); +} \ No newline at end of file diff --git a/api/src/main/java/net/momirealms/customcrops/api/common/Pair.java b/common/src/main/java/net/momirealms/customcrops/common/util/TriFunction.java similarity index 59% rename from api/src/main/java/net/momirealms/customcrops/api/common/Pair.java rename to common/src/main/java/net/momirealms/customcrops/common/util/TriFunction.java index 1c9ac50..e30b964 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/common/Pair.java +++ b/common/src/main/java/net/momirealms/customcrops/common/util/TriFunction.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,11 +15,18 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.api.common; +package net.momirealms.customcrops.common.util; -public record Pair(L left, R right) { +import java.util.Objects; +import java.util.function.Function; - public static Pair of(final L left, final R right) { - return new Pair<>(left, right); +public interface TriFunction { + R apply(T var1, U var2, V var3); + + default TriFunction andThen(Function after) { + Objects.requireNonNull(after); + return (t, u, v) -> { + return after.apply(this.apply(t, u, v)); + }; } } \ No newline at end of file diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/Tristate.java b/common/src/main/java/net/momirealms/customcrops/common/util/Tristate.java new file mode 100644 index 0000000..5f30888 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/Tristate.java @@ -0,0 +1,98 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.common.util; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents three different states of a setting. + * + *

Possible values:

+ *

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

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

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

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

+ * + * @return a boolean representation of the Tristate. + */ + public boolean asBoolean() { + return this.booleanValue; + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/Tuple.java b/common/src/main/java/net/momirealms/customcrops/common/util/Tuple.java new file mode 100644 index 0000000..0e27b09 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/Tuple.java @@ -0,0 +1,114 @@ +/* + * 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.common.util; + +/** + * A generic class representing a tuple with three values. + * This class provides methods for creating and accessing tuples with three values. + * + * @param the type of the left value + * @param the type of the middle value + * @param the type of the right value + */ +public class Tuple { + private L left; + private M mid; + private R right; + + /** + * Constructs a new {@link Tuple} with the specified left, middle, and right values. + * + * @param left the left value + * @param mid the middle value + * @param right the right value + */ + public Tuple(L left, M mid, R right) { + this.left = left; + this.mid = mid; + this.right = right; + } + + /** + * Returns the left value. + * + * @return the left value + */ + public L left() { + return left; + } + + /** + * Sets the left value. + * + * @param left the new left value + */ + public void left(L left) { + this.left = left; + } + + /** + * Returns the middle value. + * + * @return the middle value + */ + public M mid() { + return mid; + } + + /** + * Sets the middle value. + * + * @param mid the new middle value + */ + public void mid(M mid) { + this.mid = mid; + } + + /** + * Returns the right value. + * + * @return the right value + */ + public R right() { + return right; + } + + /** + * Sets the right value. + * + * @param right the new right value + */ + public void right(R right) { + this.right = right; + } + + /** + * Creates a new {@link Tuple} with the specified left, middle, and right values. + * + * @param left the left value + * @param mid the middle value + * @param right the right value + * @param the type of the left value + * @param the type of the middle value + * @param the type of the right value + * @return a new {@link Tuple} with the specified values + */ + public static Tuple of(final L left, final M mid, final R right) { + return new Tuple<>(left, mid, right); + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/UUIDUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/UUIDUtils.java new file mode 100644 index 0000000..43a2340 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/UUIDUtils.java @@ -0,0 +1,87 @@ +/* + * 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.common.util; + +import java.math.BigInteger; +import java.util.UUID; + +/** + * Utility class for handling operations related to UUIDs. + * Provides methods for converting between different UUID formats and representations. + */ +public class UUIDUtils { + + /** + * Converts a UUID string without dashes to a {@link UUID} object. + * + * @param id the UUID string without dashes + * @return the corresponding {@link UUID} object, or null if the input string is null + */ + public static UUID fromUnDashedUUID(String id) { + return id == null ? null : new UUID( + new BigInteger(id.substring(0, 16), 16).longValue(), + new BigInteger(id.substring(16, 32), 16).longValue() + ); + } + + /** + * Converts a {@link UUID} object to a string without dashes. + * + * @param uuid the {@link UUID} object + * @return the UUID string without dashes + */ + public static String toUnDashedUUID(UUID uuid) { + return uuid.toString().replace("-", ""); + } + + /** + * Converts an integer array to a {@link UUID} object. + * The array must contain exactly four integers. + * + * @param array the integer array + * @return the corresponding {@link UUID} object + * @throws IllegalArgumentException if the array length is not four + */ + public static UUID uuidFromIntArray(int[] array) { + return new UUID((long)array[0] << 32 | (long)array[1] & 4294967295L, (long)array[2] << 32 | (long)array[3] & 4294967295L); + } + + /** + * Converts a {@link UUID} object to an integer array. + * The resulting array contains exactly four integers. + * + * @param uuid the {@link UUID} object + * @return the integer array representation of the UUID + */ + public static int[] uuidToIntArray(UUID uuid) { + long l = uuid.getMostSignificantBits(); + long m = uuid.getLeastSignificantBits(); + return leastMostToIntArray(l, m); + } + + /** + * Converts the most significant and least significant bits of a UUID to an integer array. + * + * @param uuidMost the most significant bits of the UUID + * @param uuidLeast the least significant bits of the UUID + * @return the integer array representation of the UUID bits + */ + private static int[] leastMostToIntArray(long uuidMost, long uuidLeast) { + return new int[]{(int)(uuidMost >> 32), (int)uuidMost, (int)(uuidLeast >> 32), (int)uuidLeast}; + } +} diff --git a/common/src/main/java/net/momirealms/customcrops/common/util/WeightUtils.java b/common/src/main/java/net/momirealms/customcrops/common/util/WeightUtils.java new file mode 100644 index 0000000..e66eab0 --- /dev/null +++ b/common/src/main/java/net/momirealms/customcrops/common/util/WeightUtils.java @@ -0,0 +1,108 @@ +/* + * 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.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * Utility class for selecting random items based on weights. + */ +@SuppressWarnings("DuplicatedCode") +public class WeightUtils { + + private WeightUtils() {} + + /** + * Get a random item from a list of pairs, each associated with a weight. + * + * @param pairs A list of pairs where the left element is the item and the right element is its weight. + * @param The type of items in the list. + * @return A randomly selected item from the list, or null if no item was selected. + */ + public static T getRandom(List> pairs) { + List available = new ArrayList<>(); + double[] weights = new double[pairs.size()]; + int index = 0; + for (Pair pair : pairs){ + double weight = pair.right(); + T key = pair.left(); + if (weight <= 0) continue; + available.add(key); + weights[index++] = weight; + } + return getRandom(weights, available, index); + } + + /** + * Get a random item from a map where each entry is associated with a weight. + * + * @param map A map where each entry's key is an item, and the value is its weight. + * @param The type of items in the map. + * @return A randomly selected item from the map, or null if no item was selected. + */ + public static T getRandom(Map map) { + List available = new ArrayList<>(); + double[] weights = new double[map.size()]; + int index = 0; + for (Map.Entry entry : map.entrySet()){ + double weight = entry.getValue(); + T key = entry.getKey(); + if (weight <= 0) continue; + available.add(key); + weights[index++] = weight; + } + return getRandom(weights, available, index); + } + + /** + * Get a random item from a list of items with associated weights. + * + * @param weights An array of weights corresponding to the available items. + * @param available A list of available items. + * @param effectiveSize The effective size of the array and list after filtering out items with non-positive weights. + * @param The type of items. + * @return A randomly selected item from the list, or null if no item was selected. + */ + private static T getRandom(double[] weights, List available, int effectiveSize) { + if (available.isEmpty()) return null; + double total = Arrays.stream(weights).sum(); + double[] weightRatios = new double[effectiveSize]; + for (int i = 0; i < effectiveSize; i++){ + weightRatios[i] = weights[i]/total; + } + double[] weightRange = new double[effectiveSize]; + double startPos = 0; + for (int i = 0; i < effectiveSize; i++) { + weightRange[i] = startPos + weightRatios[i]; + startPos += weightRatios[i]; + } + double random = Math.random(); + int pos = Arrays.binarySearch(weightRange, random); + + if (pos < 0) { + pos = -pos - 1; + } + if (pos < weightRange.length && random < weightRange[pos]) { + return available.get(pos); + } + return null; + } +} diff --git a/common/src/main/resources/custom-crops.properties b/common/src/main/resources/custom-crops.properties new file mode 100644 index 0000000..931312c --- /dev/null +++ b/common/src/main/resources/custom-crops.properties @@ -0,0 +1,20 @@ +builder=${builder} +git=${git_version} +config=${config_version} +asm=${asm_version} +asm-commons=${asm_commons_version} +jar-relocator=${jar_relocator_version} +cloud-core=${cloud_core_version} +cloud-brigadier=${cloud_brigadier_version} +cloud-services=${cloud_services_version} +cloud-bukkit=${cloud_bukkit_version} +cloud-paper=${cloud_paper_version} +cloud-minecraft-extras=${cloud_minecraft_extras_version} +boosted-yaml=${boosted_yaml_version} +bstats-base=${bstats_version} +geantyref=${geantyref_version} +gson=${gson_version} +caffeine=${caffeine_version} +exp4j=${exp4j_version} +slf4j=${slf4j_version} +zstd-jni=${zstd_version} \ No newline at end of file diff --git a/compatibility-asp-r1/build.gradle.kts b/compatibility-asp-r1/build.gradle.kts new file mode 100644 index 0000000..afff1d1 --- /dev/null +++ b/compatibility-asp-r1/build.gradle.kts @@ -0,0 +1,27 @@ +repositories { + mavenCentral() + maven("https://repo.rapture.pw/repository/maven-releases/") + maven("https://repo.infernalsuite.com/repository/maven-snapshots/") + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + compileOnly(project(":api")) + compileOnly(project(":common")) + compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") + compileOnly("com.infernalsuite.aswm:api:1.20.4-R0.1-SNAPSHOT") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/compatibility-asp-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r1/SlimeWorldAdaptorR1.java b/compatibility-asp-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r1/SlimeWorldAdaptorR1.java new file mode 100644 index 0000000..b29042f --- /dev/null +++ b/compatibility-asp-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r1/SlimeWorldAdaptorR1.java @@ -0,0 +1,227 @@ +package net.momirealms.customcrops.bukkit.integration.adaptor.asp_r1; + +import com.flowpowered.nbt.*; +import com.infernalsuite.aswm.api.world.SlimeWorld; +import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.Registries; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.world.*; +import net.momirealms.customcrops.api.core.world.adaptor.AbstractWorldAdaptor; +import net.momirealms.customcrops.api.util.StringUtils; +import net.momirealms.customcrops.common.helper.GsonHelper; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public class SlimeWorldAdaptorR1 extends AbstractWorldAdaptor { + + private final Function getSlimeWorldFunction; + + public SlimeWorldAdaptorR1(int version) { + try { + if (version == 1) { + Plugin plugin = Bukkit.getPluginManager().getPlugin("SlimeWorldManager"); + Class slimeClass = Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); + Method method = slimeClass.getMethod("getWorld", String.class); + this.getSlimeWorldFunction = (name) -> { + try { + return (SlimeWorld) method.invoke(plugin, name); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }; + } else if (version == 2) { + Class apiClass = Class.forName("com.infernalsuite.aswm.api.AdvancedSlimePaperAPI"); + Object apiInstance = apiClass.getMethod("instance").invoke(null); + Method method = apiClass.getMethod("getLoadedWorld", String.class); + this.getSlimeWorldFunction = (name) -> { + try { + return (SlimeWorld) method.invoke(apiInstance, name); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }; + } else { + throw new IllegalArgumentException("Unsupported version: " + version); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public CustomCropsWorld adapt(Object world) { + return CustomCropsWorld.create((SlimeWorld) world, this); + } + + @Override + public SlimeWorld getWorld(String worldName) { + return getSlimeWorldFunction.apply(worldName); + } + + private CompoundMap createOrGetDataMap(SlimeWorld world) { + Optional optionalCompoundTag = world.getExtraData().getAsCompoundTag("customcrops"); + CompoundMap ccDataMap; + if (optionalCompoundTag.isEmpty()) { + ccDataMap = new CompoundMap(); + world.getExtraData().getValue().put(new CompoundTag("customcrops", ccDataMap)); + } else { + ccDataMap = optionalCompoundTag.get().getValue(); + } + return ccDataMap; + } + + @Override + public WorldExtraData loadExtraData(SlimeWorld world) { + CompoundMap ccDataMap = createOrGetDataMap(world); + String json = Optional.ofNullable(ccDataMap.get("world-info")).map(tag -> tag.getAsStringTag().get().getValue()).orElse(null); + return (json == null || json.equals("null")) ? WorldExtraData.empty() : GsonHelper.get().fromJson(json, WorldExtraData.class); + } + + @Override + public void saveExtraData(CustomCropsWorld world) { + CompoundMap ccDataMap = createOrGetDataMap(world.world()); + ccDataMap.put(new StringTag("world-info", GsonHelper.get().toJson(world.extraData()))); + } + + @Nullable + @Override + public CustomCropsRegion loadRegion(CustomCropsWorld world, RegionPos pos, boolean createIfNotExists) { + return null; + } + + @Nullable + @Override + public CustomCropsChunk loadChunk(CustomCropsWorld world, ChunkPos pos, boolean createIfNotExists) { + long time1 = System.currentTimeMillis(); + CompoundMap ccDataMap = createOrGetDataMap(world.world()); + Tag chunkTag = ccDataMap.get(pos.asString()); + if (chunkTag == null) { + return createIfNotExists ? world.createChunk(pos) : null; + } + Optional chunkCompoundTag = chunkTag.getAsCompoundTag(); + if (chunkCompoundTag.isEmpty()) { + return createIfNotExists ? world.createChunk(pos) : null; + } + CustomCropsChunk chunk = tagToChunk(world, chunkCompoundTag.get()); + long time2 = System.currentTimeMillis(); + BukkitCustomCropsPlugin.getInstance().debug("Took " + (time2-time1) + "ms to load chunk " + pos); + return chunk; + } + + @Override + public void saveRegion(CustomCropsWorld world, CustomCropsRegion region) {} + + @Override + public void saveChunk(CustomCropsWorld world, CustomCropsChunk chunk) { + CompoundMap ccDataMap = createOrGetDataMap(world.world()); + SerializableChunk serializableChunk = toSerializableChunk(chunk); + Runnable runnable = () -> { + if (serializableChunk.canPrune()) { + ccDataMap.remove(chunk.chunkPos().asString()); + } else { + ccDataMap.put(chunkToTag(serializableChunk)); + } + }; + if (Bukkit.isPrimaryThread()) { + runnable.run(); + } else { + BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(runnable, null); + } + } + + @Override + public String getName(SlimeWorld world) { + return world.getName(); + } + + @Override + public long getWorldFullTime(SlimeWorld world) { + return Objects.requireNonNull(Bukkit.getWorld(world.getName())).getFullTime(); + } + + @Override + public int priority() { + return SLIME_WORLD_PRIORITY; + } + + private CompoundTag chunkToTag(SerializableChunk serializableChunk) { + CompoundMap map = new CompoundMap(); + map.put(new IntTag("x", serializableChunk.x())); + map.put(new IntTag("z", serializableChunk.z())); + map.put(new IntTag("version", CHUNK_VERSION)); + map.put(new IntTag("loadedSeconds", serializableChunk.loadedSeconds())); + map.put(new LongTag("lastLoadedTime", serializableChunk.lastLoadedTime())); + map.put(new IntArrayTag("queued", serializableChunk.queuedTasks())); + map.put(new IntArrayTag("ticked", serializableChunk.ticked())); + CompoundMap sectionMap = new CompoundMap(); + for (SerializableSection section : serializableChunk.sections()) { + sectionMap.put(new ListTag<>(String.valueOf(section.sectionID()), TagType.TAG_COMPOUND, section.blocks())); + } + map.put(new CompoundTag("sections", sectionMap)); + return new CompoundTag(serializableChunk.x() + "," + serializableChunk.z(), map); + } + + @SuppressWarnings("all") + private CustomCropsChunk tagToChunk(CustomCropsWorld world, CompoundTag tag) { + CompoundMap map = tag.getValue(); + int version = map.get("version").getAsIntTag().orElse(new IntTag("version", 1)).getValue(); + Function keyFunction = version < 2 ? + (s) -> { + return Key.key("customcrops", StringUtils.toLowerCase(s)); + } : s -> { + return Key.key(s); + }; + int x = (int) map.get("x").getValue(); + int z = (int) map.get("z").getValue(); + + ChunkPos coordinate = new ChunkPos(x, z); + int loadedSeconds = (int) map.get("loadedSeconds").getValue(); + long lastLoadedTime = (long) map.get("lastLoadedTime").getValue(); + int[] queued = (int[]) map.get("queued").getValue(); + int[] ticked = (int[]) map.get("ticked").getValue(); + + PriorityQueue queue = new PriorityQueue<>(Math.max(11, queued.length / 2)); + for (int i = 0, size = queued.length / 2; i < size; i++) { + BlockPos pos = new BlockPos(queued[2*i+1]); + queue.add(new DelayedTickTask(queued[2*i], pos)); + } + + HashSet tickedSet = new HashSet<>(Math.max(11, ticked.length)); + for (int tick : ticked) { + tickedSet.add(new BlockPos(tick)); + } + + ConcurrentHashMap sectionMap = new ConcurrentHashMap<>(); + CompoundMap sectionCompoundMap = (CompoundMap) map.get("sections").getValue(); + for (Map.Entry> entry : sectionCompoundMap.entrySet()) { + if (entry.getValue() instanceof ListTag listTag) { + int id = Integer.parseInt(entry.getKey()); + ConcurrentHashMap blockMap = new ConcurrentHashMap<>(); + ListTag blocks = (ListTag) listTag; + for (CompoundTag blockTag : blocks.getValue()) { + CompoundMap block = blockTag.getValue(); + CompoundMap data = (CompoundMap) block.get("data").getValue(); + Key key = keyFunction.apply((String) block.get("type").getValue()); + CustomCropsBlock customBlock = Registries.BLOCK.get(key); + if (customBlock == null) { + BukkitCustomCropsPlugin.getInstance().getInstance().getPluginLogger().warn("[" + world.worldName() + "] Unrecognized custom block " + key + " has been removed from chunk " + ChunkPos.of(x, z)); + continue; + } + for (int pos : (int[]) block.get("pos").getValue()) { + BlockPos blockPos = new BlockPos(pos); + blockMap.put(blockPos, CustomCropsBlockState.create(customBlock, data)); + } + } + sectionMap.put(id, CustomCropsSection.restore(id, blockMap)); + } + } + return world.restoreChunk(coordinate, loadedSeconds, lastLoadedTime, sectionMap, queue, tickedSet); + } +} diff --git a/oraxen-legacy/build.gradle.kts b/compatibility-itemsadder-r1/build.gradle.kts similarity index 59% rename from oraxen-legacy/build.gradle.kts rename to compatibility-itemsadder-r1/build.gradle.kts index 2506dc0..18aec11 100644 --- a/oraxen-legacy/build.gradle.kts +++ b/compatibility-itemsadder-r1/build.gradle.kts @@ -1,12 +1,14 @@ -dependencies { - compileOnly(project(":api")) - compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") - compileOnly("com.github.oraxen:oraxen:1.172.0") +repositories { + mavenCentral() + maven("https://jitpack.io/") + maven("https://repo.papermc.io/repository/maven-public/") } -tasks.withType { - options.encoding = "UTF-8" - options.release.set(17) +dependencies { + compileOnly(project(":api")) + compileOnly(project(":common")) + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") + compileOnly("com.github.LoneDev6:api-itemsadder:3.6.3-beta-14") } java { @@ -15,4 +17,10 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(17) } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) } \ No newline at end of file diff --git a/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java new file mode 100644 index 0000000..217383d --- /dev/null +++ b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderListener.java @@ -0,0 +1,79 @@ +package net.momirealms.customcrops.bukkit.integration.custom.itemsadder_r1; + +import dev.lone.itemsadder.api.Events.*; +import net.momirealms.customcrops.api.core.AbstractCustomEventListener; +import net.momirealms.customcrops.api.core.AbstractItemManager; +import net.momirealms.customcrops.api.util.FakeCancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.inventory.EquipmentSlot; + +public class ItemsAdderListener extends AbstractCustomEventListener { + + public ItemsAdderListener(AbstractItemManager itemManager) { + super(itemManager); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractFurniture(FurnitureInteractEvent event) { + itemManager.handlePlayerInteractFurniture( + event.getPlayer(), + event.getBukkitEntity().getLocation(), event.getNamespacedID(), + EquipmentSlot.HAND, event.getPlayer().getInventory().getItemInMainHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractCustomBlock(CustomBlockInteractEvent event) { + itemManager.handlePlayerInteractBlock( + event.getPlayer(), + event.getBlockClicked(), + event.getNamespacedID(), event.getBlockFace(), + event.getHand(), + event.getItem(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakFurniture(FurnitureBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBukkitEntity().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getNamespacedID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakCustomBlock(CustomBlockBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBlock().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getNamespacedID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceCustomBlock(CustomBlockPlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBlock().getLocation(), + event.getNamespacedID(), + EquipmentSlot.HAND, + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceFurniture(FurniturePlaceSuccessEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBukkitEntity().getLocation(), + event.getNamespacedID(), + EquipmentSlot.HAND, + event.getPlayer().getInventory().getItemInMainHand(), + new FakeCancellable() + ); + } +} diff --git a/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderProvider.java b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderProvider.java new file mode 100644 index 0000000..23f0f43 --- /dev/null +++ b/compatibility-itemsadder-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/itemsadder_r1/ItemsAdderProvider.java @@ -0,0 +1,104 @@ +package net.momirealms.customcrops.bukkit.integration.custom.itemsadder_r1; + +import dev.lone.itemsadder.api.CustomBlock; +import dev.lone.itemsadder.api.CustomFurniture; +import dev.lone.itemsadder.api.CustomStack; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.CustomItemProvider; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class ItemsAdderProvider implements CustomItemProvider { + + @Override + public boolean removeCustomBlock(Location location) { + return CustomBlock.remove(location); + } + + @Override + public boolean placeCustomBlock(Location location, String id) { + CustomBlock block = CustomBlock.place(id, location); + if (block == null) { + CustomStack furniture = CustomFurniture.getInstance(id); + if (furniture == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Detected that you mistakenly configured the custom block[" + id + "] as furniture."); + } else { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Detected that custom block[" + id + "] doesn't exist in ItemsAdder configs. Please double check if that block exists."); + } + return false; + } + return true; + } + + @Override + public Entity placeFurniture(Location location, String id) { + try { + CustomFurniture furniture = CustomFurniture.spawnPreciseNonSolid(id, location); + if (furniture == null) return null; + return furniture.getEntity(); + } catch (RuntimeException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Failed to place furniture[" + id + "]. If this is not a problem caused by furniture not existing, consider increasing max-furniture-vehicles-per-chunk in ItemsAdder config.yml.", e); + return null; + } + } + + @Override + public boolean removeFurniture(Entity entity) { + if (isFurniture(entity)) { + CustomFurniture.remove(entity, false); + return true; + } else { + return false; + } + } + + @Override + public String blockID(Block block) { + CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block); + if (customBlock == null) { + return null; + } + return customBlock.getNamespacedID(); + } + + @Override + public String itemID(ItemStack itemStack) { + CustomStack customStack = CustomStack.byItemStack(itemStack); + if (customStack == null) { + return null; + } + return customStack.getNamespacedID(); + } + + @Override + public ItemStack itemStack(Player player, String id) { + if (id == null) return new ItemStack(Material.AIR); + CustomStack customStack = CustomStack.getInstance(id); + if (customStack == null) { + return null; + } + return customStack.getItemStack().clone(); + } + + @Override + public String furnitureID(Entity entity) { + CustomFurniture customFurniture = CustomFurniture.byAlreadySpawned(entity); + if (customFurniture == null) { + return null; + } + return customFurniture.getNamespacedID(); + } + + @Override + public boolean isFurniture(Entity entity) { + try { + return CustomFurniture.byAlreadySpawned(entity) != null; + } catch (Exception e) { + return false; + } + } +} diff --git a/legacy-api/.gitignore b/compatibility-oraxen-r1/.gitignore similarity index 100% rename from legacy-api/.gitignore rename to compatibility-oraxen-r1/.gitignore diff --git a/compatibility-oraxen-r1/build.gradle.kts b/compatibility-oraxen-r1/build.gradle.kts new file mode 100644 index 0000000..1c3cabc --- /dev/null +++ b/compatibility-oraxen-r1/build.gradle.kts @@ -0,0 +1,25 @@ +repositories { + mavenCentral() + maven("https://repo.oraxen.com/releases/") + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + compileOnly(project(":api")) + compileOnly(project(":common")) + compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") + compileOnly("io.th0rgal:oraxen:1.180.0") +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} \ No newline at end of file diff --git a/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenListener.java b/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenListener.java new file mode 100644 index 0000000..a9a9e30 --- /dev/null +++ b/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenListener.java @@ -0,0 +1,118 @@ +package net.momirealms.customcrops.bukkit.integration.custom.oraxen_r1; + +import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureBreakEvent; +import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureInteractEvent; +import io.th0rgal.oraxen.api.events.furniture.OraxenFurniturePlaceEvent; +import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockBreakEvent; +import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockInteractEvent; +import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockPlaceEvent; +import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockBreakEvent; +import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockInteractEvent; +import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockPlaceEvent; +import net.momirealms.customcrops.api.core.AbstractCustomEventListener; +import net.momirealms.customcrops.api.core.AbstractItemManager; +import org.bukkit.event.EventHandler; + +public class OraxenListener extends AbstractCustomEventListener { + + public OraxenListener(AbstractItemManager itemManager) { + super(itemManager); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractFurniture(OraxenFurnitureInteractEvent event) { + itemManager.handlePlayerInteractFurniture( + event.getPlayer(), + event.getBaseEntity().getLocation(), event.getMechanic().getItemID(), + event.getHand(), event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractCustomBlock(OraxenNoteBlockInteractEvent event) { + itemManager.handlePlayerInteractBlock( + event.getPlayer(), + event.getBlock(), + event.getMechanic().getItemID(), event.getBlockFace(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractCustomBlock(OraxenStringBlockInteractEvent event) { + itemManager.handlePlayerInteractBlock( + event.getPlayer(), + event.getBlock(), + event.getMechanic().getItemID(), event.getBlockFace(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakFurniture(OraxenFurnitureBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBaseEntity().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakCustomBlock(OraxenNoteBlockBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBlock().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakCustomBlock(OraxenStringBlockBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBlock().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceFurniture(OraxenFurniturePlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBaseEntity().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceCustomBlock(OraxenNoteBlockPlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBlock().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceCustomBlock(OraxenStringBlockPlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBlock().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } +} diff --git a/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenProvider.java b/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenProvider.java new file mode 100644 index 0000000..6be0cb5 --- /dev/null +++ b/compatibility-oraxen-r1/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r1/OraxenProvider.java @@ -0,0 +1,97 @@ +package net.momirealms.customcrops.bukkit.integration.custom.oraxen_r1; + +import io.th0rgal.oraxen.api.OraxenBlocks; +import io.th0rgal.oraxen.api.OraxenFurniture; +import io.th0rgal.oraxen.api.OraxenItems; +import io.th0rgal.oraxen.items.ItemBuilder; +import io.th0rgal.oraxen.mechanics.Mechanic; +import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.CustomItemProvider; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Rotation; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class OraxenProvider implements CustomItemProvider { + + @Override + public boolean removeCustomBlock(Location location) { + Block block = location.getBlock(); + if (OraxenBlocks.isOraxenBlock(block)) { + block.setType(Material.AIR, false); + return true; + } + return false; + } + + @Override + public boolean placeCustomBlock(Location location, String id) { + if (OraxenItems.exists(id)) { + OraxenBlocks.place(id, location); + return true; + } else { + return false; + } + } + + @Override + public @Nullable Entity placeFurniture(Location location, String id) { + Entity entity = OraxenFurniture.place(id, location, Rotation.NONE, BlockFace.UP); + if (entity == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Furniture[" + id +"] doesn't exist. Please double check if that furniture exists."); + } + return entity; + } + + @Override + public boolean removeFurniture(Entity entity) { + if (isFurniture(entity)) { + OraxenFurniture.remove(entity, null, null); + return true; + } + return false; + } + + @Override + public @Nullable String blockID(Block block) { + Mechanic mechanic = OraxenBlocks.getOraxenBlock(block.getBlockData()); + if (mechanic == null) { + return null; + } + return mechanic.getItemID(); + } + + @Override + public @Nullable String itemID(ItemStack itemStack) { + return OraxenItems.getIdByItem(itemStack); + } + + @Override + public @Nullable ItemStack itemStack(Player player, String id) { + ItemBuilder builder = OraxenItems.getItemById(id); + if (builder == null) { + return null; + } + return builder.build(); + } + + @Override + public @Nullable String furnitureID(Entity entity) { + FurnitureMechanic mechanic = OraxenFurniture.getFurnitureMechanic(entity); + if (mechanic == null) { + return null; + } + return mechanic.getItemID(); + } + + @Override + public boolean isFurniture(Entity entity) { + return OraxenFurniture.isFurniture(entity); + } +} diff --git a/oraxen-j21/.gitignore b/compatibility-oraxen-r2/.gitignore similarity index 100% rename from oraxen-j21/.gitignore rename to compatibility-oraxen-r2/.gitignore diff --git a/oraxen-j21/build.gradle.kts b/compatibility-oraxen-r2/build.gradle.kts similarity index 70% rename from oraxen-j21/build.gradle.kts rename to compatibility-oraxen-r2/build.gradle.kts index 9c4c091..170df2e 100644 --- a/oraxen-j21/build.gradle.kts +++ b/compatibility-oraxen-r2/build.gradle.kts @@ -1,5 +1,12 @@ +repositories { + mavenCentral() + maven("https://repo.oraxen.com/snapshots/") + maven("https://repo.papermc.io/repository/maven-public/") +} + dependencies { compileOnly(project(":api")) + compileOnly(project(":common")) compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") compileOnly("io.th0rgal:oraxen:2.0-SNAPSHOT") } diff --git a/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenListener.java b/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenListener.java new file mode 100644 index 0000000..c6a0b2c --- /dev/null +++ b/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenListener.java @@ -0,0 +1,118 @@ +package net.momirealms.customcrops.bukkit.integration.custom.oraxen_r2; + +import io.th0rgal.oraxen.api.events.custom_block.noteblock.OraxenNoteBlockBreakEvent; +import io.th0rgal.oraxen.api.events.custom_block.noteblock.OraxenNoteBlockInteractEvent; +import io.th0rgal.oraxen.api.events.custom_block.noteblock.OraxenNoteBlockPlaceEvent; +import io.th0rgal.oraxen.api.events.custom_block.stringblock.OraxenStringBlockBreakEvent; +import io.th0rgal.oraxen.api.events.custom_block.stringblock.OraxenStringBlockInteractEvent; +import io.th0rgal.oraxen.api.events.custom_block.stringblock.OraxenStringBlockPlaceEvent; +import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureBreakEvent; +import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureInteractEvent; +import io.th0rgal.oraxen.api.events.furniture.OraxenFurniturePlaceEvent; +import net.momirealms.customcrops.api.core.AbstractCustomEventListener; +import net.momirealms.customcrops.api.core.AbstractItemManager; +import org.bukkit.event.EventHandler; + +public class OraxenListener extends AbstractCustomEventListener { + + public OraxenListener(AbstractItemManager itemManager) { + super(itemManager); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractFurniture(OraxenFurnitureInteractEvent event) { + itemManager.handlePlayerInteractFurniture( + event.player(), + event.baseEntity().getLocation(), event.mechanic().getItemID(), + event.hand(), event.itemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractCustomBlock(OraxenNoteBlockInteractEvent event) { + itemManager.handlePlayerInteractBlock( + event.getPlayer(), + event.getBlock(), + event.getMechanic().getItemID(), event.getBlockFace(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onInteractCustomBlock(OraxenStringBlockInteractEvent event) { + itemManager.handlePlayerInteractBlock( + event.getPlayer(), + event.getBlock(), + event.getMechanic().getItemID(), event.getBlockFace(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakFurniture(OraxenFurnitureBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBaseEntity().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakCustomBlock(OraxenNoteBlockBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBlock().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakCustomBlock(OraxenStringBlockBreakEvent event) { + itemManager.handlePlayerBreak( + event.getPlayer(), + event.getBlock().getLocation(), event.getPlayer().getInventory().getItemInMainHand(), event.getMechanic().getItemID(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceFurniture(OraxenFurniturePlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBaseEntity().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceCustomBlock(OraxenNoteBlockPlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBlock().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceCustomBlock(OraxenStringBlockPlaceEvent event) { + itemManager.handlePlayerPlace( + event.getPlayer(), + event.getBlock().getLocation(), + event.getMechanic().getItemID(), + event.getHand(), + event.getItemInHand(), + event + ); + } +} diff --git a/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenProvider.java b/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenProvider.java new file mode 100644 index 0000000..7999c17 --- /dev/null +++ b/compatibility-oraxen-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/custom/oraxen_r2/OraxenProvider.java @@ -0,0 +1,97 @@ +package net.momirealms.customcrops.bukkit.integration.custom.oraxen_r2; + +import io.th0rgal.oraxen.api.OraxenBlocks; +import io.th0rgal.oraxen.api.OraxenFurniture; +import io.th0rgal.oraxen.api.OraxenItems; +import io.th0rgal.oraxen.items.ItemBuilder; +import io.th0rgal.oraxen.mechanics.Mechanic; +import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.CustomItemProvider; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Rotation; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class OraxenProvider implements CustomItemProvider { + + @Override + public boolean removeCustomBlock(Location location) { + Block block = location.getBlock(); + if (OraxenBlocks.isCustomBlock(block)) { + block.setType(Material.AIR, false); + return true; + } + return false; + } + + @Override + public boolean placeCustomBlock(Location location, String id) { + if (OraxenItems.exists(id)) { + OraxenBlocks.place(id, location); + return true; + } else { + return false; + } + } + + @Override + public @Nullable Entity placeFurniture(Location location, String id) { + Entity entity = OraxenFurniture.place(id, location, Rotation.NONE, BlockFace.UP); + if (entity == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Furniture[" + id +"] doesn't exist. Please double check if that furniture exists."); + } + return entity; + } + + @Override + public boolean removeFurniture(Entity entity) { + if (isFurniture(entity)) { + OraxenFurniture.remove(entity, null, null); + return true; + } + return false; + } + + @Override + public @Nullable String blockID(Block block) { + Mechanic mechanic = OraxenBlocks.getCustomBlockMechanic(block.getBlockData()); + if (mechanic == null) { + return null; + } + return mechanic.getItemID(); + } + + @Override + public @Nullable String itemID(ItemStack itemStack) { + return OraxenItems.getIdByItem(itemStack); + } + + @Override + public @Nullable ItemStack itemStack(Player player, String id) { + ItemBuilder builder = OraxenItems.getItemById(id); + if (builder == null) { + return null; + } + return builder.build(); + } + + @Override + public @Nullable String furnitureID(Entity entity) { + FurnitureMechanic mechanic = OraxenFurniture.getFurnitureMechanic(entity); + if (mechanic == null) { + return null; + } + return mechanic.getItemID(); + } + + @Override + public boolean isFurniture(Entity entity) { + return OraxenFurniture.isFurniture(entity); + } +} diff --git a/compatibility/build.gradle.kts b/compatibility/build.gradle.kts new file mode 100644 index 0000000..f2bea9b --- /dev/null +++ b/compatibility/build.gradle.kts @@ -0,0 +1,72 @@ +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://maven.enginehub.org/repo/") // worldguard worldedit + maven("https://jitpack.io/") + maven("https://papermc.io/repo/repository/maven-public/") // paper + maven("https://mvn.lumine.io/repository/maven-public/") // mythicmobs + maven("https://nexus.phoenixdevt.fr/repository/maven-public/") // mmoitems + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi + maven("https://r.irepo.space/maven/") // neigeitems + maven("https://repo.oraxen.com/releases/") // oraxen + maven("https://repo.auxilor.io/repository/maven-public/") // eco + maven("https://nexus.betonquest.org/repository/betonquest/") // betonquest + maven("https://repo.dmulloy2.net/repository/public/") // betonquest needs packet wrapper? + maven("https://oss.sonatype.org/content/repositories/snapshots") +} + +dependencies { + compileOnly(project(":common")) + compileOnly(project(":api")) + compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") { + exclude(module = "adventure-bom") + exclude(module = "checker-qual") + exclude(module = "annotations") + } + // server + compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + // papi + compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}") + // vault + compileOnly("com.github.MilkBowl:VaultAPI:${rootProject.properties["vault_version"]}") + // season + compileOnly(files("libs/RealisticSeasons-api.jar")) + compileOnly(files("libs/AdvancedSeasons-API.jar")) + // leveler + compileOnly(files("libs/mcMMO-api.jar")) + compileOnly("net.Indyuce:MMOCore-API:1.12.1-SNAPSHOT") + compileOnly("dev.aurelium:auraskills-api-bukkit:2.1.5") + compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") + compileOnly("com.github.Zrips:Jobs:v5.2.2.3") + // quest + compileOnly(files("libs/BattlePass-4.0.6-api.jar")) + compileOnly(files("libs/ClueScrolls-4.8.7-api.jar")) + compileOnly("org.betonquest:betonquest:2.1.3") + // item + compileOnly(files("libs/zaphkiel-2.0.24.jar")) + compileOnly("net.Indyuce:MMOItems-API:6.10-SNAPSHOT") + compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT") + compileOnly("pers.neige.neigeitems:NeigeItems:1.17.13") + compileOnly("io.lumine:Mythic-Dist:5.6.2") + compileOnly("com.github.Xiao-MoMi:Custom-Fishing:2.2.19") + // eco + compileOnly("com.willfp:eco:6.70.1") + compileOnly("com.willfp:EcoJobs:3.56.1") + compileOnly("com.willfp:EcoSkills:3.46.1") + compileOnly("com.willfp:libreforge:4.58.1") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) +} \ No newline at end of file diff --git a/plugin/libs/AdvancedSeasons-API.jar b/compatibility/libs/AdvancedSeasons-API.jar similarity index 100% rename from plugin/libs/AdvancedSeasons-API.jar rename to compatibility/libs/AdvancedSeasons-API.jar diff --git a/plugin/libs/BattlePass-4.0.6-api.jar b/compatibility/libs/BattlePass-4.0.6-api.jar similarity index 100% rename from plugin/libs/BattlePass-4.0.6-api.jar rename to compatibility/libs/BattlePass-4.0.6-api.jar diff --git a/plugin/libs/ClueScrolls-api.jar b/compatibility/libs/ClueScrolls-api.jar similarity index 100% rename from plugin/libs/ClueScrolls-api.jar rename to compatibility/libs/ClueScrolls-api.jar diff --git a/plugin/libs/RealisticSeasons-api.jar b/compatibility/libs/RealisticSeasons-api.jar similarity index 100% rename from plugin/libs/RealisticSeasons-api.jar rename to compatibility/libs/RealisticSeasons-api.jar diff --git a/plugin/libs/mcMMO-api.jar b/compatibility/libs/mcMMO-api.jar similarity index 100% rename from plugin/libs/mcMMO-api.jar rename to compatibility/libs/mcMMO-api.jar diff --git a/plugin/libs/zaphkiel-2.0.24.jar b/compatibility/libs/zaphkiel-2.0.24.jar similarity index 100% rename from plugin/libs/zaphkiel-2.0.24.jar rename to compatibility/libs/zaphkiel-2.0.24.jar diff --git a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/VaultHook.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/VaultHook.java new file mode 100644 index 0000000..4937305 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/VaultHook.java @@ -0,0 +1,79 @@ +/* + * 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.bukkit.integration; + +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class VaultHook { + + private static boolean isHooked = false; + + public static void init() { + Singleton.initialize(); + VaultHook.isHooked = true; + } + + public static boolean isHooked() { + return isHooked; + } + + public static void deposit(Player player, double amount) { + Singleton.deposit(player, amount); + } + + public static void withdraw(OfflinePlayer player, double amount) { + Singleton.withdraw(player, amount); + } + + public static double getBalance(OfflinePlayer player) { + return Singleton.getBalance(player); + } + + private static class Singleton { + private static Economy economy; + + private static boolean initialize() { + RegisteredServiceProvider rsp = Bukkit.getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + economy = rsp.getProvider(); + return true; + } + + private static Economy getEconomy() { + return economy; + } + + private static void deposit(OfflinePlayer player, double amount) { + economy.depositPlayer(player, amount); + } + + private static void withdraw(OfflinePlayer player, double amount) { + economy.withdrawPlayer(player, amount); + } + + private static double getBalance(OfflinePlayer player) { + return economy.getBalance(player); + } + } +} diff --git a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/CustomFishingItemProvider.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/CustomFishingItemProvider.java new file mode 100644 index 0000000..c7260a9 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/CustomFishingItemProvider.java @@ -0,0 +1,57 @@ +/* + * 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.bukkit.integration.item; + +import net.momirealms.customcrops.api.integration.ItemProvider; +import net.momirealms.customfishing.api.BukkitCustomFishingPlugin; +import net.momirealms.customfishing.api.mechanic.context.Context; +import net.momirealms.customfishing.api.mechanic.context.ContextKeys; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import static java.util.Objects.requireNonNull; + +public class CustomFishingItemProvider implements ItemProvider { + + @Override + public String identifier() { + return "CustomFishing"; + } + + @NotNull + @Override + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + String[] split = id.split(":", 2); + String finalID; + if (split.length == 1) { + // CustomFishing:ID + finalID = split[0]; + } else { + // CustomFishing:TYPE:ID + finalID = split[1]; + } + ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player).arg(ContextKeys.ID, finalID), finalID); + return requireNonNull(itemStack); + } + + @Override + public String itemID(@NotNull ItemStack itemStack) { + return BukkitCustomFishingPlugin.getInstance().getItemManager().getCustomFishingItemID(itemStack); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MMOItemsItemImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MMOItemsItemProvider.java similarity index 66% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MMOItemsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MMOItemsItemProvider.java index 8336b16..2c8fb23 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MMOItemsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MMOItemsItemProvider.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,37 +15,41 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.item; +package net.momirealms.customcrops.bukkit.integration.item; import io.lumine.mythic.lib.api.item.NBTItem; import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem; -import net.momirealms.customcrops.api.integration.ItemLibrary; +import net.momirealms.customcrops.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.Locale; -public class MMOItemsItemImpl implements ItemLibrary { +import static java.util.Objects.requireNonNull; + +public class MMOItemsItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "MMOItems"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { String[] split = id.split(":"); MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), split[1].toUpperCase(Locale.ENGLISH)); - return mmoItem == null ? new ItemStack(Material.AIR) : mmoItem.newBuilder().build(); + return mmoItem == null ? new ItemStack(Material.AIR) : requireNonNull(mmoItem.newBuilder().build()); } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { NBTItem nbtItem = NBTItem.get(itemStack); if (!nbtItem.hasTag("MMOITEMS_ITEM_ID")) return null; - return nbtItem.getString("MMOITEMS_ITEM_TYPE") + ":" + nbtItem.getString("MMOITEMS_ITEM_ID"); + return nbtItem.getString("MMOITEMS_ITEM_ID"); } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MythicMobsItemImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MythicMobsItemProvider.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MythicMobsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MythicMobsItemProvider.java index 1ff645e..768055e 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/MythicMobsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/MythicMobsItemProvider.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,28 +15,26 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.item; +package net.momirealms.customcrops.bukkit.integration.item; import io.lumine.mythic.bukkit.MythicBukkit; -import net.momirealms.customcrops.api.integration.ItemLibrary; +import net.momirealms.customcrops.api.integration.ItemProvider; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class MythicMobsItemImpl implements ItemLibrary { +public class MythicMobsItemProvider implements ItemProvider { private MythicBukkit mythicBukkit; - public MythicMobsItemImpl() { - this.mythicBukkit = MythicBukkit.inst(); - } - @Override - public String identification() { + public String identifier() { return "MythicMobs"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { if (mythicBukkit == null || mythicBukkit.isClosed()) { this.mythicBukkit = MythicBukkit.inst(); } @@ -44,7 +42,10 @@ public class MythicMobsItemImpl implements ItemLibrary { } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { + if (mythicBukkit == null || mythicBukkit.isClosed()) { + this.mythicBukkit = MythicBukkit.inst(); + } return mythicBukkit.getItemManager().getMythicTypeFromItem(itemStack); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/NeigeItemsItemImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/NeigeItemsItemProvider.java similarity index 66% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/item/NeigeItemsItemImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/NeigeItemsItemProvider.java index 53a56c0..1a36695 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/NeigeItemsItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/NeigeItemsItemProvider.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,29 +15,33 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.item; +package net.momirealms.customcrops.bukkit.integration.item; -import net.momirealms.customcrops.api.integration.ItemLibrary; +import net.momirealms.customcrops.api.integration.ItemProvider; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import pers.neige.neigeitems.item.ItemInfo; import pers.neige.neigeitems.manager.ItemManager; import pers.neige.neigeitems.utils.ItemUtils; -public class NeigeItemsItemImpl implements ItemLibrary { +import java.util.Objects; + +public class NeigeItemsItemProvider implements ItemProvider { @Override - public String identification() { + public String identifier() { return "NeigeItems"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { - return ItemManager.INSTANCE.getItemStack(id, player); + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + return Objects.requireNonNull(ItemManager.INSTANCE.getItemStack(id, player)); } @Override - public String getItemID(ItemStack itemStack) { + public String itemID(@NotNull ItemStack itemStack) { ItemInfo itemInfo = ItemUtils.isNiItem(itemStack); if (itemInfo != null) { return itemInfo.getId(); diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/ZaphkielItemImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/ZaphkielItemProvider.java similarity index 61% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/item/ZaphkielItemImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/ZaphkielItemProvider.java index 8b79a93..cd716e0 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/item/ZaphkielItemImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/item/ZaphkielItemProvider.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,36 +15,40 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.item; +package net.momirealms.customcrops.bukkit.integration.item; import ink.ptms.zaphkiel.ZapAPI; import ink.ptms.zaphkiel.Zaphkiel; -import net.momirealms.customcrops.api.integration.ItemLibrary; +import net.momirealms.customcrops.api.integration.ItemProvider; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; -public class ZaphkielItemImpl implements ItemLibrary { +import java.util.Objects; + +public class ZaphkielItemProvider implements ItemProvider { private final ZapAPI zapAPI; - public ZaphkielItemImpl() { + public ZaphkielItemProvider() { this.zapAPI = Zaphkiel.INSTANCE.api(); } @Override - public String identification() { + public String identifier() { return "Zaphkiel"; } + @NotNull @Override - public ItemStack buildItem(Player player, String id) { - return zapAPI.getItemManager().generateItemStack(id, player); + public ItemStack buildItem(@NotNull Player player, @NotNull String id) { + return Objects.requireNonNull(zapAPI.getItemManager().generateItemStack(id, player)); } @Override - public String getItemID(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) return null; + public String itemID(@NotNull ItemStack itemStack) { + if (itemStack.getType() == Material.AIR) return null; return zapAPI.getItemHandler().getItemId(itemStack); } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AuraSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AuraSkillsLevelerProvider.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AuraSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AuraSkillsLevelerProvider.java index 8c80239..b7f3dc7 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AuraSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AuraSkillsLevelerProvider.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,23 +15,29 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import dev.aurelium.auraskills.api.AuraSkillsApi; import dev.aurelium.auraskills.api.registry.NamespacedId; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class AuraSkillsImpl implements LevelInterface { +public class AuraSkillsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "AuraSkills"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { AuraSkillsApi.get().getUser(player.getUniqueId()) .addSkillXp(AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target)), amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return AuraSkillsApi.get().getUser(player.getUniqueId()).getSkillLevel( AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target)) ); diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AureliumSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AureliumSkillsProvider.java similarity index 52% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AureliumSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AureliumSkillsProvider.java index b9eb4bb..071f30c 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/AureliumSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/AureliumSkillsProvider.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,21 +15,34 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import com.archyx.aureliumskills.api.AureliumAPI; -import net.momirealms.customcrops.api.integration.LevelInterface; +import com.archyx.aureliumskills.leveler.Leveler; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class AureliumSkillsImpl implements LevelInterface { +public class AureliumSkillsProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { - AureliumAPI.getPlugin().getLeveler().addXp(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target), amount); + public String identifier() { + return "AureliumSkills"; + } + + private final Leveler leveler; + + public AureliumSkillsProvider() { + leveler = AureliumAPI.getPlugin().getLeveler(); } @Override - public int getLevel(Player player, String target) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { + leveler.addXp(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target), amount); + } + + @Override + public int getLevel(@NotNull Player player, @NotNull String target) { return AureliumAPI.getSkillLevel(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target)); } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoJobsImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoJobsLevelerProvider.java similarity index 69% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoJobsImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoJobsLevelerProvider.java index ea60512..919336b 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoJobsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoJobsLevelerProvider.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,19 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import com.willfp.ecojobs.api.EcoJobsAPI; import com.willfp.ecojobs.jobs.Job; import com.willfp.ecojobs.jobs.Jobs; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class EcoJobsImpl implements LevelInterface { +public class EcoJobsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { for (Job job : EcoJobsAPI.getActiveJobs(player)) { if (job.getId().equals(target)) { EcoJobsAPI.giveJobExperience(player, job, amount); @@ -35,9 +36,14 @@ public class EcoJobsImpl implements LevelInterface { } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { Job job = Jobs.getByID(target); if (job == null) return 0; return EcoJobsAPI.getJobLevel(player, job); } + + @Override + public String identifier() { + return "EcoJobs"; + } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoSkillsImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoSkillsLevelerProvider.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoSkillsImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoSkillsLevelerProvider.java index 0ae090f..cf662c1 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/EcoSkillsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/EcoSkillsLevelerProvider.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,24 +15,30 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import com.willfp.ecoskills.api.EcoSkillsAPI; import com.willfp.ecoskills.skills.Skills; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.Objects; -public class EcoSkillsImpl implements LevelInterface { +public class EcoSkillsLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { EcoSkillsAPI.gainSkillXP(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target)), amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return EcoSkillsAPI.getSkillLevel(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target))); } + + @Override + public String identifier() { + return "EcoSkills"; + } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/JobsRebornImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/JobsRebornLevelerProvider.java similarity index 67% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/JobsRebornImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/JobsRebornLevelerProvider.java index 0b12ee1..7168050 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/JobsRebornImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/JobsRebornLevelerProvider.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,33 +15,30 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import com.gamingmesh.jobs.Jobs; import com.gamingmesh.jobs.container.Job; import com.gamingmesh.jobs.container.JobProgression; import com.gamingmesh.jobs.container.JobsPlayer; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import java.util.List; -public class JobsRebornImpl implements LevelInterface { +public class JobsRebornLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public void addXp(@NotNull Player player, @NotNull String target, double amount) { JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player); - if (jobsPlayer != null) { - List jobs = jobsPlayer.getJobProgression(); - Job job = Jobs.getJob(target); - for (JobProgression progression : jobs) - if (progression.getJob().equals(job)) - progression.addExperience(amount); - } + Job job = Jobs.getJob(target); + if (jobsPlayer != null && jobsPlayer.isInJob(job)) + Jobs.getPlayerManager().addExperience(jobsPlayer, job, amount); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player); if (jobsPlayer != null) { List jobs = jobsPlayer.getJobProgression(); @@ -52,4 +49,9 @@ public class JobsRebornImpl implements LevelInterface { } return 0; } + + @Override + public String identifier() { + return "JobsReborn"; + } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/MMOCoreImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/MMOCoreLevelerProvider.java similarity index 68% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/MMOCoreImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/MMOCoreLevelerProvider.java index ce1758d..70a7ce3 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/MMOCoreImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/MMOCoreLevelerProvider.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,23 +15,29 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.experience.EXPSource; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class MMOCoreImpl implements LevelInterface { +public class MMOCoreLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "MMOCore"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { MMOCore.plugin.professionManager.get(target).giveExperience(PlayerData.get(player), amount, null ,EXPSource.OTHER); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return PlayerData.get(player).getCollectionSkills().getLevel(MMOCore.plugin.professionManager.get(target)); } } \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/McMMOImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/McMMOLevelerProvider.java similarity index 66% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/level/McMMOImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/McMMOLevelerProvider.java index 7bc4f74..615d9f5 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/level/McMMOImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/level/McMMOLevelerProvider.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,22 +15,28 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.level; +package net.momirealms.customcrops.bukkit.integration.level; import com.gmail.nossr50.api.ExperienceAPI; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import net.momirealms.customcrops.api.integration.LevelInterface; +import net.momirealms.customcrops.api.integration.LevelerProvider; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; -public class McMMOImpl implements LevelInterface { +public class McMMOLevelerProvider implements LevelerProvider { @Override - public void addXp(Player player, String target, double amount) { + public String identifier() { + return "mcMMO"; + } + + @Override + public void addXp(@NotNull Player player, @NotNull String target, double amount) { ExperienceAPI.addRawXP(player, target, (float) amount, "UNKNOWN"); } @Override - public int getLevel(Player player, String target) { + public int getLevel(@NotNull Player player, @NotNull String target) { return ExperienceAPI.getLevel(player, PrimarySkillType.valueOf(target)); } } \ No newline at end of file diff --git a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/papi/CustomCropsPapi.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/papi/CustomCropsPapi.java new file mode 100644 index 0000000..91c836b --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/papi/CustomCropsPapi.java @@ -0,0 +1,81 @@ +package net.momirealms.customcrops.bukkit.integration.papi; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CustomCropsPapi extends PlaceholderExpansion { + + private final BukkitCustomCropsPlugin plugin; + + public CustomCropsPapi(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + super.register(); + } + + public void unload() { + super.unregister(); + } + + @NotNull + @Override + public String getIdentifier() { + return "customcrops"; + } + + @NotNull + @Override + public String getAuthor() { + return "XiaoMoMi"; + } + + @NotNull + @Override + public String getVersion() { + return "3.6"; + } + + @Nullable + @Override + public String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { + String[] split = params.split("_", 2); + switch (split[0]) { + case "season" -> { + if (split.length == 1) { + Player player = offlinePlayer.getPlayer(); + if (player == null) + return null; + return plugin.getWorldManager().getSeason(player.getWorld()).translation(); + } else { + try { + return plugin.getWorldManager().getSeason(Bukkit.getWorld(split[1])).translation(); + } catch (NullPointerException e) { + plugin.getPluginLogger().severe("World " + split[1] + " does not exist"); + } + } + } + case "date" -> { + if (split.length == 1) { + Player player = offlinePlayer.getPlayer(); + if (player == null) + return null; + return String.valueOf(plugin.getWorldManager().getDate(player.getWorld())); + } else { + try { + return String.valueOf(plugin.getWorldManager().getDate(Bukkit.getWorld(split[1]))); + } catch (NullPointerException e) { + plugin.getPluginLogger().severe("World " + split[1] + " does not exist"); + } + } + } + } + return null; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/AdvancedSeasonsImpl.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/AdvancedSeasonsProvider.java similarity index 59% rename from plugin/src/main/java/net/momirealms/customcrops/compatibility/season/AdvancedSeasonsImpl.java rename to compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/AdvancedSeasonsProvider.java index ee32d8e..70d9d6b 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/AdvancedSeasonsImpl.java +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/AdvancedSeasonsProvider.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,21 +15,22 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.compatibility.season; +package net.momirealms.customcrops.bukkit.integration.season; import net.advancedplugins.seasons.Core; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.mechanic.world.season.Season; +import net.momirealms.customcrops.api.core.world.Season; +import net.momirealms.customcrops.api.integration.SeasonProvider; import org.bukkit.World; import org.jetbrains.annotations.NotNull; -public class AdvancedSeasonsImpl implements SeasonInterface { +public class AdvancedSeasonsProvider implements SeasonProvider { + @NotNull @Override public Season getSeason(@NotNull World world) { net.advancedplugins.seasons.enums.Season season = Core.getSeasonHandler().getSeason(world); if (season == null) { - return null; + return Season.DISABLE; } return switch (season.getType()) { case SPRING -> Season.SPRING; @@ -40,23 +41,7 @@ public class AdvancedSeasonsImpl implements SeasonInterface { } @Override - public int getDate(World world) { - return 0; - } - - @Override - public void setSeason(World world, Season season) { - String seasonName = switch (season) { - case AUTUMN -> "FALL"; - case WINTER -> "WINTER"; - case SUMMER -> "SUMMER"; - case SPRING -> "SPRING"; - }; - Core.getSeasonHandler().setSeason(net.advancedplugins.seasons.enums.Season.valueOf(seasonName), world.getName()); - } - - @Override - public void setDate(World world, int date) { - + public String identifier() { + return "AdvancedSeasons"; } } diff --git a/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/RealisticSeasonsProvider.java b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/RealisticSeasonsProvider.java new file mode 100644 index 0000000..b43ee39 --- /dev/null +++ b/compatibility/src/main/java/net/momirealms/customcrops/bukkit/integration/season/RealisticSeasonsProvider.java @@ -0,0 +1,44 @@ +/* + * 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.bukkit.integration.season; + +import me.casperge.realisticseasons.api.SeasonsAPI; +import net.momirealms.customcrops.api.core.world.Season; +import net.momirealms.customcrops.api.integration.SeasonProvider; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; + +public class RealisticSeasonsProvider implements SeasonProvider { + + @NotNull + @Override + public Season getSeason(@NotNull World world) { + return switch (SeasonsAPI.getInstance().getSeason(world)) { + case WINTER -> Season.WINTER; + case SPRING -> Season.SPRING; + case SUMMER -> Season.SUMMER; + case FALL -> Season.AUTUMN; + case DISABLED, RESTORE -> Season.DISABLE; + }; + } + + @Override + public String identifier() { + return "RealisticSeasons"; + } +} diff --git a/gradle.properties b/gradle.properties index 16d5100..9009dbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,46 @@ -#systemProp.socks.proxyHost=127.0.0.1 -#systemProp.socks.proxyPort=7890 +# Project settings +# Rule: [major update].[feature update].[bug fix] +project_version=3.6.0 +config_version=38 +project_group=net.momirealms -#systemProp.http.proxyHost=127.0.0.1 -#systemProp.http.proxyPort=7890 +# Dependency settings +paper_version=1.20.4 +jetbrains_annotations_version=24.0.0 +slf4j_version=2.0.13 +log4j_version=2.23.1 +gson_version=2.10.1 +asm_version=9.7 +asm_commons_version=9.7 +jar_relocator_version=1.7 +adventure_bundle_version=4.17.0 +adventure_platform_version=4.3.3 +sparrow_heart_version=0.38 +cloud_core_version=2.0.0-rc.2 +cloud_services_version=2.0.0-rc.2 +cloud_brigadier_version=2.0.0-beta.9 +cloud_bukkit_version=2.0.0-beta.9 +cloud_paper_version=2.0.0-beta.9 +cloud_minecraft_extras_version=2.0.0-beta.9 +boosted_yaml_version=1.3.7 +byte_buddy_version=1.14.18 +mojang_brigadier_version=1.0.18 +bstats_version=3.0.2 +geantyref_version=1.3.15 +caffeine_version=3.1.8 +rtag_version=6290733498 +exp4j_version=0.4.8 +placeholder_api_version=2.11.6 +anti_grief_version=0.12 +zstd_version=1.5.6-5 +flow_nbt_version=2.0.2 +guava_version=33.2.0-jre +vault_version=1.7 -#systemProp.https.proxyHost=127.0.0.1 -#systemProp.https.proxyPort=7890 +# Proxy settings +systemProp.socks.proxyHost=127.0.0.1 +systemProp.socks.proxyPort=7890 +systemProp.http.proxyHost=127.0.0.1 +systemProp.http.proxyPort=7890 +systemProp.https.proxyHost=127.0.0.1 +systemProp.https.proxyPort=7890 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 269ed93..f7bde25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https://services.gradle.org/distributions/gradle-8.10-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/legacy-api/build.gradle.kts b/legacy-api/build.gradle.kts deleted file mode 100644 index 9612fc8..0000000 --- a/legacy-api/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -dependencies { - compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT") -} - -tasks.withType { - options.encoding = "UTF-8" - options.release.set(17) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} \ No newline at end of file diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemMode.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemMode.java deleted file mode 100644 index bafdc53..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemMode.java +++ /dev/null @@ -1,31 +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.object; - -import java.io.Serializable; - -@Deprecated -public enum ItemMode implements Serializable { - - ARMOR_STAND, - TRIPWIRE, - ITEM_FRAME, - ITEM_DISPLAY, - NOTE_BLOCK, - CHORUS -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemType.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemType.java deleted file mode 100644 index d973fed..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/ItemType.java +++ /dev/null @@ -1,30 +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.object; - -@Deprecated -public enum ItemType { - - GLASS, - POT, - CROP, - SPRINKLER, - SCARECROW, - WATERING_CAN, - UNKNOWN -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/OfflineReplaceTask.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/OfflineReplaceTask.java deleted file mode 100644 index f80dcf9..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/OfflineReplaceTask.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.momirealms.customcrops.api.object; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class OfflineReplaceTask implements Serializable { - - @Serial - private static final long serialVersionUID = -7700789811612423911L; - - private final String id; - private final ItemType itemType; - private final ItemMode itemMode; - - public OfflineReplaceTask(String id, ItemType itemType, ItemMode itemMode) { - this.id = id; - this.itemMode = itemMode; - this.itemType = itemType; - } - - public String getId() { - return id; - } - - public ItemMode getItemMode() { - return itemMode; - } - - public ItemType getItemType() { - return itemType; - } -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/crop/GrowingCrop.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/crop/GrowingCrop.java deleted file mode 100644 index 07290c5..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/crop/GrowingCrop.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.object.crop; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class GrowingCrop implements Serializable { - - @Serial - private static final long serialVersionUID = 2828962866548871991L; - - private int points; - private final String crop; - - public GrowingCrop(String crop, int points) { - this.points = points; - this.crop = crop; - } - - public int getPoints() { - return points; - } - - public void setPoints(int points) { - this.points = points; - } - - public String getKey() { - return crop; - } -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/fertilizer/Fertilizer.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/fertilizer/Fertilizer.java deleted file mode 100644 index 5251783..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/fertilizer/Fertilizer.java +++ /dev/null @@ -1,44 +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.object.fertilizer; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class Fertilizer implements Serializable { - - @Serial - private static final long serialVersionUID = -7593869247329159078L; - - private final String key; - private final int times; - - public Fertilizer(String key, int times) { - this.key = key; - this.times = times; - } - - public String getKey() { - return key; - } - - public int getTimes() { - return times; - } -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/pot/Pot.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/pot/Pot.java deleted file mode 100644 index ecfa1cf..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/pot/Pot.java +++ /dev/null @@ -1,52 +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.object.pot; - -import net.momirealms.customcrops.api.object.fertilizer.Fertilizer; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class Pot implements Serializable { - - @Serial - private static final long serialVersionUID = -6598493908660891824L; - - private Fertilizer fertilizer; - private int water; - private final String key; - - public Pot(String key, Fertilizer fertilizer, int water) { - this.key = key; - this.fertilizer = fertilizer; - this.water = water; - } - - public Fertilizer getFertilizer() { - return fertilizer; - } - - public int getWater() { - return water; - } - - public String getKey() { - return key; - } -} \ No newline at end of file diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/sprinkler/Sprinkler.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/sprinkler/Sprinkler.java deleted file mode 100644 index 1d8023c..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/sprinkler/Sprinkler.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.object.sprinkler; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class Sprinkler implements Serializable { - - @Serial - private static final long serialVersionUID = -1994328062935821245L; - - private int water; - private final String key; - - public Sprinkler(String key, int water) { - this.water = water; - this.key = key; - } - - public int getWater() { - return water; - } - - public void setWater(int water) { - this.water = water; - } - - public String getKey() { - return key; - } -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java deleted file mode 100644 index d18a8e2..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/CCChunk.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.api.object.world; - -import net.momirealms.customcrops.api.object.OfflineReplaceTask; -import net.momirealms.customcrops.api.object.crop.GrowingCrop; -import net.momirealms.customcrops.api.object.pot.Pot; -import net.momirealms.customcrops.api.object.sprinkler.Sprinkler; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -@Deprecated -public class CCChunk implements Serializable { - - @Serial - private static final long serialVersionUID = 5300805317167684402L; - - private final ConcurrentHashMap growingCropMap; - private final ConcurrentHashMap potMap; - private final ConcurrentHashMap sprinklerMap; - private ConcurrentHashMap replaceTaskMap; - private final Set greenhouseSet; - private final Set scarecrowSet; - - public CCChunk() { - this.growingCropMap = new ConcurrentHashMap<>(64); - this.potMap = new ConcurrentHashMap<>(64); - this.sprinklerMap = new ConcurrentHashMap<>(16); - this.greenhouseSet = Collections.synchronizedSet(new HashSet<>(64)); - this.scarecrowSet = Collections.synchronizedSet(new HashSet<>(4)); - this.replaceTaskMap = new ConcurrentHashMap<>(64); - } - - public ConcurrentHashMap getGrowingCropMap() { - return growingCropMap; - } - - public ConcurrentHashMap getPotMap() { - return potMap; - } - - public ConcurrentHashMap getSprinklerMap() { - return sprinklerMap; - } - - public ConcurrentHashMap getReplaceTaskMap() { - return replaceTaskMap; - } - - public Set getGreenhouseSet() { - return greenhouseSet; - } - - public Set getScarecrowSet() { - return scarecrowSet; - } -} \ No newline at end of file diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/ChunkCoordinate.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/ChunkCoordinate.java deleted file mode 100644 index e3d985c..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/ChunkCoordinate.java +++ /dev/null @@ -1,72 +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.object.world; - -import java.io.Serial; -import java.io.Serializable; - -@Deprecated -public class ChunkCoordinate implements Serializable { - - @Serial - private static final long serialVersionUID = 8143293419668383618L; - - private final int x; - private final int z; - - public ChunkCoordinate(int x, int z) { - this.x = x; - this.z = z; - } - - public int getX() { - return x; - } - - public int getZ() { - return z; - } - - public String getFileName() { - return x + "," + z; - } - - @Override - public int hashCode() { - long combined = (long) x << 32 | z; - return Long.hashCode(combined); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ChunkCoordinate other = (ChunkCoordinate) obj; - if (this.x != other.x) { - return false; - } - if (this.z != other.z) { - return false; - } - return true; - } -} diff --git a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/SimpleLocation.java b/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/SimpleLocation.java deleted file mode 100644 index 6a11d26..0000000 --- a/legacy-api/src/main/java/net/momirealms/customcrops/api/object/world/SimpleLocation.java +++ /dev/null @@ -1,108 +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.object.world; - -import java.io.Serial; -import java.io.Serializable; -import java.util.Objects; - -@Deprecated -public class SimpleLocation implements Serializable { - - @Serial - private static final long serialVersionUID = -1288860694388882412L; - - private final int x; - private final int y; - private final int z; - private final String worldName; - - public SimpleLocation(String worldName, int x, int y, int z){ - this.worldName = worldName; - this.x = x; - this.y = y; - this.z = z; - } - - public int getX() { - return x; - } - - public int getZ() { - return z; - } - - public int getY() { - return y; - } - - public String getWorldName() { - return worldName; - } - - public ChunkCoordinate getChunkCoordinate() { - return new ChunkCoordinate(x >> 4, z >> 4); - } - - public SimpleLocation add(int x, int y, int z) { - return new SimpleLocation(worldName, this.x + x, this.y + y, this.z + z); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final SimpleLocation other = (SimpleLocation) obj; - if (!Objects.equals(worldName, other.getWorldName())) { - return false; - } - if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) { - return false; - } - if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) { - return false; - } - if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - int hash = 3; - //hash = 19 * hash + (worldName != null ? worldName.hashCode() : 0); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32)); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32)); - hash = 19 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32)); - return hash; - } - - @Override - public String toString() { - return "[" + worldName + "," + x + "," + y + "," + z + "]"; - } - - public SimpleLocation copy() { - return new SimpleLocation(worldName, x, y, z); - } -} \ No newline at end of file diff --git a/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenListener.java b/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenListener.java deleted file mode 100644 index 57fc32d..0000000 --- a/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenListener.java +++ /dev/null @@ -1,108 +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.mechanic.item.custom.oraxen; - -import io.th0rgal.oraxen.api.events.custom_block.noteblock.OraxenNoteBlockBreakEvent; -import io.th0rgal.oraxen.api.events.custom_block.noteblock.OraxenNoteBlockPlaceEvent; -import io.th0rgal.oraxen.api.events.custom_block.stringblock.OraxenStringBlockBreakEvent; -import io.th0rgal.oraxen.api.events.custom_block.stringblock.OraxenStringBlockPlaceEvent; -import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureBreakEvent; -import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureInteractEvent; -import io.th0rgal.oraxen.api.events.furniture.OraxenFurniturePlaceEvent; -import net.momirealms.customcrops.api.manager.ItemManager; -import net.momirealms.customcrops.api.mechanic.item.custom.AbstractCustomListener; -import net.momirealms.customcrops.api.util.LocationUtils; -import org.bukkit.event.EventHandler; - -public class OraxenListener extends AbstractCustomListener { - - public OraxenListener(ItemManager itemManager) { - super(itemManager); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCustomNoteBlock(OraxenNoteBlockBreakEvent event) { - this.itemManager.handlePlayerBreakBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCustomStringBlock(OraxenStringBlockBreakEvent event) { - this.itemManager.handlePlayerBreakBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceCustomBlock(OraxenNoteBlockPlaceEvent event) { - super.onPlaceBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceCustomBlock(OraxenStringBlockPlaceEvent event) { - super.onPlaceBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceFurniture(OraxenFurniturePlaceEvent event) { - super.onPlaceFurniture( - event.getPlayer(), - event.getBlock().getLocation(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakFurniture(OraxenFurnitureBreakEvent event) { - super.onBreakFurniture( - event.getPlayer(), - LocationUtils.toBlockLocation(event.getBaseEntity().getLocation()), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onInteractFurniture(OraxenFurnitureInteractEvent event) { - super.onInteractFurniture( - event.player(), - LocationUtils.toBlockLocation(event.baseEntity().getLocation()), - event.mechanic().getItemID(), - event.baseEntity(), - event - ); - } -} diff --git a/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenProvider.java b/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenProvider.java deleted file mode 100644 index c0b52ec..0000000 --- a/oraxen-j21/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxen/OraxenProvider.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.custom.oraxen; - -import io.th0rgal.oraxen.api.OraxenBlocks; -import io.th0rgal.oraxen.api.OraxenFurniture; -import io.th0rgal.oraxen.api.OraxenItems; -import io.th0rgal.oraxen.items.ItemBuilder; -import io.th0rgal.oraxen.mechanics.Mechanic; -import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic; -import net.momirealms.customcrops.api.mechanic.item.custom.CustomProvider; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Rotation; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.inventory.ItemStack; - -public class OraxenProvider implements CustomProvider { - - @Override - public boolean removeBlock(Location location) { - Block block = location.getBlock(); - if (block.getType() == Material.AIR) { - return false; - } - block.setType(Material.AIR); - return true; - } - - @Override - public void placeCustomBlock(Location location, String id) { - OraxenBlocks.place(id, location); - } - - @Override - public Entity placeFurniture(Location location, String id) { - Entity entity = OraxenFurniture.place(id, location, Rotation.NONE, BlockFace.UP); - if (entity == null) { - LogUtils.warn("Furniture(" + id +") doesn't exist in Oraxen configs. Please double check if that furniture exists."); - } - return entity; - } - - @Override - public void removeFurniture(Entity entity) { - OraxenFurniture.remove(entity, null); - } - - @Override - public String getBlockID(Block block) { - Mechanic mechanic = OraxenBlocks.getCustomBlockMechanic(block.getLocation()); - if (mechanic == null) { - return block.getType().name(); - } - return mechanic.getItemID(); - } - - @Override - public String getItemID(ItemStack itemStack) { - return OraxenItems.getIdByItem(itemStack); - } - - @Override - public ItemStack getItemStack(String id) { - if (id == null) return new ItemStack(Material.AIR); - ItemBuilder builder = OraxenItems.getItemById(id); - if (builder == null) { - return null; - } - return builder.build(); - } - - @Override - public String getEntityID(Entity entity) { - FurnitureMechanic mechanic = OraxenFurniture.getFurnitureMechanic(entity); - if (mechanic == null) { - return entity.getType().name(); - } - return mechanic.getItemID(); - } - - @Override - public boolean isFurniture(Entity entity) { - return OraxenFurniture.isFurniture(entity); - } -} diff --git a/oraxen-legacy/.gitignore b/oraxen-legacy/.gitignore deleted file mode 100644 index b63da45..0000000 --- a/oraxen-legacy/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store \ No newline at end of file diff --git a/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenListener.java b/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenListener.java deleted file mode 100644 index f9343a4..0000000 --- a/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenListener.java +++ /dev/null @@ -1,108 +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.mechanic.item.custom.oraxenlegacy; - -import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureBreakEvent; -import io.th0rgal.oraxen.api.events.furniture.OraxenFurnitureInteractEvent; -import io.th0rgal.oraxen.api.events.furniture.OraxenFurniturePlaceEvent; -import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockBreakEvent; -import io.th0rgal.oraxen.api.events.noteblock.OraxenNoteBlockPlaceEvent; -import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockBreakEvent; -import io.th0rgal.oraxen.api.events.stringblock.OraxenStringBlockPlaceEvent; -import net.momirealms.customcrops.api.manager.ItemManager; -import net.momirealms.customcrops.api.mechanic.item.custom.AbstractCustomListener; -import net.momirealms.customcrops.api.util.LocationUtils; -import org.bukkit.event.EventHandler; - -public class LegacyOraxenListener extends AbstractCustomListener { - - public LegacyOraxenListener(ItemManager itemManager) { - super(itemManager); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCustomNoteBlock(OraxenNoteBlockBreakEvent event) { - this.itemManager.handlePlayerBreakBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCustomStringBlock(OraxenStringBlockBreakEvent event) { - this.itemManager.handlePlayerBreakBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceCustomBlock(OraxenNoteBlockPlaceEvent event) { - super.onPlaceBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceCustomBlock(OraxenStringBlockPlaceEvent event) { - super.onPlaceBlock( - event.getPlayer(), - event.getBlock(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceFurniture(OraxenFurniturePlaceEvent event) { - super.onPlaceFurniture( - event.getPlayer(), - event.getBlock().getLocation(), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakFurniture(OraxenFurnitureBreakEvent event) { - super.onBreakFurniture( - event.getPlayer(), - LocationUtils.toBlockLocation(event.getBaseEntity().getLocation()), - event.getMechanic().getItemID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onInteractFurniture(OraxenFurnitureInteractEvent event) { - super.onInteractFurniture( - event.getPlayer(), - LocationUtils.toBlockLocation(event.getBaseEntity().getLocation()), - event.getMechanic().getItemID(), - event.getBaseEntity(), - event - ); - } -} diff --git a/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenProvider.java b/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenProvider.java deleted file mode 100644 index b66c20d..0000000 --- a/oraxen-legacy/src/main/java/net/momirealms/customcrops/mechanic/item/custom/oraxenlegacy/LegacyOraxenProvider.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.custom.oraxenlegacy; - -import io.th0rgal.oraxen.api.OraxenBlocks; -import io.th0rgal.oraxen.api.OraxenFurniture; -import io.th0rgal.oraxen.api.OraxenItems; -import io.th0rgal.oraxen.items.ItemBuilder; -import io.th0rgal.oraxen.mechanics.Mechanic; -import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic; -import net.momirealms.customcrops.api.mechanic.item.custom.CustomProvider; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Rotation; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.inventory.ItemStack; - -public class LegacyOraxenProvider implements CustomProvider { - - @Override - public boolean removeBlock(Location location) { - Block block = location.getBlock(); - if (block.getType() == Material.AIR) { - return false; - } - block.setType(Material.AIR); - return true; - } - - @Override - public void placeCustomBlock(Location location, String id) { - OraxenBlocks.place(id, location); - } - - @Override - public Entity placeFurniture(Location location, String id) { - Entity entity = OraxenFurniture.place(id, location, Rotation.NONE, BlockFace.UP); - if (entity == null) { - LogUtils.warn("Furniture(" + id +") doesn't exist in Oraxen configs. Please double check if that furniture exists."); - } - return entity; - } - - @Override - public void removeFurniture(Entity entity) { - OraxenFurniture.remove(entity, null); - } - - @Override - public String getBlockID(Block block) { - Mechanic mechanic = OraxenBlocks.getOraxenBlock(block.getLocation()); - if (mechanic == null) { - return block.getType().name(); - } - return mechanic.getItemID(); - } - - @Override - public String getItemID(ItemStack itemStack) { - return OraxenItems.getIdByItem(itemStack); - } - - @Override - public ItemStack getItemStack(String id) { - if (id == null) return new ItemStack(Material.AIR); - ItemBuilder builder = OraxenItems.getItemById(id); - if (builder == null) { - return null; - } - return builder.build(); - } - - @Override - public String getEntityID(Entity entity) { - FurnitureMechanic mechanic = OraxenFurniture.getFurnitureMechanic(entity); - if (mechanic == null) { - return entity.getType().name(); - } - return mechanic.getItemID(); - } - - @Override - public boolean isFurniture(Entity entity) { - return OraxenFurniture.isFurniture(entity); - } -} diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 4ee5625..d6d93ff 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,75 +1,56 @@ +val commitID: String by project + +plugins { + id("io.github.goooler.shadow") version "8.1.8" +} + +repositories { + mavenCentral() + maven("https://repo.rapture.pw/repository/maven-releases/") + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://jitpack.io/") + maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi +} + dependencies { // Platform compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") - compileOnly("com.infernalsuite.aswm:api:1.20.4-R0.1-SNAPSHOT") - // Command - compileOnly("dev.jorel:commandapi-bukkit-core:9.5.3") + implementation(project(":api")) { + exclude("dev.dejvokep", "boosted-yaml") + } + implementation(project(":common")) + implementation(project(":compatibility")) + implementation(project(":compatibility-oraxen-r1")) + implementation(project(":compatibility-oraxen-r2")) + implementation(project(":compatibility-itemsadder-r1")) + implementation(project(":compatibility-asp-r1")) - // Common hooks - compileOnly("me.clip:placeholderapi:2.11.6") - compileOnly("com.comphenix.protocol:ProtocolLib:5.1.0") - compileOnly("com.github.MilkBowl:VaultAPI:1.7") - - // Utils - compileOnly("dev.dejvokep:boosted-yaml:1.3.6") - compileOnly("commons-io:commons-io:2.15.1") - compileOnly("com.google.code.gson:gson:2.10.1") - compileOnly("net.objecthunter:exp4j:0.4.8") - - // eco - compileOnly("com.willfp:eco:6.67.2") - compileOnly("com.willfp:EcoJobs:3.47.1") - compileOnly("com.willfp:EcoSkills:3.21.0") - compileOnly("com.willfp:libreforge:4.48.1") - - compileOnly("net.Indyuce:MMOCore-API:1.12-SNAPSHOT") - compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.23") - - compileOnly("com.github.Zrips:Jobs:v4.17.2") - compileOnly("dev.aurelium:auraskills-api-bukkit:2.1.2") - - // Items - compileOnly("com.github.LoneDev6:api-itemsadder:3.6.2-beta-r3-b") - compileOnly("pers.neige.neigeitems:NeigeItems:1.16.24") - compileOnly("net.Indyuce:MMOItems-API:6.9.2-SNAPSHOT") - compileOnly("io.lumine:MythicLib-dist:1.6-SNAPSHOT") - compileOnly("io.lumine:Mythic-Dist:5.6.1") - compileOnly("io.lumine:MythicCrucible-Dist:2.1.0-SNAPSHOT") - - // Quests - compileOnly("org.betonquest:betonquest:2.1.3") - - compileOnly(files("libs/BattlePass-4.0.6-api.jar")) - compileOnly(files("libs/ClueScrolls-api.jar")) - compileOnly(files("libs/AdvancedSeasons-API.jar")) - compileOnly(files("libs/zaphkiel-2.0.24.jar")) - compileOnly(files("libs/mcMMO-api.jar")) - compileOnly(files("libs/RealisticSeasons-api.jar")) - compileOnly("org.bstats:bstats-bukkit:3.0.2") - - implementation(project(":api")) - implementation(project(":oraxen-legacy")) - implementation(project(":oraxen-j21")) - implementation(project(":legacy-api")) - - - implementation("net.kyori:adventure-api:4.17.0") - implementation("net.kyori:adventure-platform-bukkit:4.3.3") - implementation("net.kyori:adventure-text-minimessage:4.17.0") - implementation("net.kyori:adventure-text-serializer-legacy:4.17.0") - implementation("com.github.Xiao-MoMi:AntiGriefLib:0.12") - implementation("com.github.Xiao-MoMi:Sparrow-Heart:0.35") - implementation("com.flowpowered:flow-nbt:2.0.2") - implementation("com.saicone.rtag:rtag:1.5.5") - implementation("com.saicone.rtag:rtag-item:1.5.5") - implementation("com.github.luben:zstd-jni:1.5.6-4") + implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") + implementation("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") + implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}") + implementation("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") { + exclude("com.google.code.gson", "gson") + } + implementation("net.kyori:adventure-text-serializer-legacy:${rootProject.properties["anti_grief_version"]}") + implementation("com.github.Xiao-MoMi:AntiGriefLib:${rootProject.properties["anti_grief_version"]}") + implementation("com.github.Xiao-MoMi:Sparrow-Heart:${rootProject.properties["sparrow_heart_version"]}") + implementation("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}") + implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") + implementation("com.flowpowered:flow-nbt:${rootProject.properties["flow_nbt_version"]}") // do not relocate for compatibility with SWM + compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}") + compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}") + compileOnly("org.incendo:cloud-paper:${rootProject.properties["cloud_paper_version"]}") + compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}") + compileOnly("org.bstats:bstats-bukkit:${rootProject.properties["bstats_version"]}") + compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}") } tasks { shadowJar { - relocate ("de.tr7zw.changeme", "net.momirealms.customcrops.libraries.changeme") - relocate ("dev.jorel.commandapi", "net.momirealms.customcrops.libraries.commandapi") + archiveFileName = "CustomCrops-${rootProject.properties["project_version"]}.jar" + destinationDirectory.set(file("$rootDir/target")) relocate ("net.kyori", "net.momirealms.customcrops.libraries") relocate ("org.objenesis", "net.momirealms.customcrops.libraries.objenesis") relocate ("org.bstats", "net.momirealms.customcrops.libraries.bstats") @@ -78,12 +59,13 @@ tasks { relocate ("net.momirealms.antigrieflib", "net.momirealms.customcrops.libraries.antigrieflib") relocate ("net.objecthunter.exp4j", "net.momirealms.customcrops.libraries.exp4j") relocate ("com.saicone.rtag", "net.momirealms.customcrops.libraries.rtag") + relocate ("com.github.benmanes.caffeine", "net.momirealms.customcrops.libraries.caffeine") + relocate("org.incendo", "net.momirealms.customcrops.libraries") } } -tasks.withType { - options.encoding = "UTF-8" - options.release.set(17) +artifacts { + archives(tasks.shadowJar) } java { @@ -92,4 +74,10 @@ java { toolchain { languageVersion = JavaLanguageVersion.of(17) } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(17) + dependsOn(tasks.clean) } \ No newline at end of file diff --git a/plugin/libs/Sparrow-Heart-0.19.jar b/plugin/libs/Sparrow-Heart-0.19.jar deleted file mode 100644 index 669b9bb14c5f4391db2e7e6dc84ec93710b97d9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177287 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
{r*6&Fn8()w(Z?r8ykQ)_kh;d55{>Kh^D1SH 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 diff --git a/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java b/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java deleted file mode 100644 index 4999768..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/CustomCropsPluginImpl.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops; - -import net.momirealms.antigrieflib.AntiGriefLib; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.event.CustomCropsReloadEvent; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.CoolDownManager; -import net.momirealms.customcrops.api.util.EventUtils; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.compatibility.IntegrationManagerImpl; -import net.momirealms.customcrops.libraries.classpath.ReflectionClassPathAppender; -import net.momirealms.customcrops.libraries.dependencies.Dependency; -import net.momirealms.customcrops.libraries.dependencies.DependencyManager; -import net.momirealms.customcrops.libraries.dependencies.DependencyManagerImpl; -import net.momirealms.customcrops.manager.*; -import net.momirealms.customcrops.mechanic.action.ActionManagerImpl; -import net.momirealms.customcrops.mechanic.condition.ConditionManagerImpl; -import net.momirealms.customcrops.mechanic.item.ItemManagerImpl; -import net.momirealms.customcrops.mechanic.item.factory.BukkitItemFactory; -import net.momirealms.customcrops.mechanic.misc.migrator.Migration; -import net.momirealms.customcrops.mechanic.requirement.RequirementManagerImpl; -import net.momirealms.customcrops.mechanic.world.WorldManagerImpl; -import net.momirealms.customcrops.scheduler.SchedulerImpl; -import org.bstats.bukkit.Metrics; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; - -import java.util.ArrayList; -import java.util.List; - -public class CustomCropsPluginImpl extends CustomCropsPlugin { - - private DependencyManager dependencyManager; - private PacketManager packetManager; - private CommandManager commandManager; - private HologramManager hologramManager; - - @Override - public void onLoad() { - this.versionManager = new VersionManagerImpl(this); - this.dependencyManager = new DependencyManagerImpl(this, new ReflectionClassPathAppender(this.getClassLoader())); - this.dependencyManager.loadDependencies(new ArrayList<>( - List.of( - Dependency.GSON, - Dependency.EXP4J, - Dependency.SLF4J_API, - Dependency.SLF4J_SIMPLE, - versionManager.isMojmap() ? Dependency.COMMAND_API_MOJMAP : Dependency.COMMAND_API, - Dependency.BOOSTED_YAML, - Dependency.BSTATS_BASE, - Dependency.BSTATS_BUKKIT - ) - )); - } - - @Override - public void onEnable() { - instance = this; - this.adventure = new AdventureManagerImpl(this); - this.scheduler = new SchedulerImpl(this); - this.configManager = new ConfigManagerImpl(this); - this.integrationManager = new IntegrationManagerImpl(this); - this.conditionManager = new ConditionManagerImpl(this); - this.actionManager = new ActionManagerImpl(this); - this.requirementManager = new RequirementManagerImpl(this); - this.coolDownManager = new CoolDownManager(this); - this.worldManager = new WorldManagerImpl(this); - this.itemManager = new ItemManagerImpl(this, - AntiGriefLib.builder(this) - .silentLogs(true) - .ignoreOP(true) - .build() - ); - this.messageManager = new MessageManagerImpl(this); - this.packetManager = new PacketManager(this); - this.commandManager = new CommandManager(this); - this.placeholderManager = new PlaceholderManagerImpl(this); - this.hologramManager = new HologramManager(this); - this.commandManager.init(); - try { - this.integrationManager.init(); - } catch (Exception e) { - e.printStackTrace(); - } - BukkitItemFactory.create(this); - Migration.tryUpdating(); - this.reload(); - if (ConfigManager.metrics()) new Metrics(this, 16593); - if (ConfigManager.checkUpdate()) { - this.versionManager.checkUpdate().thenAccept(result -> { - if (!result) this.getAdventure().sendConsoleMessage("[CustomCrops] You are using the latest version."); - else this.getAdventure().sendConsoleMessage("[CustomCrops] Update is available: https://polymart.org/resource/2625"); - }); - } - } - - @Override - public void onDisable() { - if (this.commandManager != null) this.commandManager.disable(); - if (this.adventure != null) this.adventure.disable(); - if (this.requirementManager != null) this.requirementManager.disable(); - if (this.actionManager != null) this.actionManager.disable(); - if (this.worldManager != null) this.worldManager.disable(); - if (this.itemManager != null) this.itemManager.disable(); - if (this.conditionManager != null) this.conditionManager.disable(); - if (this.coolDownManager != null) this.coolDownManager.disable(); - if (this.placeholderManager != null) this.placeholderManager.disable(); - if (this.scheduler != null) ((SchedulerImpl) scheduler).shutdown(); - instance = null; - } - - @Override - public void reload() { - this.configManager.reload(); - this.messageManager.reload(); - this.itemManager.reload(); - this.worldManager.reload(); - this.actionManager.reload(); - this.requirementManager.reload(); - this.conditionManager.reload(); - this.coolDownManager.reload(); - this.placeholderManager.reload(); - this.hologramManager.reload(); - ((SchedulerImpl) scheduler).reload(); - EventUtils.fireAndForget(new CustomCropsReloadEvent(this)); - } - - @Override - public void debug(String debug) { - if (ConfigManager.debug()) { - LogUtils.info(debug); - } - } - - public DependencyManager getDependencyManager() { - return dependencyManager; - } - - public PacketManager getPacketManager() { - return packetManager; - } - - public HologramManager getHologramManager() { - return hologramManager; - } - - @Override - public boolean isHookedPluginEnabled(String plugin) { - return Bukkit.getPluginManager().isPluginEnabled(plugin); - } - - @Override - public boolean isHookedPluginEnabled(String hooked, String... versionPrefix) { - Plugin p = Bukkit.getPluginManager().getPlugin(hooked); - if (p != null) { - String ver = p.getDescription().getVersion(); - for (String prefix : versionPrefix) { - if (ver.startsWith(prefix)) { - return true; - } - } - } - return false; - } - - @Override - public boolean doesHookedPluginExist(String plugin) { - return Bukkit.getPluginManager().getPlugin(plugin) != null; - } - - @Override - public String getServerVersion() { - return Bukkit.getServer().getBukkitVersion().split("-")[0]; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/ExpressionValue.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitBootstrap.java similarity index 55% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/ExpressionValue.java rename to plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitBootstrap.java index 9125b51..2ec7fa6 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/value/ExpressionValue.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitBootstrap.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,24 +15,28 @@ * along with this program. If not, see . */ -package net.momirealms.customcrops.mechanic.misc.value; +package net.momirealms.customcrops.bukkit; -import net.momirealms.customcrops.api.mechanic.misc.Value; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.entity.Player; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import org.bukkit.plugin.java.JavaPlugin; -import java.util.HashMap; +public class BukkitBootstrap extends JavaPlugin { -public class ExpressionValue implements Value { + private BukkitCustomCropsPlugin plugin; - private final String expression; - - public ExpressionValue(String expression) { - this.expression = expression; + @Override + public void onLoad() { + this.plugin = new BukkitCustomCropsPluginImpl(this); + this.plugin.load(); } @Override - public double get(Player player) { - return ConfigUtils.getExpressionValue(player, expression, new HashMap<>(0)); + public void onEnable() { + this.plugin.enable(); + } + + @Override + public void onDisable() { + this.plugin.disable(); } } diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitCustomCropsPluginImpl.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitCustomCropsPluginImpl.java new file mode 100644 index 0000000..852394a --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/BukkitCustomCropsPluginImpl.java @@ -0,0 +1,223 @@ +package net.momirealms.customcrops.bukkit; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.SimpleRegistryAccess; +import net.momirealms.customcrops.api.core.block.*; +import net.momirealms.customcrops.api.core.item.*; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.event.CustomCropsReloadEvent; +import net.momirealms.customcrops.api.misc.cooldown.CoolDownManager; +import net.momirealms.customcrops.api.misc.placeholder.BukkitPlaceholderManager; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.bukkit.action.BlockActionManager; +import net.momirealms.customcrops.bukkit.action.PlayerActionManager; +import net.momirealms.customcrops.bukkit.command.BukkitCommandManager; +import net.momirealms.customcrops.bukkit.config.BukkitConfigManager; +import net.momirealms.customcrops.bukkit.integration.BukkitIntegrationManager; +import net.momirealms.customcrops.bukkit.item.BukkitItemManager; +import net.momirealms.customcrops.bukkit.misc.HologramManager; +import net.momirealms.customcrops.bukkit.requirement.BlockRequirementManager; +import net.momirealms.customcrops.bukkit.requirement.PlayerRequirementManager; +import net.momirealms.customcrops.bukkit.scheduler.BukkitSchedulerAdapter; +import net.momirealms.customcrops.bukkit.sender.BukkitSenderFactory; +import net.momirealms.customcrops.bukkit.world.BukkitWorldManager; +import net.momirealms.customcrops.common.config.ConfigLoader; +import net.momirealms.customcrops.common.dependency.Dependency; +import net.momirealms.customcrops.common.dependency.DependencyManagerImpl; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.locale.TranslationManager; +import net.momirealms.customcrops.common.plugin.classpath.ClassPathAppender; +import net.momirealms.customcrops.common.plugin.classpath.ReflectionClassPathAppender; +import net.momirealms.customcrops.common.plugin.feature.Reloadable; +import net.momirealms.customcrops.common.plugin.logging.JavaPluginLogger; +import net.momirealms.customcrops.common.plugin.logging.PluginLogger; +import net.momirealms.sparrow.heart.SparrowHeart; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; + +public class BukkitCustomCropsPluginImpl extends BukkitCustomCropsPlugin { + + private final ClassPathAppender classPathAppender; + private final PluginLogger logger; + private BukkitCommandManager commandManager; + private HologramManager hologramManager; + private Consumer debugger; + private String buildByBit = "%%__BUILTBYBIT__%%"; + private String polymart = "%%__POLYMART__%%"; + private String time = "%%__TIMESTAMP__%%"; + private String user = "%%__USER__%%"; + private String username = "%%__USERNAME__%%"; + + public BukkitCustomCropsPluginImpl(Plugin boostrap) { + super(boostrap); + VersionHelper.init(getServerVersion()); + this.scheduler = new BukkitSchedulerAdapter(this); + this.logger = new JavaPluginLogger(getBoostrap().getLogger()); + this.classPathAppender = new ReflectionClassPathAppender(this); + this.dependencyManager = new DependencyManagerImpl(this); + this.registryAccess = new SimpleRegistryAccess(this); + } + + @Override + public void debug(Object message) { + if (this.debugger != null) + this.debugger.accept(message); + } + + @Override + public InputStream getResourceStream(String filePath) { + return getBoostrap().getResource(filePath); + } + + @Override + public PluginLogger getPluginLogger() { + return logger; + } + + @Override + public ClassPathAppender getClassPathAppender() { + return classPathAppender; + } + + @Override + public Path getDataDirectory() { + return getBoostrap().getDataFolder().toPath().toAbsolutePath(); + } + + @Override + public ConfigLoader getConfigManager() { + return configManager; + } + + @Override + public String getServerVersion() { + return Bukkit.getServer().getBukkitVersion().split("-")[0]; + } + + @SuppressWarnings("deprecation") + @Override + public String getPluginVersion() { + return getBoostrap().getDescription().getVersion(); + } + + @Override + public void load() { + this.dependencyManager.loadDependencies( + List.of( + Dependency.BOOSTED_YAML, + Dependency.BSTATS_BASE, Dependency.BSTATS_BUKKIT, + Dependency.CAFFEINE, + Dependency.GEANTY_REF, + Dependency.CLOUD_CORE, Dependency.CLOUD_SERVICES, Dependency.CLOUD_BUKKIT, Dependency.CLOUD_PAPER, Dependency.CLOUD_BRIGADIER, Dependency.CLOUD_MINECRAFT_EXTRAS, + Dependency.GSON, + Dependency.EXP4J, + Dependency.ZSTD + ) + ); + this.registerDefaultMechanics(); + } + + @Override + public void enable() { + SparrowHeart.getInstance(); + this.configManager = new BukkitConfigManager(this); + super.requirementManagers.put(Player.class, new PlayerRequirementManager(this)); + super.requirementManagers.put(CustomCropsBlockState.class, new BlockRequirementManager(this)); + super.actionManagers.put(Player.class, new PlayerActionManager(this)); + super.actionManagers.put(CustomCropsBlockState.class, new BlockActionManager(this)); + this.translationManager = new TranslationManager(this); + this.senderFactory = new BukkitSenderFactory(this); + this.itemManager = new BukkitItemManager(this); + this.integrationManager = new BukkitIntegrationManager(this); + this.placeholderManager = new BukkitPlaceholderManager(this); + this.coolDownManager = new CoolDownManager(this); + this.worldManager = new BukkitWorldManager(this); + this.hologramManager = new HologramManager(this); + this.commandManager = new BukkitCommandManager(this); + this.commandManager.registerDefaultFeatures(); + + boolean downloadFromPolymart = polymart.equals("1"); + boolean downloadFromBBB = buildByBit.equals("true"); + + this.getScheduler().sync().runLater(() -> { + this.reload(); + ((SimpleRegistryAccess) registryAccess).freeze(); + if (ConfigManager.metrics()) new Metrics((JavaPlugin) getBoostrap(), 16593); + if (ConfigManager.checkUpdate()) { + VersionHelper.UPDATE_CHECKER.apply(this).thenAccept(result -> { + String link; + if (downloadFromPolymart) { + link = "https://polymart.org/resource/2625/"; + } else if (downloadFromBBB) { + link = "https://builtbybit.com/resources/36363/"; + } else { + link = "https://github.com/Xiao-MoMi/Custom-Crops/"; + } + if (!result) { + this.getPluginLogger().info("You are using the latest version."); + } else { + this.getPluginLogger().warn("Update is available: " + link); + } + }); + } + }, 1, null); + } + + @Override + public void disable() { + this.worldManager.disable(); + this.placeholderManager.disable(); + this.hologramManager.disable(); + this.integrationManager.disable(); + this.coolDownManager.disable(); + this.commandManager.unregisterFeatures(); + } + + @Override + public void reload() { + + this.worldManager.unload(); + + this.configManager.reload(); + this.debugger = ConfigManager.debug() ? (s) -> logger.info("[DEBUG] " + s.toString()) : (s) -> {}; + this.coolDownManager.reload(); + this.placeholderManager.reload(); + this.translationManager.reload(); + this.hologramManager.reload(); + + this.actionManagers.values().forEach(Reloadable::reload); + this.requirementManagers.values().forEach(Reloadable::reload); + + this.worldManager.load(); + + EventUtils.fireAndForget(new CustomCropsReloadEvent(this)); + } + + private void registerDefaultMechanics() { + registryAccess.registerFertilizerType(FertilizerType.SPEED_GROW); + registryAccess.registerFertilizerType(FertilizerType.QUALITY); + registryAccess.registerFertilizerType(FertilizerType.SOIL_RETAIN); + registryAccess.registerFertilizerType(FertilizerType.VARIATION); + registryAccess.registerFertilizerType(FertilizerType.YIELD_INCREASE); + + registryAccess.registerBlockMechanic(new CropBlock()); + registryAccess.registerBlockMechanic(new PotBlock()); + registryAccess.registerBlockMechanic(new ScarecrowBlock()); + registryAccess.registerBlockMechanic(new SprinklerBlock()); + registryAccess.registerBlockMechanic(new GreenhouseBlock()); + + registryAccess.registerItemMechanic(new SeedItem()); + registryAccess.registerItemMechanic(new WateringCanItem()); + registryAccess.registerItemMechanic(new FertilizerItem()); + registryAccess.registerItemMechanic(new SprinklerItem()); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/BlockActionManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/BlockActionManager.java new file mode 100644 index 0000000..a09aed2 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/BlockActionManager.java @@ -0,0 +1,110 @@ +package net.momirealms.customcrops.bukkit.action; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.AbstractActionManager; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.context.ContextKeys; +import net.momirealms.customcrops.api.core.CustomForm; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.FurnitureRotation; +import net.momirealms.customcrops.api.core.block.CropBlock; +import net.momirealms.customcrops.api.core.block.PotBlock; +import net.momirealms.customcrops.api.core.block.VariationData; +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 org.bukkit.Location; + +import java.util.*; + +import static java.util.Objects.requireNonNull; + +public class BlockActionManager extends AbstractActionManager { + + public BlockActionManager(BukkitCustomCropsPlugin plugin) { + super(plugin); + } + + @Override + public void load() { + loadExpansions(CustomCropsBlockState.class); + } + + @Override + protected void registerBuiltInActions() { + super.registerBuiltInActions(); + super.registerBundleAction(CustomCropsBlockState.class); + this.registerVariationAction(); + } + + private void registerVariationAction() { + registerAction((args, chance) -> { + if (args instanceof Section section) { + boolean ignore = section.getBoolean("ignore-fertilizer", false); + List variationDataList = new ArrayList<>(); + for (Map.Entry entry : section.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + VariationData variationData = new VariationData( + inner.getString("item"), + CustomForm.valueOf(inner.getString("type", "TripWire").toUpperCase(Locale.ENGLISH)).existenceForm(), + inner.getDouble("chance") + ); + variationDataList.add(variationData); + } + } + VariationData[] variations = variationDataList.toArray(new VariationData[0]); + return context -> { + if (Math.random() > chance) return; + if (!(context.holder().type() instanceof CropBlock cropBlock)) { + return; + } + Fertilizer[] fertilizers = null; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Optional> world = plugin.getWorldManager().getWorld(location.getWorld()); + if (world.isEmpty()) { + return; + } + Pos3 pos3 = Pos3.from(location); + if (!ignore) { + 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 (VariationData variationData : variations) { + double variationChance = variationData.chance(); + for (FertilizerConfig fertilizer : configs) { + variationChance = fertilizer.processVariationChance(variationChance); + } + if (Math.random() < variationChance) { + plugin.getItemManager().remove(location, ExistenceForm.ANY); + world.get().removeBlockState(pos3); + plugin.getItemManager().place(location, variationData.existenceForm(), variationData.id(), FurnitureRotation.random()); + cropBlock.fixOrGetState(world.get(), pos3, variationData.id()); + break; + } + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at variation action which is expected to be `Section`"); + return Action.empty(); + } + }, "variation"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/PlayerActionManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/PlayerActionManager.java new file mode 100644 index 0000000..d6cd172 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/action/PlayerActionManager.java @@ -0,0 +1,524 @@ +package net.momirealms.customcrops.bukkit.action; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.AbstractActionManager; +import net.momirealms.customcrops.api.action.Action; +import net.momirealms.customcrops.api.context.ContextKeys; +import net.momirealms.customcrops.api.core.block.CropBlock; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.CropStageConfig; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.CustomCropsWorld; +import net.momirealms.customcrops.api.core.world.Pos3; +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.util.LocationUtils; +import net.momirealms.customcrops.api.util.PlayerUtils; +import net.momirealms.customcrops.bukkit.integration.VaultHook; +import net.momirealms.customcrops.bukkit.misc.HologramManager; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import net.momirealms.customcrops.common.util.ListUtils; +import net.momirealms.customcrops.common.util.RandomUtils; +import net.momirealms.sparrow.heart.SparrowHeart; +import net.momirealms.sparrow.heart.feature.inventory.HandSlot; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.*; + +import static java.util.Objects.requireNonNull; + +public class PlayerActionManager extends AbstractActionManager { + + public PlayerActionManager(BukkitCustomCropsPlugin plugin) { + super(plugin); + } + + @Override + public void load() { + loadExpansions(Player.class); + } + + @Override + protected void registerBuiltInActions() { + super.registerBuiltInActions(); + super.registerBundleAction(Player.class); + this.registerPlayerCommandAction(); + this.registerCloseInvAction(); + this.registerActionBarAction(); + this.registerExpAction(); + this.registerFoodAction(); + this.registerItemAction(); + this.registerMoneyAction(); + this.registerPotionAction(); + this.registerSoundAction(); + this.registerPluginExpAction(); + this.registerTitleAction(); + this.registerSwingHandAction(); + this.registerForceTickAction(); + this.registerHologramAction(); + this.registerMessageAction(); + } + + private void registerMessageAction() { + registerAction((args, chance) -> { + List messages = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + List replaced = plugin.getPlaceholderManager().parse(context.holder(), messages, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.holder()); + for (String text : replaced) { + audience.sendMessage(AdventureHelper.miniMessage(text)); + } + }; + }, "message"); + registerAction((args, chance) -> { + List messages = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + String random = messages.get(RandomUtils.generateRandomInt(0, messages.size() - 1)); + random = BukkitPlaceholderManager.getInstance().parse(context.holder(), random, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.holder()); + audience.sendMessage(AdventureHelper.miniMessage(random)); + }; + }, "random-message"); + } + + private void registerPlayerCommandAction() { + registerAction((args, chance) -> { + List commands = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + List replaced = BukkitPlaceholderManager.getInstance().parse(context.holder(), commands, context.placeholderMap()); + plugin.getScheduler().sync().run(() -> { + for (String text : replaced) { + context.holder().performCommand(text); + } + }, context.holder().getLocation()); + }; + }, "player-command"); + } + + private void registerCloseInvAction() { + registerAction((args, chance) -> context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + context.holder().closeInventory(); + }, "close-inv"); + } + + private void registerActionBarAction() { + registerAction((args, chance) -> { + String text = (String) args; + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Audience audience = plugin.getSenderFactory().getAudience(context.holder()); + Component component = AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(context.holder(), text, context.placeholderMap())); + audience.sendActionBar(component); + }; + }, "actionbar"); + registerAction((args, chance) -> { + List texts = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + String random = texts.get(RandomUtils.generateRandomInt(0, texts.size() - 1)); + random = plugin.getPlaceholderManager().parse(context.holder(), random, context.placeholderMap()); + Audience audience = plugin.getSenderFactory().getAudience(context.holder()); + audience.sendActionBar(AdventureHelper.miniMessage(random)); + }; + }, "random-actionbar"); + } + + private void registerExpAction() { + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + final Player player = context.holder(); + ExperienceOrb entity = player.getLocation().getWorld().spawn(player.getLocation().clone().add(0,0.5,0), ExperienceOrb.class); + entity.setExperience((int) value.evaluate(context)); + }; + }, "mending"); + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + final Player player = context.holder(); + player.giveExp((int) Math.round(value.evaluate(context))); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.playSound(audience, Sound.sound(Key.key("minecraft:entity.experience_orb.pickup"), Sound.Source.PLAYER, 1, 1)); + }; + }, "exp"); + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Player player = context.holder(); + player.setLevel((int) Math.max(0, player.getLevel() + value.evaluate(context))); + }; + }, "level"); + } + + private void registerFoodAction() { + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Player player = context.holder(); + player.setFoodLevel((int) (player.getFoodLevel() + value.evaluate(context))); + }; + }, "food"); + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Player player = context.holder(); + player.setSaturation((float) (player.getSaturation() + value.evaluate(context))); + }; + }, "saturation"); + } + + private void registerItemAction() { + registerAction((args, chance) -> { + if (args instanceof Section section) { + boolean mainOrOff = section.getString("hand", "main").equalsIgnoreCase("main"); + int amount = section.getInt("amount", 1); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Player player = context.holder(); + boolean tempHand = mainOrOff; + EquipmentSlot hand = context.arg(ContextKeys.SLOT); + if (hand == EquipmentSlot.OFF_HAND || hand == EquipmentSlot.HAND) { + tempHand = hand == EquipmentSlot.HAND; + } + ItemStack itemStack = tempHand ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand(); + itemStack.setAmount(Math.max(0, itemStack.getAmount() + amount)); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at item-amount action which is expected to be `Section`"); + return Action.empty(); + } + }, "item-amount"); + registerAction((args, chance) -> { + int amount; + EquipmentSlot slot; + if (args instanceof Integer integer) { + slot = null; + amount = integer; + } else if (args instanceof Section section) { + slot = Optional.ofNullable(section.getString("slot")) + .map(hand -> EquipmentSlot.valueOf(hand.toUpperCase(Locale.ENGLISH))) + .orElse(null); + amount = section.getInt("amount", 1); + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at durability action which is expected to be `Section`"); + return Action.empty(); + } + return context -> { + if (Math.random() > chance) return; + Player player = context.holder(); + if (player == null) return; + EquipmentSlot tempSlot = slot; + EquipmentSlot equipmentSlot = context.arg(ContextKeys.SLOT); + if (equipmentSlot != null) { + tempSlot = equipmentSlot; + } + if (tempSlot == null) { + return; + } + ItemStack itemStack = player.getInventory().getItem(tempSlot); + if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + if (itemStack.getItemMeta() == null) + return; + if (amount > 0) { + plugin.getItemManager().decreaseDamage(context.holder(), itemStack, amount); + } else { + plugin.getItemManager().increaseDamage(context.holder(), itemStack, -amount); + } + }; + }, "durability"); + registerAction((args, chance) -> { + if (args instanceof Section section) { + String id = section.getString("item"); + int amount = section.getInt("amount", 1); + boolean toInventory = section.getBoolean("to-inventory", false); + return context -> { + if (Math.random() > chance) return; + Player player = context.holder(); + if (player == null) return; + ItemStack itemStack = plugin.getItemManager().build(context.holder(), id); + if (itemStack != null) { + int maxStack = itemStack.getMaxStackSize(); + int amountToGive = amount; + while (amountToGive > 0) { + int perStackSize = Math.min(maxStack, amountToGive); + amountToGive -= perStackSize; + ItemStack more = itemStack.clone(); + more.setAmount(perStackSize); + if (toInventory) { + PlayerUtils.giveItem(player, more, more.getAmount()); + } else { + PlayerUtils.dropItem(player, more, true, true, false); + } + } + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at give-item action which is expected to be `Section`"); + return Action.empty(); + } + }, "give-item"); + } + + private void registerMoneyAction() { + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + if (!VaultHook.isHooked()) return; + VaultHook.deposit(context.holder(), value.evaluate(context)); + }; + }, "give-money"); + registerAction((args, chance) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + if (!VaultHook.isHooked()) return; + VaultHook.withdraw(context.holder(), value.evaluate(context)); + }; + }, "take-money"); + } + + private void registerPotionAction() { + registerAction((args, chance) -> { + if (args instanceof Section section) { + PotionEffect potionEffect = new PotionEffect( + Objects.requireNonNull(PotionEffectType.getByName(section.getString("type", "BLINDNESS").toUpperCase(Locale.ENGLISH))), + section.getInt("duration", 20), + section.getInt("amplifier", 0) + ); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + context.holder().addPotionEffect(potionEffect); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at potion-effect action which is expected to be `Section`"); + return Action.empty(); + } + }, "potion-effect"); + } + + private void registerSoundAction() { + registerAction((args, chance) -> { + if (args instanceof Section section) { + Sound sound = Sound.sound( + Key.key(section.getString("key")), + Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)), + section.getDouble("volume", 1.0).floatValue(), + section.getDouble("pitch", 1.0).floatValue() + ); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Audience audience = plugin.getSenderFactory().getAudience(context.holder()); + AdventureHelper.playSound(audience, sound); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at sound action which is expected to be `Section`"); + return Action.empty(); + } + }, "sound"); + } + + private void registerPluginExpAction() { + registerAction((args, chance) -> { + if (args instanceof Section section) { + String pluginName = section.getString("plugin"); + MathValue value = MathValue.auto(section.get("exp")); + String target = section.getString("target"); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Optional.ofNullable(plugin.getIntegrationManager().getLevelerProvider(pluginName)).ifPresentOrElse(it -> { + it.addXp(context.holder(), target, value.evaluate(context)); + }, () -> plugin.getPluginLogger().warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.")); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plugin-exp action which is expected to be `Section`"); + return Action.empty(); + } + }, "plugin-exp"); + } + + private void registerTitleAction() { + 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); + return context -> { + if (Math.random() > chance) return; + final Player player = context.holder(); + if (player == null) return; + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.sendTitle(audience, + AdventureHelper.miniMessage(title.render(context)), + AdventureHelper.miniMessage(subtitle.render(context)), + fadeIn, stay, fadeOut + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title action which is expected to be `Section`"); + return Action.empty(); + } + }, "title"); + registerAction((args, chance) -> { + if (args instanceof Section section) { + List titles = section.getStringList("titles"); + if (titles.isEmpty()) titles.add(""); + List subtitles = section.getStringList("subtitles"); + if (subtitles.isEmpty()) subtitles.add(""); + int fadeIn = section.getInt("fade-in", 20); + int stay = section.getInt("stay", 30); + int fadeOut = section.getInt("fade-out", 10); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + TextValue title = TextValue.auto(titles.get(RandomUtils.generateRandomInt(0, titles.size() - 1))); + TextValue subtitle = TextValue.auto(subtitles.get(RandomUtils.generateRandomInt(0, subtitles.size() - 1))); + final Player player = context.holder(); + Audience audience = plugin.getSenderFactory().getAudience(player); + AdventureHelper.sendTitle(audience, + AdventureHelper.miniMessage(title.render(context)), + AdventureHelper.miniMessage(subtitle.render(context)), + fadeIn, stay, fadeOut + ); + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at random-title action which is expected to be `Section`"); + return Action.empty(); + } + }, "random-title"); + } + + private void registerHologramAction() { + registerAction(((args, chance) -> { + if (args instanceof Section section) { + TextValue text = TextValue.auto(section.getString("text", "")); + MathValue duration = MathValue.auto(section.get("duration", 20)); + boolean position = section.getString("position", "other").equals("other"); + MathValue x = MathValue.auto(section.get("x", 0)); + MathValue y = MathValue.auto(section.get("y", 0)); + MathValue z = MathValue.auto(section.get("z", 0)); + boolean applyCorrection = section.getBoolean("apply-correction", false); + boolean onlyShowToOne = !section.getBoolean("visible-to-all", false); + int range = section.getInt("range", 32); + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Player owner = context.holder(); + Location location = position ? requireNonNull(context.arg(ContextKeys.LOCATION)).clone() : owner.getLocation().clone(); + location.add(x.evaluate(context), y.evaluate(context), z.evaluate(context)); + Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + Pos3 pos3 = Pos3.from(location); + if (applyCorrection) { + Optional optionalState = optionalWorld.get().getBlockState(pos3); + if (optionalState.isPresent()) { + if (optionalState.get().type() instanceof CropBlock cropBlock) { + CropConfig config = cropBlock.config(optionalState.get()); + int point = cropBlock.point(optionalState.get()); + if (config != null) { + int tempPoints = point; + while (tempPoints >= 0) { + Map.Entry entry = config.getFloorStageEntry(tempPoints); + CropStageConfig stage = entry.getValue(); + if (stage.stageID() != null) { + location.add(0, stage.displayInfoOffset(), 0); + break; + } + tempPoints = stage.point() - 1; + } + } + } + } + } + ArrayList viewers = new ArrayList<>(); + if (onlyShowToOne) { + if (owner == null) return; + viewers.add(owner); + } else { + for (Player player : owner.getWorld().getPlayers()) { + if (LocationUtils.getDistance(player.getLocation(), location) <= range) { + viewers.add(player); + } + } + } + Component component = AdventureHelper.miniMessage(text.render(context)); + for (Player viewer : viewers) { + HologramManager.getInstance().showHologram(viewer, location, component, (int) (duration.evaluate(context) * 50)); + } + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at hologram action which is expected to be `Section`"); + return Action.empty(); + } + }), "hologram"); + } + + private void registerSwingHandAction() { + registerAction((args, chance) -> { + boolean arg = (boolean) args; + return context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + SparrowHeart.getInstance().swingHand(context.holder(), arg ? HandSlot.MAIN : HandSlot.OFF); + }; + }, "swing-hand"); + } + + private void registerForceTickAction() { + registerAction((args, chance) -> context -> { + if (context.holder() == null) return; + if (Math.random() > chance) return; + Location location = requireNonNull(context.arg(ContextKeys.LOCATION)); + Pos3 pos3 = Pos3.from(location); + Optional> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld()); + optionalWorld.ifPresent(world -> world.getChunk(pos3.toChunkPos()).flatMap(chunk -> chunk.getBlockState(pos3)).ifPresent(state -> { + state.type().randomTick(state, world, pos3); + state.type().scheduledTick(state, world, pos3); + })); + }, "force-tick"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandFeature.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandFeature.java new file mode 100644 index 0000000..768e021 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandFeature.java @@ -0,0 +1,61 @@ +/* + * 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.bukkit.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.common.command.AbstractCommandFeature; +import net.momirealms.customcrops.common.command.CustomCropsCommandManager; +import net.momirealms.customcrops.common.sender.SenderFactory; +import net.momirealms.customcrops.common.util.Pair; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.incendo.cloud.bukkit.data.Selector; + +import java.util.Collection; + +public abstract class BukkitCommandFeature extends AbstractCommandFeature { + + public BukkitCommandFeature(CustomCropsCommandManager commandManager) { + super(commandManager); + } + + @Override + @SuppressWarnings("unchecked") + protected SenderFactory getSenderFactory() { + return (SenderFactory) BukkitCustomCropsPlugin.getInstance().getSenderFactory(); + } + + public Pair resolveSelector(Selector selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) { + Collection entities = selector.values(); + if (entities.size() == 1) { + return Pair.of(single, Component.text(entities.iterator().next().getName())); + } else { + return Pair.of(multiple, Component.text(entities.size())); + } + } + + public Pair resolveSelector(Collection selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) { + if (selector.size() == 1) { + return Pair.of(single, Component.text(selector.iterator().next().getName())); + } else { + return Pair.of(multiple, Component.text(selector.size())); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandManager.java new file mode 100644 index 0000000..beed78b --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/BukkitCommandManager.java @@ -0,0 +1,70 @@ +/* + * 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.bukkit.command; + +import net.kyori.adventure.util.Index; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.bukkit.command.feature.DebugDataCommand; +import net.momirealms.customcrops.bukkit.command.feature.ReloadCommand; +import net.momirealms.customcrops.common.command.AbstractCommandManager; +import net.momirealms.customcrops.common.command.CommandFeature; +import net.momirealms.customcrops.common.sender.Sender; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.SenderMapper; +import org.incendo.cloud.bukkit.CloudBukkitCapabilities; +import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.paper.LegacyPaperCommandManager; +import org.incendo.cloud.setting.ManagerSetting; + +import java.util.List; + +public class BukkitCommandManager extends AbstractCommandManager { + + private final List> FEATURES = List.of( + new ReloadCommand(this), + new DebugDataCommand(this) + ); + + private final Index> INDEX = Index.create(CommandFeature::getFeatureID, FEATURES); + + public BukkitCommandManager(BukkitCustomCropsPlugin plugin) { + super(plugin, new LegacyPaperCommandManager<>( + plugin.getBoostrap(), + ExecutionCoordinator.simpleCoordinator(), + SenderMapper.identity() + )); + final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); + manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); + if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) { + manager.registerBrigadier(); + manager.brigadierManager().setNativeNumberSuggestions(true); + } else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) { + manager.registerAsynchronousCompletions(); + } + } + + @Override + protected Sender wrapSender(CommandSender sender) { + return ((BukkitCustomCropsPlugin) plugin).getSenderFactory().wrap(sender); + } + + @Override + public Index> getFeatures() { + return INDEX; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugDataCommand.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugDataCommand.java new file mode 100644 index 0000000..2de532d --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/DebugDataCommand.java @@ -0,0 +1,73 @@ +/* + * 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.bukkit.command.feature; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.core.world.Pos3; +import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature; +import net.momirealms.customcrops.common.command.CustomCropsCommandManager; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +import java.util.Optional; + +public class DebugDataCommand extends BukkitCommandFeature { + + public DebugDataCommand(CustomCropsCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .senderType(Player.class) + .flag(manager.flagBuilder("this").build()) + .handler(context -> { + Player player = context.sender(); + Location location; + if (context.flags().hasFlag("this")) { + location = player.getLocation(); + } else { + Block block = player.getTargetBlockExact(10); + if (block == null) return; + location = block.getLocation(); + } + BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(location.getWorld()).ifPresent(world -> { + Optional state = world.getBlockState(Pos3.from(location)); + if (state.isPresent()) { + BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(player) + .sendMessage(AdventureHelper.miniMessage(state.get().toString())); + } else { + BukkitCustomCropsPlugin.getInstance().getSenderFactory().wrap(player) + .sendMessage(AdventureHelper.miniMessage("Data not found")); + } + }); + }); + } + + @Override + public String getFeatureID() { + return "debug_data"; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ReloadCommand.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ReloadCommand.java new file mode 100644 index 0000000..44c0ef4 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/command/feature/ReloadCommand.java @@ -0,0 +1,50 @@ +/* + * 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.bukkit.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.bukkit.command.BukkitCommandFeature; +import net.momirealms.customcrops.common.command.CustomCropsCommandManager; +import net.momirealms.customcrops.common.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.CommandManager; + +public class ReloadCommand extends BukkitCommandFeature { + + public ReloadCommand(CustomCropsCommandManager commandManager) { + super(commandManager); + } + + @Override + public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .handler(context -> { + long time1 = System.currentTimeMillis(); + BukkitCustomCropsPlugin.getInstance().reload(); + handleFeedback(context, MessageConstants.COMMAND_RELOAD_SUCCESS, Component.text(System.currentTimeMillis() - time1)); + }); + } + + @Override + public String getFeatureID() { + return "reload"; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java new file mode 100644 index 0000000..81ab091 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java @@ -0,0 +1,249 @@ +package net.momirealms.customcrops.bukkit.config; + +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.exceptions.ConstructorException; +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.*; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.CropStageConfig; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +import net.momirealms.customcrops.api.core.item.FertilizerConfig; +import net.momirealms.customcrops.api.core.item.WateringCanConfig; +import net.momirealms.customcrops.api.util.PluginUtils; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.locale.TranslationManager; +import net.momirealms.customcrops.common.plugin.CustomCropsProperties; +import net.momirealms.customcrops.common.util.ListUtils; +import org.bukkit.Bukkit; +import org.bukkit.Particle; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class BukkitConfigManager extends ConfigManager { + + private static YamlDocument MAIN_CONFIG; + + public static YamlDocument getMainConfig() { + return MAIN_CONFIG; + } + + public BukkitConfigManager(BukkitCustomCropsPlugin plugin) { + super(plugin); + } + + @Override + public void load() { + String configVersion = CustomCropsProperties.getValue("config"); + try (InputStream inputStream = new FileInputStream(resolveConfig("config.yml").toFile())) { + MAIN_CONFIG = YamlDocument.create( + inputStream, + plugin.getResourceStream("config.yml"), + GeneralSettings.builder() + .setRouteSeparator('.') + .setUseDefaults(false) + .build(), + LoaderSettings + .builder() + .setAutoUpdate(true) + .build(), + DumperSettings.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")) + .addIgnoredRoute(configVersion, "other-settings.placeholder-register", '.') + .build() + ); + MAIN_CONFIG.save(resolveConfig("config.yml").toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.loadSettings(); + this.loadConfigs(); + } + + private void loadSettings() { + YamlDocument config = getMainConfig(); + + TranslationManager.forceLocale(TranslationManager.parseLocale(config.getString("force-locale", ""))); + AdventureHelper.legacySupport = config.getBoolean("other-settings.legacy-color-code-support", true); + + metrics = config.getBoolean("metrics", true); + checkUpdate = config.getBoolean("update-checker", true); + debug = config.getBoolean("debug", false); + + protectOriginalLore = config.getBoolean("other-settings.protect-original-lore", false); + doubleCheck = config.getBoolean("other-settings.double-check", false); + + enableScarecrow = config.getBoolean("mechanics.scarecrow.enable", true); + scarecrow = new HashSet<>(ListUtils.toList(config.get("mechanics.scarecrow.id"))); + scarecrowExistenceForm = CustomForm.valueOf(config.getString("mechanics.scarecrow.type", "ITEM_FRAME")).existenceForm(); + scarecrowRange = config.getInt("mechanics.scarecrow.range", 7); + scarecrowProtectChunk = config.getBoolean("mechanics.scarecrow.protect-chunk", false); + + enableGreenhouse = config.getBoolean("mechanics.greenhouse.enable", true); + greenhouse = new HashSet<>(ListUtils.toList(config.get("mechanics.greenhouse.id"))); + greenhouseExistenceForm = CustomForm.valueOf(config.getString("mechanics.greenhouse.type", "CHORUS")).existenceForm(); + greenhouseRange = config.getInt("mechanics.greenhouse.range", 5); + + syncSeasons = config.getBoolean("mechanics.sync-season.enable", false); + referenceWorld = config.getString("mechanics.sync-season.reference", "world"); + + itemDetectOrder = config.getStringList("other-settings.item-detection-order").toArray(new String[0]); + + absoluteWorldPath = config.getString("worlds.absolute-world-folder-path"); + + defaultQualityRatio = getQualityRatio(config.getString("mechanics.default-quality-ratio", "17/2/1")); + + hasNamespace = PluginUtils.isEnabled("ItemsAdder"); + } + + @Override + public void saveResource(String filePath) { + File file = new File(plugin.getDataFolder(), filePath); + if (!file.exists()) { + plugin.getBoostrap().saveResource(filePath, false); + addDefaultNamespace(file); + } + } + + @Override + public void unload() { + this.clearConfigs(); + } + + private void loadConfigs() { + Deque fileDeque = new ArrayDeque<>(); + for (ConfigType type : ConfigType.values()) { + File typeFolder = new File(plugin.getDataFolder(), "contents" + File.separator + type.path()); + if (!typeFolder.exists()) { + if (!typeFolder.mkdirs()) return; + saveResource("contents" + File.separator + type.path() + File.separator + "default.yml"); + } + fileDeque.push(typeFolder); + while (!fileDeque.isEmpty()) { + File file = fileDeque.pop(); + File[] files = file.listFiles(); + if (files == null) continue; + for (File subFile : files) { + if (subFile.isDirectory()) { + fileDeque.push(subFile); + } else if (subFile.isFile() && subFile.getName().endsWith(".yml")) { + try { + YamlDocument document = plugin.getConfigManager().loadData(subFile); + boolean save = false; + for (Map.Entry entry : document.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section section) { + if (type.parse(this, entry.getKey(), section)) { + save = true; + } + } + } + if (save) { + document.save(subFile); + } + } catch (ConstructorException e) { + plugin.getPluginLogger().warn("Could not load config file: " + subFile.getAbsolutePath() + ". Is it a corrupted file?"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + } + } + + private void clearConfigs() { + Registries.CROP.clear(); + Registries.SEED_TO_CROP.clear(); + Registries.STAGE_TO_CROP_UNSAFE.clear(); + + Registries.SPRINKLER.clear(); + Registries.ITEM_TO_SPRINKLER.clear(); + + Registries.POT.clear(); + Registries.ITEM_TO_POT.clear(); + + Registries.FERTILIZER.clear(); + Registries.ITEM_TO_SPRINKLER.clear(); + + Registries.WATERING_CAN.clear(); + + Registries.ITEMS.clear(); + Registries.BLOCKS.clear(); + } + + @Override + public void registerWateringCanConfig(WateringCanConfig config) { + Registries.WATERING_CAN.register(config.id(), config); + Registries.ITEMS.register(config.itemID(), BuiltInItemMechanics.WATERING_CAN.mechanic()); + } + + @Override + public void registerFertilizerConfig(FertilizerConfig config) { + Registries.FERTILIZER.register(config.id(), config); + Registries.ITEM_TO_FERTILIZER.register(config.itemID(), config); + Registries.ITEMS.register(config.itemID(), BuiltInItemMechanics.FERTILIZER.mechanic()); + } + + @Override + public void registerCropConfig(CropConfig config) { + Registries.CROP.register(config.id(), config); + Registries.SEED_TO_CROP.register(config.seed(), config); + Registries.ITEMS.register(config.seed(), BuiltInItemMechanics.SEED.mechanic()); + for (CropStageConfig stageConfig : config.stages()) { + String stageID = stageConfig.stageID(); + if (stageID != null) { + List list = Registries.STAGE_TO_CROP_UNSAFE.get(stageID); + if (list != null) { + list.add(config); + } else { + Registries.STAGE_TO_CROP_UNSAFE.register(stageID, new ArrayList<>(List.of(config))); + Registries.BLOCKS.register(stageID, BuiltInBlockMechanics.CROP.mechanic()); + } + } + } + } + + @Override + public void registerPotConfig(PotConfig config) { + Registries.POT.register(config.id(), config); + for (String pot : config.blocks()) { + Registries.ITEM_TO_POT.register(pot, config); + Registries.BLOCKS.register(pot, BuiltInBlockMechanics.POT.mechanic()); + } + } + + @Override + public void registerSprinklerConfig(SprinklerConfig config) { + Registries.SPRINKLER.register(config.id(), config); + for (String id : new HashSet<>(List.of(config.threeDItem(), config.threeDItemWithWater()))) { + Registries.ITEM_TO_SPRINKLER.register(id, config); + Registries.BLOCKS.register(id, BuiltInBlockMechanics.SPRINKLER.mechanic()); + } + if (config.twoDItem() != null) { + Registries.ITEM_TO_SPRINKLER.register(config.twoDItem(), config); + Registries.ITEMS.register(config.twoDItem(), BuiltInItemMechanics.SPRINKLER_ITEM.mechanic()); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/ConfigType.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/ConfigType.java new file mode 100644 index 0000000..0922040 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/ConfigType.java @@ -0,0 +1,320 @@ +/* + * 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.bukkit.config; + +import com.google.common.base.Preconditions; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.CustomForm; +import net.momirealms.customcrops.api.core.ExistenceForm; +import net.momirealms.customcrops.api.core.Registries; +import net.momirealms.customcrops.api.core.block.CropConfig; +import net.momirealms.customcrops.api.core.block.CropStageConfig; +import net.momirealms.customcrops.api.core.block.PotConfig; +import net.momirealms.customcrops.api.core.block.SprinklerConfig; +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.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.misc.WaterBar; +import net.momirealms.customcrops.api.misc.value.TextValue; +import net.momirealms.customcrops.api.requirement.RequirementManager; +import net.momirealms.customcrops.common.util.Pair; +import net.momirealms.customcrops.common.util.TriFunction; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; + +/** + * Configuration types for various mechanics. + */ +public class ConfigType { + + public static final ConfigType WATERING_CAN = of( + "watering-cans", + (manager, id, section) -> { + + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + + WateringCanConfig config = WateringCanConfig.builder() + .id(id) + .itemID(section.getString("item")) + .storage(section.getInt("capacity", 3)) + .wateringAmount(section.getInt("water", 1)) + .infinite(section.getBoolean("infinite", false)) + .width(section.getInt("effective-range.width", 1)) + .length(section.getInt("effective-range.length", 1)) + .potWhitelist(new HashSet<>(section.getStringList("pot-whitelist"))) + .sprinklerWhitelist(new HashSet<>(section.getStringList("sprinkler-whitelist"))) + .dynamicLore(section.getBoolean("dynamic-lore")) + .lore(section.getStringList("dynamic-lore.lore").stream().map(TextValue::auto).toList()) + .fillMethods(manager.getFillMethods(section.getSection("fill-method"))) + .requirements(BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true)) + .fullActions(pam.parseActions(section.getSection("events.full"))) + .addWaterActions(pam.parseActions(section.getSection("events.add_water"))) + .consumeWaterActions(pam.parseActions(section.getSection("events.consume_water"))) + .runOutOfWaterActions(pam.parseActions(section.getSection("events.no_water"))) + .wrongPotActions(pam.parseActions(section.getSection("events.wrong_pot"))) + .wrongSprinklerActions(pam.parseActions(section.getSection("events.wrong_sprinkler"))) + .appearances(manager.getInt2IntMap(section.getSection("appearance"))) + .waterBar(section.contains("water-bar") ? WaterBar.of( + section.getString("water-bar.left", ""), + section.getString("water-bar.empty", ""), + section.getString("water-bar.full", ""), + section.getString("water-bar.right", "") + ) : null) + .build(); + + manager.registerWateringCanConfig(config); + return false; + } + ); + + public static final ConfigType FERTILIZER = of( + "fertilizers", + (manager, id, section) -> { + String typeName = Preconditions.checkNotNull(section.getString("type"), "Fertilizer type can't be null").toLowerCase(Locale.ENGLISH); + FertilizerType type = Registries.FERTILIZER_TYPE.get(typeName); + if (type == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Fertilizer type " + typeName + " not found"); + return false; + } + FertilizerConfig config = type.parse(manager, id, section); + manager.registerFertilizerConfig(config); + return false; + } + ); + + public static final ConfigType POT = of( + "pots", + (manager, id, section) -> { + + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + ActionManager bam = BukkitCustomCropsPlugin.getInstance().getActionManager(CustomCropsBlockState.class); + RequirementManager prm = BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class); + + PotConfig config = PotConfig.builder() + .id(id) + .isRainDropAccepted(section.getBoolean("absorb-rainwater", false)) + .isNearbyWaterAccepted(section.getBoolean("absorb-nearby-water", false)) + .maxFertilizers(section.getInt("max-fertilizers", 1)) + .basicAppearance(Pair.of(section.getString("base.dry"), section.getString("base.wet"))) + .potAppearanceMap(manager.getFertilizedPotMap(section.getSection("fertilized-pots"))) + .wateringMethods(manager.getWateringMethods(section.getSection("fill-method"))) + .addWaterActions(pam.parseActions(section.getSection("events.add_water"))) + .placeActions(pam.parseActions(section.getSection("events.place"))) + .breakActions(pam.parseActions(section.getSection("events.break"))) + .interactActions(pam.parseActions(section.getSection("events.interact"))) + .reachLimitActions(pam.parseActions(section.getSection("events.reach_limit"))) + .fullWaterActions(pam.parseActions(section.getSection("events.full"))) + .tickActions(bam.parseActions(section.getSection("events.tick"))) + .useRequirements(prm.parseRequirements(section.getSection("requirements.use"), true)) + .placeRequirements(prm.parseRequirements(section.getSection("requirements.place"), true)) + .breakRequirements(prm.parseRequirements(section.getSection("requirements.break"), true)) + .waterBar(section.contains("water-bar") ? WaterBar.of( + section.getString("water-bar.left", ""), + section.getString("water-bar.empty", ""), + section.getString("water-bar.full", ""), + section.getString("water-bar.right", "") + ) : null) + .build(); + + manager.registerPotConfig(config); + return false; + } + ); + + public static final ConfigType CROP = of( + "crops", + (manager, id, section) -> { + + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + ActionManager bam = BukkitCustomCropsPlugin.getInstance().getActionManager(CustomCropsBlockState.class); + RequirementManager prm = BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class); + + boolean needUpdate = false; + + ExistenceForm form = CustomForm.valueOf(section.getString("type").toUpperCase(Locale.ENGLISH)).existenceForm(); + + Section growConditionSection = section.getSection("grow-conditions"); + if (growConditionSection != null) { + for (Map.Entry entry : growConditionSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + if (inner.contains("type")) { + needUpdate = true; + break; + } + } + } + } + + if (needUpdate) { + section.remove("grow-conditions"); + section.set("grow-conditions.default.point", 1); + Section newSection = section.createSection("grow-conditions.default.conditions"); + newSection.setValue(growConditionSection.getStoredValue()); + } + + Section pointSection = section.getSection("points"); + if (pointSection == null) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("points section not found in crop[" + id + "]"); + return false; + } + + ArrayList builders = new ArrayList<>(); + for (Map.Entry entry : pointSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + int point = Integer.parseInt(entry.getKey()); + CropStageConfig.Builder builder = CropStageConfig.builder() + .point(point) + .displayInfoOffset(inner.getDouble("hologram-offset-correction")) + .stageID(inner.getString("model")) + .breakRequirements(prm.parseRequirements(inner.getSection("requirements.break"), true)) + .interactRequirements(prm.parseRequirements(inner.getSection("requirements.interact"), true)) + .breakActions(pam.parseActions(inner.getSection("events.break"))) + .interactActions(pam.parseActions(inner.getSection("events.interact"))) + .growActions(bam.parseActions(inner.getSection("events.grow"))); + builders.add(builder); + } + } + + CropConfig config = CropConfig.builder() + .id(id) + .seed(section.getString("seed")) + .rotation(section.getBoolean("random-rotation", false)) + .maxPoints(section.getInt("max-points", 1)) + .potWhitelist(new HashSet<>(section.getStringList("pot-whitelist"))) + .wrongPotActions(pam.parseActions(section.getSection("events.wrong_pot"))) + .plantActions(pam.parseActions(section.getSection("events.plant"))) + .breakActions(pam.parseActions(section.getSection("events.break"))) + .interactActions(pam.parseActions(section.getSection("events.interact"))) + .reachLimitActions(pam.parseActions(section.getSection("events.reach_limit"))) + .interactRequirements(prm.parseRequirements(section.getSection("requirements.interact"), true)) + .plantRequirements(prm.parseRequirements(section.getSection("requirements.plant"), true)) + .breakRequirements(prm.parseRequirements(section.getSection("requirements.break"), true)) + .boneMeals(manager.getBoneMeals(section.getSection("custom-bone-meal"))) + .deathConditions(manager.getDeathConditions(section.getSection("death-conditions"), form)) + .growConditions(manager.getGrowConditions(section.getSection("grow-conditions"))) + .stages(builders) + .build(); + + manager.registerCropConfig(config); + return needUpdate; + } + ); + + public static final ConfigType SPRINKLER = of( + "sprinklers", + (manager, id, section) -> { + int rangeValue = section.getInt("range",1); + int workingMode = section.getInt("working-mode", 1); + int[][] range; + if (workingMode == 1) { + int blocks = 4 * rangeValue * rangeValue + 4 * rangeValue + 1; + range = new int[blocks][2]; + int index = 0; + for (int i = -rangeValue; i <= rangeValue; i++) { + for (int j = -rangeValue; j <= rangeValue; j++) { + range[index++] = new int[]{i, j}; + } + } + } else if (workingMode == 2) { + int blocks = (2 * rangeValue * rangeValue) + 2 * rangeValue + 1; + range = new int[blocks][2]; + int index = 0; + for (int i = -rangeValue; i <= rangeValue; i++) { + for (int j = -rangeValue; j <= rangeValue; j++) { + if (Math.abs(i) + Math.abs(j) <= rangeValue) { + range[index++] = new int[]{i, j}; + } + } + } + } else { + throw new IllegalArgumentException("Unrecognized working mode: " + workingMode); + } + + ActionManager pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class); + ActionManager bam = BukkitCustomCropsPlugin.getInstance().getActionManager(CustomCropsBlockState.class); + RequirementManager prm = BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class); + + SprinklerConfig config = SprinklerConfig.builder() + .id(id) + .range(range) + .storage(section.getInt("storage", 4)) + .infinite(section.getBoolean("infinite", false)) + .twoDItem(section.getString("2D-item")) + .sprinklingAmount(section.getInt("water", 1)) + .threeDItem(section.getString("3D-item")) + .threeDItemWithWater(section.getString("3D-item-with-water")) + .wateringMethods(manager.getWateringMethods(section.getSection("fill-method"))) + .potWhitelist(new HashSet<>(section.getStringList("pot-whitelist"))) + .existenceForm(CustomForm.valueOf(section.getString("type", "ITEM_FRAME").toUpperCase(Locale.ENGLISH)).existenceForm()) + .addWaterActions(pam.parseActions(section.getSection("events.add_water"))) + .breakActions(pam.parseActions(section.getSection("events.break"))) + .placeActions(pam.parseActions(section.getSection("events.place"))) + .fullWaterActions(pam.parseActions(section.getSection("events.full"))) + .reachLimitActions(pam.parseActions(section.getSection("events.reach_limit"))) + .interactActions(pam.parseActions(section.getSection("events.interact"))) + .workActions(bam.parseActions(section.getSection("events.work"))) + .useRequirements(prm.parseRequirements(section.getSection("requirements.use"), true)) + .placeRequirements(prm.parseRequirements(section.getSection("requirements.place"), true)) + .breakRequirements(prm.parseRequirements(section.getSection("requirements.break"), true)) + .waterBar(section.contains("water-bar") ? WaterBar.of( + section.getString("water-bar.left", ""), + section.getString("water-bar.empty", ""), + section.getString("water-bar.full", ""), + section.getString("water-bar.right", "") + ) : null) + .build(); + + manager.registerSprinklerConfig(config); + return false; + } + ); + + private static final ConfigType[] values = new ConfigType[] {CROP, SPRINKLER, WATERING_CAN, POT, FERTILIZER}; + + public static ConfigType[] values() { + return values; + } + + private final String path; + private final TriFunction argumentConsumer; + + public ConfigType(String path, TriFunction argumentConsumer) { + this.path = path; + this.argumentConsumer = argumentConsumer; + } + + public static ConfigType of(String path, TriFunction argumentConsumer) { + return new ConfigType(path, argumentConsumer); + } + + public String path() { + return path; + } + + public boolean parse(ConfigManager manager, String id, Section section) { + return argumentConsumer.apply(manager, id, section); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/BukkitIntegrationManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/BukkitIntegrationManager.java new file mode 100644 index 0000000..f1b37b5 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/BukkitIntegrationManager.java @@ -0,0 +1,164 @@ +/* + * 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.bukkit.integration; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.integration.IntegrationManager; +import net.momirealms.customcrops.api.integration.ItemProvider; +import net.momirealms.customcrops.api.integration.LevelerProvider; +import net.momirealms.customcrops.api.integration.SeasonProvider; +import net.momirealms.customcrops.bukkit.integration.item.*; +import net.momirealms.customcrops.bukkit.integration.level.*; +import net.momirealms.customcrops.bukkit.integration.papi.CustomCropsPapi; +import net.momirealms.customcrops.bukkit.integration.season.AdvancedSeasonsProvider; +import net.momirealms.customcrops.bukkit.integration.season.RealisticSeasonsProvider; +import net.momirealms.customcrops.bukkit.item.BukkitItemManager; +import net.momirealms.customcrops.bukkit.world.BukkitWorldManager; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; + +public class BukkitIntegrationManager implements IntegrationManager { + + private final BukkitCustomCropsPlugin plugin; + private final HashMap levelerProviders = new HashMap<>(); + + public BukkitIntegrationManager(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + try { + this.load(); + } catch (Exception e) { + plugin.getPluginLogger().warn("Failed to load integrations", e); + } + } + + @Override + public void disable() { + this.levelerProviders.clear(); + } + + @Override + public void load() { + if (isHooked("MMOItems")) { + registerItemProvider(new MMOItemsItemProvider()); + } + if (isHooked("Zaphkiel")) { + registerItemProvider(new ZaphkielItemProvider()); + } + if (isHooked("NeigeItems")) { + registerItemProvider(new NeigeItemsItemProvider()); + } + if (isHooked("CustomFishing", "2.2", "2.3", "2.4")) { + registerItemProvider(new CustomFishingItemProvider()); + } + if (isHooked("MythicMobs", "5")) { + registerItemProvider(new MythicMobsItemProvider()); + } + if (isHooked("EcoJobs")) { + registerLevelerProvider(new EcoJobsLevelerProvider()); + } + if (isHooked("EcoSkills")) { + registerLevelerProvider(new EcoSkillsLevelerProvider()); + } + if (isHooked("Jobs")) { + registerLevelerProvider(new JobsRebornLevelerProvider()); + } + if (isHooked("MMOCore")) { + registerLevelerProvider(new MMOCoreLevelerProvider()); + } + if (isHooked("mcMMO")) { + registerLevelerProvider(new McMMOLevelerProvider()); + } + if (isHooked("AureliumSkills")) { + registerLevelerProvider(new AureliumSkillsProvider()); + } + if (isHooked("AuraSkills")) { + registerLevelerProvider(new AuraSkillsLevelerProvider()); + } + if (isHooked("RealisticSeasons")) { + registerSeasonProvider(new RealisticSeasonsProvider()); + } else if (isHooked("AdvancedSeasons", "1.4", "1.5", "1.6")) { + registerSeasonProvider(new AdvancedSeasonsProvider()); + } + if (isHooked("Vault")) { + VaultHook.init(); + } + if (isHooked("PlaceholderAPI")) { + new CustomCropsPapi(plugin).load(); + } + } + + private boolean isHooked(String hooked) { + if (Bukkit.getPluginManager().getPlugin(hooked) != null) { + plugin.getPluginLogger().info(hooked + " hooked!"); + return true; + } + return false; + } + + @SuppressWarnings("deprecation") + private boolean isHooked(String hooked, String... versionPrefix) { + Plugin p = Bukkit.getPluginManager().getPlugin(hooked); + if (p != null) { + String ver = p.getDescription().getVersion(); + for (String prefix : versionPrefix) { + if (ver.startsWith(prefix)) { + plugin.getPluginLogger().info(hooked + " hooked!"); + return true; + } + } + } + return false; + } + + @Override + public boolean registerLevelerProvider(@NotNull LevelerProvider leveler) { + if (levelerProviders.containsKey(leveler.identifier())) return false; + levelerProviders.put(leveler.identifier(), leveler); + return true; + } + + @Override + public boolean unregisterLevelerProvider(@NotNull String id) { + return levelerProviders.remove(id) != null; + } + + @Override + @Nullable + public LevelerProvider getLevelerProvider(String plugin) { + return levelerProviders.get(plugin); + } + + @Override + public void registerSeasonProvider(@NotNull SeasonProvider season) { + ((BukkitWorldManager) plugin.getWorldManager()).seasonProvider(season); + } + + @Override + public boolean registerItemProvider(@NotNull ItemProvider item) { + return ((BukkitItemManager) plugin.getItemManager()).registerItemProvider(item); + } + + @Override + public boolean unregisterItemProvider(@NotNull String id) { + return ((BukkitItemManager) plugin.getItemManager()).unregisterItemProvider(id); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java new file mode 100644 index 0000000..c40d815 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/BukkitWorldAdaptor.java @@ -0,0 +1,424 @@ +package net.momirealms.customcrops.bukkit.integration.adaptor; + +import com.flowpowered.nbt.CompoundMap; +import com.flowpowered.nbt.CompoundTag; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.flowpowered.nbt.stream.NBTOutputStream; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.Registries; +import net.momirealms.customcrops.api.core.block.CustomCropsBlock; +import net.momirealms.customcrops.api.core.world.*; +import net.momirealms.customcrops.api.core.world.adaptor.AbstractWorldAdaptor; +import net.momirealms.customcrops.api.util.StringUtils; +import net.momirealms.customcrops.common.helper.GsonHelper; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.util.Key; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class BukkitWorldAdaptor extends AbstractWorldAdaptor { + + private static BiFunction regionFileProvider; + private static Function worldFolderProvider; + private static final NamespacedKey WORLD_DATA = new NamespacedKey(BukkitCustomCropsPlugin.getInstance().getBoostrap(), "data"); + private static final String DATA_FILE = "customcrops.dat"; + + public BukkitWorldAdaptor() { + worldFolderProvider = (world -> { + if (ConfigManager.absoluteWorldPath().isEmpty()) { + return world.getWorldFolder(); + } else { + return new File(ConfigManager.absoluteWorldPath(), world.getName()); + } + }); + regionFileProvider = (world, pos) -> new File(worldFolderProvider.apply(world), "customcrops" + File.separator + getRegionDataFile(pos)); + + } + + public static void regionFileProvider(BiFunction regionFileProvider) { + BukkitWorldAdaptor.regionFileProvider = regionFileProvider; + } + + public static void worldFolderProvider(Function worldFolderProvider) { + BukkitWorldAdaptor.worldFolderProvider = worldFolderProvider; + } + + @Override + public World getWorld(String worldName) { + return Bukkit.getWorld(worldName); + } + + @Override + public CustomCropsWorld adapt(Object world) { + return CustomCropsWorld.create((World) world, this); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public WorldExtraData loadExtraData(World world) { + if (VersionHelper.isVersionNewerThan1_18()) { + // init world basic info + String json = world.getPersistentDataContainer().get(WORLD_DATA, PersistentDataType.STRING); + WorldExtraData data = (json == null || json.equals("null")) ? WorldExtraData.empty() : GsonHelper.get().fromJson(json, WorldExtraData.class); + if (data == null) data = WorldExtraData.empty(); + return data; + } else { + File data = new File(getWorldFolder(world), DATA_FILE); + if (data.exists()) { + byte[] fileBytes = new byte[(int) data.length()]; + try (FileInputStream fis = new FileInputStream(data)) { + fis.read(fileBytes); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.getName() + "] Failed to load extra data from " + data.getAbsolutePath(), e); + } + String jsonContent = new String(fileBytes, StandardCharsets.UTF_8); + return GsonHelper.get().fromJson(jsonContent, WorldExtraData.class); + } else { + return WorldExtraData.empty(); + } + } + } + + @Override + public void saveExtraData(CustomCropsWorld world) { + if (VersionHelper.isVersionNewerThan1_18()) { + world.world().getPersistentDataContainer().set(WORLD_DATA, PersistentDataType.STRING, + GsonHelper.get().toJson(world.extraData())); + } else { + File data = new File(getWorldFolder(world.world()), DATA_FILE); + try (FileWriter file = new FileWriter(data)) { + GsonHelper.get().toJson(world.extraData(), file); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.worldName() + "] Failed to save extra data to " + data.getAbsolutePath(), e); + } + } + } + + @Nullable + @Override + public CustomCropsRegion loadRegion(CustomCropsWorld world, RegionPos pos, boolean createIfNotExist) { + File data = getRegionDataFile(world.world(), pos); + // if the data file not exists + if (!data.exists()) { + return createIfNotExist ? world.createRegion(pos) : null; + } else { + // load region from local files + try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(data))) { + DataInputStream dataStream = new DataInputStream(bis); + CustomCropsRegion region = deserializeRegion(world, dataStream, pos); + dataStream.close(); + return region; + } catch (Exception e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.worldName() + "] Failed to load CustomCrops region data at " + pos + ". Deleting the corrupted region.", e); + boolean success = data.delete(); + if (success) { + return createIfNotExist ? world.createRegion(pos) : null; + } else { + throw new RuntimeException("[" + world.worldName() + "] Failed to delete corrupted CustomCrops region data at " + pos); + } + } + } + } + + @Nullable + @Override + public CustomCropsChunk loadChunk(CustomCropsWorld world, ChunkPos pos, boolean createIfNotExist) { + CustomCropsRegion region = world.getOrCreateRegion(pos.toRegionPos()); + // In order to reduce frequent disk reads to determine whether a region exists, we read the region into the cache + if (!region.isLoaded()) { + region.load(); + } + byte[] bytes = region.getCachedChunkBytes(pos); + if (bytes == null) { + return createIfNotExist ? world.createChunk(pos) : null; + } else { + try { + long time1 = System.currentTimeMillis(); + DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(bytes)); + CustomCropsChunk chunk = deserializeChunk(world, dataStream); + dataStream.close(); + long time2 = System.currentTimeMillis(); + BukkitCustomCropsPlugin.getInstance().debug("[" + world.worldName() + "] Took " + (time2-time1) + "ms to load chunk " + pos + " from cached region"); + return chunk; + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.worldName() + "] Failed to load CustomCrops data at " + pos, e); + region.removeCachedChunk(pos); + return createIfNotExist ? world.createChunk(pos) : null; + } + } + } + + @Override + public void saveRegion(CustomCropsWorld world, CustomCropsRegion region) { + File file = getRegionDataFile(world.world(), region.regionPos()); + long time1 = System.currentTimeMillis(); + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { + bos.write(serializeRegion(region)); + long time2 = System.currentTimeMillis(); + BukkitCustomCropsPlugin.getInstance().debug("[" + world.worldName() + "] Took " + (time2-time1) + "ms to save region " + region.regionPos()); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.worldName() + "] Failed to save CustomCrops region data." + region.regionPos(), e); + } + } + + @Override + public void saveChunk(CustomCropsWorld world, CustomCropsChunk chunk) { + RegionPos pos = chunk.chunkPos().toRegionPos(); + Optional region = world.getLoadedRegion(pos); + if (region.isEmpty()) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("[" + world.worldName() + "] Region " + pos + " unloaded before chunk " + chunk.chunkPos() + " saving."); + } else { + CustomCropsRegion cropsRegion = region.get(); + SerializableChunk serializableChunk = toSerializableChunk(chunk); + if (serializableChunk.canPrune()) { + cropsRegion.removeCachedChunk(chunk.chunkPos()); + } else { + cropsRegion.setCachedChunk(chunk.chunkPos(), serializeChunk(serializableChunk)); + } + } + } + + @Override + public String getName(World world) { + return world.getName(); + } + + @Override + public long getWorldFullTime(World world) { + return world.getFullTime(); + } + + @Override + public int priority() { + return BUKKIT_WORLD_PRIORITY; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private CustomCropsRegion deserializeRegion(CustomCropsWorld world, DataInputStream dataStream, RegionPos pos) throws IOException { + int regionVersion = dataStream.readByte(); + int regionX = dataStream.readInt(); + int regionZ = dataStream.readInt(); + RegionPos regionPos = RegionPos.of(regionX, regionZ); + ConcurrentHashMap map = new ConcurrentHashMap<>(); + int chunkAmount = dataStream.readInt(); + for (int i = 0; i < chunkAmount; i++) { + int chunkX = dataStream.readInt(); + int chunkZ = dataStream.readInt(); + ChunkPos chunkPos = ChunkPos.of(chunkX, chunkZ); + byte[] chunkData = new byte[dataStream.readInt()]; + dataStream.read(chunkData); + map.put(chunkPos, chunkData); + } + return world.restoreRegion(pos, map); + } + + private byte[] serializeRegion(CustomCropsRegion region) { + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); + DataOutputStream outStream = new DataOutputStream(outByteStream); + try { + outStream.writeByte(REGION_VERSION); + outStream.writeInt(region.regionPos().x()); + outStream.writeInt(region.regionPos().z()); + Map map = region.dataToSave(); + outStream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + outStream.writeInt(entry.getKey().x()); + outStream.writeInt(entry.getKey().z()); + byte[] dataArray = entry.getValue(); + outStream.writeInt(dataArray.length); + outStream.write(dataArray); + } + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to serialize CustomCrops region data." + region.regionPos(), e); + } + return outByteStream.toByteArray(); + } + + private CustomCropsChunk deserializeChunk(CustomCropsWorld world, DataInputStream dataStream) throws IOException { + int chunkVersion = dataStream.readByte(); + byte[] blockData = readCompressedBytes(dataStream); + return deserializeChunk(world, blockData, chunkVersion); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private byte[] readCompressedBytes(DataInputStream dataStream) throws IOException { + int compressedLength = dataStream.readInt(); + int decompressedLength = dataStream.readInt(); + byte[] compressedData = new byte[compressedLength]; + byte[] decompressedData = new byte[decompressedLength]; + + dataStream.read(compressedData); + zstdDecompress(decompressedData, compressedData); + return decompressedData; + } + + private File getWorldFolder(World world) { + return worldFolderProvider.apply(world); + } + + private File getRegionDataFile(World world, RegionPos regionPos) { + return regionFileProvider.apply(world, regionPos); + } + + private String getRegionDataFile(RegionPos regionPos) { + return "r." + regionPos.x() + "." + regionPos.z() + ".mcc"; + } + + private byte[] serializeChunk(SerializableChunk serializableChunk) { + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); + DataOutputStream outStream = new DataOutputStream(outByteStream); + try { + outStream.writeByte(CHUNK_VERSION); + byte[] serializedSections = toBytes(serializableChunk); + byte[] compressed = zstdCompress(serializedSections); + outStream.writeInt(compressed.length); + outStream.writeInt(serializedSections.length); + outStream.write(compressed); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to serialize chunk " + ChunkPos.of(serializableChunk.x(), serializableChunk.z())); + } + return outByteStream.toByteArray(); + } + + private byte[] toBytes(SerializableChunk chunk) throws IOException { + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(16384); + DataOutputStream outStream = new DataOutputStream(outByteStream); + outStream.writeInt(chunk.x()); + outStream.writeInt(chunk.z()); + outStream.writeInt(chunk.loadedSeconds()); + outStream.writeLong(chunk.lastLoadedTime()); + // write queue + int[] queue = chunk.queuedTasks(); + outStream.writeInt(queue.length / 2); + for (int i : queue) { + outStream.writeInt(i); + } + // write ticked blocks + int[] tickedSet = chunk.ticked(); + outStream.writeInt(tickedSet.length); + for (int i : tickedSet) { + outStream.writeInt(i); + } + // write block data + List sectionsToSave = chunk.sections(); + outStream.writeInt(sectionsToSave.size()); + for (SerializableSection section : sectionsToSave) { + outStream.writeInt(section.sectionID()); + byte[] blockData = toBytes(section.blocks()); + outStream.writeInt(blockData.length); + outStream.write(blockData); + } + return outByteStream.toByteArray(); + } + + private byte[] toBytes(Collection blocks) throws IOException { + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(16384); + DataOutputStream outStream = new DataOutputStream(outByteStream); + outStream.writeInt(blocks.size()); + for (CompoundTag block : blocks) { + byte[] blockData = toBytes(block); + outStream.writeInt(blockData.length); + outStream.write(blockData); + } + return outByteStream.toByteArray(); + } + + private byte[] toBytes(CompoundTag tag) throws IOException { + if (tag == null || tag.getValue().isEmpty()) + return new byte[0]; + ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); + NBTOutputStream outStream = new NBTOutputStream( + outByteStream, + NBTInputStream.NO_COMPRESSION, + ByteOrder.BIG_ENDIAN + ); + outStream.writeTag(tag); + return outByteStream.toByteArray(); + } + + @SuppressWarnings("all") + private CustomCropsChunk deserializeChunk(CustomCropsWorld world, byte[] bytes, int chunkVersion) throws IOException { + Function keyFunction = chunkVersion < 2 ? + (s) -> { + return Key.key("customcrops", StringUtils.toLowerCase(s)); + } : s -> { + return Key.key(s); + }; + DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(bytes)); + // read coordinate + int x = chunkData.readInt(); + int z = chunkData.readInt(); + ChunkPos coordinate = new ChunkPos(x, z); + // read loading info + int loadedSeconds = chunkData.readInt(); + long lastLoadedTime = chunkData.readLong(); + // read task queue + int tasksSize = chunkData.readInt(); + PriorityQueue queue = new PriorityQueue<>(Math.max(11, tasksSize)); + for (int i = 0; i < tasksSize; i++) { + int time = chunkData.readInt(); + BlockPos pos = new BlockPos(chunkData.readInt()); + queue.add(new DelayedTickTask(time, pos)); + } + // read ticked blocks + int tickedSize = chunkData.readInt(); + HashSet tickedSet = new HashSet<>(Math.max(11, tickedSize)); + for (int i = 0; i < tickedSize; i++) { + tickedSet.add(new BlockPos(chunkData.readInt())); + } + // read block data + ConcurrentHashMap sectionMap = new ConcurrentHashMap<>(); + int sections = chunkData.readInt(); + // read sections + for (int i = 0; i < sections; i++) { + ConcurrentHashMap blockMap = new ConcurrentHashMap<>(); + int sectionID = chunkData.readInt(); + byte[] sectionBytes = new byte[chunkData.readInt()]; + chunkData.read(sectionBytes); + DataInputStream sectionData = new DataInputStream(new ByteArrayInputStream(sectionBytes)); + int blockAmount = sectionData.readInt(); + // read blocks + for (int j = 0; j < blockAmount; j++){ + byte[] blockData = new byte[sectionData.readInt()]; + sectionData.read(blockData); + CompoundMap block = readCompound(blockData).getValue(); + Key key = keyFunction.apply((String) block.get("type").getValue()); + CompoundMap data = (CompoundMap) block.get("data").getValue(); + CustomCropsBlock customBlock = Registries.BLOCK.get(key); + if (customBlock == null) { + BukkitCustomCropsPlugin.getInstance().getInstance().getPluginLogger().warn("[" + world.worldName() + "] Unrecognized custom block " + key + " has been removed from chunk " + ChunkPos.of(x, z)); + continue; + } + for (int pos : (int[]) block.get("pos").getValue()) { + BlockPos blockPos = new BlockPos(pos); + blockMap.put(blockPos, CustomCropsBlockState.create(customBlock, data)); + } + } + sectionMap.put(sectionID, CustomCropsSection.restore(sectionID, blockMap)); + } + return world.restoreChunk(coordinate, loadedSeconds, lastLoadedTime, sectionMap, queue, tickedSet); + } + + private CompoundTag readCompound(byte[] bytes) throws IOException { + if (bytes.length == 0) + return null; + NBTInputStream nbtInputStream = new NBTInputStream( + new ByteArrayInputStream(bytes), + NBTInputStream.NO_COMPRESSION, + ByteOrder.BIG_ENDIAN + ); + return (CompoundTag) nbtInputStream.readTag(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/BukkitItemFactory.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemFactory.java similarity index 87% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/BukkitItemFactory.java rename to plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemFactory.java index efa092b..7b8ce39 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/BukkitItemFactory.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemFactory.java @@ -1,9 +1,9 @@ -package net.momirealms.customcrops.mechanic.item.factory; +package net.momirealms.customcrops.bukkit.item; import com.saicone.rtag.RtagItem; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.mechanic.item.factory.impl.ComponentItemFactory; -import net.momirealms.customcrops.mechanic.item.factory.impl.UniversalItemFactory; +import net.momirealms.customcrops.common.item.Item; +import net.momirealms.customcrops.common.item.ItemFactory; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import org.bukkit.inventory.ItemStack; import java.util.Objects; @@ -83,4 +83,9 @@ public abstract class BukkitItemFactory extends ItemFactory itemProviders = new HashMap<>(); + private ItemProvider[] itemDetectArray = new ItemProvider[0]; + private final BukkitItemFactory factory; + + public BukkitItemManager(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + try { + this.hookDefaultPlugins(); + } catch (ReflectiveOperationException e) { + plugin.getPluginLogger().warn("Failed to load CustomItemProvider", e); + } + if (this.provider == null) { + plugin.getPluginLogger().warn("ItemsAdder/Oraxen are not installed, which can cause problems unless you use the CustomCrops API."); + } + this.factory = BukkitItemFactory.create(plugin); + } + + @Override + public void setCustomEventListener(@NotNull AbstractCustomEventListener listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + if (this.eventListener != null) { + HandlerList.unregisterAll(this.eventListener); + } + this.eventListener = listener; + Bukkit.getPluginManager().registerEvents(this.eventListener, plugin.getBoostrap()); + plugin.debug("Custom event listener set to " + listener.getClass().getName()); + } + + @Override + public void setCustomItemProvider(@NotNull CustomItemProvider provider) { + Objects.requireNonNull(provider, "provider cannot be null"); + this.provider = provider; + plugin.debug("Custom item provider set to " + provider.getClass().getName()); + } + + public boolean registerItemProvider(ItemProvider item) { + if (itemProviders.containsKey(item.identifier())) return false; + itemProviders.put(item.identifier(), item); + this.resetItemDetectionOrder(); + return true; + } + + public boolean unregisterItemProvider(String id) { + boolean success = itemProviders.remove(id) != null; + if (success) + this.resetItemDetectionOrder(); + return success; + } + + private void resetItemDetectionOrder() { + ArrayList list = new ArrayList<>(); + for (String plugin : ConfigManager.itemDetectOrder()) { + ItemProvider provider = itemProviders.get(plugin); + if (provider != null) + list.add(provider); + } + this.itemDetectArray = list.toArray(new ItemProvider[0]); + } + + private void hookDefaultPlugins() throws ReflectiveOperationException { + if (PluginUtils.isEnabled("Oraxen")) { + String rVersion; + if (PluginUtils.getPluginVersion("Oraxen").startsWith("2")) { + rVersion = "r2"; + } else { + rVersion = "r1"; + } + Class oraxenProviderClass = Class.forName("net.momirealms.customcrops.bukkit.integration.custom.oraxen_" + rVersion + ".OraxenProvider"); + Constructor oraxenProviderConstructor = oraxenProviderClass.getDeclaredConstructor(); + oraxenProviderConstructor.setAccessible(true); + this.provider = (CustomItemProvider) oraxenProviderConstructor.newInstance(); + + Class oraxenListenerClass = Class.forName("net.momirealms.customcrops.bukkit.integration.custom.oraxen_" + rVersion + ".OraxenListener"); + Constructor oraxenListenerConstructor = oraxenListenerClass.getDeclaredConstructor(AbstractItemManager.class); + oraxenListenerConstructor.setAccessible(true); + this.setCustomEventListener((AbstractCustomEventListener) oraxenListenerConstructor.newInstance(this)); + } else if (PluginUtils.isEnabled("ItemsAdder")) { + String rVersion = "r1"; + Class itemsAdderProviderClass = Class.forName("net.momirealms.customcrops.bukkit.integration.custom.itemsadder_" + rVersion + ".ItemsAdderProvider"); + Constructor itemsAdderProviderConstructor = itemsAdderProviderClass.getDeclaredConstructor(); + itemsAdderProviderConstructor.setAccessible(true); + this.provider = (CustomItemProvider) itemsAdderProviderConstructor.newInstance(); + + Class itemsAdderListenerClass = Class.forName("net.momirealms.customcrops.bukkit.integration.custom.itemsadder_" + rVersion + ".ItemsAdderListener"); + Constructor itemsAdderListenerConstructor = itemsAdderListenerClass.getDeclaredConstructor(AbstractItemManager.class); + itemsAdderListenerConstructor.setAccessible(true); + this.setCustomEventListener((AbstractCustomEventListener) itemsAdderListenerConstructor.newInstance(this)); + } + } + + @Override + public void place(@NotNull Location location, @NotNull ExistenceForm form, @NotNull String id, FurnitureRotation rotation) { + switch (form) { + case BLOCK -> placeBlock(location, id); + case FURNITURE -> placeFurniture(location, id, rotation); + case ANY -> throw new IllegalArgumentException("Invalid existence form: " + form); + } + } + + @Override + public FurnitureRotation remove(@NotNull Location location, @NotNull ExistenceForm form) { + switch (form) { + case BLOCK -> { + this.removeBlock(location); + return FurnitureRotation.NONE; + } + case FURNITURE -> { + return this.removeFurniture(location); + } + case ANY -> { + this.removeBlock(location); + return this.removeFurniture(location); + } + } + return FurnitureRotation.NONE; + } + + @Override + public void placeBlock(@NotNull Location location, @NotNull String id) { + if (StringUtils.isCapitalLetter(id)) { + location.getWorld().getBlockAt(location).setType(Material.valueOf(id), false); + } else { + this.provider.placeCustomBlock(location, id); + } + } + + @Override + public void placeFurniture(@NotNull Location location, @NotNull String id, FurnitureRotation rotation) { + Entity entity = this.provider.placeFurniture(location, id); + if (entity != null) { + if (entity instanceof ItemFrame itemFrame) { + itemFrame.setRotation(rotation.getBukkitRotation()); + } else if (entity instanceof LivingEntity livingEntity) { + livingEntity.setRotation(rotation.getYaw(), 0); + } + } + } + + @Override + public void removeBlock(@NotNull Location location) { + if (!this.provider.removeCustomBlock(location)) { + location.getBlock().setType(Material.AIR, false); + } + } + + @Override + public FurnitureRotation removeFurniture(@NotNull Location location) { + Collection entities = location.getWorld().getNearbyEntities(LocationUtils.toSurfaceCenterLocation(location), 0.5,0.25,0.5); + FurnitureRotation rotation = null; + for (Entity entity : entities) { + if (this.provider.removeFurniture(entity) && rotation == null) { + if (entity instanceof ItemFrame itemFrame) { + rotation = FurnitureRotation.getByRotation(itemFrame.getRotation()); + } else { + rotation = FurnitureRotation.getByYaw(entity.getYaw()); + } + } + } + return rotation; + } + + @NotNull + @Override + public String blockID(@NotNull Block block) { + String id = this.provider.blockID(block); + if (id == null) { + id = block.getType().toString(); + } + return id; + } + + @Nullable + @Override + public String furnitureID(@NotNull Entity entity) { + return this.provider.furnitureID(entity); + } + + @Override + @NotNull + public String entityID(@NotNull Entity entity) { + String id = furnitureID(entity); + if (id == null) { + id = entity.getType().toString(); + } + return id; + } + + @Override + @Nullable + public String furnitureID(Location location) { + Collection entities = location.getWorld().getNearbyEntities(LocationUtils.toSurfaceCenterLocation(location), 0.5,0.25,0.5); + for (Entity entity : entities) { + if (provider.isFurniture(entity)) { + return provider.furnitureID(entity); + } + } + return null; + } + + @NotNull + @Override + public String anyID(Location location) { + Block block = location.getBlock(); + if (block.getType() == Material.AIR) { + String id = furnitureID(location); + if (id == null) { + return "AIR"; + } + return id; + } else { + return blockID(location); + } + } + + @Override + public @Nullable String id(Location location, ExistenceForm form) { + return switch (form) { + case BLOCK -> blockID(location); + case FURNITURE -> furnitureID(location); + case ANY -> anyID(location); + }; + } + + @NotNull + @Override + public String id(@Nullable ItemStack itemStack) { + if (itemStack == null || itemStack.getType() == Material.AIR) return "AIR"; + String id = provider.itemID(itemStack); + if (id != null) return id; + for (ItemProvider p : itemDetectArray) { + id = p.itemID(itemStack); + if (id != null) return p.identifier() + ":" + id; + } + return itemStack.getType().name(); + } + + @Nullable + @Override + public ItemStack build(Player player, @NotNull String id) { + ItemStack itemStack = provider.itemStack(player, id); + if (itemStack != null) { + return itemStack; + } + if (!id.contains(":")) { + try { + return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH))); + } catch (IllegalArgumentException e) { + plugin.getPluginLogger().severe("Item " + id + " not exists", e); + return new ItemStack(Material.PAPER); + } + } else { + String[] split = id.split(":", 2); + ItemProvider provider = requireNonNull(itemProviders.get(split[0]), "Item provider: " + split[0] + " not found"); + return requireNonNull(provider.buildItem(player, split[1]), "Item: " + split[0] + " not found"); + } + } + + @Override + public Item wrap(ItemStack itemStack) { + return factory.wrap(itemStack); + } + + @Override + public void decreaseDamage(Player player, ItemStack itemStack, int amount) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + Item wrapped = factory.wrap(itemStack); + if (wrapped.unbreakable()) return; + wrapped.damage(Math.max(0, wrapped.damage().orElse(0) - amount)); + wrapped.load(); + } + + @Override + public void increaseDamage(Player player, ItemStack itemStack, int amount) { + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) + return; + Item wrapped = factory.wrap(itemStack); + if (wrapped.unbreakable()) + return; + ItemMeta previousMeta = itemStack.getItemMeta().clone(); + PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(player, itemStack, amount); + if (EventUtils.fireAndCheckCancel(itemDamageEvent)) { + plugin.debug("Another plugin modified the item from `PlayerItemDamageEvent` called by CustomCrops"); + return; + } + if (!itemStack.getItemMeta().equals(previousMeta)) { + return; + } + int damage = wrapped.damage().orElse(0); + if (damage + amount >= wrapped.maxDamage().orElse((int) itemStack.getType().getMaxDurability())) { + plugin.getSenderFactory().getAudience(player).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1)); + itemStack.setAmount(0); + return; + } + wrapped.damage(damage + amount); + wrapped.load(); + } + + @Override + public void handlePlayerInteractAir(Player player, EquipmentSlot hand, ItemStack itemInHand) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + String itemID = id(itemInHand); + CustomCropsItem customCropsItem = Registries.ITEMS.get(itemID); + if (customCropsItem != null) { + customCropsItem.interactAir(new WrappedInteractAirEvent( + optionalWorld.get(), + player, + hand, + itemInHand, + itemID + )); + } + } + + @Override + public void handlePlayerInteractBlock(Player player, Block block, String blockID, BlockFace blockFace, EquipmentSlot hand, ItemStack itemInHand, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + String itemID = id(itemInHand); + CustomCropsWorld world = optionalWorld.get(); + WrappedInteractEvent wrapped = new WrappedInteractEvent(ExistenceForm.BLOCK, player, world, block.getLocation(), blockID, itemInHand, itemID, hand, blockFace, event); + + handleInteractEvent(blockID, itemID, wrapped); + } + + @Override + public void handlePlayerInteractFurniture(Player player, Location location, String furnitureID, EquipmentSlot hand, ItemStack itemInHand, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + String itemID = id(itemInHand); + CustomCropsWorld world = optionalWorld.get(); + WrappedInteractEvent wrapped = new WrappedInteractEvent(ExistenceForm.FURNITURE, player, world, location, furnitureID, itemInHand, itemID, hand, null, event); + + handleInteractEvent(furnitureID, itemID, wrapped); + } + + private void handleInteractEvent(String blockID, String itemID, WrappedInteractEvent wrapped) { + CustomCropsItem customCropsItem = Registries.ITEMS.get(itemID); + if (customCropsItem != null) { + InteractionResult result = customCropsItem.interactAt(wrapped); + if (result != InteractionResult.PASS) + return; + } + + if (wrapped.isCancelled()) return; + + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(blockID); + if (customCropsBlock != null) { + customCropsBlock.onInteract(wrapped); + } + } + + @Override + public void handlePlayerBreak(Player player, Location location, ItemStack itemInHand, String brokenID, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + String itemID = id(itemInHand); + CustomCropsWorld world = optionalWorld.get(); + WrappedBreakEvent wrapped = new WrappedBreakEvent(player, null, world, location, brokenID, itemInHand, itemID, BreakReason.BREAK, event); + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(brokenID); + if (customCropsBlock != null) { + customCropsBlock.onBreak(wrapped); + } + } + + @Override + public void handlePlayerPlace(Player player, Location location, String placedID, EquipmentSlot hand, ItemStack itemInHand, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + String itemID = id(itemInHand); + CustomCropsWorld world = optionalWorld.get(); + WrappedPlaceEvent wrapped = new WrappedPlaceEvent(player, world, location, placedID, hand, itemInHand, itemID, event); + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(placedID); + if (customCropsBlock != null) { + customCropsBlock.onPlace(wrapped); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/ComponentItemFactory.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/ComponentItemFactory.java similarity index 92% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/ComponentItemFactory.java rename to plugin/src/main/java/net/momirealms/customcrops/bukkit/item/ComponentItemFactory.java index 5266f84..fb6ad11 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/ComponentItemFactory.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/ComponentItemFactory.java @@ -1,10 +1,9 @@ -package net.momirealms.customcrops.mechanic.item.factory.impl; +package net.momirealms.customcrops.bukkit.item; import com.saicone.rtag.RtagItem; import com.saicone.rtag.data.ComponentType; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.mechanic.item.factory.BukkitItemFactory; -import net.momirealms.customcrops.mechanic.item.factory.ComponentKeys; +import net.momirealms.customcrops.common.item.ComponentKeys; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.util.List; import java.util.Optional; diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/UniversalItemFactory.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/UniversalItemFactory.java similarity index 89% rename from plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/UniversalItemFactory.java rename to plugin/src/main/java/net/momirealms/customcrops/bukkit/item/UniversalItemFactory.java index 5fb923c..5cfc346 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/impl/UniversalItemFactory.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/UniversalItemFactory.java @@ -1,10 +1,7 @@ -package net.momirealms.customcrops.mechanic.item.factory.impl; - - +package net.momirealms.customcrops.bukkit.item; import com.saicone.rtag.RtagItem; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.mechanic.item.factory.BukkitItemFactory; +import net.momirealms.customcrops.common.plugin.CustomCropsPlugin; import java.util.List; import java.util.Optional; diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/misc/HologramManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/misc/HologramManager.java new file mode 100644 index 0000000..69db254 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/misc/HologramManager.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.bukkit.misc; + +import net.kyori.adventure.text.Component; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.common.helper.AdventureHelper; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.plugin.feature.Reloadable; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; +import net.momirealms.customcrops.common.util.Pair; +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.FakeTextDisplay; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.ArrayList; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class HologramManager implements Listener, Reloadable { + + private final ConcurrentHashMap hologramMap = new ConcurrentHashMap<>(); + private final BukkitCustomCropsPlugin plugin; + private SchedulerTask cacheCheckTask; + private static HologramManager manager; + + public static HologramManager getInstance() { + return manager; + } + + public HologramManager(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + manager = this; + } + + @Override + public void load() { + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + this.cacheCheckTask = plugin.getScheduler().asyncRepeating(() -> { + ArrayList removed = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (Map.Entry entry : hologramMap.entrySet()) { + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null || !player.isOnline()) { + removed.add(entry.getKey()); + } else { + entry.getValue().removeOutDated(current, player); + } + } + for (UUID uuid : removed) { + hologramMap.remove(uuid); + } + }, 100, 100, TimeUnit.MILLISECONDS); + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + for (Map.Entry entry : hologramMap.entrySet()) { + Player player = Bukkit.getPlayer(entry.getKey()); + if (player != null && player.isOnline()) { + entry.getValue().removeAll(player); + } + } + if (cacheCheckTask != null) cacheCheckTask.cancel(); + this.hologramMap.clear(); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.hologramMap.remove(event.getPlayer().getUniqueId()); + } + + public void showHologram(Player player, Location location, Component component, int millis) { + HologramCache hologramCache = hologramMap.get(player.getUniqueId()); + if (hologramCache != null) { + hologramCache.showHologram(player, location, component, millis); + } else { + hologramCache = new HologramCache(); + hologramCache.showHologram(player, location, component, millis); + hologramMap.put(player.getUniqueId(), hologramCache); + } + } + + public static class HologramCache { + + private final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); + + public void removeOutDated(long current, Player player) { + ArrayList removed = new ArrayList<>(); + for (Map.Entry> entry : cache.entrySet()) { + if (entry.getValue().right() < current) { + entry.getValue().left().destroy(player); + removed.add(entry.getKey()); + } + } + for (Location location : removed) { + cache.remove(location); + } + } + + public void showHologram(Player player, Location location, Component component, int millis) { + Pair pair = cache.get(location); + if (pair != null) { + pair.left().destroy(player); + pair.right(System.currentTimeMillis() + millis); + if (VersionHelper.isVersionNewerThan1_19_4()) { + FakeTextDisplay fakeEntity = SparrowHeart.getInstance().createFakeTextDisplay(location.clone().add(0,1.25,0)); + fakeEntity.name(AdventureHelper.componentToJson(component)); + fakeEntity.rgba(0, 0, 0, 0); + fakeEntity.spawn(player); + pair.left(fakeEntity); + } else { + FakeArmorStand fakeEntity = SparrowHeart.getInstance().createFakeArmorStand(location); + fakeEntity.name(AdventureHelper.componentToJson(component)); + fakeEntity.small(true); + fakeEntity.invisible(true); + fakeEntity.spawn(player); + pair.left(fakeEntity); + } + } else { + long removeTime = System.currentTimeMillis() + millis; + if (VersionHelper.isVersionNewerThan1_19_4()) { + FakeTextDisplay fakeEntity = SparrowHeart.getInstance().createFakeTextDisplay(location.clone().add(0,1.25,0)); + fakeEntity.name(AdventureHelper.componentToJson(component)); + fakeEntity.rgba(0, 0, 0, 0); + fakeEntity.spawn(player); + this.cache.put(location, Pair.of(fakeEntity, removeTime)); + } else { + FakeArmorStand fakeEntity = SparrowHeart.getInstance().createFakeArmorStand(location); + fakeEntity.name(AdventureHelper.componentToJson(component)); + fakeEntity.small(true); + fakeEntity.invisible(true); + fakeEntity.spawn(player); + this.cache.put(location, Pair.of(fakeEntity, removeTime)); + } + } + } + + public void removeAll(Player player) { + for (Map.Entry> entry : this.cache.entrySet()) { + entry.getValue().left().destroy(player); + } + cache.clear(); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/BlockRequirementManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/BlockRequirementManager.java new file mode 100644 index 0000000..23e247d --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/BlockRequirementManager.java @@ -0,0 +1,52 @@ +package net.momirealms.customcrops.bukkit.requirement; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.core.block.CropBlock; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import net.momirealms.customcrops.api.requirement.AbstractRequirementManager; + +public class BlockRequirementManager extends AbstractRequirementManager { + + public BlockRequirementManager(BukkitCustomCropsPlugin plugin) { + super(plugin, CustomCropsBlockState.class); + } + + @Override + protected void registerBuiltInRequirements() { + super.registerBuiltInRequirements(); + this.registerPointCondition(); + } + + @Override + public void load() { + loadExpansions(CustomCropsBlockState.class); + } + + private void registerPointCondition() { + registerRequirement((args, actions, runActions) -> { + int value = (int) args; + return (context) -> { + CustomCropsBlockState state = context.holder(); + if (state.type() instanceof CropBlock cropBlock) { + int point = cropBlock.point(state); + if (point > value) return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "point_more_than", "point-more-than"); + registerRequirement((args, actions, runActions) -> { + int value = (int) args; + return (context) -> { + CustomCropsBlockState state = context.holder(); + if (state.type() instanceof CropBlock cropBlock) { + int point = cropBlock.point(state); + if (point < value) return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "point_less_than", "point-less-than"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/PlayerRequirementManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/PlayerRequirementManager.java new file mode 100644 index 0000000..2049255 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/requirement/PlayerRequirementManager.java @@ -0,0 +1,247 @@ +package net.momirealms.customcrops.bukkit.requirement; + +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.integration.LevelerProvider; +import net.momirealms.customcrops.api.misc.value.MathValue; +import net.momirealms.customcrops.api.requirement.AbstractRequirementManager; +import net.momirealms.customcrops.api.requirement.Requirement; +import net.momirealms.customcrops.bukkit.integration.VaultHook; +import net.momirealms.customcrops.common.util.ListUtils; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.List; +import java.util.Locale; + +public class PlayerRequirementManager extends AbstractRequirementManager { + + public PlayerRequirementManager(BukkitCustomCropsPlugin plugin) { + super(plugin, Player.class); + } + + @Override + protected void registerBuiltInRequirements() { + super.registerBuiltInRequirements(); + this.registerItemInHandRequirement(); + this.registerPermissionRequirement(); + this.registerPluginLevelRequirement(); + this.registerCoolDownRequirement(); + this.registerLevelRequirement(); + this.registerMoneyRequirement(); + this.registerPotionEffectRequirement(); + this.registerSneakRequirement(); + this.registerGameModeRequirement(); + } + + @Override + public void load() { + loadExpansions(Player.class); + } + + private void registerItemInHandRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + boolean mainOrOff = section.getString("hand","main").equalsIgnoreCase("main"); + int amount = section.getInt("amount", 1); + List items = ListUtils.toList(section.get("item")); + return context -> { + if (context.holder() == null) return true; + ItemStack itemStack = mainOrOff ? + context.holder().getInventory().getItemInMainHand() + : context.holder().getInventory().getItemInOffHand(); + String id = plugin.getItemManager().id(itemStack); + if (items.contains(id) && itemStack.getAmount() >= amount) return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at item-in-hand requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "item-in-hand"); + } + + private void registerPluginLevelRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + String pluginName = section.getString("plugin"); + int level = section.getInt("level"); + String target = section.getString("target"); + return context -> { + if (context.holder() == null) return true; + LevelerProvider levelerProvider = plugin.getIntegrationManager().getLevelerProvider(pluginName); + if (levelerProvider == null) { + plugin.getPluginLogger().warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."); + return true; + } + if (levelerProvider.getLevel(context.holder(), target) >= level) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plugin-level requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "plugin-level"); + } + + private void registerLevelRequirement() { + registerRequirement((args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return true; + int current = context.holder().getLevel(); + if (current >= value.evaluate(context, true)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "level"); + } + + private void registerMoneyRequirement() { + registerRequirement((args, actions, runActions) -> { + MathValue value = MathValue.auto(args); + return context -> { + if (context.holder() == null) return true; + double current = VaultHook.getBalance(context.holder()); + if (current >= value.evaluate(context, true)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "money"); + } + + private void registerCoolDownRequirement() { + registerRequirement((args, actions, runActions) -> { + if (args instanceof Section section) { + String key = section.getString("key"); + int time = section.getInt("time"); + return context -> { + if (context.holder() == null) return true; + if (!plugin.getCoolDownManager().isCoolDown(context.holder().getUniqueId(), key, time)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + } else { + plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at cooldown requirement which is expected be `Section`"); + return Requirement.empty(); + } + }, "cooldown"); + } + + private void registerPermissionRequirement() { + registerRequirement((args, actions, runActions) -> { + List perms = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return true; + for (String perm : perms) + if (context.holder().hasPermission(perm)) + return true; + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "permission"); + registerRequirement((args, actions, runActions) -> { + List perms = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return true; + for (String perm : perms) + if (context.holder().hasPermission(perm)) { + if (runActions) ActionManager.trigger(context, actions); + return false; + } + return true; + }; + }, "!permission"); + } + + @SuppressWarnings("deprecation") + private void registerPotionEffectRequirement() { + registerRequirement((args, actions, runActions) -> { + String potions = (String) args; + String[] split = potions.split("(<=|>=|<|>|==)", 2); + PotionEffectType type = PotionEffectType.getByName(split[0]); + if (type == null) { + plugin.getPluginLogger().warn("Potion effect doesn't exist: " + split[0]); + return Requirement.empty(); + } + int required = Integer.parseInt(split[1]); + String operator = potions.substring(split[0].length(), potions.length() - split[1].length()); + return context -> { + if (context.holder() == null) return true; + int level = -1; + PotionEffect potionEffect = context.holder().getPotionEffect(type); + if (potionEffect != null) { + level = potionEffect.getAmplifier(); + } + boolean result = false; + switch (operator) { + case ">=" -> { + if (level >= required) result = true; + } + case ">" -> { + if (level > required) result = true; + } + case "==" -> { + if (level == required) result = true; + } + case "!=" -> { + if (level != required) result = true; + } + case "<=" -> { + if (level <= required) result = true; + } + case "<" -> { + if (level < required) result = true; + } + } + if (result) { + return true; + } + if (runActions) ActionManager.trigger(context, actions); + return false; + }; + }, "potion-effect"); + } + + private void registerSneakRequirement() { + registerRequirement((args, actions, advanced) -> { + boolean sneak = (boolean) args; + return context -> { + if (context.holder() == null) return true; + if (sneak) { + if (context.holder().isSneaking()) + return true; + } else { + if (!context.holder().isSneaking()) + return true; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "sneak"); + } + + protected void registerGameModeRequirement() { + registerRequirement((args, actions, advanced) -> { + List modes = ListUtils.toList(args); + return context -> { + if (context.holder() == null) return true; + var name = context.holder().getGameMode().name().toLowerCase(Locale.ENGLISH); + if (modes.contains(name)) { + return true; + } + if (advanced) ActionManager.trigger(context, actions); + return false; + }; + }, "gamemode"); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/BukkitSchedulerAdapter.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/BukkitSchedulerAdapter.java new file mode 100644 index 0000000..b475eca --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/BukkitSchedulerAdapter.java @@ -0,0 +1,53 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.bukkit.scheduler; + +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.bukkit.scheduler.impl.BukkitExecutor; +import net.momirealms.customcrops.bukkit.scheduler.impl.FoliaExecutor; +import net.momirealms.customcrops.common.helper.VersionHelper; +import net.momirealms.customcrops.common.plugin.scheduler.AbstractJavaScheduler; +import net.momirealms.customcrops.common.plugin.scheduler.RegionExecutor; +import org.bukkit.Location; +import org.bukkit.World; + +public class BukkitSchedulerAdapter extends AbstractJavaScheduler { + protected RegionExecutor sync; + + public BukkitSchedulerAdapter(BukkitCustomCropsPlugin plugin) { + super(plugin); + if (VersionHelper.isFolia()) { + this.sync = new FoliaExecutor(plugin.getBoostrap()); + } else { + this.sync = new BukkitExecutor(plugin.getBoostrap()); + } + } + + @Override + public RegionExecutor sync() { + return this.sync; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/DummyTask.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/DummyTask.java new file mode 100644 index 0000000..2c98218 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/DummyTask.java @@ -0,0 +1,15 @@ +package net.momirealms.customcrops.bukkit.scheduler; + +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; + +public class DummyTask implements SchedulerTask { + + @Override + public void cancel() { + } + + @Override + public boolean isCancelled() { + return true; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/BukkitExecutor.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/BukkitExecutor.java new file mode 100644 index 0000000..55fff5e --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/BukkitExecutor.java @@ -0,0 +1,95 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.bukkit.scheduler.impl; + +import net.momirealms.customcrops.bukkit.scheduler.DummyTask; +import net.momirealms.customcrops.common.plugin.scheduler.RegionExecutor; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +public class BukkitExecutor implements RegionExecutor { + + private final Plugin plugin; + + public BukkitExecutor(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void run(Runnable r, Location l) { + if (Bukkit.isPrimaryThread()) { + r.run(); + } else { + Bukkit.getScheduler().runTask(plugin, r); + } + } + + @Override + public void run(Runnable r, World world, int x, int z) { + run(r); + } + + @Override + public SchedulerTask runLater(Runnable r, long delayTicks, Location l) { + if (delayTicks == 0) { + if (Bukkit.isPrimaryThread()) { + r.run(); + return new DummyTask(); + } else { + return new BukkitCancellable(Bukkit.getScheduler().runTask(plugin, r)); + } + } + return new BukkitCancellable(Bukkit.getScheduler().runTaskLater(plugin, r, delayTicks)); + } + + @Override + public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) { + return new BukkitCancellable(Bukkit.getScheduler().runTaskTimer(plugin, r, delayTicks, period)); + } + + public static class BukkitCancellable implements SchedulerTask { + + private final BukkitTask task; + + public BukkitCancellable(BukkitTask task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } + + @Override + public boolean isCancelled() { + return this.task.isCancelled(); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/FoliaExecutor.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/FoliaExecutor.java new file mode 100644 index 0000000..07516d1 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/scheduler/impl/FoliaExecutor.java @@ -0,0 +1,100 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.bukkit.scheduler.impl; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import net.momirealms.customcrops.common.plugin.scheduler.RegionExecutor; +import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; + +import java.util.Optional; + +public class FoliaExecutor implements RegionExecutor { + + private final Plugin plugin; + + public FoliaExecutor(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void run(Runnable r, Location l) { + Optional.ofNullable(l).ifPresentOrElse(loc -> Bukkit.getRegionScheduler().execute(plugin, loc, r), () -> Bukkit.getGlobalRegionScheduler().execute(plugin, r)); + } + + @Override + public void run(Runnable r, World world, int x, int z) { + Bukkit.getRegionScheduler().execute(plugin, world, x, z, r); + } + + @Override + public SchedulerTask runLater(Runnable r, long delayTicks, Location l) { + if (l == null) { + if (delayTicks == 0) { + return new FoliaCancellable(Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> r.run(), delayTicks)); + } else { + return new FoliaCancellable(Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> r.run())); + } + } else { + if (delayTicks == 0) { + return new FoliaCancellable(Bukkit.getRegionScheduler().run(plugin, l, scheduledTask -> r.run())); + } else { + return new FoliaCancellable(Bukkit.getRegionScheduler().runDelayed(plugin, l, scheduledTask -> r.run(), delayTicks)); + } + } + } + + @Override + public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) { + if (l == null) { + return new FoliaCancellable(Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> r.run(), delayTicks, period)); + } else { + return new FoliaCancellable(Bukkit.getRegionScheduler().runAtFixedRate(plugin, l, scheduledTask -> r.run(), delayTicks, period)); + } + } + + public static class FoliaCancellable implements SchedulerTask { + + private final ScheduledTask task; + + public FoliaCancellable(ScheduledTask task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } + + @Override + public boolean isCancelled() { + return this.task.isCancelled(); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/sender/BukkitSenderFactory.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/sender/BukkitSenderFactory.java new file mode 100644 index 0000000..d9ee214 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/sender/BukkitSenderFactory.java @@ -0,0 +1,112 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.momirealms.customcrops.bukkit.sender; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.common.sender.Sender; +import net.momirealms.customcrops.common.sender.SenderFactory; +import net.momirealms.customcrops.common.util.Tristate; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class BukkitSenderFactory extends SenderFactory { + private final BukkitAudiences audiences; + + public BukkitSenderFactory(BukkitCustomCropsPlugin plugin) { + super(plugin); + this.audiences = BukkitAudiences.create(plugin.getBoostrap()); + } + + @Override + protected String getName(CommandSender sender) { + if (sender instanceof Player) { + return sender.getName(); + } + return Sender.CONSOLE_NAME; + } + + @Override + protected UUID getUniqueId(CommandSender sender) { + if (sender instanceof Player) { + return ((Player) sender).getUniqueId(); + } + return Sender.CONSOLE_UUID; + } + + @Override + public Audience getAudience(CommandSender sender) { + return this.audiences.sender(sender); + } + + @Override + protected void sendMessage(CommandSender sender, Component message) { + // we can safely send async for players and the console - otherwise, send it sync + if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) { + getAudience(sender).sendMessage(message); + } else { + getPlugin().getScheduler().executeSync(() -> getAudience(sender).sendMessage(message)); + } + } + + @Override + protected Tristate getPermissionValue(CommandSender sender, String node) { + if (sender.hasPermission(node)) { + return Tristate.TRUE; + } else if (sender.isPermissionSet(node)) { + return Tristate.FALSE; + } else { + return Tristate.UNDEFINED; + } + } + + @Override + protected boolean hasPermission(CommandSender sender, String node) { + return sender.hasPermission(node); + } + + @Override + protected void performCommand(CommandSender sender, String command) { + getPlugin().getBoostrap().getServer().dispatchCommand(sender, command); + } + + @Override + protected boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender; + } + + @Override + public void close() { + super.close(); + this.audiences.close(); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java new file mode 100644 index 0000000..4df5103 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java @@ -0,0 +1,327 @@ +package net.momirealms.customcrops.bukkit.world; + +import dev.dejvokep.boostedyaml.YamlDocument; +import dev.dejvokep.boostedyaml.block.implementation.Section; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.core.ConfigManager; +import net.momirealms.customcrops.api.core.world.*; +import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor; +import net.momirealms.customcrops.api.integration.SeasonProvider; +import net.momirealms.customcrops.bukkit.config.BukkitConfigManager; +import net.momirealms.customcrops.bukkit.integration.adaptor.BukkitWorldAdaptor; +import net.momirealms.customcrops.bukkit.integration.adaptor.asp_r1.SlimeWorldAdaptorR1; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.world.*; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class BukkitWorldManager implements WorldManager, Listener { + + private final BukkitCustomCropsPlugin plugin; + private final Set> adaptors = new TreeSet<>(); + private final ConcurrentHashMap> worlds = new ConcurrentHashMap<>(); + private final HashMap worldSettings = new HashMap<>(); + private WorldSetting defaultWorldSetting; + private MatchRule matchRule; + private HashSet worldList; + private SeasonProvider seasonProvider; + + public BukkitWorldManager(BukkitCustomCropsPlugin plugin) { + this.plugin = plugin; + try { + Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); + adaptors.add(new SlimeWorldAdaptorR1(1)); + } catch (ClassNotFoundException ignored) { + } + if (Bukkit.getPluginManager().isPluginEnabled("SlimeWorldPlugin")) { + adaptors.add(new SlimeWorldAdaptorR1(2)); + } + this.adaptors.add(new BukkitWorldAdaptor()); + this.seasonProvider = new SeasonProvider() { + @NotNull + @Override + public Season getSeason(@NotNull World world) { + return BukkitWorldManager.this.getWorld(world).map(w -> w.extraData().getSeason()).orElse(Season.DISABLE); + } + @Override + public String identifier() { + return "CustomCrops"; + } + }; + } + + public void seasonProvider(SeasonProvider seasonProvider) { + this.seasonProvider = seasonProvider; + } + + @Override + public Season getSeason(World world) { + if (ConfigManager.syncSeasons()) { + World reference = Bukkit.getWorld(ConfigManager.referenceWorld()); + if (reference != null) { + return seasonProvider.getSeason(reference); + } else { + return Season.DISABLE; + } + } else { + return seasonProvider.getSeason(world); + } + } + + @Override + public int getDate(World world) { + if (ConfigManager.syncSeasons()) { + World reference = Bukkit.getWorld(ConfigManager.referenceWorld()); + if (reference != null) { + return getWorld(reference).map(w -> w.extraData().getDate()).orElse(-1); + } else { + return -1; + } + } else { + return getWorld(world).map(w -> w.extraData().getDate()).orElse(-1); + } + } + + @Override + public void load() { + this.loadConfig(); + Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap()); + // load and unload worlds + for (World world : Bukkit.getWorlds()) { + if (isMechanicEnabled(world)) { + loadWorld(world); + } else { + unloadWorld(world); + } + } + } + + private void loadConfig() { + YamlDocument config = BukkitConfigManager.getMainConfig(); + + Section section = config.getSection("worlds"); + if (section == null) { + plugin.getPluginLogger().warn("worlds section should not be null"); + return; + } + + this.matchRule = MatchRule.valueOf(section.getString("mode", "blacklist").toUpperCase(Locale.ENGLISH)); + this.worldList = new HashSet<>(section.getStringList("list")); + + Section settingSection = section.getSection("settings"); + if (settingSection == null) { + plugin.getPluginLogger().warn("worlds.settings section should not be null"); + return; + } + + Section defaultSection = settingSection.getSection("_DEFAULT_"); + if (defaultSection == null) { + plugin.getPluginLogger().warn("worlds.settings._DEFAULT_ section should not be null"); + return; + } + + this.defaultWorldSetting = sectionToWorldSetting(defaultSection); + + Section worldSection = settingSection.getSection("_WORLDS_"); + if (worldSection != null) { + for (Map.Entry entry : worldSection.getStringRouteMappedValues(false).entrySet()) { + if (entry.getValue() instanceof Section inner) { + this.worldSettings.put(entry.getKey(), sectionToWorldSetting(inner)); + } + } + } + } + + @Override + public void unload() { + HandlerList.unregisterAll(this); + this.worldSettings.clear(); + } + + @Override + public void disable() { + this.unload(); + for (World world : Bukkit.getWorlds()) { + unloadWorld(world); + } + } + + @Override + public CustomCropsWorld loadWorld(World world) { + Optional> optionalWorld = getWorld(world); + if (optionalWorld.isPresent()) { + CustomCropsWorld customCropsWorld = optionalWorld.get(); + customCropsWorld.setting(Optional.ofNullable(worldSettings.get(world.getName())).orElse(defaultWorldSetting)); + return customCropsWorld; + } + CustomCropsWorld adaptedWorld = adapt(world); + adaptedWorld.setting(Optional.ofNullable(worldSettings.get(world.getName())).orElse(defaultWorldSetting)); + adaptedWorld.setTicking(true); + this.worlds.put(world.getName(), adaptedWorld); + for (Chunk chunk : world.getLoadedChunks()) { + loadLoadedChunk(adaptedWorld, ChunkPos.fromBukkitChunk(chunk)); + } + return adaptedWorld; + } + + // Before using the method, make sure that the bukkit chunk is loaded + public void loadLoadedChunk(CustomCropsWorld world, ChunkPos pos) { + if (world.isChunkLoaded(pos)) return; + Optional customChunk = world.getChunk(pos); + // don't load bukkit chunk again since it has been loaded + customChunk.ifPresent(customCropsChunk -> customCropsChunk.load(false)); + } + + @Override + public boolean unloadWorld(World world) { + CustomCropsWorld removedWorld = worlds.remove(world.getName()); + if (removedWorld == null) { + return false; + } + removedWorld.setTicking(false); + removedWorld.save(); + return true; + } + + @EventHandler + public void onWorldSave(WorldSaveEvent event) { + final World world = event.getWorld(); + getWorld(world).ifPresent(CustomCropsWorld::save); + } + + @EventHandler (priority = EventPriority.HIGH) + public void onWorldLoad(WorldLoadEvent event) { + World world = event.getWorld(); + if (!isMechanicEnabled(world)) return; + loadWorld(world); + } + + @EventHandler (priority = EventPriority.LOW) + public void onWorldUnload(WorldUnloadEvent event) { + World world = event.getWorld(); + if (!isMechanicEnabled(world)) return; + unloadWorld(world); + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + final Chunk chunk = event.getChunk(); + final World world = event.getWorld(); + this.getWorld(world) + .flatMap(customWorld -> customWorld.getLoadedChunk(ChunkPos.fromBukkitChunk(chunk))) + .ifPresent(customChunk -> customChunk.unload(true)); + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + final Chunk chunk = event.getChunk(); + final World world = event.getWorld(); + this.getWorld(world).ifPresent(customWorld -> loadLoadedChunk(customWorld, ChunkPos.fromBukkitChunk(chunk))); + } + + @EventHandler + public void onEntitiesLoad(EntitiesLoadEvent event) { + + } + + public boolean isMechanicEnabled(World world) { + if (matchRule == MatchRule.WHITELIST) { + return worldList.contains(world.getName()); + } else if (matchRule == MatchRule.BLACKLIST) { + return !worldList.contains(world.getName()); + } else { + for (String regex : worldList) { + if (world.getName().matches(regex)) { + return true; + } + } + return false; + } + } + + @Override + public Optional> getWorld(World world) { + return getWorld(world.getName()); + } + + @Override + public Optional> getWorld(String world) { + return Optional.ofNullable(worlds.get(world)); + } + + @Override + public boolean isWorldLoaded(World world) { + return worlds.containsKey(world.getName()); + } + + @Override + public Set> adaptors() { + return adaptors; + } + + @Override + public CustomCropsWorld adapt(World world) { + return adapt(world.getName()); + } + + @Override + public CustomCropsWorld adapt(String name) { + for (WorldAdaptor adaptor : adaptors) { + Object world = adaptor.getWorld(name); + if (world != null) { + return adaptor.adapt(world); + } + } + throw new RuntimeException("Unable to adapt world " + name); + } + + public enum MatchRule { + + BLACKLIST, + WHITELIST, + REGEX + } + + private static WorldSetting sectionToWorldSetting(Section section) { + return WorldSetting.of( + section.getBoolean("enable", true), + section.getInt("min-tick-unit", 300), + getRandomTickModeByString(section.getString("crop.mode")), + section.getInt("crop.tick-interval", 1), + getRandomTickModeByString(section.getString("pot.mode")), + section.getInt("pot.tick-interval", 2), + getRandomTickModeByString(section.getString("sprinkler.mode")), + section.getInt("sprinkler.tick-interval", 2), + section.getBoolean("offline-tick.enable", false), + section.getInt("offline-tick.max-offline-seconds", 1200), + section.getBoolean("season.enable", false), + section.getBoolean("season.auto-alternation", false), + section.getInt("season.duration", 28), + section.getInt("crop.max-per-chunk", 128), + section.getInt("pot.max-per-chunk", -1), + section.getInt("sprinkler.max-per-chunk", 32), + section.getInt("random-tick-speed", 0) + ); + } + + private static boolean getRandomTickModeByString(String str) { + if (str == null) { + return false; + } + if (str.equalsIgnoreCase("RANDOM_TICK")) { + return true; + } + if (str.equalsIgnoreCase("SCHEDULED_TICK")) { + return false; + } + throw new IllegalArgumentException("Invalid mode found when loading world settings: " + str); + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/IntegrationManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/IntegrationManagerImpl.java deleted file mode 100644 index 73fdd60..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/IntegrationManagerImpl.java +++ /dev/null @@ -1,153 +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.compatibility; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.integration.LevelInterface; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.manager.IntegrationManager; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.compatibility.item.MMOItemsItemImpl; -import net.momirealms.customcrops.compatibility.item.MythicMobsItemImpl; -import net.momirealms.customcrops.compatibility.item.NeigeItemsItemImpl; -import net.momirealms.customcrops.compatibility.item.ZaphkielItemImpl; -import net.momirealms.customcrops.compatibility.level.*; -import net.momirealms.customcrops.compatibility.quest.BattlePassHook; -import net.momirealms.customcrops.compatibility.quest.BetonQuestHook; -import net.momirealms.customcrops.compatibility.quest.ClueScrollsHook; -import net.momirealms.customcrops.compatibility.season.AdvancedSeasonsImpl; -import net.momirealms.customcrops.compatibility.season.InBuiltSeason; -import net.momirealms.customcrops.compatibility.season.RealisticSeasonsImpl; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; - -public class IntegrationManagerImpl implements IntegrationManager { - - private final CustomCropsPlugin plugin; - private final HashMap levelPluginMap; - private SeasonInterface seasonInterface; - - public IntegrationManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.levelPluginMap = new HashMap<>(); - } - - @Override - public void init() { - if (plugin.doesHookedPluginExist("MMOItems")) { - plugin.getItemManager().registerItemLibrary(new MMOItemsItemImpl()); - hookMessage("MMOItems"); - } - if (plugin.doesHookedPluginExist("Zaphkiel")) { - plugin.getItemManager().registerItemLibrary(new ZaphkielItemImpl()); - hookMessage("Zaphkiel"); - } - if (plugin.doesHookedPluginExist("NeigeItems")) { - plugin.getItemManager().registerItemLibrary(new NeigeItemsItemImpl()); - hookMessage("NeigeItems"); - } - if (plugin.doesHookedPluginExist("MythicMobs")) { - plugin.getItemManager().registerItemLibrary(new MythicMobsItemImpl()); - hookMessage("MythicMobs"); - } - if (plugin.doesHookedPluginExist("EcoJobs")) { - registerLevelPlugin("EcoJobs", new EcoJobsImpl()); - hookMessage("EcoJobs"); - } - if (plugin.doesHookedPluginExist("Jobs")) { - registerLevelPlugin("JobsReborn", new JobsRebornImpl()); - hookMessage("JobsReborn"); - } - if (plugin.doesHookedPluginExist("AureliumSkills")) { - registerLevelPlugin("AureliumSkills", new AureliumSkillsImpl()); - hookMessage("AureliumSkills"); - } - if (plugin.doesHookedPluginExist("EcoSkills")) { - registerLevelPlugin("EcoSkills", new EcoSkillsImpl()); - hookMessage("EcoSkills"); - } - if (plugin.doesHookedPluginExist("mcMMO")) { - registerLevelPlugin("mcMMO", new McMMOImpl()); - hookMessage("mcMMO"); - } - if (plugin.doesHookedPluginExist("MMOCore")) { - registerLevelPlugin("MMOCore", new MMOCoreImpl()); - hookMessage("MMOCore"); - } - if (plugin.doesHookedPluginExist("AuraSkills")) { - registerLevelPlugin("AuraSkills", new AuraSkillsImpl()); - hookMessage("AuraSkills"); - } - if (plugin.isHookedPluginEnabled("BattlePass")){ - BattlePassHook battlePassHook = new BattlePassHook(); - battlePassHook.register(); - hookMessage("BattlePass"); - } - if (plugin.isHookedPluginEnabled("ClueScrolls")) { - ClueScrollsHook clueScrollsHook = new ClueScrollsHook(); - clueScrollsHook.register(); - hookMessage("ClueScrolls"); - } - if (plugin.isHookedPluginEnabled("BetonQuest")) { - BetonQuestHook.register(); - hookMessage("BetonQuest"); - } - if (plugin.isHookedPluginEnabled("RealisticSeasons")) { - this.seasonInterface = new RealisticSeasonsImpl(); - hookMessage("RealisticSeasons"); - } else if (plugin.isHookedPluginEnabled("AdvancedSeasons", "1.4", "1.5")) { - this.seasonInterface = new AdvancedSeasonsImpl(); - hookMessage("AdvancedSeasons"); - } else { - this.seasonInterface = new InBuiltSeason(plugin.getWorldManager()); - } - } - - @Override - public void disable() { - - } - - @Override - public boolean registerLevelPlugin(String plugin, LevelInterface level) { - if (levelPluginMap.containsKey(plugin)) return false; - levelPluginMap.put(plugin, level); - return true; - } - - @Override - public boolean unregisterLevelPlugin(String plugin) { - return levelPluginMap.remove(plugin) != null; - } - - private void hookMessage(String plugin) { - LogUtils.info( plugin + " hooked!"); - } - - @Override - @Nullable - public LevelInterface getLevelPlugin(String plugin) { - return levelPluginMap.get(plugin); - } - - @Override - public SeasonInterface getSeasonInterface() { - return seasonInterface; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/VaultHook.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/VaultHook.java deleted file mode 100644 index 2da91f4..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/VaultHook.java +++ /dev/null @@ -1,40 +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.compatibility; - -import net.milkbowl.vault.economy.Economy; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import org.bukkit.plugin.RegisteredServiceProvider; - -public class VaultHook { - - private static Economy economy; - - public static boolean initialize() { - RegisteredServiceProvider rsp = CustomCropsPlugin.getInstance().getServer().getServicesManager().getRegistration(Economy.class); - if (rsp == null) { - return false; - } - economy = rsp.getProvider(); - return true; - } - - public static Economy getEconomy() { - return economy; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/CCPapi.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/CCPapi.java deleted file mode 100644 index 1150848..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/CCPapi.java +++ /dev/null @@ -1,103 +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.compatibility.papi; - -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.MessageManager; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CCPapi extends PlaceholderExpansion { - - private final CustomCropsPlugin plugin; - - public CCPapi(CustomCropsPlugin plugin) { - this.plugin = plugin; - } - - public void load() { - super.register(); - } - - public void unload() { - super.unregister(); - } - - @Override - public @NotNull String getIdentifier() { - return "customcrops"; - } - - @Override - public @NotNull String getAuthor() { - return "XiaoMoMi"; - } - - @Override - public @NotNull String getVersion() { - return "3.4"; - } - - @Override - public boolean persist() { - return true; - } - - @Override - public @Nullable String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { - String[] split = params.split("_", 2); - - Player player = offlinePlayer.getPlayer(); - if (player == null) - return null; - - switch (split[0]) { - case "season" -> { - if (split.length == 1) { - return MessageManager.seasonTranslation(plugin.getIntegrationManager().getSeasonInterface().getSeason(player.getWorld())); - } else { - try { - return MessageManager.seasonTranslation(plugin.getIntegrationManager().getSeasonInterface().getSeason(Bukkit.getWorld(split[1]))); - } catch (NullPointerException e) { - LogUtils.severe("World " + split[1] + " does not exist"); - e.printStackTrace(); - } - } - } - case "date" -> { - if (split.length == 1) { - return String.valueOf(plugin.getIntegrationManager().getSeasonInterface().getDate(player.getWorld())); - } else { - try { - return String.valueOf(plugin.getIntegrationManager().getSeasonInterface().getDate(Bukkit.getWorld(split[1]))); - } catch (NullPointerException e) { - LogUtils.severe("World " + split[1] + " does not exist"); - e.printStackTrace(); - } - } - } - } - - return null; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/ParseUtils.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/ParseUtils.java deleted file mode 100644 index c99a86a..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/papi/ParseUtils.java +++ /dev/null @@ -1,33 +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.compatibility.papi; - -import me.clip.placeholderapi.PlaceholderAPI; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -public class ParseUtils { - - public static String setPlaceholders(Player player, String text) { - return PlaceholderAPI.setPlaceholders(player, text); - } - - public static String setPlaceholders(OfflinePlayer player, String text) { - return PlaceholderAPI.setPlaceholders(player, text); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BattlePassHook.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BattlePassHook.java deleted file mode 100644 index 2831efa..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BattlePassHook.java +++ /dev/null @@ -1,91 +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.compatibility.quest; - -import io.github.battlepass.BattlePlugin; -import io.github.battlepass.api.events.server.PluginReloadEvent; -import net.advancedplugins.bp.impl.actions.ActionRegistry; -import net.advancedplugins.bp.impl.actions.external.executor.ActionQuestExecutor; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.event.CropBreakEvent; -import net.momirealms.customcrops.api.event.CropPlantEvent; -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.plugin.java.JavaPlugin; - -public class BattlePassHook implements Listener { - - public BattlePassHook() { - Bukkit.getPluginManager().registerEvents(this, CustomCropsPlugin.get()); - } - - public void register() { - ActionRegistry actionRegistry = BattlePlugin.getPlugin().getActionRegistry(); - actionRegistry.hook("customcrops", BPHarvestCropsQuest::new); - } - - @EventHandler(ignoreCancelled = true) - public void onBattlePassReload(PluginReloadEvent event){ - register(); - } - - private static class BPHarvestCropsQuest extends ActionQuestExecutor { - public BPHarvestCropsQuest(JavaPlugin plugin) { - super(plugin, "customcrops"); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCrop(CropBreakEvent event) { - Player player = event.getPlayer(); - if (player == null) return; - - WorldCrop worldCrop = event.getWorldCrop(); - if (worldCrop == null) return; - String id = worldCrop.getConfig().getStageItemByPoint(worldCrop.getPoint()); - - if (id.contains(":")) { - id = id.split(":")[1]; - } - - // Harvest crops - this.executionBuilder("harvest") - .player(player) - .root(id) - .progress(1) - .buildAndExecute(); - } - - @EventHandler (ignoreCancelled = true) - public void onPlantCrop(CropPlantEvent event){ - Player player = event.getPlayer(); - - Crop crop = event.getCrop(); - - // Harvest crops - this.executionBuilder("plant") - .player(player) - .root(crop.getKey()) - .progress(1) - .buildAndExecute(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BetonQuestHook.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BetonQuestHook.java deleted file mode 100644 index 21461ae..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/BetonQuestHook.java +++ /dev/null @@ -1,199 +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.compatibility.quest; - -import net.momirealms.customcrops.api.event.CropBreakEvent; -import net.momirealms.customcrops.api.event.CropPlantEvent; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import net.momirealms.customcrops.api.util.LogUtils; -import org.betonquest.betonquest.BetonQuest; -import org.betonquest.betonquest.Instruction; -import org.betonquest.betonquest.api.CountingObjective; -import org.betonquest.betonquest.api.config.quest.QuestPackage; -import org.betonquest.betonquest.api.profiles.OnlineProfile; -import org.betonquest.betonquest.api.profiles.Profile; -import org.betonquest.betonquest.exceptions.InstructionParseException; -import org.betonquest.betonquest.exceptions.QuestRuntimeException; -import org.betonquest.betonquest.instruction.variable.VariableNumber; -import org.betonquest.betonquest.instruction.variable.location.VariableLocation; -import org.betonquest.betonquest.utils.PlayerConverter; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; - -import java.util.Collections; -import java.util.HashSet; - -@SuppressWarnings("DuplicatedCode") -public class BetonQuestHook { - - public static void register() { - BetonQuest.getInstance().registerObjectives("customcrops_harvest", HarvestObjective.class); - BetonQuest.getInstance().registerObjectives("customcrops_plant", PlantObjective.class); - } - - public static class HarvestObjective extends CountingObjective implements Listener { - - private final VariableLocation playerLocation; - private final VariableNumber rangeVar; - private final HashSet crop_ids; - - public HarvestObjective(Instruction instruction) throws InstructionParseException { - super(instruction, "crop_to_harvest"); - crop_ids = new HashSet<>(); - Collections.addAll(crop_ids, instruction.getArray()); - targetAmount = instruction.getVarNum(VariableNumber.NOT_LESS_THAN_ONE_CHECKER); - final QuestPackage pack = instruction.getPackage(); - final String loc = instruction.getOptional("playerLocation"); - final String range = instruction.getOptional("range"); - if (loc != null && range != null) { - playerLocation = new VariableLocation(BetonQuest.getInstance().getVariableProcessor(), pack, loc); - rangeVar = new VariableNumber(BetonQuest.getInstance().getVariableProcessor(), pack, range); - } else { - playerLocation = null; - rangeVar = null; - } - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCrop(CropBreakEvent event) { - if (event.getPlayer() == null) { - return; - } - WorldCrop crop = event.getWorldCrop(); - if (crop == null) return; - - OnlineProfile onlineProfile = PlayerConverter.getID(event.getPlayer()); - if (!containsPlayer(onlineProfile)) { - return; - } - if (isInvalidLocation(event, onlineProfile)) { - return; - } - if (this.crop_ids.contains(crop.getConfig().getStageItemByPoint(crop.getPoint())) && this.checkConditions(onlineProfile)) { - getCountingData(onlineProfile).progress(1); - completeIfDoneOrNotify(onlineProfile); - } - } - - private boolean isInvalidLocation(CropBreakEvent event, final Profile profile) { - if (playerLocation == null || rangeVar == null) { - return false; - } - - final Location targetLocation; - try { - targetLocation = playerLocation.getValue(profile); - } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { - LogUtils.warn(e.getMessage()); - return true; - } - int range; - try { - range = rangeVar.getValue(profile).intValue(); - } catch (QuestRuntimeException e) { - throw new RuntimeException(e); - } - final Location playerLoc = event.getPlayer().getLocation(); - return !playerLoc.getWorld().equals(targetLocation.getWorld()) || targetLocation.distanceSquared(playerLoc) > range * range; - } - - @Override - public void start() { - Bukkit.getPluginManager().registerEvents(this, BetonQuest.getInstance()); - } - - @Override - public void stop() { - HandlerList.unregisterAll(this); - } - } - - public static class PlantObjective extends CountingObjective implements Listener { - - private final VariableLocation playerLocation; - private final VariableNumber rangeVar; - private final HashSet crops; - - public PlantObjective(Instruction instruction) throws InstructionParseException { - super(instruction, "crop_to_plant"); - crops = new HashSet<>(); - Collections.addAll(crops, instruction.getArray()); - targetAmount = instruction.getVarNum(VariableNumber.NOT_LESS_THAN_ONE_CHECKER); - final QuestPackage pack = instruction.getPackage(); - final String loc = instruction.getOptional("playerLocation"); - final String range = instruction.getOptional("range"); - if (loc != null && range != null) { - playerLocation = new VariableLocation(BetonQuest.getInstance().getVariableProcessor(), pack, loc); - rangeVar = new VariableNumber(BetonQuest.getInstance().getVariableProcessor(), pack, range); - } else { - playerLocation = null; - rangeVar = null; - } - } - - @EventHandler (ignoreCancelled = true) - public void onPlantCrop(CropPlantEvent event) { - OnlineProfile onlineProfile = PlayerConverter.getID(event.getPlayer()); - if (!containsPlayer(onlineProfile)) { - return; - } - if (isInvalidLocation(event, onlineProfile)) { - return; - } - if (this.crops.contains(event.getCrop().getKey()) && this.checkConditions(onlineProfile)) { - getCountingData(onlineProfile).progress(1); - completeIfDoneOrNotify(onlineProfile); - } - } - - private boolean isInvalidLocation(CropPlantEvent event, final Profile profile) { - if (playerLocation == null || rangeVar == null) { - return false; - } - - final Location targetLocation; - try { - targetLocation = playerLocation.getValue(profile); - } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { - LogUtils.warn(e.getMessage()); - return true; - } - int range; - try { - range = rangeVar.getValue(profile).intValue(); - } catch (QuestRuntimeException e) { - throw new RuntimeException(e); - } - final Location playerLoc = event.getPlayer().getLocation(); - return !playerLoc.getWorld().equals(targetLocation.getWorld()) || targetLocation.distanceSquared(playerLoc) > range * range; - } - - @Override - public void start() { - Bukkit.getPluginManager().registerEvents(this, BetonQuest.getInstance()); - } - - @Override - public void stop() { - HandlerList.unregisterAll(this); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/ClueScrollsHook.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/ClueScrollsHook.java deleted file mode 100644 index 997e379..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/quest/ClueScrollsHook.java +++ /dev/null @@ -1,67 +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.compatibility.quest; - -import com.electro2560.dev.cluescrolls.api.*; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.event.CropBreakEvent; -import net.momirealms.customcrops.api.event.CropPlantEvent; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -public class ClueScrollsHook implements Listener { - - private final CustomClue harvestClue; - private final CustomClue plantClue; - - public ClueScrollsHook() { - harvestClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomCropsPlugin.getInstance(), "harvest", new ClueConfigData("id", DataType.STRING)); - plantClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomCropsPlugin.getInstance(), "plant", new ClueConfigData("id", DataType.STRING)); - } - - public void register() { - Bukkit.getPluginManager().registerEvents(this, CustomCropsPlugin.get()); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCrop(CropBreakEvent event) { - final Player player = event.getPlayer(); - if (player == null) return; - - WorldCrop crop = event.getWorldCrop(); - if (crop == null) return; - - harvestClue.handle( - player, - 1, - new ClueDataPair("id", crop.getConfig().getStageItemByPoint(crop.getPoint())) - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlantCrop(CropPlantEvent event) { - plantClue.handle( - event.getPlayer(), - 1, - new ClueDataPair("id", event.getCrop().getKey()) - ); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/InBuiltSeason.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/InBuiltSeason.java deleted file mode 100644 index 16a0346..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/InBuiltSeason.java +++ /dev/null @@ -1,74 +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.compatibility.season; - -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.level.WorldInfoData; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import org.bukkit.World; -import org.jetbrains.annotations.Nullable; - -public class InBuiltSeason implements SeasonInterface { - - private final WorldManager worldManager; - - public InBuiltSeason(WorldManager worldManager) { - this.worldManager = worldManager; - } - - @Override - public @Nullable Season getSeason(World world) { - return worldManager - .getCustomCropsWorld(world) - .map(CustomCropsWorld::getSeason) - .orElse(null); - } - - @Override - public int getDate(World world) { - if (ConfigManager.syncSeasons()) - world = ConfigManager.referenceWorld(); - if (world == null) - return 0; - return worldManager - .getCustomCropsWorld(world) - .map(CustomCropsWorld::getDate) - .orElse(0); - } - - @Override - public void setSeason(World world, Season season) { - worldManager.getCustomCropsWorld(world) - .ifPresent(customWorld -> { - WorldInfoData infoData = customWorld.getInfoData(); - infoData.setSeason(season); - }); - } - - @Override - public void setDate(World world, int date) { - worldManager.getCustomCropsWorld(world) - .ifPresent(customWorld -> { - WorldInfoData infoData = customWorld.getInfoData(); - infoData.setDate(date); - }); - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/RealisticSeasonsImpl.java b/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/RealisticSeasonsImpl.java deleted file mode 100644 index 9703511..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/compatibility/season/RealisticSeasonsImpl.java +++ /dev/null @@ -1,67 +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.compatibility.season; - -import me.casperge.realisticseasons.api.SeasonsAPI; -import me.casperge.realisticseasons.calendar.Date; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import org.bukkit.World; -import org.jetbrains.annotations.Nullable; - -public class RealisticSeasonsImpl implements SeasonInterface { - - private final SeasonsAPI api; - - public RealisticSeasonsImpl() { - this.api = SeasonsAPI.getInstance(); - } - - @Override - public @Nullable Season getSeason(World world) { - return switch (api.getSeason(world)) { - case WINTER -> Season.WINTER; - case SPRING -> Season.SPRING; - case SUMMER -> Season.SUMMER; - case FALL -> Season.AUTUMN; - case DISABLED, RESTORE -> null; - }; - } - - @Override - public int getDate(World world) { - return api.getDate(world).getDay(); - } - - @Override - public void setSeason(World world, Season season) { - me.casperge.realisticseasons.season.Season rsSeason = switch (season) { - case AUTUMN -> me.casperge.realisticseasons.season.Season.FALL; - case SUMMER -> me.casperge.realisticseasons.season.Season.SUMMER; - case WINTER -> me.casperge.realisticseasons.season.Season.WINTER; - case SPRING -> me.casperge.realisticseasons.season.Season.SPRING; - }; - api.setSeason(world, rsSeason); - } - - @Override - public void setDate(World world, int date) { - Date rsDate = api.getDate(world); - api.setDate(world, new Date(date, rsDate.getMonth(), rsDate.getYear())); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/Dependency.java b/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/Dependency.java deleted file mode 100644 index 6f6c7e3..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/dependencies/Dependency.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.momirealms.customcrops.libraries.dependencies; - -import com.google.common.collect.ImmutableList; -import net.momirealms.customcrops.libraries.dependencies.relocation.Relocation; -import org.jetbrains.annotations.Nullable; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Locale; - -/** - * The dependencies used by CustomCrops. - */ -public enum Dependency { - - ASM( - "org.ow2.asm", - "asm", - "9.7", - null, - "asm" - ), - ASM_COMMONS( - "org.ow2.asm", - "asm-commons", - "9.7", - null, - "asm-commons" - ), - JAR_RELOCATOR( - "me.lucko", - "jar-relocator", - "1.7", - null, - "jar-relocator" - ), - COMMAND_API( - "dev{}jorel", - "commandapi-bukkit-shade", - "9.5.3", - null, - "commandapi-bukkit", - Relocation.of("commandapi", "dev{}jorel{}commandapi") - ), - COMMAND_API_MOJMAP( - "dev{}jorel", - "commandapi-bukkit-shade-mojang-mapped", - "9.5.3", - null, - "commandapi-bukkit-mojang-mapped", - Relocation.of("commandapi", "dev{}jorel{}commandapi") - ), - BOOSTED_YAML( - "dev{}dejvokep", - "boosted-yaml", - "1.3.6", - null, - "boosted-yaml", - Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml") - ), - H2_DRIVER( - "com.h2database", - "h2", - "2.2.224", - null, - "h2database" - ), - SQLITE_DRIVER( - "org.xerial", - "sqlite-jdbc", - "3.45.1.0", - null, - "sqlite-jdbc" - ), - SLF4J_SIMPLE( - "org.slf4j", - "slf4j-simple", - "2.0.12", - null, - "slf4j-simple" - ), - SLF4J_API( - "org.slf4j", - "slf4j-api", - "2.0.12", - null, - "slf4j-api" - ), - BSTATS_BASE( - "org{}bstats", - "bstats-base", - "3.0.2", - null, - "bstats-base", - Relocation.of("bstats", "org{}bstats") - ), - BSTATS_BUKKIT( - "org{}bstats", - "bstats-bukkit", - "3.0.2", - null, - "bstats-bukkit", - Relocation.of("bstats", "org{}bstats") - ), - GSON( - "com.google.code.gson", - "gson", - "2.10.1", - null, - "gson" - ), - EXP4J( - "net{}objecthunter", - "exp4j", - "0.4.8", - null, - "exp4j", - Relocation.of("exp4j", "net{}objecthunter{}exp4j") - ); - - private final String mavenRepoPath; - private final String version; - private final List relocations; - private final String repo; - private final String artifact; - - private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; - - Dependency(String groupId, String artifactId, String version, String repo, String artifact) { - this(groupId, artifactId, version, repo, artifact, new Relocation[0]); - } - - Dependency(String groupId, String artifactId, String version, String repo, String artifact, Relocation... relocations) { - this.mavenRepoPath = String.format(MAVEN_FORMAT, - rewriteEscaping(groupId).replace(".", "/"), - rewriteEscaping(artifactId), - version, - rewriteEscaping(artifactId), - version - ); - this.version = version; - this.relocations = ImmutableList.copyOf(relocations); - this.repo = repo; - this.artifact = artifact; - } - - private static String rewriteEscaping(String s) { - return s.replace("{}", "."); - } - - public String getFileName(String classifier) { - String name = artifact.toLowerCase(Locale.ROOT).replace('_', '-'); - String extra = classifier == null || classifier.isEmpty() - ? "" - : "-" + classifier; - return name + "-" + this.version + extra + ".jar"; - } - - String getMavenRepoPath() { - return this.mavenRepoPath; - } - - public List getRelocations() { - return this.relocations; - } - - /** - * Creates a {@link MessageDigest} suitable for computing the checksums - * of dependencies. - * - * @return the digest - */ - public static MessageDigest createDigest() { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - @Nullable - public String getRepo() { - return repo; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/libraries/loader/JarInJarClassLoader.java b/plugin/src/main/java/net/momirealms/customcrops/libraries/loader/JarInJarClassLoader.java deleted file mode 100644 index 34b8760..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/libraries/loader/JarInJarClassLoader.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * This file is part of LuckPerms, licensed under the MIT License. - * - * Copyright (c) lucko (Luck) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.momirealms.customcrops.libraries.loader; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; - -/** - * Classloader that can load a jar from within another jar file. - * - *

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

- * - *

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

- */ -public class JarInJarClassLoader extends URLClassLoader { - static { - ClassLoader.registerAsParallelCapable(); - } - - /** - * Creates a new jar-in-jar class loader. - * - * @param loaderClassLoader the loader plugin's classloader (setup and created by the platform) - * @param jarResourcePath the path to the jar-in-jar resource within the loader jar - * @throws LoadingException if something unexpectedly bad happens - */ - public JarInJarClassLoader(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { - super(new URL[]{extractJar(loaderClassLoader, jarResourcePath)}, loaderClassLoader); - } - - public void addJarToClasspath(URL url) { - addURL(url); - } - - public void deleteJarResource() { - URL[] urls = getURLs(); - if (urls.length == 0) { - return; - } - - try { - Path path = Paths.get(urls[0].toURI()); - Files.deleteIfExists(path); - } catch (Exception e) { - // ignore - } - } - - /** - * Creates a new plugin instance. - * - * @param bootstrapClass the name of the bootstrap plugin class - * @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap - * plugin constructor - * @param loaderPlugin the loader plugin instance - * @param the type of the loader plugin - * @return the instantiated bootstrap plugin - */ - public LoaderBootstrap instantiatePlugin(String bootstrapClass, Class loaderPluginType, T loaderPlugin) throws LoadingException { - Class plugin; - try { - plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to load bootstrap class", e); - } - - Constructor constructor; - try { - constructor = plugin.getConstructor(loaderPluginType); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to get bootstrap constructor", e); - } - - try { - return constructor.newInstance(loaderPlugin); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to create bootstrap plugin instance", e); - } - } - - /** - * Extracts the "jar-in-jar" from the loader plugin into a temporary file, - * then returns a URL that can be used by the {@link JarInJarClassLoader}. - * - * @param loaderClassLoader the classloader for the "host" loader plugin - * @param jarResourcePath the inner jar resource path - * @return a URL to the extracted file - */ - private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { - // get the jar-in-jar resource - URL jarInJar = loaderClassLoader.getResource(jarResourcePath); - if (jarInJar == null) { - throw new LoadingException("Could not locate jar-in-jar"); - } - - // create a temporary file - // on posix systems by default this is only read/writable by the process owner - Path path; - try { - path = Files.createTempFile("customcrops-jarinjar", ".jar.tmp"); - } catch (IOException e) { - throw new LoadingException("Unable to create a temporary file", e); - } - - // mark that the file should be deleted on exit - path.toFile().deleteOnExit(); - - // copy the jar-in-jar to the temporary file path - try (InputStream in = jarInJar.openStream()) { - Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new LoadingException("Unable to copy jar-in-jar to temporary path", e); - } - - try { - return path.toUri().toURL(); - } catch (MalformedURLException e) { - throw new LoadingException("Unable to get URL from path", e); - } - } - -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/AdventureManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/manager/AdventureManagerImpl.java deleted file mode 100644 index 486153e..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/AdventureManagerImpl.java +++ /dev/null @@ -1,214 +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.manager; - -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.title.Title; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.AdventureManager; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.MessageManager; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; - -import java.time.Duration; - -public class AdventureManagerImpl extends AdventureManager { - - private final CustomCropsPlugin plugin; - private BukkitAudiences audiences; - - public AdventureManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - init(); - } - - @Override - public void init() { - this.audiences = BukkitAudiences.create(plugin); - } - - @Override - public void disable() { - if (this.audiences != null) - this.audiences.close(); - } - - @Override - public void sendMessage(CommandSender sender, String text) { - if (text == null) return; - if (sender instanceof Player player) sendPlayerMessage(player, text); - else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(text); - } - - - @Override - public void sendMessageWithPrefix(CommandSender sender, String text) { - if (text == null) return; - if (sender instanceof Player player) sendPlayerMessage(player, MessageManager.prefix() + text); - else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(MessageManager.prefix() + text); - } - - @Override - public void sendConsoleMessage(String text) { - if (text == null) return; - Audience au = audiences.sender(Bukkit.getConsoleSender()); - au.sendMessage(getComponentFromMiniMessage(text)); - } - - @Override - public void sendPlayerMessage(Player player, String text) { - if (player == null) return; - Audience au = audiences.player(player); - au.sendMessage(getComponentFromMiniMessage(text)); - } - - @Override - public void sendActionbar(Player player, String text) { - if (player == null) return; - Audience au = audiences.player(player); - au.sendActionBar(getComponentFromMiniMessage(text)); - } - - @Override - public void sendSound(Player player, Sound.Source source, Key key, float pitch, float volume) { - if (player == null) return; - sendSound(player, Sound.sound(key, source, volume, pitch)); - } - - @Override - public void sendSound(Player player, Sound sound) { - if (player == null) return; - Audience au = audiences.player(player); - au.playSound(sound); - } - - @Override - public void sendTitle(Player player, String title, String subTitle, int fadeIn, int stay, int fadeOut) { - if (player == null) return; - Audience au = audiences.player(player); - au.showTitle(Title.title(getComponentFromMiniMessage(title), getComponentFromMiniMessage(subTitle), Title.Times.times( - Duration.ofMillis(fadeIn * 50L), - Duration.ofMillis(stay * 50L), - Duration.ofMillis(fadeOut * 50L) - ))); - } - - @Override - public Component getComponentFromMiniMessage(String text) { - if (text == null) { - return Component.empty(); - } - if (ConfigManager.legacyColorSupport()) { - return MiniMessage.miniMessage().deserialize(legacyToMiniMessage(text)); - } else { - return MiniMessage.miniMessage().deserialize(text); - } - } - - @Override - public String legacyToMiniMessage(String legacy) { - StringBuilder stringBuilder = new StringBuilder(); - char[] chars = legacy.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (!isColorCode(chars[i])) { - stringBuilder.append(chars[i]); - continue; - } - if (i + 1 >= chars.length) { - stringBuilder.append(chars[i]); - continue; - } - switch (chars[i+1]) { - case '0' -> stringBuilder.append(""); - case '1' -> stringBuilder.append(""); - case '2' -> stringBuilder.append(""); - case '3' -> stringBuilder.append(""); - case '4' -> stringBuilder.append(""); - case '5' -> stringBuilder.append(""); - case '6' -> stringBuilder.append(""); - case '7' -> stringBuilder.append(""); - case '8' -> stringBuilder.append(""); - case '9' -> stringBuilder.append(""); - case 'a' -> stringBuilder.append(""); - case 'b' -> stringBuilder.append(""); - case 'c' -> stringBuilder.append(""); - case 'd' -> stringBuilder.append(""); - case 'e' -> stringBuilder.append(""); - case 'f' -> stringBuilder.append(""); - case 'r' -> stringBuilder.append(""); - case 'l' -> stringBuilder.append(""); - case 'm' -> stringBuilder.append(""); - case 'o' -> stringBuilder.append(""); - case 'n' -> stringBuilder.append(""); - case 'k' -> stringBuilder.append(""); - case 'x' -> { - if (i + 13 >= chars.length - || !isColorCode(chars[i+2]) - || !isColorCode(chars[i+4]) - || !isColorCode(chars[i+6]) - || !isColorCode(chars[i+8]) - || !isColorCode(chars[i+10]) - || !isColorCode(chars[i+12])) { - stringBuilder.append(chars[i]); - continue; - } - stringBuilder - .append("<#") - .append(chars[i+3]) - .append(chars[i+5]) - .append(chars[i+7]) - .append(chars[i+9]) - .append(chars[i+11]) - .append(chars[i+13]) - .append(">"); - i += 12; - } - default -> { - stringBuilder.append(chars[i]); - continue; - } - } - i++; - } - return stringBuilder.toString(); - } - - @Override - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean isColorCode(char c) { - return c == '§' || c == '&'; - } - - @Override - public int rgbaToDecimal(String rgba) { - String[] split = rgba.split(","); - int r = Integer.parseInt(split[0]); - int g = Integer.parseInt(split[1]); - int b = Integer.parseInt(split[2]); - int a = Integer.parseInt(split[3]); - return (a << 24) | (r << 16) | (g << 8) | b; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java b/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java deleted file mode 100644 index ac96dea..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/CommandManager.java +++ /dev/null @@ -1,290 +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.manager; - -import dev.jorel.commandapi.*; -import dev.jorel.commandapi.arguments.ArgumentSuggestions; -import dev.jorel.commandapi.arguments.IntegerArgument; -import dev.jorel.commandapi.arguments.StringArgument; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Initable; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.manager.AdventureManager; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.MessageManager; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsSection; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.compatibility.season.InBuiltSeason; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.generator.WorldInfo; - -import java.util.Locale; -import java.util.Optional; - -public class CommandManager implements Initable { - - private final CustomCropsPlugin plugin; - - public CommandManager(CustomCropsPlugin plugin) { - this.plugin = plugin; - init(); - } - - @Override - public void init() { - if (!CommandAPI.isLoaded()) - CommandAPI.onLoad(new CommandAPIBukkitConfig(plugin).silentLogs(true)); - new CommandAPICommand("customcrops") - .withPermission("customcrops.admin") - .withAliases("ccrops") - .withSubcommands( - getReloadCommand(), - getAboutCommand(), - getSeasonCommand(), - getDateCommand(), - getForceTickCommand(), - getUnsafeCommand() - ) - .register(); - } - - @Override - public void disable() { - CommandAPI.unregister("customcrops"); - } - - private CommandAPICommand getReloadCommand() { - return new CommandAPICommand("reload") - .executes((sender, args) -> { - long time1 = System.currentTimeMillis(); - plugin.reload(); - long time2 = System.currentTimeMillis(); - plugin.getAdventure().sendMessageWithPrefix(sender, MessageManager.reloadMessage().replace("{time}", String.valueOf(time2 - time1))); - }); - } - - private CommandAPICommand getUnsafeCommand() { - return new CommandAPICommand("unsafe") - .withSubcommands( - new CommandAPICommand("delete-chunk-data").executesPlayer((player, args) -> { - plugin.getWorldManager().getCustomCropsWorld(player.getWorld()).ifPresent(customCropsWorld -> { - var optionalChunk = customCropsWorld.getLoadedChunkAt(ChunkPos.getByBukkitChunk(player.getChunk())); - if (optionalChunk.isEmpty()) { - AdventureManager.getInstance().sendMessageWithPrefix(player, "This chunk doesn't have any data."); - return; - } - customCropsWorld.deleteChunk(ChunkPos.getByBukkitChunk(player.getChunk())); - AdventureManager.getInstance().sendMessageWithPrefix(player, "Done."); - }); - }), - new CommandAPICommand("check-data").executesPlayer((player, args) -> { - Block block = player.getTargetBlockExact(10); - if (block != null) { - Optional customCropsBlock = plugin.getWorldManager().getBlockAt(SimpleLocation.of(block.getLocation())); - if (customCropsBlock.isPresent()) { - AdventureManager.getInstance().sendMessageWithPrefix(player, customCropsBlock.get().getType() + ":" + customCropsBlock.get().getCompoundMap()); - return; - } - } - AdventureManager.getInstance().sendMessageWithPrefix(player, "Data not found"); - }) - ); - } - - private CommandAPICommand getAboutCommand() { - return new CommandAPICommand("about").executes((sender, args) -> { - plugin.getAdventure().sendMessage(sender, "<#FFA500>⛈ CustomCrops - <#87CEEB>" + CustomCropsPlugin.getInstance().getVersionManager().getPluginVersion()); - plugin.getAdventure().sendMessage(sender, "<#FFFFE0>Ultra-customizable planting experience for Minecraft servers"); - plugin.getAdventure().sendMessage(sender, "<#DA70D6>\uD83E\uDDEA Author: <#FFC0CB>XiaoMoMi"); - plugin.getAdventure().sendMessage(sender, "<#FF7F50>\uD83D\uDD25 Contributors: <#FFA07A>Cha_Shao, <#FFA07A>TopOrigin, <#FFA07A>AmazingCat"); - plugin.getAdventure().sendMessage(sender, "<#FFD700>⭐ Document <#A9A9A9>| <#FAFAD2>⛏ Github <#A9A9A9>| <#48D1CC>\uD83D\uDD14 Polymart"); - }); - } - - private CommandAPICommand getForceTickCommand() { - return new CommandAPICommand("force-tick") - .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> Bukkit.getWorlds().stream().map(WorldInfo::getName).toList().toArray(new String[0])))) - .withArguments(new StringArgument("type").replaceSuggestions(ArgumentSuggestions.strings("sprinkler", "crop", "pot", "scarecrow", "greenhouse"))) - .executes((sender, args) -> { - String worldName = (String) args.get("world"); - World world = Bukkit.getWorld(worldName); - if (world == null) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - ItemType itemType = ItemType.valueOf(((String) args.get("type")).toUpperCase(Locale.ENGLISH)); - Optional customCropsWorld = plugin.getWorldManager().getCustomCropsWorld(world); - if (customCropsWorld.isEmpty()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - plugin.getScheduler().runTaskAsync(() -> { - for (CustomCropsChunk chunk : customCropsWorld.get().getChunkStorage()) { - for (CustomCropsSection section : chunk.getSections()) { - for (CustomCropsBlock block : section.getBlocks()) { - if (block.getType() == itemType) { - block.tick(1, false); - } - } - } - } - }); - }); - } - - private CommandAPICommand getDateCommand() { - return new CommandAPICommand("date") - .withSubcommands( - new CommandAPICommand("get") - .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> plugin.getWorldManager().getCustomCropsWorlds().stream() - .filter(customCropsWorld -> customCropsWorld.getWorldSetting().isEnableSeason()) - .map(CustomCropsWorld::getWorldName) - .toList() - .toArray(new String[0])))) - .executes((sender, args) -> { - String worldName = (String) args.get("world"); - World world = Bukkit.getWorld(worldName); - if (world == null) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - plugin.getAdventure().sendMessageWithPrefix(sender, String.valueOf(plugin.getIntegrationManager().getSeasonInterface().getDate(world))); - }), - new CommandAPICommand("set") - .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> plugin.getWorldManager().getCustomCropsWorlds().stream() - .filter(customCropsWorld -> customCropsWorld.getWorldSetting().isEnableSeason()) - .map(CustomCropsWorld::getWorldName) - .toList() - .toArray(new String[0])))) - .withArguments(new IntegerArgument("date",1)) - .executes((sender, args) -> { - String worldName = (String) args.get("world"); - World world = Bukkit.getWorld(worldName); - if (world == null) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - int date = (int) args.getOrDefault("date", 1); - SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); - if (!(seasonInterface instanceof InBuiltSeason inBuiltSeason)) { - plugin.getAdventure().sendMessageWithPrefix(sender, "Detected that you are using a season plugin. Please set date in that plugin."); - return; - } - Optional customCropsWorld = plugin.getWorldManager().getCustomCropsWorld(world); - if (customCropsWorld.isEmpty()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - if (!customCropsWorld.get().getWorldSetting().isEnableSeason()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "Season is not enabled in that world"); - return; - } - if (date > customCropsWorld.get().getWorldSetting().getSeasonDuration()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "Date should be a value no higher than season duration"); - return; - } - String pre = String.valueOf(inBuiltSeason.getDate(world)); - customCropsWorld.get().getInfoData().setDate(date); - plugin.getAdventure().sendMessageWithPrefix(sender, "Date in world("+world.getName()+"): " + pre + " -> " + date); - }) - ); - } - - private CommandAPICommand getSeasonCommand() { - return new CommandAPICommand("season") - .withSubcommands( - new CommandAPICommand("get") - .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> plugin.getWorldManager().getCustomCropsWorlds().stream() - .filter(customCropsWorld -> customCropsWorld.getWorldSetting().isEnableSeason()) - .map(CustomCropsWorld::getWorldName) - .toList() - .toArray(new String[0])))) - .executes((sender, args) -> { - String worldName = (String) args.get("world"); - World world = Bukkit.getWorld(worldName); - if (world == null) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - plugin.getAdventure().sendMessageWithPrefix(sender, MessageManager.seasonTranslation(plugin.getIntegrationManager().getSeasonInterface().getSeason(world))); - }), - new CommandAPICommand("set") - .withArguments(new StringArgument("world").replaceSuggestions(ArgumentSuggestions.strings(commandSenderSuggestionInfo -> { - if (ConfigManager.syncSeasons()) { - return new String[]{ConfigManager.referenceWorld().getName()}; - } - return plugin.getWorldManager().getCustomCropsWorlds().stream() - .filter(customCropsWorld -> customCropsWorld.getWorldSetting().isEnableSeason()) - .map(CustomCropsWorld::getWorldName) - .toList() - .toArray(new String[0]); - }))) - .withArguments(new StringArgument("season") - .replaceSuggestions(ArgumentSuggestions.stringsWithTooltips(info -> - new IStringTooltip[] { - StringTooltip.ofString("Spring", MessageManager.seasonTranslation(Season.SPRING)), - StringTooltip.ofString("Summer", MessageManager.seasonTranslation(Season.SUMMER)), - StringTooltip.ofString("Autumn", MessageManager.seasonTranslation(Season.AUTUMN)), - StringTooltip.ofString("Winter", MessageManager.seasonTranslation(Season.WINTER)) - } - )) - ) - .executes((sender, args) -> { - String worldName = (String) args.get("world"); - World world = Bukkit.getWorld(worldName); - if (world == null) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - String seasonName = (String) args.get("season"); - - SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); - if (!(seasonInterface instanceof InBuiltSeason inBuiltSeason)) { - plugin.getAdventure().sendMessageWithPrefix(sender, "Detected that you are using a season plugin. Please set season in that plugin."); - return; - } - String pre = MessageManager.seasonTranslation(inBuiltSeason.getSeason(world)); - Optional customCropsWorld = plugin.getWorldManager().getCustomCropsWorld(world); - if (customCropsWorld.isEmpty()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "CustomCrops is not enabled in that world"); - return; - } - if (!customCropsWorld.get().getWorldSetting().isEnableSeason()) { - plugin.getAdventure().sendMessageWithPrefix(sender, "Season is not enabled in that world"); - return; - } - try { - Season season = Season.valueOf(seasonName.toUpperCase(Locale.ENGLISH)); - customCropsWorld.get().getInfoData().setSeason(season); - String next = MessageManager.seasonTranslation(season); - plugin.getAdventure().sendMessageWithPrefix(sender, "Season in world("+world.getName()+"): " + pre + " -> " + next); - } catch (IllegalArgumentException e) { - plugin.getAdventure().sendMessageWithPrefix(sender, "That season doesn't exist"); - } - }) - ); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/ConfigManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/manager/ConfigManagerImpl.java deleted file mode 100644 index af6e785..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/ConfigManagerImpl.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.manager; - -import dev.dejvokep.boostedyaml.YamlDocument; -import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; -import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; -import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; -import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; -import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.Locale; -import java.util.Objects; - -public class ConfigManagerImpl extends ConfigManager { - - public static final String configVersion = "37"; - private final CustomCropsPlugin plugin; - private String lang; - private int maximumPoolSize; - private int corePoolSize; - private int keepAliveTime; - private boolean debug; - private boolean metrics; - private boolean legacyColorSupport; - private boolean protectLore; - private String[] itemDetectionOrder = new String[0]; - private boolean checkUpdate; - private boolean disableMoisture; - private boolean preventTrampling; - private boolean greenhouse; - private boolean scarecrow; - private double[] defaultQualityRatio; - private String greenhouseID; - private String scarecrowID; - private int greenhouseRange; - private int scarecrowRange; - private boolean syncSeasons; - private WeakReference referenceWorld; - private boolean convertWorldOnLoad; - private boolean scarecrowProtectChunk; - private ItemCarrier scarecrowItemType; - private ItemCarrier glassItemType; - - public ConfigManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void load() { - if (!new File(plugin.getDataFolder(), "config.yml").exists()) - ConfigUtils.getConfig("config.yml"); - // update config version - try { - YamlDocument.create( - new File(CustomCropsPlugin.getInstance().getDataFolder(), "config.yml"), - Objects.requireNonNull(CustomCropsPlugin.getInstance().getResource("config.yml")), - GeneralSettings.DEFAULT, - LoaderSettings - .builder() - .setAutoUpdate(true) - .build(), - DumperSettings.DEFAULT, - UpdaterSettings - .builder() - .setVersioning(new BasicVersioning("config-version")) - .addIgnoredRoute(configVersion, "other-settings.placeholder-register", '.') - .build() - ); - } catch (IOException e) { - LogUtils.warn(e.getMessage()); - } - - YamlConfiguration config = ConfigUtils.getConfig("config.yml"); - - debug = config.getBoolean("debug"); - metrics = config.getBoolean("metrics"); - lang = config.getString("lang"); - checkUpdate = config.getBoolean("update-checker", true); - - ConfigurationSection otherSettings = config.getConfigurationSection("other-settings"); - if (otherSettings == null) { - LogUtils.severe("other-settings section should not be null"); - return; - } - - maximumPoolSize = otherSettings.getInt("thread-pool-settings.maximumPoolSize", 10); - corePoolSize = otherSettings.getInt("thread-pool-settings.corePoolSize", 10); - keepAliveTime = otherSettings.getInt("thread-pool-settings.keepAliveTime", 30); - itemDetectionOrder = otherSettings.getStringList("item-detection-order").toArray(new String[0]); - protectLore = otherSettings.getBoolean("protect-original-lore", false); - legacyColorSupport = otherSettings.getBoolean("legacy-color-code-support", true); - convertWorldOnLoad = otherSettings.getBoolean("convert-on-world-load", false); - - ConfigurationSection mechanics = config.getConfigurationSection("mechanics"); - if (mechanics == null) { - LogUtils.severe("mechanics section should not be null"); - return; - } - - defaultQualityRatio = ConfigUtils.getQualityRatio(mechanics.getString("default-quality-ratio", "17/2/1")); - disableMoisture = mechanics.getBoolean("vanilla-farmland.disable-moisture-mechanic", false); - preventTrampling = mechanics.getBoolean("vanilla-farmland.prevent-trampling", false); - greenhouse = mechanics.getBoolean("greenhouse.enable", true); - greenhouseID = mechanics.getString("greenhouse.id"); - greenhouseRange = mechanics.getInt("greenhouse.range", 5); - glassItemType = ItemCarrier.valueOf(mechanics.getString("greenhouse.type", "CHORUS").toUpperCase(Locale.ENGLISH)); - - scarecrow = mechanics.getBoolean("scarecrow.enable", true); - scarecrowID = mechanics.getString("scarecrow.id"); - scarecrowRange = mechanics.getInt("scarecrow.range", 7); - scarecrowProtectChunk = mechanics.getBoolean("scarecrow.protect-chunk", false); - scarecrowItemType = ItemCarrier.valueOf(mechanics.getString("scarecrow.type", "ITEM_FRAME").toUpperCase(Locale.ENGLISH)); - - syncSeasons = mechanics.getBoolean("sync-season.enable", true); - if (syncSeasons) { - referenceWorld = new WeakReference<>(Bukkit.getWorld(mechanics.getString("sync-season.reference", "world"))); - } - } - - @Override - public void unload() { - - } - - @Override - public boolean hasLegacyColorSupport() { - return legacyColorSupport; - } - - @Override - public int getMaximumPoolSize() { - return maximumPoolSize; - } - - @Override - public int getCorePoolSize() { - return corePoolSize; - } - - @Override - public int getKeepAliveTime() { - return keepAliveTime; - } - - @Override - public boolean isConvertWorldOnLoad() { - return convertWorldOnLoad; - } - - @Override - public double[] getDefaultQualityRatio() { - return defaultQualityRatio; - } - - @Override - public String getLang() { - return lang; - } - - @Override - public boolean getDebugMode() { - return debug; - } - - @Override - public boolean isProtectLore() { - return protectLore; - } - - @Override - public String[] getItemDetectionOrder() { - return itemDetectionOrder; - } - - @Override - public boolean hasMetrics() { - return metrics; - } - - @Override - public boolean hasCheckUpdate() { - return checkUpdate; - } - - @Override - public boolean isDisableMoisture() { - return disableMoisture; - } - - @Override - public boolean isPreventTrampling() { - return preventTrampling; - } - - @Override - public boolean isGreenhouseEnabled() { - return greenhouse; - } - - @Override - public String getGreenhouseID() { - return greenhouseID; - } - - @Override - public int getGreenhouseRange() { - return greenhouseRange; - } - - @Override - public boolean isScarecrowEnabled() { - return scarecrow; - } - - @Override - public String getScarecrowID() { - return scarecrowID; - } - - @Override - public int getScarecrowRange() { - return scarecrowRange; - } - - @Override - public boolean isSyncSeasons() { - return syncSeasons; - } - - @Override - public boolean doesScarecrowProtectChunk() { - return scarecrowProtectChunk; - } - - @Override - public ItemCarrier getScarecrowItemCarrier() { - return scarecrowItemType; - } - - @Override - public ItemCarrier getGlassItemCarrier() { - return glassItemType; - } - - @Override - public World getReferenceWorld() { - return referenceWorld.get(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java b/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java deleted file mode 100644 index 154f77f..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/HologramManager.java +++ /dev/null @@ -1,182 +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.manager; - -import net.kyori.adventure.text.Component; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.common.Tuple; -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import net.momirealms.customcrops.util.FakeEntityUtils; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class HologramManager implements Listener, Reloadable { - - private final ConcurrentHashMap hologramMap; - private final CustomCropsPlugin plugin; - private CancellableTask cacheCheckTask; - private static HologramManager manager; - - public static HologramManager getInstance() { - return manager; - } - - public HologramManager(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.hologramMap = new ConcurrentHashMap<>(); - manager = this; - } - - @Override - public void load() { - Bukkit.getPluginManager().registerEvents(this, plugin); - this.cacheCheckTask = plugin.getScheduler().runTaskAsyncTimer(() -> { - ArrayList removed = new ArrayList<>(); - long current = System.currentTimeMillis(); - for (Map.Entry entry : hologramMap.entrySet()) { - Player player = Bukkit.getPlayer(entry.getKey()); - if (player == null || !player.isOnline()) { - removed.add(entry.getKey()); - } else { - entry.getValue().removeOutDated(current, player); - } - } - for (UUID uuid : removed) { - hologramMap.remove(uuid); - } - }, 200, 200, TimeUnit.MILLISECONDS); - } - - @Override - public void unload() { - HandlerList.unregisterAll(this); - for (Map.Entry entry : hologramMap.entrySet()) { - Player player = Bukkit.getPlayer(entry.getKey()); - if (player != null && player.isOnline()) { - entry.getValue().removeAll(player); - } - } - if (cacheCheckTask != null) cacheCheckTask.cancel(); - this.hologramMap.clear(); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - this.hologramMap.remove(event.getPlayer().getUniqueId()); - } - - public void showHologram(Player player, Location location, Component component, int millis) { - HologramCache hologramCache = hologramMap.get(player.getUniqueId()); - if (hologramCache != null) { - hologramCache.showHologram(player, location, component, millis); - } else { - hologramCache = new HologramCache(); - hologramCache.showHologram(player, location, component, millis); - hologramMap.put(player.getUniqueId(), hologramCache); - } - } - - @SuppressWarnings("unchecked") - public static class HologramCache { - - private final Vector> tupleList; - private Tuple[] tuples; - - public HologramCache() { - this.tupleList = new Vector<>(); - this.tuples = new Tuple[0]; - } - - public int push(Location new_loc, int time) { - for (Tuple tuple : tuples) { - if (new_loc.equals(tuple.getLeft())) { - tuple.setRight(System.currentTimeMillis() + time); - return tuple.getMid(); - } - } - return 0; - } - - public void removeOutDated(long current, Player player) { - for (Tuple tuple : tuples) { - if (tuple.getRight() < current) { - tupleList.remove(tuple); - this.tuples = tupleList.toArray(new Tuple[0]); - PacketManager.getInstance().send(player, FakeEntityUtils.getDestroyPacket(tuple.getMid())); - } - } - } - - public void showHologram(Player player, Location location, Component component, int millis) { - int entity_id = push(location, millis); - if (entity_id == 0) { - int random = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - tupleList.add(Tuple.of(location, random, System.currentTimeMillis() + millis)); - this.tuples = tupleList.toArray(new Tuple[0]); - if (VersionManager.isHigherThan1_20_R2()) { - PacketManager.getInstance().send(player, - FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.25,0), EntityType.TEXT_DISPLAY), - FakeEntityUtils.get1_20_2TextDisplayMetaPacket(random, component) - ); - } else if (VersionManager.isHigherThan1_19_R3()) { - PacketManager.getInstance().send(player, - FakeEntityUtils.getSpawnPacket(random, location.clone().add(0,1.25,0), EntityType.TEXT_DISPLAY), - FakeEntityUtils.get1_19_4TextDisplayMetaPacket(random, component) - ); - } else { - PacketManager.getInstance().send(player, - FakeEntityUtils.getSpawnPacket(random, location, EntityType.ARMOR_STAND), - FakeEntityUtils.getVanishArmorStandMetaPacket(random, component) - ); - } - } else { - if (VersionManager.isHigherThan1_20_R2()) { - PacketManager.getInstance().send(player, FakeEntityUtils.get1_20_2TextDisplayMetaPacket(entity_id, component)); - } else if (VersionManager.isHigherThan1_19_R3()) { - PacketManager.getInstance().send(player, FakeEntityUtils.get1_19_4TextDisplayMetaPacket(entity_id, component)); - } else { - PacketManager.getInstance().send(player, FakeEntityUtils.getVanishArmorStandMetaPacket(entity_id, component)); - } - } - } - - public void removeAll(Player player) { - for (Tuple tuple : tuples) { - PacketManager.getInstance().send(player, FakeEntityUtils.getDestroyPacket(tuple.getMid())); - } - this.tupleList.clear(); - this.tuples = null; - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/MessageManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/manager/MessageManagerImpl.java deleted file mode 100644 index 5b90e30..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/MessageManagerImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.manager; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Reloadable; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.MessageManager; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; - -public class MessageManagerImpl extends MessageManager implements Reloadable { - - private CustomCropsPlugin plugin; - private String reload; - private String prefix; - private String spring; - private String summer; - private String autumn; - private String winter; - private String noSeason; - - public MessageManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void load() { - YamlConfiguration config; - try { - config = ConfigUtils.getConfig("messages" + File.separator + ConfigManager.lang() + ".yml"); - } catch (Exception e) { - LogUtils.warn(ConfigManager.lang() + ".yml doesn't exist. Using the default language file now."); - config = ConfigUtils.getConfig("messages" + File.separator + "en" + ".yml"); - } - ConfigurationSection section = config.getConfigurationSection("messages"); - if (section != null) { - prefix = section.getString("prefix", "[CustomCrops] "); - reload = section.getString("reload", "Reloaded! Took {time}ms."); - - spring = section.getString("spring", "Spring"); - summer = section.getString("summer", "Summer"); - autumn = section.getString("autumn", "Autumn"); - winter = section.getString("winter", "Winter"); - noSeason = section.getString("no-season", "Season Disabled"); - } - } - - @Override - public void unload() { - - } - - @Override - public String getSeasonTranslation(Season season) { - if (season == null) return noSeason; - return switch (season) { - case SPRING -> spring; - case SUMMER -> summer; - case AUTUMN -> autumn; - case WINTER -> winter; - }; - } - - @Override - public void reload() { - load(); - } - - @Override - public String getPrefix() { - return prefix; - } - - @Override - protected String getReload() { - return reload; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/PacketManager.java b/plugin/src/main/java/net/momirealms/customcrops/manager/PacketManager.java deleted file mode 100644 index cc6f892..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/PacketManager.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.manager; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolManager; -import com.comphenix.protocol.events.PacketContainer; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class PacketManager { - - private static PacketManager instance; - private final ProtocolManager protocolManager; - private final CustomCropsPlugin plugin; - - public PacketManager(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.protocolManager = ProtocolLibrary.getProtocolManager(); - instance = this; - } - - public static PacketManager getInstance() { - return instance; - } - - public void send(Player player, PacketContainer packet) { - this.protocolManager.sendServerPacket(player, packet); - } - - public void send(Player player, PacketContainer... packets) { - if (!player.isOnline()) return; - if (plugin.getVersionManager().isVersionNewerThan1_19_R3()) { - List bundle = new ArrayList<>(Arrays.asList(packets)); - PacketContainer bundlePacket = new PacketContainer(PacketType.Play.Server.BUNDLE); - bundlePacket.getPacketBundles().write(0, bundle); - send(player, bundlePacket); - } else { - for (PacketContainer packet : packets) { - send(player, packet); - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/manager/PlaceholderManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/manager/PlaceholderManagerImpl.java deleted file mode 100644 index 0e36df4..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/manager/PlaceholderManagerImpl.java +++ /dev/null @@ -1,120 +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.manager; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.PlaceholderManager; -import net.momirealms.customcrops.compatibility.papi.CCPapi; -import net.momirealms.customcrops.compatibility.papi.ParseUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.stream.Collectors; - -public class PlaceholderManagerImpl extends PlaceholderManager { - - private final HashMap customPlaceholderMap; - private final CustomCropsPlugin plugin; - private final boolean hasPapi; - private CCPapi ccPapi; - - public PlaceholderManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.customPlaceholderMap = new HashMap<>(); - this.hasPapi = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); - if (hasPapi) { - ccPapi = new CCPapi(plugin); - } - } - - @Override - public void load() { - if (ccPapi != null) { - ccPapi.register(); - } - this.loadCustomPlaceholders(); - } - - @Override - public void unload() { - if (ccPapi != null) { - ccPapi.unregister(); - } - this.customPlaceholderMap.clear(); - } - - public void loadCustomPlaceholders() { - YamlConfiguration config = ConfigUtils.getConfig("config.yml"); - ConfigurationSection section = config.getConfigurationSection("other-settings.placeholder-register"); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - this.customPlaceholderMap.put(entry.getKey(), (String) entry.getValue()); - } - } - } - - @Override - public String parse(@Nullable Player player, String text, Map placeholders) { - var list = detectPlaceholders(text); - for (String papi : list) { - String replacer = null; - if (placeholders != null) { - replacer = placeholders.get(papi); - } - if (replacer == null) { - String custom = customPlaceholderMap.get(papi); - if (custom != null) { - replacer = setPlaceholders(player, parse(player, custom, placeholders)); - } - } - if (replacer != null) { - text = text.replace(papi, replacer); - } - } - return text; - } - - @Override - public List parse(@Nullable Player player, List list, Map replacements) { - return list.stream() - .map(s -> parse(player, s, replacements)) - .collect(Collectors.toList()); - } - - @Override - public List detectPlaceholders(String text) { - List placeholders = new ArrayList<>(); - Matcher matcher = pattern.matcher(text); - while (matcher.find()) placeholders.add(matcher.group()); - return placeholders; - } - - @Override - public String setPlaceholders(Player player, String text) { - return hasPapi ? ParseUtils.setPlaceholders(player, text) : text; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java deleted file mode 100644 index 3aef046..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/action/ActionManagerImpl.java +++ /dev/null @@ -1,1206 +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.mechanic.action; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.event.CropBreakEvent; -import net.momirealms.customcrops.api.event.CropPlantEvent; -import net.momirealms.customcrops.api.event.PotBreakEvent; -import net.momirealms.customcrops.api.event.SprinklerBreakEvent; -import net.momirealms.customcrops.api.manager.*; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionExpansion; -import net.momirealms.customcrops.api.mechanic.action.ActionFactory; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.QualityCrop; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.Variation; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.YieldIncrease; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.misc.Value; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSprinkler; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import net.momirealms.customcrops.api.util.EventUtils; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.compatibility.VaultHook; -import net.momirealms.customcrops.manager.AdventureManagerImpl; -import net.momirealms.customcrops.manager.HologramManager; -import net.momirealms.customcrops.mechanic.item.impl.VariationCrop; -import net.momirealms.customcrops.mechanic.misc.TempFakeItem; -import net.momirealms.customcrops.mechanic.world.block.MemoryCrop; -import net.momirealms.customcrops.util.ClassUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import net.momirealms.customcrops.util.ItemUtils; -import net.momirealms.customcrops.util.ParticleUtils; -import net.momirealms.sparrow.heart.SparrowHeart; -import net.momirealms.sparrow.heart.feature.inventory.HandSlot; -import org.bukkit.*; -import org.bukkit.block.BlockFace; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.ExperienceOrb; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerItemDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class ActionManagerImpl implements ActionManager { - - private final CustomCropsPlugin plugin; - private final HashMap actionBuilderMap; - private final String EXPANSION_FOLDER = "expansions/action"; - - public ActionManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.actionBuilderMap = new HashMap<>(); - this.registerInbuiltActions(); - } - - private void registerInbuiltActions() { - this.registerMessageAction(); - this.registerCommandAction(); - this.registerMendingAction(); - this.registerExpAction(); - this.registerChainAction(); - this.registerPotionAction(); - this.registerSoundAction(); - this.registerPluginExpAction(); - this.registerTitleAction(); - this.registerActionBarAction(); - this.registerCloseInvAction(); - this.registerDelayedAction(); - this.registerConditionalAction(); - this.registerPriorityAction(); - this.registerLevelAction(); - this.registerFakeItemAction(); - this.registerFoodAction(); - this.registerItemAmountAction(); - this.registerItemDurabilityAction(); - this.registerGiveItemAction(); - this.registerMoneyAction(); - this.registerTimerAction(); - this.registerParticleAction(); - this.registerSwingHandAction(); - this.registerDropItemsAction(); - this.registerBreakAction(); - this.registerPlantAction(); - this.registerQualityCropsAction(); - this.registerVariationAction(); - this.registerForceTickAction(); - this.registerHologramAction(); - this.registerLegacyDropItemsAction(); - } - - @Override - public void load() { - loadExpansions(); - } - - @Override - public void unload() { - - } - - @Override - public void disable() { - actionBuilderMap.clear(); - } - - @Override - public boolean registerAction(String type, ActionFactory actionFactory) { - if (this.actionBuilderMap.containsKey(type)) return false; - this.actionBuilderMap.put(type, actionFactory); - return true; - } - - @Override - public boolean unregisterAction(String type) { - return this.actionBuilderMap.remove(type) != null; - } - - @Override - public Action getAction(ConfigurationSection section) { - ActionFactory factory = getActionFactory(section.getString("type")); - if (factory == null) { - LogUtils.warn("Action type: " + section.getString("type") + " doesn't exist."); - // to prevent NPE - return EmptyAction.instance; - } - return factory.build( - section.get("value"), - section.getDouble("chance", 1d) - ); - } - - @Override - @NotNull - public HashMap getActionMap(ConfigurationSection section) { - HashMap actionMap = new HashMap<>(); - if (section == null) return actionMap; - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - try { - actionMap.put( - ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)), - getActions(innerSection) - ); - } catch (IllegalArgumentException e) { - LogUtils.warn("Event: " + entry.getKey() + " doesn't exist!"); - } - } - } - return actionMap; - } - - @NotNull - @Override - public Action[] getActions(ConfigurationSection section) { - ArrayList actionList = new ArrayList<>(); - if (section == null) return actionList.toArray(new Action[0]); - - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - Action action = getAction(innerSection); - if (action != null) - actionList.add(action); - } - } - return actionList.toArray(new Action[0]); - } - - @Nullable - @Override - public ActionFactory getActionFactory(String type) { - return actionBuilderMap.get(type); - } - - private void registerHologramAction() { - registerAction("hologram", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String text = section.getString("text", ""); - int duration = section.getInt("duration", 20); - double x = section.getDouble("x"); - double y = section.getDouble("y"); - double z = section.getDouble("z"); - boolean applyCorrection = section.getBoolean("apply-correction", false); - boolean onlyShowToOne = !section.getBoolean("visible-to-all", false); - return condition -> { - if (Math.random() > chance) return; - if (condition.getArg("{offline}") != null) return; - Location location = condition.getLocation().clone().add(x,y,z); - SimpleLocation simpleLocation = SimpleLocation.of(location); - if (applyCorrection) { - SimpleLocation cropLocation = simpleLocation.copy().add(0,1,0); - Optional crop = plugin.getWorldManager().getCropAt(cropLocation); - if (crop.isPresent()) { - WorldCrop worldCrop = crop.get(); - Crop config = worldCrop.getConfig(); - Crop.Stage stage = config.getStageByItemID(config.getStageItemByPoint(worldCrop.getPoint())); - if (stage != null) { - location.add(0, stage.getHologramOffset(), 0); - } - } - } - - ArrayList viewers = new ArrayList<>(); - if (onlyShowToOne) { - if (condition.getPlayer() == null) return; - viewers.add(condition.getPlayer()); - } else { - for (Player player : Bukkit.getOnlinePlayers()) { - if (simpleLocation.isNear(SimpleLocation.of(player.getLocation()), 48)) { - viewers.add(player); - } - } - } - Component component = AdventureManager.getInstance().getComponentFromMiniMessage(PlaceholderManager.getInstance().parse(condition.getPlayer(), text, condition.getArgs())); - for (Player viewer : viewers) { - HologramManager.getInstance().showHologram(viewer, location, component, duration * 50); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: hologram"); - return EmptyAction.instance; - } - }); - } - - private void registerFakeItemAction() { - registerAction("fake-item", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String item = section.getString("item", ""); - int duration = section.getInt("duration", 20); - double x = section.getDouble("x"); - double y = section.getDouble("y"); - double z = section.getDouble("z"); - boolean onlyShowToOne = !section.getBoolean("visible-to-all", true); - return condition -> { - if (Math.random() > chance) return; - if (condition.getArg("{offline}") != null) return; - if (item.equals("")) return; - Location location = condition.getLocation().clone().add(x,y,z); - new TempFakeItem(location, item, duration, onlyShowToOne ? condition.getPlayer() : null).start(); - }; - } else { - LogUtils.warn("Illegal value format found at action: fake-item"); - return EmptyAction.instance; - } - }); - } - - private void registerMessageAction() { - registerAction("message", (args, chance) -> { - ArrayList msg = ConfigUtils.stringListArgs(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - List replaced = PlaceholderManager.getInstance().parse( - state.getPlayer(), - msg, - state.getArgs() - ); - for (String text : replaced) { - AdventureManagerImpl.getInstance().sendPlayerMessage(state.getPlayer(), text); - } - }; - }); - registerAction("broadcast", (args, chance) -> { - ArrayList msg = ConfigUtils.stringListArgs(args); - return state -> { - if (Math.random() > chance) return; - List replaced = PlaceholderManager.getInstance().parse( - state.getPlayer(), - msg, - state.getArgs() - ); - for (Player player : Bukkit.getOnlinePlayers()) { - for (String text : replaced) { - AdventureManager.getInstance().sendPlayerMessage(player, text); - } - } - }; - }); - } - - private void registerCommandAction() { - registerAction("command", (args, chance) -> { - ArrayList cmd = ConfigUtils.stringListArgs(args); - return state -> { - if (Math.random() > chance) return; - List replaced = PlaceholderManager.getInstance().parse( - state.getPlayer(), - cmd, - state.getArgs() - ); - plugin.getScheduler().runTaskSync(() -> { - for (String text : replaced) { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text); - } - }, state.getLocation()); - }; - }); - registerAction("random-command", (args, chance) -> { - ArrayList cmd = ConfigUtils.stringListArgs(args); - return state -> { - if (Math.random() > chance) return; - String random = cmd.get(ThreadLocalRandom.current().nextInt(cmd.size())); - random = PlaceholderManager.getInstance().parse(state.getPlayer(), random, state.getArgs()); - String finalRandom = random; - plugin.getScheduler().runTaskSync(() -> { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom); - }, state.getLocation()); - }; - }); - } - - private void registerCloseInvAction() { - registerAction("close-inv", (args, chance) -> state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - state.getPlayer().closeInventory(); - }); - } - - private void registerActionBarAction() { - registerAction("actionbar", (args, chance) -> { - String text = (String) args; - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - String parsed = PlaceholderManager.getInstance().parse(state.getPlayer(), text, state.getArgs()); - AdventureManagerImpl.getInstance().sendActionbar(state.getPlayer(), parsed); - }; - }); - registerAction("random-actionbar", (args, chance) -> { - ArrayList texts = ConfigUtils.stringListArgs(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - String random = texts.get(ThreadLocalRandom.current().nextInt(texts.size())); - random = PlaceholderManager.getInstance().parse(state.getPlayer(), random, state.getArgs()); - AdventureManagerImpl.getInstance().sendActionbar(state.getPlayer(), random); - }; - }); - } - - private void registerMendingAction() { - registerAction("mending", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - if (CustomCropsPlugin.get().getVersionManager().isSpigot()) { - state.getPlayer().getLocation().getWorld().spawn(state.getPlayer().getLocation(), ExperienceOrb.class, e -> e.setExperience((int) value.get(state.getPlayer()))); - } else { - state.getPlayer().giveExp((int) value.get(state.getPlayer()), true); - AdventureManagerImpl.getInstance().sendSound(state.getPlayer(), Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1f, 1f); - } - }; - }); - } - - private void registerFoodAction() { - registerAction("food", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - Player player = state.getPlayer(); - player.setFoodLevel((int) (player.getFoodLevel() + value.get(player))); - }; - }); - registerAction("saturation", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - Player player = state.getPlayer(); - player.setSaturation((float) (player.getSaturation() + value.get(player))); - }; - }); - } - - private void registerExpAction() { - registerAction("exp", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - state.getPlayer().giveExp((int) value.get(state.getPlayer())); - AdventureManagerImpl.getInstance().sendSound(state.getPlayer(), Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1, 1); - }; - }); - } - - private void registerSwingHandAction() { - registerAction("swing-hand", (args, chance) -> { - boolean arg = (boolean) args; - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - SparrowHeart.getInstance().swingHand(state.getPlayer(), arg ? HandSlot.MAIN : HandSlot.OFF); - }; - }); - } - - private void registerForceTickAction() { - registerAction("force-tick", (args, chance) -> state -> { - if (Math.random() > chance) return; - Location location = state.getLocation(); - plugin.getWorldManager().getCustomCropsWorld(location.getWorld()) - .flatMap(world -> world.getLoadedChunkAt(ChunkPos.getByBukkitChunk(location.getChunk()))) - .flatMap(chunk -> chunk.getBlockAt(SimpleLocation.of(location))) - .ifPresent(block -> { - block.tick(1, false); - if (block instanceof WorldSprinkler sprinkler) { - Sprinkler config = sprinkler.getConfig(); - state.setArg("{current}", String.valueOf(sprinkler.getWater())); - state.setArg("{water_bar}", config.getWaterBar() == null ? "" : config.getWaterBar().getWaterBar(sprinkler.getWater(), config.getStorage())); - } else if (block instanceof WorldPot pot) { - Pot config = pot.getConfig(); - state.setArg("{current}", String.valueOf(pot.getWater())); - state.setArg("{water_bar}", config.getWaterBar() == null ? "" : config.getWaterBar().getWaterBar(pot.getWater(), config.getStorage())); - state.setArg("{left_times}", String.valueOf(pot.getFertilizerTimes())); - state.setArg("{max_times}", String.valueOf(Optional.ofNullable(pot.getFertilizer()).map(Fertilizer::getTimes).orElse(0))); - state.setArg("{icon}", Optional.ofNullable(pot.getFertilizer()).map(Fertilizer::getIcon).orElse("")); - } - }); - }); - } - - private void registerVariationAction() { - registerAction("variation", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - boolean ignore = section.getBoolean("ignore-fertilizer", false); - ArrayList variationCrops = new ArrayList<>(); - for (String inner_key : section.getKeys(false)) { - if (inner_key.equals("ignore-fertilizer")) continue; - VariationCrop variationCrop = new VariationCrop( - section.getString(inner_key + ".item"), - ItemCarrier.valueOf(section.getString(inner_key + ".type", "TripWire").toUpperCase(Locale.ENGLISH)), - section.getDouble(inner_key + ".chance") - ); - variationCrops.add(variationCrop); - } - VariationCrop[] variations = variationCrops.toArray(new VariationCrop[0]); - return state -> { - if (Math.random() > chance) return; - double bonus = 0; - if (!ignore) { - Optional pot = plugin.getWorldManager().getPotAt(SimpleLocation.of(state.getLocation().clone().subtract(0,1,0))); - if (pot.isPresent()) { - Fertilizer fertilizer = pot.get().getFertilizer(); - if (fertilizer instanceof Variation variation) { - bonus += variation.getChanceBonus(); - } - } - } - for (VariationCrop variationCrop : variations) { - if (Math.random() < variationCrop.getChance() + bonus) { - SimpleLocation location = SimpleLocation.of(state.getLocation()); - plugin.getItemManager().removeAnythingAt(state.getLocation()); - plugin.getWorldManager().removeAnythingAt(location); - plugin.getItemManager().placeItem(state.getLocation(), variationCrop.getItemCarrier(), variationCrop.getItemID()); - Optional.ofNullable(plugin.getItemManager().getCropStageByStageID(variationCrop.getItemID())) - .ifPresent(stage -> plugin.getWorldManager().addCropAt(new MemoryCrop(location, stage.getCrop().getKey(), stage.getPoint()), location)); - break; - } - } - }; - } else { - LogUtils.warn("Illegal value format found at action: variation"); - return EmptyAction.instance; - } - }); - } - - private void registerQualityCropsAction() { - registerAction("quality-crops", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Value min = ConfigUtils.getValue(section.get("min")); - Value max = ConfigUtils.getValue(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) { - LogUtils.warn("items." + i + " should not be null"); - qualityLoots[i-1] = ""; - } - } - return state -> { - if (Math.random() > chance) return; - double[] ratio = ConfigManager.defaultQualityRatio(); - int random = (int) ThreadLocalRandom.current().nextDouble(min.get(state.getPlayer()), max.get(state.getPlayer()) + 1); - Optional pot = plugin.getWorldManager().getPotAt(SimpleLocation.of(state.getLocation().clone().subtract(0,1,0))); - if (pot.isPresent()) { - Fertilizer fertilizer = pot.get().getFertilizer(); - if (fertilizer instanceof YieldIncrease yieldIncrease) { - random += yieldIncrease.getAmountBonus(); - } else if (fertilizer instanceof QualityCrop qualityCrop && Math.random() < qualityCrop.getChance()) { - ratio = qualityCrop.getRatio(); - } - } - 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().getItemStack(state.getPlayer(), qualityLoots[j]); - if (drop == null || drop.getType() == Material.AIR) return; - if (toInv && state.getPlayer() != null) { - ItemUtils.giveItem(state.getPlayer(), drop, 1); - } else { - state.getLocation().getWorld().dropItemNaturally(state.getLocation(), drop); - } - break; - } - } - } - }; - } else { - LogUtils.warn("Illegal value format found at action: quality-crops"); - return EmptyAction.instance; - } - }); - } - - private void registerDropItemsAction() { - registerAction("drop-item", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - boolean ignoreFertilizer = section.getBoolean("ignore-fertilizer", true); - String item = section.getString("item"); - Value min = ConfigUtils.getValue(section.get("min")); - Value max = ConfigUtils.getValue(section.get("max")); - boolean toInv = section.getBoolean("to-inventory", false); - return state -> { - if (Math.random() > chance) return; - ItemStack itemStack = plugin.getItemManager().getItemStack(state.getPlayer(), item); - if (itemStack != null) { - int random = (int) ThreadLocalRandom.current().nextDouble(min.get(state.getPlayer()), (max.get(state.getPlayer()) + 1)); - if (!ignoreFertilizer) { - Optional pot = plugin.getWorldManager().getPotAt(SimpleLocation.of(state.getLocation().clone().subtract(0,1,0))); - if (pot.isPresent()) { - Fertilizer fertilizer = pot.get().getFertilizer(); - if (fertilizer instanceof YieldIncrease yieldIncrease) { - random += yieldIncrease.getAmountBonus(); - } - } - } - itemStack.setAmount(random); - if (toInv && state.getPlayer() != null) { - ItemUtils.giveItem(state.getPlayer(), itemStack, random); - } else { - state.getLocation().getWorld().dropItemNaturally(state.getLocation(), itemStack); - } - } else { - LogUtils.warn("Item: " + item + " doesn't exist"); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: drop-items"); - return EmptyAction.instance; - } - }); - } - - private void registerLegacyDropItemsAction() { - registerAction("drop-items", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List actions = new ArrayList<>(); - ConfigurationSection otherItemSection = section.getConfigurationSection("other-items"); - if (otherItemSection != null) { - for (Map.Entry entry : otherItemSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - actions.add(getActionFactory("drop-item").build(inner, inner.getDouble("chance", 1))); - } - } - } - ConfigurationSection qualitySection = section.getConfigurationSection("quality-crops"); - if (qualitySection != null) { - actions.add(getActionFactory("quality-crops").build(qualitySection, 1)); - } - return state -> { - if (Math.random() > chance) return; - for (Action action : actions) { - action.trigger(state); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: drop-items"); - return EmptyAction.instance; - } - }); - } - - private void registerPlantAction() { - for (String name : List.of("plant", "replant")) { - registerAction(name, (args, chance) -> { - if (args instanceof ConfigurationSection section) { - int point = section.getInt("point", 0); - String key = section.getString("crop"); - return state -> { - if (Math.random() > chance) return; - if (key == null) return; - Crop crop = plugin.getItemManager().getCropByID(key); - if (crop == null) { - LogUtils.warn("Crop: " + key + " doesn't exist."); - return; - } - Location location = state.getLocation(); - Pot pot = plugin.getItemManager().getPotByBlock(location.getBlock().getRelative(BlockFace.DOWN)); - if (pot == null) { - plugin.debug("Crop should be planted on a pot at " + location); - return; - } - // check whitelist - if (!crop.getPotWhitelist().contains(pot.getKey())) { - crop.trigger(ActionTrigger.WRONG_POT, state); - return; - } - // check plant requirements - if (!RequirementManager.isRequirementMet(state, crop.getPlantRequirements())) { - return; - } - // check limitation - if (plugin.getWorldManager().isReachLimit(SimpleLocation.of(location), ItemType.CROP)) { - crop.trigger(ActionTrigger.REACH_LIMIT, state); - return; - } - plugin.getScheduler().runTaskSync(() -> { - // fire event - if (state.getPlayer() != null) { - CropPlantEvent plantEvent = new CropPlantEvent(state.getPlayer(), state.getItemInHand(), location, crop, point); - if (EventUtils.fireAndCheckCancel(plantEvent)) { - return; - } - - plugin.getItemManager().placeItem(location, crop.getItemCarrier(), crop.getStageItemByPoint(plantEvent.getPoint()), crop.hasRotation() ? CRotation.RANDOM : CRotation.NONE); - plugin.getWorldManager().addCropAt(new MemoryCrop(SimpleLocation.of(location), crop.getKey(), plantEvent.getPoint()), SimpleLocation.of(location)); - } else { - plugin.getItemManager().placeItem(location, crop.getItemCarrier(), crop.getStageItemByPoint(point), crop.hasRotation() ? CRotation.RANDOM : CRotation.NONE); - plugin.getWorldManager().addCropAt(new MemoryCrop(SimpleLocation.of(location), crop.getKey(), point), SimpleLocation.of(location)); - } - }, state.getLocation()); - }; - } else { - LogUtils.warn("Illegal value format found at action: " + name); - return EmptyAction.instance; - } - }); - } - } - - private void registerBreakAction() { - registerAction("break", (args, chance) -> { - boolean arg = (boolean) (args == null ? true : args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) { - LogUtils.warn("Break action can only be triggered by players"); - return; - } - plugin.getScheduler().runTaskSync(() -> { - Optional removed = plugin.getWorldManager().getBlockAt(SimpleLocation.of(state.getLocation())); - if (removed.isPresent()) { - switch (removed.get().getType()) { - case SPRINKLER -> { - WorldSprinkler sprinkler = (WorldSprinkler) removed.get(); - SprinklerBreakEvent event = new SprinklerBreakEvent(state.getPlayer(), state.getLocation(), sprinkler, Reason.ACTION); - if (EventUtils.fireAndCheckCancel(event)) - return; - if (arg) sprinkler.getConfig().trigger(ActionTrigger.BREAK, state); - plugin.getItemManager().removeAnythingAt(state.getLocation()); - plugin.getWorldManager().removeAnythingAt(SimpleLocation.of(state.getLocation())); - } - case CROP -> { - WorldCrop crop = (WorldCrop) removed.get(); - CropBreakEvent event = new CropBreakEvent(state.getPlayer(), state.getLocation(), crop, Reason.ACTION); - if (EventUtils.fireAndCheckCancel(event)) - return; - Crop cropConfig = crop.getConfig(); - if (arg) { - cropConfig.trigger(ActionTrigger.BREAK, state); - cropConfig.getStageByItemID(cropConfig.getStageItemByPoint(crop.getPoint())).trigger(ActionTrigger.BREAK, state); - } - plugin.getItemManager().removeAnythingAt(state.getLocation()); - plugin.getWorldManager().removeAnythingAt(SimpleLocation.of(state.getLocation())); - } - case POT -> { - WorldPot pot = (WorldPot) removed.get(); - PotBreakEvent event = new PotBreakEvent(state.getPlayer(), state.getLocation(), pot, Reason.ACTION); - if (EventUtils.fireAndCheckCancel(event)) - return; - if (arg) pot.getConfig().trigger(ActionTrigger.BREAK, state); - plugin.getItemManager().removeAnythingAt(state.getLocation()); - plugin.getWorldManager().removeAnythingAt(SimpleLocation.of(state.getLocation())); - } - } - } else { - plugin.getItemManager().removeAnythingAt(state.getLocation()); - } - }, state.getLocation()); - }; - }); - } - - private void registerParticleAction() { - registerAction("particle", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Particle particleType = ParticleUtils.getParticle(section.getString("particle", "ASH").toUpperCase(Locale.ENGLISH)); - double x = section.getDouble("x",0); - double y = section.getDouble("y",0); - double z = section.getDouble("z",0); - double offSetX = section.getDouble("offset-x",0); - double offSetY = section.getDouble("offset-y",0); - double offSetZ = section.getDouble("offset-z",0); - int count = section.getInt("count", 1); - double extra = section.getDouble("extra", 0); - float scale = (float) section.getDouble("scale", 1d); - - ItemStack itemStack; - if (section.contains("itemStack")) - itemStack = CustomCropsPlugin.get() - .getItemManager() - .getItemStack(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 state -> { - if (Math.random() > chance) return; - state.getLocation().getWorld().spawnParticle( - particleType, - state.getLocation().getX() + x, - state.getLocation().getY() + y, - state.getLocation().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 { - LogUtils.warn("Illegal value format found at action: particle"); - return EmptyAction.instance; - } - }); - } - - private void registerItemAmountAction() { - registerAction("item-amount", (args, chance) -> { - int amount = (int) args; - return state -> { - if (Math.random() > chance) return; - Player player = state.getPlayer(); - if (player == null) return; - ItemStack itemStack = player.getInventory().getItemInMainHand(); - itemStack.setAmount(Math.max(0, itemStack.getAmount() + amount)); - }; - }); - } - - private void registerItemDurabilityAction() { - registerAction("durability", (args, chance) -> { - int amount = (int) args; - return state -> { - if (Math.random() > chance) return; - ItemStack itemStack = state.getItemInHand(); - if (itemStack.getItemMeta() == null) - return; - if (amount > 0) { - ItemUtils.increaseDurability(itemStack, amount); - } else { - if (state.getPlayer().getGameMode() == GameMode.CREATIVE || itemStack.getItemMeta().isUnbreakable()) return; - - ItemMeta previousMeta = itemStack.getItemMeta().clone(); - PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(state.getPlayer(), itemStack, amount); - Bukkit.getPluginManager().callEvent(itemDamageEvent); - if (!itemStack.getItemMeta().equals(previousMeta) || itemDamageEvent.isCancelled()) { - return; - } - ItemUtils.decreaseDurability(state.getPlayer(), itemStack, -amount); - } - }; - }); - } - - private void registerGiveItemAction() { - registerAction("give-item", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String id = section.getString("item"); - int amount = section.getInt("amount", 1); - return state -> { - if (Math.random() > chance) return; - Player player = state.getPlayer(); - if (player == null) return; - ItemUtils.giveItem(player, Objects.requireNonNull(CustomCropsPlugin.get().getItemManager().getItemStack(player, id)), amount); - }; - } else { - LogUtils.warn("Illegal value format found at action: give-item"); - return EmptyAction.instance; - } - }); - } - - private void registerChainAction() { - registerAction("chain", (args, chance) -> { - List actions = new ArrayList<>(); - if (args instanceof ConfigurationSection section) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - return state -> { - if (Math.random() > chance) return; - for (Action action : actions) { - action.trigger(state); - } - }; - }); - } - - private void registerMoneyAction() { - registerAction("give-money", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - VaultHook.getEconomy().depositPlayer(state.getPlayer(), value.get(state.getPlayer())); - }; - }); - registerAction("take-money", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - VaultHook.getEconomy().withdrawPlayer(state.getPlayer(), value.get(state.getPlayer())); - }; - }); - } - - private void registerDelayedAction() { - registerAction("delay", (args, chance) -> { - List actions = new ArrayList<>(); - int delay; - boolean async; - if (args instanceof ConfigurationSection section) { - delay = section.getInt("delay", 1); - async = section.getBoolean("async", false); - ConfigurationSection actionSection = section.getConfigurationSection("actions"); - if (actionSection != null) { - for (Map.Entry entry : actionSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - } else { - delay = 1; - async = false; - } - return state -> { - if (Math.random() > chance) return; - if (async) { - plugin.getScheduler().runTaskSyncLater(() -> { - for (Action action : actions) { - action.trigger(state); - } - }, state.getLocation(), delay * 50L, TimeUnit.MILLISECONDS); - } else { - plugin.getScheduler().runTaskSyncLater(() -> { - for (Action action : actions) { - action.trigger(state); - } - }, state.getLocation(), delay * 50L, TimeUnit.MILLISECONDS); - } - }; - }); - } - - private void registerTimerAction() { - registerAction("timer", (args, chance) -> { - List actions = new ArrayList<>(); - int delay; - int duration; - int period; - boolean async; - if (args instanceof ConfigurationSection section) { - delay = section.getInt("delay", 2); - duration = section.getInt("duration", 20); - period = section.getInt("period", 2); - async = section.getBoolean("async", false); - ConfigurationSection actionSection = section.getConfigurationSection("actions"); - if (actionSection != null) { - for (Map.Entry entry : actionSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - actions.add(getAction(innerSection)); - } - } - } - } else { - delay = 1; - async = false; - duration = 20; - period = 1; - } - return state -> { - if (Math.random() > chance) return; - CancellableTask cancellableTask; - if (async) { - cancellableTask = plugin.getScheduler().runTaskAsyncTimer(() -> { - for (Action action : actions) { - action.trigger(state); - } - }, delay * 50L, period * 50L, TimeUnit.MILLISECONDS); - } else { - cancellableTask = plugin.getScheduler().runTaskSyncTimer(() -> { - for (Action action : actions) { - action.trigger(state); - } - }, state.getLocation(), delay, period); - } - plugin.getScheduler().runTaskSyncLater(cancellableTask::cancel, state.getLocation(), duration); - }; - }); - } - - private void registerTitleAction() { - registerAction("title", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String title = section.getString("title"); - String subtitle = section.getString("subtitle"); - int fadeIn = section.getInt("fade-in", 20); - int stay = section.getInt("stay", 30); - int fadeOut = section.getInt("fade-out", 10); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - AdventureManagerImpl.getInstance().sendTitle( - state.getPlayer(), - PlaceholderManager.getInstance().parse(state.getPlayer(), title, state.getArgs()), - PlaceholderManager.getInstance().parse(state.getPlayer(), subtitle, state.getArgs()), - fadeIn, - stay, - fadeOut - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: title"); - return EmptyAction.instance; - } - }); - registerAction("random-title", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List titles = section.getStringList("titles"); - if (titles.size() == 0) titles.add(""); - List subtitles = section.getStringList("subtitles"); - if (subtitles.size() == 0) subtitles.add(""); - int fadeIn = section.getInt("fade-in", 20); - int stay = section.getInt("stay", 30); - int fadeOut = section.getInt("fade-out", 10); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - AdventureManagerImpl.getInstance().sendTitle( - state.getPlayer(), - PlaceholderManager.getInstance().parse(state.getPlayer(), titles.get(ThreadLocalRandom.current().nextInt(titles.size())), state.getArgs()), - PlaceholderManager.getInstance().parse(state.getPlayer(), subtitles.get(ThreadLocalRandom.current().nextInt(subtitles.size())), state.getArgs()), - fadeIn, - stay, - fadeOut - ); - }; - } else { - LogUtils.warn("Illegal value format found at action: random-title"); - return EmptyAction.instance; - } - }); - } - - private void registerPotionAction() { - registerAction("potion-effect", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - PotionEffect potionEffect = new PotionEffect( - Objects.requireNonNull(PotionEffectType.getByName(section.getString("type", "BLINDNESS").toUpperCase(Locale.ENGLISH))), - section.getInt("duration", 20), - section.getInt("amplifier", 0) - ); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - state.getPlayer().addPotionEffect(potionEffect); - }; - } else { - LogUtils.warn("Illegal value format found at action: potion-effect"); - return EmptyAction.instance; - } - }); - } - - private void registerLevelAction() { - registerAction("level", (args, chance) -> { - var value = ConfigUtils.getValue(args); - return state -> { - if (Math.random() > chance) return; - Player player = state.getPlayer(); - if (player == null) return; - player.setLevel((int) Math.max(0, player.getLevel() + value.get(state.getPlayer()))); - }; - }); - } - - @SuppressWarnings("all") - private void registerSoundAction() { - registerAction("sound", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Sound sound = Sound.sound( - Key.key(section.getString("key")), - Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)), - (float) section.getDouble("volume", 1), - (float) section.getDouble("pitch", 1) - ); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - AdventureManagerImpl.getInstance().sendSound(state.getPlayer(), sound); - }; - } else { - LogUtils.warn("Illegal value format found at action: sound"); - return EmptyAction.instance; - } - }); - } - - private void registerConditionalAction() { - registerAction("conditional", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - Action[] actions = getActions(section.getConfigurationSection("actions")); - Requirement[] requirements = plugin.getRequirementManager().getRequirements(section.getConfigurationSection("conditions"), true); - return state -> { - if (Math.random() > chance) return; - if (requirements != null) - for (Requirement requirement : requirements) { - if (!requirement.isStateMet(state)) { - return; - } - } - for (Action action : actions) { - action.trigger(state); - } - }; - } else { - LogUtils.warn("Illegal value format found at action: conditional"); - return EmptyAction.instance; - } - }); - } - - private void registerPriorityAction() { - registerAction("priority", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - List> stateActionPairList = new ArrayList<>(); - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - Action[] actions = getActions(inner.getConfigurationSection("actions")); - Requirement[] requirements = plugin.getRequirementManager().getRequirements(inner.getConfigurationSection("states"), false); - stateActionPairList.add(Pair.of(requirements, actions)); - } - } - return state -> { - if (Math.random() > chance) return; - outer: - for (Pair pair : stateActionPairList) { - if (pair.left() != null) - for (Requirement requirement : pair.left()) { - if (!requirement.isStateMet(state)) { - continue outer; - } - } - if (pair.right() != null) - for (Action action : pair.right()) { - action.trigger(state); - } - return; - } - }; - } else { - LogUtils.warn("Illegal value format found at action: priority"); - return EmptyAction.instance; - } - }); - } - - private void registerPluginExpAction() { - registerAction("plugin-exp", (args, chance) -> { - if (args instanceof ConfigurationSection section) { - String pluginName = section.getString("plugin"); - var value = ConfigUtils.getValue(section.get("exp")); - String target = section.getString("target"); - return state -> { - if (Math.random() > chance) return; - if (state.getPlayer() == null) return; - Optional.ofNullable(plugin.getIntegrationManager().getLevelPlugin(pluginName)).ifPresentOrElse(it -> { - it.addXp(state.getPlayer(), target, value.get(state.getPlayer())); - }, () -> LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.")); - }; - } else { - LogUtils.warn("Illegal value format found at action: plugin-exp"); - return EmptyAction.instance; - } - }); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, ActionExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterAction(expansion.getActionType()); - registerAction(expansion.getActionType(), expansion.getActionFactory()); - LogUtils.info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() ); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/ConditionManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/ConditionManagerImpl.java deleted file mode 100644 index d57bd30..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/condition/ConditionManagerImpl.java +++ /dev/null @@ -1,760 +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.mechanic.condition; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.manager.ConditionManager; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.mechanic.condition.Condition; -import net.momirealms.customcrops.api.mechanic.condition.ConditionExpansion; -import net.momirealms.customcrops.api.mechanic.condition.ConditionFactory; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.compatibility.papi.ParseUtils; -import net.momirealms.customcrops.mechanic.misc.CrowAttackAnimation; -import net.momirealms.customcrops.mechanic.world.block.MemoryCrop; -import net.momirealms.customcrops.util.ClassUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import net.momirealms.sparrow.heart.SparrowHeart; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.type.Farmland; -import org.bukkit.configuration.ConfigurationSection; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; - -public class ConditionManagerImpl implements ConditionManager { - - private final String EXPANSION_FOLDER = "expansions/condition"; - private final HashMap conditionBuilderMap; - private final CustomCropsPlugin plugin; - - public ConditionManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.conditionBuilderMap = new HashMap<>(); - this.registerInbuiltConditions(); - } - - @Override - public void load() { - this.loadExpansions(); - } - - @Override - public void unload() { - - } - - @Override - public void disable() { - this.conditionBuilderMap.clear(); - } - - private void registerInbuiltConditions() { - this.registerSeasonCondition(); - this.registerWaterCondition(); - this.registerTemperatureCondition(); - this.registerAndCondition(); - this.registerOrCondition(); - this.registerRandomCondition(); - this.registerEqualsCondition(); - this.registerNumberEqualCondition(); - this.registerRegexCondition(); - this.registerGreaterThanCondition(); - this.registerLessThanCondition(); - this.registerContainCondition(); - this.registerStartWithCondition(); - this.registerEndWithCondition(); - this.registerInListCondition(); - this.registerBiomeRequirement(); - this.registerFertilizerCondition(); - this.registerCrowAttackCondition(); - this.registerPotCondition(); - this.registerLightCondition(); - this.registerPointCondition(); - this.registerWorldRequirement(); - } - - @Override - public boolean registerCondition(String type, ConditionFactory conditionFactory) { - if (this.conditionBuilderMap.containsKey(type)) return false; - this.conditionBuilderMap.put(type, conditionFactory); - return true; - } - - @Override - public boolean unregisterCondition(String type) { - return this.conditionBuilderMap.remove(type) != null; - } - - @Override - public @NotNull Condition[] getConditions(ConfigurationSection section) { - ArrayList conditions = new ArrayList<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - String key = entry.getKey(); - if (hasCondition(key)) { - conditions.add(getCondition(key, innerSection)); - } else { - conditions.add(getCondition(section.getConfigurationSection(key))); - } - } - } - } - return conditions.toArray(new Condition[0]); - } - - @Override - public Condition getCondition(ConfigurationSection section) { - if (section == null) { - LogUtils.warn("Condition section should not be null"); - return EmptyCondition.instance; - } - return getCondition(section.getString("type"), section.get("value")); - } - - @Override - public Condition getCondition(String key, Object args) { - if (key == null) { - LogUtils.warn("Condition type should not be null"); - return EmptyCondition.instance; - } - ConditionFactory factory = getConditionFactory(key); - if (factory == null) { - LogUtils.warn("Condition type: " + key + " doesn't exist."); - return EmptyCondition.instance; - } - return factory.build(args); - } - - @Nullable - @Override - public ConditionFactory getConditionFactory(String type) { - return conditionBuilderMap.get(type); - } - - private void registerCrowAttackCondition() { - registerCondition("crow_attack", (args -> { - if (args instanceof ConfigurationSection section) { - String flyModel = section.getString("fly-model"); - String standModel = section.getString("stand-model"); - double chance = section.getDouble("chance"); - return (block, offline) -> { - if (Math.random() > chance) return false; - SimpleLocation location = block.getLocation(); - if (ConfigManager.enableScarecrow()) { - Optional world = plugin.getWorldManager().getCustomCropsWorld(location.getWorldName()); - if (world.isEmpty()) return false; - CustomCropsWorld customCropsWorld = world.get(); - if (!ConfigManager.scarecrowProtectChunk()) { - int range = ConfigManager.scarecrowRange(); - for (int i = -range; i <= range; i++) { - for (int j = -range; j <= range; j++) { - for (int k : new int[]{0,-1,1}) { - if (customCropsWorld.getScarecrowAt(location.copy().add(i, k, j)).isPresent()) { - return false; - } - } - } - } - } else { - if (customCropsWorld.doesChunkHaveScarecrow(location)) { - return false; - } - } - } - if (!offline) - new CrowAttackAnimation(location, flyModel, standModel).start(); - return true; - }; - } else { - LogUtils.warn("Wrong value format found at crow-attack condition."); - return EmptyCondition.instance; - } - })); - } - - private void registerWorldRequirement() { - registerCondition("world", (args) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> worlds.contains(block.getLocation().getWorldName()); - }); - registerCondition("!world", (args) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> !worlds.contains(block.getLocation().getWorldName()); - }); - } - - private void registerBiomeRequirement() { - registerCondition("biome", (args) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(block.getLocation().getBukkitLocation()); - return biomes.contains(currentBiome); - }; - }); - registerCondition("!biome", (args) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(block.getLocation().getBukkitLocation()); - return !biomes.contains(currentBiome); - }; - }); - } - - private void registerRandomCondition() { - registerCondition("random", (args -> { - double value = ConfigUtils.getDoubleValue(args); - return (block, offline) -> Math.random() < value; - })); - } - - private void registerPotCondition() { - registerCondition("pot", (args -> { - HashSet pots = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> pots.contains(pot.getKey())).isPresent(); - }; - })); - registerCondition("!pot", (args -> { - HashSet pots = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> !pots.contains(pot.getKey())).isPresent(); - }; - })); - } - - private void registerFertilizerCondition() { - registerCondition("fertilizer", (args -> { - HashSet fertilizer = new HashSet<>(ConfigUtils.stringListArgs(args)); - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> { - Fertilizer fertilizerInstance = pot.getFertilizer(); - if (fertilizerInstance == null) return false; - return fertilizer.contains(fertilizerInstance.getKey()); - }).isPresent(); - }; - })); - registerCondition("fertilizer_type", (args -> { - HashSet fertilizer = new HashSet<>(ConfigUtils.stringListArgs(args).stream().map(str -> str.toUpperCase(Locale.ENGLISH)).toList()); - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> { - Fertilizer fertilizerInstance = pot.getFertilizer(); - if (fertilizerInstance == null) return false; - return fertilizer.contains(fertilizerInstance.getFertilizerType().name()); - }).isPresent(); - }; - })); - } - - private void registerAndCondition() { - registerCondition("&&", (args -> { - if (args instanceof ConfigurationSection section) { - Condition[] conditions = getConditions(section); - return (block, offline) -> ConditionManager.isConditionMet(block, offline, conditions); - } else { - LogUtils.warn("Wrong value format found at && condition."); - return EmptyCondition.instance; - } - })); - } - - private void registerOrCondition() { - registerCondition("||", (args -> { - if (args instanceof ConfigurationSection section) { - Condition[] conditions = getConditions(section); - return (block, offline) -> { - for (Condition condition : conditions) { - if (condition.isConditionMet(block, offline)) { - return true; - } - } - return false; - }; - } else { - LogUtils.warn("Wrong value format found at || condition."); - return EmptyCondition.instance; - } - })); - } - - private void registerTemperatureCondition() { - registerCondition("temperature", (args) -> { - List> tempPairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return (block, offline) -> { - SimpleLocation location = block.getLocation(); - World world = location.getBukkitWorld(); - if (world == null) return false; - double temp = world.getTemperature(location.getX(), location.getY(), location.getZ()); - for (Pair pair : tempPairs) { - if (temp >= pair.left() && temp <= pair.right()) { - return true; - } - } - return false; - }; - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerGreaterThanCondition() { - registerCondition(">=", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) >= Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at >= requirement."); - return EmptyCondition.instance; - } - }); - registerCondition(">", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) > Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at > requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerRegexCondition() { - registerCondition("regex", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("papi", ""); - String v2 = section.getString("regex", ""); - return (block, offline) -> ParseUtils.setPlaceholders(null, v1).matches(v2); - } else { - LogUtils.warn("Wrong value format found at regex requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerNumberEqualCondition() { - registerCondition("==", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) == Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!=", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) != Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyCondition.instance; - } - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerLessThanCondition() { - registerCondition("<", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) < Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at < requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("<=", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return Double.parseDouble(p1) <= Double.parseDouble(p2); - }; - } else { - LogUtils.warn("Wrong value format found at <= requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerStartWithCondition() { - registerCondition("startsWith", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return p1.startsWith(p2); - }; - } else { - LogUtils.warn("Wrong value format found at startsWith requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!startsWith", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return !p1.startsWith(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerEndWithCondition() { - registerCondition("endsWith", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return p1.endsWith(p2); - }; - } else { - LogUtils.warn("Wrong value format found at endsWith requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!endsWith", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return !p1.endsWith(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !endsWith requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerContainCondition() { - registerCondition("contains", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return p1.contains(p2); - }; - } else { - LogUtils.warn("Wrong value format found at contains requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!contains", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return !p1.contains(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !contains requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerInListCondition() { - registerCondition("in-list", (args) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - HashSet values = new HashSet<>(ConfigUtils.stringListArgs(section.get("values"))); - return (block, offline) -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(null, papi) : papi; - return values.contains(p1); - }; - } else { - LogUtils.warn("Wrong value format found at in-list requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!in-list", (args) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - HashSet values = new HashSet<>(ConfigUtils.stringListArgs(section.get("values"))); - return (block, offline) -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(null, papi) : papi; - return !values.contains(p1); - }; - } else { - LogUtils.warn("Wrong value format found at in-list requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerEqualsCondition() { - registerCondition("equals", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return p1.equals(p2); - }; - } else { - LogUtils.warn("Wrong value format found at equals requirement."); - return EmptyCondition.instance; - } - }); - registerCondition("!equals", (args) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return (block, offline) -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(null, v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(null, v2) : v2; - return !p1.equals(p2); - }; - } else { - LogUtils.warn("Wrong value format found at !equals requirement."); - return EmptyCondition.instance; - } - }); - } - - private void registerSeasonCondition() { - registerCondition("suitable_season", (args) -> { - HashSet seasons = new HashSet<>(ConfigUtils.stringListArgs(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); - return (block, offline) -> { - Season season = plugin.getIntegrationManager().getSeasonInterface().getSeason(block.getLocation().getBukkitWorld()); - if (season == null) { - return true; - } - if (seasons.contains(season.name())) { - return true; - } - if (ConfigManager.enableGreenhouse()) { - SimpleLocation location = block.getLocation(); - Optional world = plugin.getWorldManager().getCustomCropsWorld(location.getWorldName()); - if (world.isEmpty()) return false; - CustomCropsWorld customCropsWorld = world.get(); - for (int i = 1, range = ConfigManager.greenhouseRange(); i <= range; i++) { - if (customCropsWorld.getGlassAt(location.copy().add(0,i,0)).isPresent()) { - return true; - } - } - } - return false; - }; - }); - registerCondition("unsuitable_season", (args) -> { - HashSet seasons = new HashSet<>(ConfigUtils.stringListArgs(args).stream().map(it -> it.toUpperCase(Locale.ENGLISH)).toList()); - return (block, offline) -> { - Season season = plugin.getIntegrationManager().getSeasonInterface().getSeason(block.getLocation().getBukkitWorld()); - if (season == null) { - return false; - } - if (seasons.contains(season.name())) { - if (ConfigManager.enableGreenhouse()) { - SimpleLocation location = block.getLocation(); - Optional world = plugin.getWorldManager().getCustomCropsWorld(location.getWorldName()); - if (world.isEmpty()) return false; - CustomCropsWorld customCropsWorld = world.get(); - for (int i = 1, range = ConfigManager.greenhouseRange(); i <= range; i++) { - if (customCropsWorld.getGlassAt(location.copy().add(0,i,0)).isPresent()) { - return false; - } - } - } - return true; - } - return false; - }; - }); - } - - private void registerLightCondition() { - registerCondition("skylight_more_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - int light = block.getLocation().getBukkitLocation().getBlock().getLightFromSky(); - return value > light; - }; - }); - registerCondition("skylight_less_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - int light = block.getLocation().getBukkitLocation().getBlock().getLightFromSky(); - return value < light; - }; - }); - registerCondition("light_more_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - int light = block.getLocation().getBukkitLocation().getBlock().getLightLevel(); - return value > light; - }; - }); - registerCondition("light_less_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - int light = block.getLocation().getBukkitLocation().getBlock().getLightLevel(); - return value < light; - }; - }); - } - - private void registerPointCondition() { - registerCondition("point_more_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - if (block instanceof MemoryCrop crop) { - return crop.getPoint() > value; - } - return false; - }; - }); - registerCondition("point_less_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - if (block instanceof MemoryCrop crop) { - return crop.getPoint() < value; - } - return false; - }; - }); - } - - private void registerWaterCondition() { - registerCondition("water_more_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> pot.getWater() > value).isPresent(); - }; - }); - registerCondition("water_less_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - Optional worldPot = plugin.getWorldManager().getPotAt(block.getLocation().copy().add(0,-1,0)); - return worldPot.filter(pot -> pot.getWater() < value).isPresent(); - }; - }); - registerCondition("moisture_more_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - Block underBlock = block.getLocation().copy().add(0,-1,0).getBukkitLocation().getBlock(); - if (underBlock.getBlockData() instanceof Farmland farmland) { - return farmland.getMoisture() > value; - } - return false; - }; - }); - registerCondition("moisture_less_than", (args) -> { - int value = (int) args; - return (block, offline) -> { - Block underBlock = block.getLocation().copy().add(0,-1,0).getBukkitLocation().getBlock(); - if (underBlock.getBlockData() instanceof Farmland farmland) { - return farmland.getMoisture() < value; - } - return false; - }; - }); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, ConditionExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - ConditionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterCondition(expansion.getConditionType()); - registerCondition(expansion.getConditionType(), expansion.getConditionFactory()); - LogUtils.info("Loaded condition expansion: " + expansion.getConditionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor()); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/AbstractEventItem.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/AbstractEventItem.java deleted file mode 100644 index 92685f1..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/AbstractEventItem.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item; - -import net.momirealms.customcrops.api.common.item.EventItem; -import net.momirealms.customcrops.api.manager.ActionManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.requirement.State; - -import java.util.HashMap; - -public abstract class AbstractEventItem implements EventItem { - - private final HashMap actionMap; - - public AbstractEventItem(HashMap actionMap) { - this.actionMap = actionMap; - } - - @Override - public void trigger(ActionTrigger actionTrigger, State state) { - Action[] actions = actionMap.get(actionTrigger); - if (actions != null) { - ActionManager.triggerActions(state, actions); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java deleted file mode 100644 index 035cd66..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/ItemManagerImpl.java +++ /dev/null @@ -1,2766 +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.mechanic.item; - -import com.google.common.base.Preconditions; -import net.momirealms.antigrieflib.AntiGriefLib; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.event.*; -import net.momirealms.customcrops.api.integration.ItemLibrary; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.ItemManager; -import net.momirealms.customcrops.api.manager.RequirementManager; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.condition.Conditions; -import net.momirealms.customcrops.api.mechanic.condition.DeathConditions; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.item.custom.AbstractCustomListener; -import net.momirealms.customcrops.api.mechanic.item.custom.CustomProvider; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.item.water.PositiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import net.momirealms.customcrops.api.mechanic.misc.Reason; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.*; -import net.momirealms.customcrops.api.util.EventUtils; -import net.momirealms.customcrops.api.util.LocationUtils; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.item.custom.itemsadder.ItemsAdderListener; -import net.momirealms.customcrops.mechanic.item.custom.itemsadder.ItemsAdderProvider; -import net.momirealms.customcrops.mechanic.item.custom.oraxenlegacy.LegacyOraxenListener; -import net.momirealms.customcrops.mechanic.item.custom.oraxenlegacy.LegacyOraxenProvider; -import net.momirealms.customcrops.mechanic.item.function.CFunction; -import net.momirealms.customcrops.mechanic.item.function.FunctionResult; -import net.momirealms.customcrops.mechanic.item.function.FunctionTrigger; -import net.momirealms.customcrops.mechanic.item.function.wrapper.*; -import net.momirealms.customcrops.mechanic.item.impl.CropConfig; -import net.momirealms.customcrops.mechanic.item.impl.PotConfig; -import net.momirealms.customcrops.mechanic.item.impl.SprinklerConfig; -import net.momirealms.customcrops.mechanic.item.impl.WateringCanConfig; -import net.momirealms.customcrops.mechanic.item.impl.fertilizer.*; -import net.momirealms.customcrops.mechanic.world.block.*; -import net.momirealms.customcrops.util.ConfigUtils; -import net.momirealms.customcrops.util.ItemUtils; -import net.momirealms.customcrops.util.RotationUtils; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.Waterlogged; -import org.bukkit.block.data.type.Farmland; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Entity; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.lang.reflect.Constructor; -import java.util.*; - -public class ItemManagerImpl implements ItemManager { - - private final CustomCropsPlugin plugin; - private AbstractCustomListener listener; - private CustomProvider customProvider; - private final HashMap itemLibraryMap; - private ItemLibrary[] itemDetectionArray; - private final HashMap>> itemID2FunctionMap; - private final HashMap id2WateringCanMap; - private final HashMap item2WateringCanMap; - private final HashMap id2SprinklerMap; - private final HashMap twoDItem2SprinklerMap; - private final HashMap threeDItem2SprinklerMap; - private final HashMap id2PotMap; - private final HashMap item2PotMap; - private final HashMap stage2CropMap; - private final HashMap seed2CropMap; - private final HashMap id2CropMap; - private final HashMap stage2CropStageMap; - private final HashMap id2FertilizerMap; - private final HashMap item2FertilizerMap; - private final AntiGriefLib antiGrief; - private final HashSet deadCrops; - - public ItemManagerImpl(CustomCropsPlugin plugin, AntiGriefLib antiGriefLib) { - this.plugin = plugin; - this.antiGrief = antiGriefLib; - this.itemLibraryMap = new HashMap<>(); - this.itemID2FunctionMap = new HashMap<>(); - this.id2WateringCanMap = new HashMap<>(); - this.item2WateringCanMap = new HashMap<>(); - this.id2SprinklerMap = new HashMap<>(); - this.twoDItem2SprinklerMap = new HashMap<>(); - this.threeDItem2SprinklerMap = new HashMap<>(); - this.id2PotMap = new HashMap<>(); - this.item2PotMap = new HashMap<>(); - this.stage2CropMap = new HashMap<>(); - this.seed2CropMap = new HashMap<>(); - this.id2CropMap = new HashMap<>(); - this.id2FertilizerMap = new HashMap<>(); - this.item2FertilizerMap = new HashMap<>(); - this.stage2CropStageMap = new HashMap<>(); - this.deadCrops = new HashSet<>(); - if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) { - if (Bukkit.getPluginManager().getPlugin("Oraxen").getDescription().getVersion().startsWith("2")) { - try { - Class oraxenListenerClass = Class.forName("net.momirealms.customcrops.mechanic.item.custom.oraxen.OraxenListener"); - Constructor oraxenListenerConstructor = oraxenListenerClass.getDeclaredConstructor(ItemManager.class); - oraxenListenerConstructor.setAccessible(true); - this.listener = (AbstractCustomListener) oraxenListenerConstructor.newInstance(this); - Class oraxenProviderClass = Class.forName("net.momirealms.customcrops.mechanic.item.custom.oraxen.OraxenProvider"); - Constructor oraxenProviderConstructor = oraxenProviderClass.getDeclaredConstructor(); - oraxenProviderConstructor.setAccessible(true); - this.customProvider = (CustomProvider) oraxenProviderConstructor.newInstance(); - } catch (ReflectiveOperationException e) { - e.printStackTrace(); - } - } else { - listener = new LegacyOraxenListener(this); - customProvider = new LegacyOraxenProvider(); - } - } else if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { - listener = new ItemsAdderListener(this); - customProvider = new ItemsAdderProvider(); - } else if (Bukkit.getPluginManager().getPlugin("MythicCrucible") != null) { -// listener = new CrucibleListener(this); -// customProvider = new CrucibleProvider(); - } else { - LogUtils.severe("======================================================"); - LogUtils.severe(" Please install ItemsAdder or Oraxen as dependency."); - LogUtils.severe(" ItemsAdder: https://www.spigotmc.org/resources/73355/"); - LogUtils.severe(" Oraxen: https://www.spigotmc.org/resources/72448/"); - LogUtils.severe("======================================================"); - } - } - - @Override - public void load() { - this.loadItems(); - if (this.listener != null) { - Bukkit.getPluginManager().registerEvents(this.listener, this.plugin); - } - this.resetItemDetectionOrder(); - } - - @Override - public void unload() { - if (this.listener != null) { - HandlerList.unregisterAll(this.listener); - } - this.itemID2FunctionMap.clear(); - this.id2WateringCanMap.clear(); - this.item2WateringCanMap.clear(); - this.id2SprinklerMap.clear(); - this.twoDItem2SprinklerMap.clear(); - this.threeDItem2SprinklerMap.clear(); - this.id2PotMap.clear(); - this.item2PotMap.clear(); - this.stage2CropMap.clear(); - this.seed2CropMap.clear(); - this.id2CropMap.clear(); - this.stage2CropStageMap.clear(); - this.id2FertilizerMap.clear(); - this.item2FertilizerMap.clear(); - this.deadCrops.clear(); - CFunction.resetID(); - } - - @Override - public void disable() { - unload(); - this.itemLibraryMap.clear(); - } - - @Override - public boolean registerItemLibrary(@NotNull ItemLibrary itemLibrary) { - if (this.itemLibraryMap.containsKey(itemLibrary.identification())) - return false; - this.itemLibraryMap.put(itemLibrary.identification(), itemLibrary); - resetItemDetectionOrder(); - return true; - } - - @Override - public boolean unregisterItemLibrary(String identification) { - boolean success = this.itemLibraryMap.remove(identification) != null; - if (success) { - resetItemDetectionOrder(); - } - return success; - } - - private void resetItemDetectionOrder() { - ArrayList itemLibraries = new ArrayList<>(); - for (String identification : ConfigManager.itemDetectionOrder()) { - ItemLibrary library = itemLibraryMap.get(identification); - if (library != null) { - itemLibraries.add(library); - } - } - this.itemDetectionArray = itemLibraries.toArray(new ItemLibrary[0]); - } - - @Override - public String getItemID(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) - return "AIR"; - String id; - id = customProvider.getItemID(itemStack); - if (id != null) return id; - else { - for (ItemLibrary library : itemDetectionArray) { - id = library.getItemID(itemStack); - if (id != null) - return library.identification() + ":" + id; - } - } - return itemStack.getType().name(); - } - - @Override - public ItemStack getItemStack(Player player, String id) { - ItemStack itemStack; - itemStack = customProvider.getItemStack(id); - if (itemStack != null) - return itemStack; - if (id.contains(":")) { - String[] split = id.split(":", 2); - ItemLibrary library = itemLibraryMap.get(split[0]); - if (library == null) { - LogUtils.warn("Error occurred when building item: " + id + ". Possible causes:"); - LogUtils.warn("① Item library: " + split[0] + " doesn't exist."); - LogUtils.warn("② If you are using ItemsAdder, " + id + " doesn't exist in your ItemsAdder config"); - return new ItemStack(Material.AIR); - } - return library.buildItem(player, split[1]); - } else { - try { - return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH))); - } catch (IllegalArgumentException e) { - return new ItemStack(Material.AIR); - } - } - } - - public boolean registerFertilizer(@NotNull Fertilizer fertilizer) { - if (this.id2FertilizerMap.containsKey(fertilizer.getKey())) { - return false; - } - this.id2FertilizerMap.put(fertilizer.getKey(), fertilizer); - if (this.item2FertilizerMap.put(fertilizer.getItemID(), fertilizer) != null) { - LogUtils.warn("Item " + fertilizer.getItemID() + " has more than one fertilizer config."); - return false; - } - return true; - } - - public boolean registerWateringCan(@NotNull WateringCan wateringCan) { - if (this.id2WateringCanMap.containsKey(wateringCan.getKey())) { - return false; - } - this.id2WateringCanMap.put(wateringCan.getKey(), wateringCan); - if (this.item2WateringCanMap.put(wateringCan.getItemID(), wateringCan) != null) { - LogUtils.warn("Item " + wateringCan.getItemID() + " has more than one watering-can config."); - return false; - } - return true; - } - - public boolean registerPot(@NotNull Pot pot) { - if (this.id2PotMap.containsKey(pot.getKey())) { - return false; - } - this.id2PotMap.put(pot.getKey(), pot); - for (String block : pot.getPotBlocks()) { - if (this.item2PotMap.put(block, pot) != null) { - LogUtils.warn("Block " + block + " has more than one pot config."); - return false; - } - } - return true; - } - - public boolean registerCrop(@NotNull Crop crop) { - if (this.id2CropMap.containsKey(crop.getKey())) { - return false; - } - this.id2CropMap.put(crop.getKey(), crop); - if (this.seed2CropMap.put(crop.getSeedItemID(), crop) != null) { - LogUtils.warn("Item " + crop.getSeedItemID() + " has more than one crop config."); - return false; - } - for (Crop.Stage stage : crop.getStages()) { - if (stage.getStageID() != null) { - if (this.stage2CropMap.put(stage.getStageID(), crop) != null) { - LogUtils.warn("Item " + stage.getStageID() + " has more than one crop config."); - return false; - } - if (this.stage2CropStageMap.put(stage.getStageID(), stage) != null) { - LogUtils.warn("Item " + stage.getStageID() + " has more than one crop config."); - return false; - } - } - } - return true; - } - - public boolean registerSprinkler(@NotNull Sprinkler sprinkler) { - if (this.id2SprinklerMap.containsKey(sprinkler.getKey())) { - return false; - } - this.id2SprinklerMap.put(sprinkler.getKey(), sprinkler); - if (sprinkler.get2DItemID() != null) { - if (this.twoDItem2SprinklerMap.put(sprinkler.get2DItemID(), sprinkler) != null) { - LogUtils.warn("Item " + sprinkler.get2DItemID() + " has more than one sprinkler config."); - return false; - } - } - if (this.threeDItem2SprinklerMap.put(sprinkler.get3DItemID(), sprinkler) != null) { - LogUtils.warn("Item " + sprinkler.get3DItemID() + " has more than one sprinkler config."); - return false; - } - if (sprinkler.get3DItemWithWater() != null) { - if (this.threeDItem2SprinklerMap.put(sprinkler.get3DItemWithWater(), sprinkler) != null) { - LogUtils.warn("Item " + sprinkler.get3DItemWithWater() + " has more than one sprinkler config."); - return false; - } - } - return true; - } - - @Override - public void placeItem(Location location, ItemCarrier carrier, String id) { - switch (carrier) { - case ITEM_DISPLAY, ITEM_FRAME -> { - customProvider.placeFurniture(location, id); - } - case TRIPWIRE, NOTE_BLOCK, CHORUS, MUSHROOM -> { - customProvider.placeBlock(location, id); - } - } - } - - @Override - public void placeItem(Location location, ItemCarrier carrier, String id, CRotation rotate) { - switch (carrier) { - case ITEM_DISPLAY, ITEM_FRAME -> { - Entity entity = customProvider.placeFurniture(location, id); - if (rotate == null || rotate == CRotation.NONE) return; - if (entity instanceof ItemFrame frame) { - frame.setRotation(RotationUtils.getBukkitRotation(rotate)); - } else if (entity instanceof ItemDisplay display) { - display.setRotation(RotationUtils.getFloatRotation(rotate), display.getLocation().getPitch()); - } - } - case TRIPWIRE, NOTE_BLOCK, CHORUS, MUSHROOM -> customProvider.placeBlock(location, id); - } - } - - @Override - public CRotation removeAnythingAt(Location location) { - return customProvider.removeAnythingAt(location); - } - - @Override - public CRotation getRotation(Location location) { - return customProvider.getRotation(location); - } - - @Override - public WateringCan getWateringCanByID(@NotNull String id) { - return id2WateringCanMap.get(id); - } - - @Override - public WateringCan getWateringCanByItemID(@NotNull String id) { - return item2WateringCanMap.get(id); - } - - @Override - public WateringCan getWateringCanByItemStack(@NotNull ItemStack itemStack) { - if (itemStack.getType() == Material.AIR) - return null; - return getWateringCanByItemID(getItemID(itemStack)); - } - - @Override - public Sprinkler getSprinklerByID(@NotNull String id) { - return id2SprinklerMap.get(id); - } - - @Override - public Sprinkler getSprinklerBy3DItemID(@NotNull String id) { - return threeDItem2SprinklerMap.get(id); - } - - @Override - public Sprinkler getSprinklerBy2DItemID(@NotNull String id) { - return twoDItem2SprinklerMap.get(id); - } - - @Override - public Sprinkler getSprinklerByEntity(@NotNull Entity entity) { - return Optional.ofNullable(customProvider.getEntityID(entity)).map(threeDItem2SprinklerMap::get).orElse(null); - } - - @Override - public Sprinkler getSprinklerByBlock(@NotNull Block block) { - return Optional.ofNullable(customProvider.getBlockID(block)).map(threeDItem2SprinklerMap::get).orElse(null); - } - - @Override - public Sprinkler getSprinklerBy2DItemStack(@NotNull ItemStack itemStack) { - return getSprinklerBy2DItemID(getItemID(itemStack)); - } - - @Override - public Sprinkler getSprinklerBy3DItemStack(@NotNull ItemStack itemStack) { - return getSprinklerBy3DItemID(getItemID(itemStack)); - } - - @Override - public Pot getPotByID(@NotNull String id) { - return id2PotMap.get(id); - } - - @Override - public Pot getPotByBlockID(@NotNull String id) { - return item2PotMap.get(id); - } - - @Override - public Pot getPotByBlock(@NotNull Block block) { - return getPotByBlockID(customProvider.getBlockID(block)); - } - - @Override - public Pot getPotByItemStack(@NotNull ItemStack itemStack) { - return getPotByBlockID(getItemID(itemStack)); - } - - @Override - public Fertilizer getFertilizerByID(@NotNull String id) { - return id2FertilizerMap.get(id); - } - - @Override - public Fertilizer getFertilizerByItemID(@NotNull String id) { - return item2FertilizerMap.get(id); - } - - @Override - public Fertilizer getFertilizerByItemStack(@NotNull ItemStack itemStack) { - if (itemStack.getType() == Material.AIR) - return null; - return item2FertilizerMap.get(getItemID(itemStack)); - } - - @Override - public Crop getCropByID(String id) { - return id2CropMap.get(id); - } - - @Override - public Crop getCropBySeedID(String id) { - return seed2CropMap.get(id); - } - - @Override - public Crop getCropBySeedItemStack(ItemStack itemStack) { - if (itemStack.getType() == Material.AIR) - return null; - return getCropByID(getItemID(itemStack)); - } - - @Override - public Crop getCropByStageID(String id) { - return stage2CropMap.get(id); - } - - @Override - public Crop getCropByEntity(Entity entity) { - return Optional.ofNullable(customProvider.getEntityID(entity)).map(stage2CropMap::get).orElse(null); - } - - @Override - public Crop getCropByBlock(Block block) { - return stage2CropMap.get(customProvider.getBlockID(block)); - } - - @Override - public Crop.Stage getCropStageByStageID(String id) { - return stage2CropStageMap.get(id); - } - - @SuppressWarnings("DuplicatedCode") - private void loadItems() { - for (String item : List.of("watering-cans", "pots", "crops", "sprinklers", "fertilizers")) { - File folder = new File(plugin.getDataFolder(), "contents" + File.separator + item); - if (!folder.exists()) { - plugin.saveResource("contents" + File.separator + item + File.separator + "default.yml", true); - ConfigUtils.addDefaultNamespace(new File(plugin.getDataFolder(), "contents" + File.separator + item + File.separator + "default.yml")); - } - List files = ConfigUtils.getFilesRecursively(new File(plugin.getDataFolder(), "contents" + File.separator + item)); - for (File file : files) { - YamlConfiguration config = YamlConfiguration.loadConfiguration(file); - for (Map.Entry entry : config.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection section) { - switch (item) { - case "watering-cans" -> loadWateringCan(entry.getKey(), section); - case "pots" -> loadPot(entry.getKey(), section); - case "crops" -> loadCrop(entry.getKey(), section); - case "sprinklers" -> loadSprinkler(entry.getKey(), section); - case "fertilizers" -> loadFertilizer(entry.getKey(), section); - } - } - } - } - } - this.loadDeadCrops(); - if (ConfigManager.enableGreenhouse()) - this.loadGreenhouse(); - if (ConfigManager.enableScarecrow()) - this.loadScarecrow(); - } - - private void loadDeadCrops() { - // register functions for dead crops - // or just keep it empty and let ItemsAdder/Oraxen handle the events - for (String id : deadCrops) { - this.registerItemFunction(id, FunctionTrigger.BE_INTERACTED, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractFurnitureWrapper furnitureWrapper)) { - return FunctionResult.PASS; - } - Player player = furnitureWrapper.getPlayer(); - Location cropLocation = furnitureWrapper.getLocation(); - ItemStack itemInHand = furnitureWrapper.getItemInHand(); - String itemID = getItemID(itemInHand); - int itemAmount = itemInHand.getAmount(); - Location potLocation = cropLocation.clone().subtract(0,1,0); - Pot pot = getPotByBlock(potLocation.getBlock()); - State potState = new State(player, itemInHand, potLocation); - // check pot use requirements - if (pot != null && RequirementManager.isRequirementMet(potState, pot.getUseRequirements())) { - // get water in pot - int waterInPot = plugin.getWorldManager().getPotAt(SimpleLocation.of(potLocation)).map(WorldPot::getWater).orElse(0); - // water the pot - for (PassiveFillMethod method : pot.getPassiveFillMethods()) { - if (method.getUsed().equals(itemID) && itemAmount >= method.getUsedAmount()) { - if (method.canFill(potState)) { - if (waterInPot < pot.getStorage()) { - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - method.getUsedAmount()); - if (method.getReturned() != null) { - ItemStack returned = getItemStack(player, method.getReturned()); - ItemUtils.giveItem(player, returned, method.getReturnedAmount()); - } - } - method.trigger(potState); - pot.trigger(ActionTrigger.ADD_WATER, potState); - plugin.getWorldManager().addWaterToPot(pot, method.getAmount(), SimpleLocation.of(potLocation)); - } else { - pot.trigger(ActionTrigger.FULL, potState); - } - } - return FunctionResult.RETURN; - } - } - } - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL), - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - Player player = blockWrapper.getPlayer(); - Location cropLocation = blockWrapper.getLocation(); - ItemStack itemInHand = blockWrapper.getItemInHand(); - String itemID = getItemID(itemInHand); - int itemAmount = itemInHand.getAmount(); - Location potLocation = cropLocation.clone().subtract(0,1,0); - Pot pot = getPotByBlock(potLocation.getBlock()); - State potState = new State(player, itemInHand, potLocation); - // check pot use requirements - if (pot != null && RequirementManager.isRequirementMet(potState, pot.getUseRequirements())) { - // get water in pot - int waterInPot = plugin.getWorldManager().getPotAt(SimpleLocation.of(potLocation)).map(WorldPot::getWater).orElse(0); - // water the pot - for (PassiveFillMethod method : pot.getPassiveFillMethods()) { - if (method.getUsed().equals(itemID) && itemAmount >= method.getUsedAmount()) { - if (method.canFill(potState)) { - if (waterInPot < pot.getStorage()) { - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - method.getUsedAmount()); - if (method.getReturned() != null) { - ItemStack returned = getItemStack(player, method.getReturned()); - ItemUtils.giveItem(player, returned, method.getReturnedAmount()); - } - } - method.trigger(potState); - pot.trigger(ActionTrigger.ADD_WATER, potState); - plugin.getWorldManager().addWaterToPot(pot, method.getAmount(), SimpleLocation.of(potLocation)); - } else { - pot.trigger(ActionTrigger.FULL, potState); - } - } - return FunctionResult.RETURN; - } - } - } - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - } - } - - private void loadGreenhouse() { - this.registerItemFunction(ConfigManager.greenhouseID(), FunctionTrigger.PLACE, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof PlaceWrapper placeWrapper)) { - return FunctionResult.PASS; - } - // fire event - GreenhouseGlassPlaceEvent event = new GreenhouseGlassPlaceEvent(placeWrapper.getPlayer(), placeWrapper.getLocation()); - if (EventUtils.fireAndCheckCancel(event)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - SimpleLocation simpleLocation = SimpleLocation.of(placeWrapper.getLocation()); - plugin.getWorldManager().addGlassAt(new MemoryGlass(simpleLocation), simpleLocation); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - this.registerItemFunction(ConfigManager.greenhouseID(), FunctionTrigger.BREAK, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof BreakWrapper breakWrapper)) { - return FunctionResult.PASS; - } - - // get or fix - Location location = breakWrapper.getLocation(); - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalWorldGlass = plugin.getWorldManager().getGlassAt(simpleLocation); - if (optionalWorldGlass.isEmpty()) { - WorldGlass glass = new MemoryGlass(simpleLocation); - optionalWorldGlass = Optional.of(glass); - plugin.getWorldManager().addGlassAt(glass, simpleLocation); - } - - // fire event - GreenhouseGlassBreakEvent event = new GreenhouseGlassBreakEvent(breakWrapper.getPlayer(), location, optionalWorldGlass.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(event)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - plugin.getWorldManager().removeGlassAt(simpleLocation); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - private void loadScarecrow() { - this.registerItemFunction(ConfigManager.scarecrowID(), FunctionTrigger.PLACE, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof PlaceWrapper placeWrapper)) { - return FunctionResult.PASS; - } - // fire event - ScarecrowPlaceEvent event = new ScarecrowPlaceEvent(placeWrapper.getPlayer(), placeWrapper.getLocation()); - if (EventUtils.fireAndCheckCancel(event)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - SimpleLocation simpleLocation = SimpleLocation.of(placeWrapper.getLocation()); - plugin.getWorldManager().addScarecrowAt(new MemoryScarecrow(simpleLocation), simpleLocation); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - this.registerItemFunction(ConfigManager.scarecrowID(), FunctionTrigger.BREAK, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof BreakWrapper breakWrapper)) { - return FunctionResult.PASS; - } - - // get or fix - Location location = breakWrapper.getLocation(); - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalWorldScarecrow = plugin.getWorldManager().getScarecrowAt(simpleLocation); - if (optionalWorldScarecrow.isEmpty()) { - WorldScarecrow scarecrow = new MemoryScarecrow(simpleLocation); - optionalWorldScarecrow = Optional.of(scarecrow); - plugin.getWorldManager().addScarecrowAt(scarecrow, simpleLocation); - } - - // fire event - ScarecrowBreakEvent event = new ScarecrowBreakEvent(breakWrapper.getPlayer(), location, optionalWorldScarecrow.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(event)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - plugin.getWorldManager().removeScarecrowAt(simpleLocation); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - @SuppressWarnings("DuplicatedCode") - private void loadWateringCan(String key, ConfigurationSection section) { - String itemID = section.getString("item"); - int width = section.getInt("effective-range.width"); - int length = section.getInt("effective-range.length"); - HashSet potWhiteList = new HashSet<>(section.getStringList("pot-whitelist")); - HashSet sprinklerWhiteList = new HashSet<>(section.getStringList("sprinkler-whitelist")); - boolean hasDynamicLore = section.getBoolean("dynamic-lore.enable", false); - List lore = section.getStringList("dynamic-lore.lore"); - - WateringCanConfig wateringCan = new WateringCanConfig( - key, - itemID, section.getBoolean("infinite", false), width, - length, section.getInt("capacity", 3), section.getInt("water", 1), - hasDynamicLore, lore, - potWhiteList, sprinklerWhiteList, - ConfigUtils.getPositiveFillMethods(section.getConfigurationSection("fill-method")), - ConfigUtils.getInt2IntMap(section.getConfigurationSection("appearance")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")), - section.contains("water-bar") ? WaterBar.of( - section.getString("water-bar.left", ""), - section.getString("water-bar.empty", ""), - section.getString("water-bar.full", ""), - section.getString("water-bar.right", "") - ) : null - ); - - if (!this.registerWateringCan(wateringCan)) { - LogUtils.warn("Failed to register new watering can: " + key + " due to duplicated entries."); - return; - } - - this.registerItemFunction(itemID, FunctionTrigger.INTERACT_AT, - /* - * Handle clicking sprinkler with a watering can - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - // is a pot - Block block = blockWrapper.getClickedBlock(); - Sprinkler sprinkler = getSprinklerBy3DItemID(customProvider.getBlockID(block)); - if (sprinkler == null) { - return FunctionResult.PASS; - } - final Player player = blockWrapper.getPlayer(); - final ItemStack itemInHand = blockWrapper.getItemInHand(); - final Location clicked = block.getLocation(); - State state = new State(player, itemInHand, clicked); - // check watering-can requirements - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!wateringCan.getSprinklerWhitelist().contains(sprinkler.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_SPRINKLER, state); - return FunctionResult.RETURN; - } - // get water in can - int waterInCan = wateringCan.getCurrentWater(itemInHand); - - // check sprinkler requirements - if (!RequirementManager.isRequirementMet(state, sprinkler.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!wateringCan.getSprinklerWhitelist().contains(sprinkler.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_SPRINKLER, state); - return FunctionResult.RETURN; - } - // check amount of water - if (waterInCan > 0 || wateringCan.isInfinite()) { - // get sprinkler data - SimpleLocation simpleLocation = SimpleLocation.of(clicked); - Optional worldSprinkler = plugin.getWorldManager().getSprinklerAt(simpleLocation); - if (worldSprinkler.isEmpty()) { - plugin.debug("Player " + player.getName() + " tried to interact a sprinkler which not exists in memory. Fixing the data..."); - WorldSprinkler sp = new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0); - plugin.getWorldManager().addSprinklerAt(sp, simpleLocation); - worldSprinkler = Optional.of(sp); - } else { - if (sprinkler.getStorage() <= worldSprinkler.get().getWater()) { - return FunctionResult.RETURN; - } - } - - // fire the event - WateringCanWaterEvent waterEvent = new WateringCanWaterEvent(player, itemInHand, new HashSet<>(Set.of(clicked)), wateringCan, worldSprinkler.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan - 1)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan - 1, wateringCan.getStorage())); - wateringCan.updateItem(player, itemInHand, waterInCan - 1, state.getArgs()); - wateringCan.trigger(ActionTrigger.CONSUME_WATER, state); - plugin.getWorldManager().addWaterToSprinkler(sprinkler, simpleLocation, 1); - } else { - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", "0"); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(0, wateringCan.getStorage())); - wateringCan.trigger(ActionTrigger.NO_WATER, state); - } - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.HIGH), - /* - * Handle clicking pot with a watering can - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - // click the upper face - if (blockWrapper.getClickedFace() != BlockFace.UP) { - return FunctionResult.PASS; - } - // is a pot - Block block = blockWrapper.getClickedBlock(); - Pot pot = getPotByBlock(block); - if (pot == null) { - return FunctionResult.PASS; - } - final Player player = blockWrapper.getPlayer(); - final ItemStack itemStack = blockWrapper.getItemInHand(); - final Location clicked = block.getLocation(); - State state = new State(player, itemStack, clicked); - // check watering-can requirements - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!wateringCan.getPotWhitelist().contains(pot.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check amount of water - int waterInCan = wateringCan.getCurrentWater(itemStack); - - if (waterInCan > 0 || wateringCan.isInfinite()) { - - Collection pots = getPotInRange(clicked, wateringCan.getWidth(), wateringCan.getLength(), player.getLocation().getYaw(), pot.getKey()); - // get or fix pot - SimpleLocation simpleLocation = SimpleLocation.of(clicked); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + simpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(simpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, simpleLocation); - worldPot = Optional.of(memoryPot); - } - - // fire the event - WateringCanWaterEvent waterEvent = new WateringCanWaterEvent(player, itemStack, new HashSet<>(pots), wateringCan, worldPot.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan - 1)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan - 1, wateringCan.getStorage())); - wateringCan.updateItem(player, itemStack, waterInCan - 1, state.getArgs()); - wateringCan.trigger(ActionTrigger.CONSUME_WATER, state); - - for (Location location : waterEvent.getLocation()) { - plugin.getWorldManager().addWaterToPot(pot, wateringCan.getWater(), SimpleLocation.of(location)); - pot.trigger(ActionTrigger.ADD_WATER, new State(player, itemStack, location)); - } - } else { - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", "0"); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(0, wateringCan.getStorage())); - wateringCan.trigger(ActionTrigger.NO_WATER, state); - } - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.HIGH), - /* - * Handle clicking crop with a watering can - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - // is a crop - String id = customProvider.getBlockID(blockWrapper.getClickedBlock()); - Crop crop = getCropByStageID(id); - if (crop == null && !deadCrops.contains(id)) { - return FunctionResult.PASS; - } - // get pot block - Block potBlock = blockWrapper.getClickedBlock().getRelative(BlockFace.DOWN); - Pot pot = getPotByBlock(potBlock); - if (pot == null) { - plugin.debug("Unexpected issue: Detetced that crops are not planted on a pot: " + blockWrapper.getClickedBlock().getLocation()); - return FunctionResult.RETURN; - } - - final Player player = blockWrapper.getPlayer(); - final ItemStack itemStack = blockWrapper.getItemInHand(); - final Location clicked = blockWrapper.getClickedBlock().getLocation(); - State state = new State(player, itemStack, clicked); - // check watering-can use requirements - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // check crop interact requirements - if (crop != null && !RequirementManager.isRequirementMet(state, crop.getInteractRequirements())) { - return FunctionResult.RETURN; - } - // check pot use requirements - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check watering-can whitelist - if (!wateringCan.getPotWhitelist().contains(pot.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check amount of water - int waterInCan = wateringCan.getCurrentWater(itemStack); - - if (waterInCan > 0 || wateringCan.isInfinite()) { - - Collection pots = getPotInRange(potBlock.getLocation(), wateringCan.getWidth(), wateringCan.getLength(), player.getLocation().getYaw(), pot.getKey()); - - // get or fix pot - SimpleLocation simpleLocation = SimpleLocation.of(potBlock.getLocation()); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + simpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(simpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, simpleLocation); - worldPot = Optional.of(memoryPot); - } - - // fire the event - WateringCanWaterEvent waterEvent = new WateringCanWaterEvent(player, itemStack, new HashSet<>(pots), wateringCan, worldPot.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan - 1)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan - 1, wateringCan.getStorage())); - wateringCan.updateItem(player, itemStack, waterInCan - 1, state.getArgs()); - wateringCan.trigger(ActionTrigger.CONSUME_WATER, state); - - for (Location location : waterEvent.getLocation()) { - plugin.getWorldManager().addWaterToPot(pot, wateringCan.getWater(), SimpleLocation.of(location)); - pot.trigger(ActionTrigger.ADD_WATER, new State(player, itemStack, location)); - } - } else { - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", "0"); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(0, wateringCan.getStorage())); - wateringCan.trigger(ActionTrigger.NO_WATER, state); - } - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL), - /* - * Handle clicking crop with a watering can - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractFurnitureWrapper furnitureWrapper)) { - return FunctionResult.PASS; - } - // is a crop - String id = furnitureWrapper.getID(); - Crop crop = getCropByStageID(id); - if (crop == null && !deadCrops.contains(id)) { - return FunctionResult.PASS; - } - // get pot block - Block potBlock = furnitureWrapper.getLocation().getBlock().getRelative(BlockFace.DOWN); - Pot pot = getPotByBlock(potBlock); - if (pot == null) { - LogUtils.warn("Unexpected issue: Detetced that crops are not planted on a pot: " + furnitureWrapper.getLocation()); - return FunctionResult.RETURN; - } - - final Player player = furnitureWrapper.getPlayer(); - final ItemStack itemStack = furnitureWrapper.getItemInHand(); - final Location clicked = furnitureWrapper.getLocation(); - State state = new State(player, itemStack, clicked); - // check watering-can use requirements - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // check crop interact requirements - if (crop != null && !RequirementManager.isRequirementMet(state, crop.getInteractRequirements())) { - return FunctionResult.RETURN; - } - // check pot use requirements - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check watering-can whitelist - if (!wateringCan.getPotWhitelist().contains(pot.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check amount of water - int waterInCan = wateringCan.getCurrentWater(itemStack); - - if (waterInCan > 0 || wateringCan.isInfinite()) { - - Collection pots = getPotInRange(potBlock.getLocation(), wateringCan.getWidth(), wateringCan.getLength(), player.getLocation().getYaw(), pot.getKey()); - - // get or fix pot - SimpleLocation simpleLocation = SimpleLocation.of(potBlock.getLocation()); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + simpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(simpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, simpleLocation); - worldPot = Optional.of(memoryPot); - } - - // fire the event - WateringCanWaterEvent waterEvent = new WateringCanWaterEvent(player, itemStack, new HashSet<>(pots), wateringCan, worldPot.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan - 1)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan - 1, wateringCan.getStorage())); - wateringCan.updateItem(player, itemStack, waterInCan - 1, state.getArgs()); - wateringCan.trigger(ActionTrigger.CONSUME_WATER, state); - - for (Location location : waterEvent.getLocation()) { - plugin.getWorldManager().addWaterToPot(pot, wateringCan.getWater(), SimpleLocation.of(location)); - pot.trigger(ActionTrigger.ADD_WATER, new State(player, itemStack, location)); - } - } else { - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", "0"); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(0, wateringCan.getStorage())); - wateringCan.trigger(ActionTrigger.NO_WATER, state); - } - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL), - /* - * Handle clicking furniture with a watering can - * This furniture may be a sprinkler, or it may be a custom piece of furniture such as a well - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractFurnitureWrapper furnitureWrapper)) { - return FunctionResult.PASS; - } - // check watering-can requirements - Player player = furnitureWrapper.getPlayer(); - ItemStack itemInHand = furnitureWrapper.getItemInHand(); - Location location = furnitureWrapper.getLocation(); - State state = new State(player, itemInHand, location); - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // get water in can - int waterInCan = wateringCan.getCurrentWater(itemInHand); - String clickedFurnitureID = furnitureWrapper.getID(); - Sprinkler sprinkler = getSprinklerBy3DItemID(clickedFurnitureID); - // is a sprinkler - if (sprinkler != null) { - // check sprinkler requirements - if (!RequirementManager.isRequirementMet(state, sprinkler.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!wateringCan.getSprinklerWhitelist().contains(sprinkler.getKey())) { - wateringCan.trigger(ActionTrigger.WRONG_SPRINKLER, state); - return FunctionResult.RETURN; - } - // check amount of water - if (waterInCan > 0 || wateringCan.isInfinite()) { - // get sprinkler data - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional worldSprinkler = plugin.getWorldManager().getSprinklerAt(simpleLocation); - if (worldSprinkler.isEmpty()) { - plugin.debug("Player " + player.getName() + " tried to interact a sprinkler which not exists in memory. Fixing the data..."); - WorldSprinkler sp = new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0); - plugin.getWorldManager().addSprinklerAt(sp, simpleLocation); - worldSprinkler = Optional.of(sp); - } else { - if (sprinkler.getStorage() <= worldSprinkler.get().getWater()) { - return FunctionResult.RETURN; - } - } - - // fire the event - WateringCanWaterEvent waterEvent = new WateringCanWaterEvent(player, itemInHand, new HashSet<>(Set.of(location)), wateringCan, worldSprinkler.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan - 1)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan - 1, wateringCan.getStorage())); - wateringCan.updateItem(player, furnitureWrapper.getItemInHand(), waterInCan - 1, state.getArgs()); - wateringCan.trigger(ActionTrigger.CONSUME_WATER, state); - plugin.getWorldManager().addWaterToSprinkler(sprinkler, simpleLocation, 1); - } else { - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", "0"); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(0, wateringCan.getStorage())); - wateringCan.trigger(ActionTrigger.NO_WATER, state); - } - return FunctionResult.RETURN; - } - - // get water from furniture and add it to watering-can - if (!wateringCan.isInfinite()) { - PositiveFillMethod[] methods = wateringCan.getPositiveFillMethods(); - for (PositiveFillMethod method : methods) { - if (method.getID().equals(clickedFurnitureID)) { - if (method.canFill(state)) { - // fire the event - WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, itemInHand, location, wateringCan, method); - if (EventUtils.fireAndCheckCancel(fillEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - if (waterInCan < wateringCan.getStorage()) { - waterInCan += method.getAmount(); - waterInCan = Math.min(waterInCan, wateringCan.getStorage()); - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(waterInCan)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(waterInCan, wateringCan.getStorage())); - wateringCan.updateItem(player, furnitureWrapper.getItemInHand(), waterInCan, state.getArgs()); - wateringCan.trigger(ActionTrigger.ADD_WATER, state); - method.trigger(state); - } else { - wateringCan.trigger(ActionTrigger.FULL, state); - } - } - return FunctionResult.RETURN; - } - } - } - - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL), - /* - * Handle clicking a block or air with watering can - * The priority of handling this is the lowest, - * because the priority of watering is much higher than that of adding water, - * and there should be no further judgment on adding water when watering occurs. - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractWrapper interactWrapper)) { - return FunctionResult.PASS; - } - if (wateringCan.isInfinite()) { - return FunctionResult.PASS; - } - Player player = interactWrapper.getPlayer(); - ItemStack itemInHand = interactWrapper.getItemInHand(); - // get the clicked block - Block targetBlock = player.getTargetBlockExact(5, FluidCollisionMode.ALWAYS); - if (targetBlock == null) - return FunctionResult.PASS; - // check watering-can requirements - State state = new State(player, itemInHand, targetBlock.getLocation()); - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // get the exact block id - String blockID = customProvider.getBlockID(targetBlock); - if (targetBlock.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged()) { - blockID = "WATER"; - } - int water = wateringCan.getCurrentWater(itemInHand); - PositiveFillMethod[] methods = wateringCan.getPositiveFillMethods(); - for (PositiveFillMethod method : methods) { - if (method.getID().equals(blockID)) { - if (method.canFill(state)) { - if (water < wateringCan.getStorage()) { - // fire the event - WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, itemInHand, state.getLocation(), wateringCan, method); - if (EventUtils.fireAndCheckCancel(fillEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - water += method.getAmount(); - water = Math.min(water, wateringCan.getStorage()); - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(water)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(water, wateringCan.getStorage())); - wateringCan.updateItem(player, itemInHand, water, state.getArgs()); - wateringCan.trigger(ActionTrigger.ADD_WATER, state); - method.trigger(state); - } else { - wateringCan.trigger(ActionTrigger.FULL, state); - } - } - return FunctionResult.RETURN; - } - } - return FunctionResult.PASS; - }, CFunction.FunctionPriority.LOW) - ); - - this.registerItemFunction(itemID, FunctionTrigger.INTERACT_AIR, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractWrapper interactWrapper)) { - return FunctionResult.PASS; - } - if (wateringCan.isInfinite()) { - return FunctionResult.PASS; - } - Player player = interactWrapper.getPlayer(); - // get the clicked block - Block targetBlock = player.getTargetBlockExact(5, FluidCollisionMode.ALWAYS); - if (targetBlock == null) - return FunctionResult.PASS; - // check watering-can requirements - ItemStack itemInHand = interactWrapper.getItemInHand(); - State state = new State(player, itemInHand, targetBlock.getLocation()); - if (!RequirementManager.isRequirementMet(state, wateringCan.getRequirements())) { - return FunctionResult.RETURN; - } - // get the exact block id - String blockID = customProvider.getBlockID(targetBlock); - if (targetBlock.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged()) { - blockID = "WATER"; - } - int water = wateringCan.getCurrentWater(itemInHand); - PositiveFillMethod[] methods = wateringCan.getPositiveFillMethods(); - for (PositiveFillMethod method : methods) { - if (method.getID().equals(blockID)) { - if (method.canFill(state)) { - if (water < wateringCan.getStorage()) { - // fire the event - WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, itemInHand, state.getLocation(), wateringCan, method); - if (EventUtils.fireAndCheckCancel(fillEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - water += method.getAmount(); - water = Math.min(water, wateringCan.getStorage()); - state.setArg("{storage}", String.valueOf(wateringCan.getStorage())); - state.setArg("{current}", String.valueOf(water)); - state.setArg("{water_bar}", wateringCan.getWaterBar() == null ? "" : wateringCan.getWaterBar().getWaterBar(water, wateringCan.getStorage())); - wateringCan.updateItem(player, itemInHand, water, state.getArgs()); - wateringCan.trigger(ActionTrigger.ADD_WATER, state); - method.trigger(state); - } else { - wateringCan.trigger(ActionTrigger.FULL, state); - } - } - return FunctionResult.RETURN; - } - } - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - @SuppressWarnings("DuplicatedCode") - private void loadSprinkler(String key, ConfigurationSection section) { - int storage = section.getInt("storage", 4); - boolean infinite = section.getBoolean("infinite", false); - int range = section.getInt("range",1); - int water = section.getInt("water", 1); - ItemCarrier itemCarrier = ItemCarrier.valueOf(section.getString("type", "ITEM_FRAME").toUpperCase(Locale.ENGLISH)); - - SprinklerConfig sprinkler = new SprinklerConfig( - key, - itemCarrier, - section.getString("2D-item"), - Preconditions.checkNotNull(section.getString("3D-item"), "3D-item can't be null"), - section.getString("3D-item-with-water"), - range, - storage, - water, - infinite, - section.contains("water-bar") ? WaterBar.of( - section.getString("water-bar.left", ""), - section.getString("water-bar.empty", ""), - section.getString("water-bar.full", ""), - section.getString("water-bar.right", "") - ) : null, - new HashSet<>(section.getStringList("pot-whitelist")), - ConfigUtils.getPassiveFillMethods(section.getConfigurationSection("fill-method")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.place")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.break")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.use")) - ); - - if (!this.registerSprinkler(sprinkler)) { - LogUtils.warn("Failed to register new sprinkler: " + key + " due to duplicated entries."); - return; - } - - if (sprinkler.get2DItemID() != null) { - this.registerItemFunction(sprinkler.get2DItemID(), FunctionTrigger.INTERACT_AT, - /* - * 2D item -> 3D item - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper interactBlockWrapper)) { - return FunctionResult.PASS; - } - if (interactBlockWrapper.getClickedFace() != BlockFace.UP) { - return FunctionResult.PASS; - } - if (!interactBlockWrapper.getClickedBlock().getType().isSolid()) { - return FunctionResult.PASS; - } - ItemStack itemInHand = interactBlockWrapper.getItemInHand(); - Location placed = interactBlockWrapper.getClickedBlock().getLocation().clone().add(0,1,0); - Player player = interactBlockWrapper.getPlayer(); - // check if the place is empty - if (!customProvider.isAir(placed)) { - return FunctionResult.RETURN; - } - // check place requirements - State state = new State(player, itemInHand, placed); - if (!RequirementManager.isRequirementMet(state, sprinkler.getPlaceRequirements())) { - return FunctionResult.RETURN; - } - // check limitation - SimpleLocation simpleLocation = SimpleLocation.of(placed); - if (plugin.getWorldManager().isReachLimit(simpleLocation, ItemType.SPRINKLER)) { - sprinkler.trigger(ActionTrigger.REACH_LIMIT, state); - return FunctionResult.RETURN; - } - // fire event - SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, itemInHand, placed, sprinkler); - if (EventUtils.fireAndCheckCancel(placeEvent)) { - return FunctionResult.RETURN; - } - // place the sprinkler - switch (sprinkler.getItemCarrier()) { - case ITEM_FRAME, ITEM_DISPLAY -> customProvider.placeFurniture(placed, sprinkler.get3DItemID()); - case TRIPWIRE -> customProvider.placeBlock(placed, sprinkler.get3DItemID()); - default -> { - LogUtils.warn("Unsupported type for sprinkler: " + sprinkler.getItemCarrier().name()); - return FunctionResult.RETURN; - } - } - // reduce item - if (player.getGameMode() != GameMode.CREATIVE) - itemInHand.setAmount(itemInHand.getAmount() - 1); - sprinkler.trigger(ActionTrigger.PLACE, state); - plugin.getWorldManager().addSprinklerAt(new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0), simpleLocation); - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - this.registerItemFunction(new String[]{sprinkler.get3DItemID(), sprinkler.get3DItemWithWater()}, FunctionTrigger.PLACE, - /* - * This will only trigger if the sprinkler has only 3D items - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof PlaceFurnitureWrapper placeFurnitureWrapper)) { - return FunctionResult.PASS; - } - Location location = placeFurnitureWrapper.getLocation(); - Player player = placeFurnitureWrapper.getPlayer(); - // check place requirements - State state = new State(player, placeFurnitureWrapper.getItemInHand(), location); - if (!RequirementManager.isRequirementMet(state, sprinkler.getPlaceRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // check limitation - SimpleLocation simpleLocation = SimpleLocation.of(location); - if (plugin.getWorldManager().isReachLimit(simpleLocation, ItemType.SPRINKLER)) { - sprinkler.trigger(ActionTrigger.REACH_LIMIT, state); - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // fire event - SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, placeFurnitureWrapper.getItemInHand(), location, sprinkler); - if (EventUtils.fireAndCheckCancel(placeEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // add data - plugin.getWorldManager().addSprinklerAt(new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0), simpleLocation); - sprinkler.trigger(ActionTrigger.PLACE, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - - this.registerItemFunction(new String[]{sprinkler.get3DItemID(), sprinkler.get3DItemWithWater()}, FunctionTrigger.BE_INTERACTED, - /* - * Interact the sprinkler - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractWrapper interactWrapper)) { - return FunctionResult.PASS; - } - ItemStack itemInHand = interactWrapper.getItemInHand(); - Player player = interactWrapper.getPlayer(); - Location location = interactWrapper.getLocation(); - // check use requirements - State state = new State(player, itemInHand, location); - if (!RequirementManager.isRequirementMet(state, sprinkler.getUseRequirements())) { - return FunctionResult.RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalSprinkler = plugin.getWorldManager().getSprinklerAt(simpleLocation); - if (optionalSprinkler.isEmpty()) { - plugin.debug("Found a sprinkler without data interacted by " + player.getName() + " at " + location); - WorldSprinkler newSprinkler = new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0); - plugin.getWorldManager().addSprinklerAt(newSprinkler, simpleLocation); - optionalSprinkler = Optional.of(newSprinkler); - } else { - if (!optionalSprinkler.get().getKey().equals(sprinkler.getKey())) { - LogUtils.warn("Found a sprinkler having inconsistent data interacted by " + player.getName() + " at " + location + "."); - plugin.getWorldManager().addSprinklerAt(new MemorySprinkler(simpleLocation, sprinkler.getKey(), 0), simpleLocation); - return FunctionResult.RETURN; - } - } - - // fire the event - SprinklerInteractEvent interactEvent = new SprinklerInteractEvent(player, itemInHand, location, optionalSprinkler.get()); - if (EventUtils.fireAndCheckCancel(interactEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // add water to sprinkler - String itemID = getItemID(itemInHand); - int itemAmount = itemInHand.getAmount(); - int waterInSprinkler = optionalSprinkler.get().getWater(); - // if it's not infinite - if (!sprinkler.isInfinite()) { - for (PassiveFillMethod method : sprinkler.getPassiveFillMethods()) { - if (method.getUsed().equals(itemID) && itemAmount >= method.getUsedAmount()) { - if (method.canFill(state)) { - if (waterInSprinkler < sprinkler.getStorage()) { - // fire the event - SprinklerFillEvent fillEvent = new SprinklerFillEvent(player, itemInHand, location, method, optionalSprinkler.get()); - if (EventUtils.fireAndCheckCancel(fillEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - method.getUsedAmount()); - if (method.getReturned() != null) { - ItemStack returned = getItemStack(player, method.getReturned()); - ItemUtils.giveItem(player, returned, method.getReturnedAmount()); - } - } - int current = Math.min(waterInSprinkler + method.getAmount(), sprinkler.getStorage()); - state.setArg("{storage}", String.valueOf(sprinkler.getStorage())); - state.setArg("{current}", String.valueOf(current)); - state.setArg("{water_bar}", sprinkler.getWaterBar() == null ? "" : sprinkler.getWaterBar().getWaterBar(current, sprinkler.getStorage())); - method.trigger(state); - sprinkler.trigger(ActionTrigger.ADD_WATER, state); - plugin.getWorldManager().addWaterToSprinkler(sprinkler, simpleLocation, method.getAmount()); - } else { - state.setArg("{storage}", String.valueOf(sprinkler.getStorage())); - state.setArg("{current}", String.valueOf(sprinkler.getStorage())); - state.setArg("{water_bar}", sprinkler.getWaterBar() == null ? "" : sprinkler.getWaterBar().getWaterBar(sprinkler.getStorage(), sprinkler.getStorage())); - sprinkler.trigger(ActionTrigger.FULL, state); - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - } - return FunctionResult.RETURN; - } - } - } - - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - - this.registerItemFunction(new String[]{sprinkler.get3DItemID(), sprinkler.get3DItemWithWater()}, FunctionTrigger.BE_INTERACTED, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractWrapper interactWrapper)) { - return FunctionResult.PASS; - } - - Location location = interactWrapper.getLocation(); - // trigger interact actions - plugin.getScheduler().runTaskSyncLater(() -> { - State state = new State(interactWrapper.getPlayer(), interactWrapper.getItemInHand(), location); - Optional optionalSprinkler = plugin.getWorldManager().getSprinklerAt(SimpleLocation.of(location)); - if (optionalSprinkler.isEmpty()) { - return; - } - - state.setArg("{storage}", String.valueOf(sprinkler.getStorage())); - state.setArg("{current}", String.valueOf(optionalSprinkler.get().getWater())); - state.setArg("{water_bar}", sprinkler.getWaterBar() == null ? "" : sprinkler.getWaterBar().getWaterBar(optionalSprinkler.get().getWater(), sprinkler.getStorage())); - - sprinkler.trigger(ActionTrigger.INTERACT, state); - }, location, 1); - - return FunctionResult.PASS; - }, CFunction.FunctionPriority.LOWEST) - ); - - this.registerItemFunction(new String[]{sprinkler.get3DItemID(), sprinkler.get3DItemWithWater()}, FunctionTrigger.BREAK, - /* - * Handle breaking sprinklers - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof BreakFurnitureWrapper breakFurnitureWrapper)) { - return FunctionResult.PASS; - } - // check break requirements - Location location = breakFurnitureWrapper.getLocation(); - State state = new State(breakFurnitureWrapper.getPlayer(), breakFurnitureWrapper.getItemInHand(), location); - if (!RequirementManager.isRequirementMet(state, sprinkler.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalSprinkler = plugin.getWorldManager().getSprinklerAt(simpleLocation); - if (optionalSprinkler.isEmpty()) { - plugin.debug("Found a sprinkler without data broken by " + state.getPlayer().getName() + " at " + location); - return FunctionResult.RETURN; - } - if (!optionalSprinkler.get().getKey().equals(sprinkler.getKey())) { - LogUtils.warn("Found a sprinkler having inconsistent data broken by " + state.getPlayer().getName() + " at " + location + "."); - plugin.getWorldManager().removeSprinklerAt(simpleLocation); - return FunctionResult.RETURN; - } - // fire event - SprinklerBreakEvent breakEvent = new SprinklerBreakEvent(breakFurnitureWrapper.getPlayer(), location, optionalSprinkler.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(breakEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // remove data - plugin.getWorldManager().removeSprinklerAt(simpleLocation); - sprinkler.trigger(ActionTrigger.BREAK, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - @SuppressWarnings("DuplicatedCode") - private void loadFertilizer(String key, ConfigurationSection section) { - FertilizerType type = switch (Preconditions.checkNotNull(section.getString("type"), "Fertilizer type can't be null").toUpperCase(Locale.ENGLISH)) { - case "QUALITY" -> FertilizerType.QUALITY; - case "SOIL_RETAIN" -> FertilizerType.SOIL_RETAIN; - case "SPEED_GROW" -> FertilizerType.SPEED_GROW; - case "VARIATION" -> FertilizerType.VARIATION; - case "YIELD_INCREASE" -> FertilizerType.YIELD_INCREASE; - default -> null; - }; - - if (type == null) { - LogUtils.warn("Fertilizer type: " + section.getString("type") + " is invalid."); - return; - } - - String icon = section.getString("icon", ""); - int times = section.getInt("times", 14); - String itemID = section.getString("item"); - HashSet potWhitelist = new HashSet<>(section.getStringList("pot-whitelist")); - boolean beforePlant = section.getBoolean("before-plant", false); - - Fertilizer fertilizer; - switch (type) { - case QUALITY -> fertilizer = new QualityCropConfig( - key, itemID, times, - section.getDouble("chance", 1), type, potWhitelist, - beforePlant, icon, - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getQualityRatio(Preconditions.checkNotNull(section.getString("ratio"), "Quality ratio should not be null")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")) - ); - case VARIATION -> fertilizer = new VariationConfig(key, itemID, times, - section.getDouble("chance", 1), type, potWhitelist, - beforePlant, icon, - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")) - ); - case SOIL_RETAIN -> fertilizer = new SoilRetainConfig(key, itemID, times, - section.getDouble("chance", 1), type, potWhitelist, - beforePlant, icon, - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")) - ); - case YIELD_INCREASE -> fertilizer = new YieldIncreaseConfig(key, itemID, times, - type, potWhitelist, - beforePlant, icon, - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getIntChancePair(section.getConfigurationSection("chance")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")) - ); - case SPEED_GROW -> fertilizer = new SpeedGrowConfig(key, itemID, times, - type, potWhitelist, - beforePlant, icon, - ConfigUtils.getRequirements(section.getConfigurationSection("requirements")), - ConfigUtils.getIntChancePair(section.getConfigurationSection("chance")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")) - ); - default -> fertilizer = null; - } - - if (!registerFertilizer(fertilizer)) { - LogUtils.warn("Failed to register new fertilizer: " + key + " due to duplicated entries."); - return; - } - - this.registerItemFunction(fertilizer.getItemID(), FunctionTrigger.INTERACT_AT, - /* - * Processing logic for players to use fertilizer - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper interactBlockWrapper)) { - return FunctionResult.PASS; - } - // is a pot - Block clicked = interactBlockWrapper.getClickedBlock(); - Pot pot = getPotByBlock(clicked); - if (pot == null) { - return FunctionResult.PASS; - } - ItemStack itemInHand = interactBlockWrapper.getItemInHand(); - Location location = clicked.getLocation(); - // check fertilizer requirements - State state = new State(interactBlockWrapper.getPlayer(), itemInHand, location); - if (!RequirementManager.isRequirementMet(state, fertilizer.getRequirements())) { - return FunctionResult.RETURN; - } - // check pot use requirements - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!fertilizer.getPotWhitelist().contains(pot.getKey())) { - fertilizer.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check before plant - if (fertilizer.isBeforePlant()) { - Optional worldCrop = plugin.getWorldManager().getCropAt(SimpleLocation.of(location.clone().add(0,1,0))); - if (worldCrop.isPresent()) { - fertilizer.trigger(ActionTrigger.BEFORE_PLANT, state); - return FunctionResult.RETURN; - } - } - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - boolean hasWater = false; - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + simpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(simpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, simpleLocation); - worldPot = Optional.of(memoryPot); - } else { - hasWater = worldPot.get().getWater() > 0; - } - // fire the event - FertilizerUseEvent useEvent = new FertilizerUseEvent(state.getPlayer(), itemInHand, fertilizer, location, worldPot.get()); - if (EventUtils.fireAndCheckCancel(useEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - // add data - plugin.getWorldManager().addFertilizerToPot(pot, fertilizer, simpleLocation); - if (interactBlockWrapper.getPlayer().getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemInHand.getAmount() - 1); - } - fertilizer.trigger(ActionTrigger.USE, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL), - - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper interactBlockWrapper)) { - return FunctionResult.PASS; - } - // is a crop - Block clicked = interactBlockWrapper.getClickedBlock(); - String id = customProvider.getBlockID(clicked); - Crop crop = getCropByStageID(id); - if (crop == null && !deadCrops.contains(id)) { - return FunctionResult.PASS; - } - ItemStack itemInHand = interactBlockWrapper.getItemInHand(); - Location location = clicked.getLocation(); - Player player = interactBlockWrapper.getPlayer(); - Location potLocation = location.clone().subtract(0,1,0); - // check fertilizer requirements - State state = new State(player, itemInHand, potLocation); - if (!RequirementManager.isRequirementMet(state, fertilizer.getRequirements())) { - return FunctionResult.RETURN; - } - // check pot data - Pot pot = getPotByBlock(potLocation.getBlock()); - if (pot == null) { - LogUtils.warn("Found a crop without pot interacted by player " + player.getName() + " with a fertilizer at " + location); - customProvider.removeAnythingAt(location); - return FunctionResult.RETURN; - } - // check pot use requirements - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!fertilizer.getPotWhitelist().contains(pot.getKey())) { - fertilizer.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check before plant - if (fertilizer.isBeforePlant()) { - fertilizer.trigger(ActionTrigger.BEFORE_PLANT, state); - return FunctionResult.RETURN; - } - - SimpleLocation simpleLocation = SimpleLocation.of(potLocation); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - boolean hasWater = false; - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + simpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(simpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, simpleLocation); - worldPot = Optional.of(memoryPot); - } else { - hasWater = worldPot.get().getWater() > 0; - } - // fire the event - FertilizerUseEvent useEvent = new FertilizerUseEvent(state.getPlayer(), itemInHand, fertilizer, location, worldPot.get()); - if (EventUtils.fireAndCheckCancel(useEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - // add data - plugin.getWorldManager().addFertilizerToPot(pot, fertilizer, simpleLocation); - if (interactBlockWrapper.getPlayer().getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemInHand.getAmount() - 1); - } - fertilizer.trigger(ActionTrigger.USE, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL), - - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractFurnitureWrapper furnitureWrapper)) { - return FunctionResult.PASS; - } - // is a crop - String id = furnitureWrapper.getID(); - Crop crop = getCropByStageID(id); - if (crop == null && !deadCrops.contains(id)) { - return FunctionResult.PASS; - } - ItemStack itemInHand = furnitureWrapper.getItemInHand(); - Location location = furnitureWrapper.getLocation(); - Player player = furnitureWrapper.getPlayer(); - Location potLocation = location.clone().subtract(0,1,0); - // check fertilizer requirements - State state = new State(player, itemInHand, potLocation); - if (!RequirementManager.isRequirementMet(state, fertilizer.getRequirements())) { - return FunctionResult.RETURN; - } - // check pot data - Pot pot = getPotByBlock(potLocation.getBlock()); - if (pot == null) { - LogUtils.warn("Found a crop without pot interacted by player " + player.getName() + " with a fertilizer at " + location); - customProvider.removeAnythingAt(location); - return FunctionResult.RETURN; - } - // check pot use requirements - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - // check whitelist - if (!fertilizer.getPotWhitelist().contains(pot.getKey())) { - fertilizer.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check before plant - if (fertilizer.isBeforePlant()) { - fertilizer.trigger(ActionTrigger.BEFORE_PLANT, state); - return FunctionResult.RETURN; - } - - SimpleLocation simpleLocation = SimpleLocation.of(potLocation); - Optional worldPot = plugin.getWorldManager().getPotAt(simpleLocation); - boolean hasWater = false; - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + potLocation); - } else { - hasWater = worldPot.get().getWater() > 0; - } - - // add data - plugin.getWorldManager().addFertilizerToPot(pot, fertilizer, simpleLocation); - if (furnitureWrapper.getPlayer().getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemInHand.getAmount() - 1); - } - fertilizer.trigger(ActionTrigger.USE, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - } - - @SuppressWarnings("DuplicatedCode") - private void loadCrop(String key, ConfigurationSection section) { - ItemCarrier itemCarrier = ItemCarrier.valueOf(section.getString("type").toUpperCase(Locale.ENGLISH)); - if (itemCarrier != ItemCarrier.TRIPWIRE && itemCarrier != ItemCarrier.ITEM_DISPLAY && itemCarrier != ItemCarrier.ITEM_FRAME && itemCarrier != ItemCarrier.NOTE_BLOCK) { - LogUtils.warn("Unsupported crop type: " + itemCarrier.name()); - return; - } - - String seedItemID = section.getString("seed"); - boolean rotation = section.getBoolean("random-rotation", false); - int maxPoints = section.getInt("max-points"); - - ConfigurationSection pointSection = section.getConfigurationSection("points"); - if (pointSection == null) { - LogUtils.warn(key + ".points section can't be null"); - return; - } - - CropConfig crop = new CropConfig( - key, seedItemID, itemCarrier, - new HashSet<>(section.getStringList("pot-whitelist")), rotation, maxPoints, - ConfigUtils.getBoneMeals(section.getConfigurationSection("custom-bone-meal")), - new Conditions(ConfigUtils.getConditions(section.getConfigurationSection("grow-conditions"))), - ConfigUtils.getDeathConditions(section.getConfigurationSection("death-conditions"), itemCarrier), - ConfigUtils.getActionMap(section.getConfigurationSection("events")), - ConfigUtils.getStageConfigs(pointSection), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.plant")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.break")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.interact")) - ); - - if (!this.registerCrop(crop)) { - LogUtils.warn("Failed to register new crop: " + key + " due to duplicated entries."); - return; - } - - for (DeathConditions deathConditions : crop.getDeathConditions()) { - if (deathConditions.getDeathItem() != null) { - deadCrops.add(deathConditions.getDeathItem()); - } - } - - this.registerItemFunction(crop.getSeedItemID(), FunctionTrigger.INTERACT_AT, - /* - * Handle crop planting - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - // is a pot - Block clicked = blockWrapper.getClickedBlock(); - Pot pot = getPotByBlock(clicked); - if (pot == null) { - return FunctionResult.PASS; - } - // click the upper face - if (blockWrapper.getClickedFace() != BlockFace.UP) { - return FunctionResult.PASS; - } - if (!customProvider.isAir(clicked.getRelative(BlockFace.UP).getLocation())) { - return FunctionResult.RETURN; - } - Player player = blockWrapper.getPlayer(); - ItemStack itemInHand = blockWrapper.getItemInHand(); - Location seedLocation = clicked.getLocation().clone().add(0,1,0); - State state = new State(player, itemInHand, seedLocation); - // check whitelist - if (!crop.getPotWhitelist().contains(pot.getKey())) { - crop.trigger(ActionTrigger.WRONG_POT, state); - return FunctionResult.RETURN; - } - // check plant requirements - if (!RequirementManager.isRequirementMet(state, crop.getPlantRequirements())) { - return FunctionResult.RETURN; - } - // check limitation - SimpleLocation simpleLocation = SimpleLocation.of(seedLocation); - if (plugin.getWorldManager().isReachLimit(simpleLocation, ItemType.CROP)) { - crop.trigger(ActionTrigger.REACH_LIMIT, state); - return FunctionResult.RETURN; - } - // fire event - CropPlantEvent plantEvent = new CropPlantEvent(player, itemInHand, seedLocation, crop, 0); - if (EventUtils.fireAndCheckCancel(plantEvent)) { - return FunctionResult.RETURN; - } - // place the crop - this.placeItem(seedLocation, crop.getItemCarrier(), crop.getStageItemByPoint(plantEvent.getPoint()), crop.hasRotation() ? CRotation.RANDOM : CRotation.NONE); - - // reduce item - if (player.getGameMode() != GameMode.CREATIVE) - itemInHand.setAmount(itemInHand.getAmount() - 1); - crop.trigger(ActionTrigger.PLANT, state); - - plugin.getWorldManager().addCropAt(new MemoryCrop(simpleLocation, crop.getKey(), plantEvent.getPoint()), simpleLocation); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - - for (Crop.Stage stage : crop.getStages()) { - if (stage.getStageID() != null) { - this.registerItemFunction(stage.getStageID(), FunctionTrigger.BE_INTERACTED, - /* - * Add water to pot if player is clicking a crop - * Trigger crop interaction - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractWrapper interactWrapper)) { - return FunctionResult.PASS; - } - - Player player = interactWrapper.getPlayer(); - Location cropLocation = LocationUtils.toBlockLocation(interactWrapper.getLocation()); - ItemStack itemInHand = interactWrapper.getItemInHand(); - State cropState = new State(player, itemInHand, cropLocation); - - // check crop interact requirements - if (!RequirementManager.isRequirementMet(cropState, crop.getInteractRequirements())) { - return FunctionResult.RETURN; - } - if (!RequirementManager.isRequirementMet(cropState, stage.getInteractRequirements())) { - return FunctionResult.RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(cropLocation); - Optional optionalCrop = plugin.getWorldManager().getCropAt(simpleLocation); - if (optionalCrop.isEmpty()) { - plugin.debug("Found a crop without data interacted by " + player.getName() + " at " + cropLocation); - WorldCrop newCrop = new MemoryCrop(simpleLocation, crop.getKey(), stage.getPoint()); - plugin.getWorldManager().addCropAt(newCrop, simpleLocation); - optionalCrop = Optional.of(newCrop); - } else { - if (!optionalCrop.get().getKey().equals(crop.getKey())) { - LogUtils.warn("Found a crop having inconsistent data interacted by " + player.getName() + " at " + cropLocation + "."); - plugin.getWorldManager().addCropAt(new MemoryCrop(simpleLocation, crop.getKey(), stage.getPoint()), simpleLocation); - return FunctionResult.RETURN; - } - } - - CropInteractEvent interactEvent = new CropInteractEvent(conditionWrapper.getPlayer(), interactWrapper.getItemInHand(), cropLocation, optionalCrop.orElse(null)); - if (EventUtils.fireAndCheckCancel(interactEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - - String itemID = getItemID(itemInHand); - int itemAmount = itemInHand.getAmount(); - - Location potLocation = cropLocation.clone().subtract(0,1,0); - Pot pot = getPotByBlock(potLocation.getBlock()); - State potState = new State(player, itemInHand, potLocation); - - // check pot use requirements - if (pot != null && RequirementManager.isRequirementMet(potState, pot.getUseRequirements())) { - // get water in pot - int waterInPot = plugin.getWorldManager().getPotAt(SimpleLocation.of(potLocation)).map(WorldPot::getWater).orElse(0); - // water the pot - for (PassiveFillMethod method : pot.getPassiveFillMethods()) { - if (method.getUsed().equals(itemID) && itemAmount >= method.getUsedAmount()) { - if (method.canFill(potState)) { - if (waterInPot < pot.getStorage()) { - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - method.getUsedAmount()); - if (method.getReturned() != null) { - ItemStack returned = getItemStack(player, method.getReturned()); - ItemUtils.giveItem(player, returned, method.getReturnedAmount()); - } - } - method.trigger(potState); - pot.trigger(ActionTrigger.ADD_WATER, potState); - plugin.getWorldManager().addWaterToPot(pot, method.getAmount(), SimpleLocation.of(potLocation)); - } else { - pot.trigger(ActionTrigger.FULL, potState); - } - } - return FunctionResult.RETURN; - } - } - } - - // if not reached the max point, try detecting bone meals - if (optionalCrop.get().getPoint() < crop.getMaxPoints()) { - for (BoneMeal boneMeal : crop.getBoneMeals()) { - if (boneMeal.getItem().equals(itemID)) { - // fire the event - BoneMealUseEvent useEvent = new BoneMealUseEvent(player, itemInHand, cropLocation, boneMeal, optionalCrop.get()); - if (EventUtils.fireAndCheckCancel(useEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - boneMeal.getUsedAmount()); - if (boneMeal.getReturned() != null) { - ItemStack returned = getItemStack(player, boneMeal.getReturned()); - ItemUtils.giveItem(player, returned, boneMeal.getReturnedAmount()); - } - } - boneMeal.trigger(cropState); - plugin.getWorldManager().addPointToCrop(crop, boneMeal.getPoint(), SimpleLocation.of(cropLocation)); - return FunctionResult.RETURN; - } - } - } else { - crop.trigger(ActionTrigger.RIPE, cropState); - } - - // trigger interact actions - crop.trigger(ActionTrigger.INTERACT, cropState); - stage.trigger(ActionTrigger.INTERACT, cropState); - return FunctionResult.PASS; - }, CFunction.FunctionPriority.HIGH) - ); - - this.registerItemFunction(stage.getStageID(), FunctionTrigger.BREAK, - /* - * Break the crop - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof BreakWrapper breakWrapper)) { - return FunctionResult.PASS; - } - Player player = breakWrapper.getPlayer(); - Location cropLocation = LocationUtils.toBlockLocation(breakWrapper.getLocation()); - State state = new State(player, breakWrapper.getItemInHand(), cropLocation); - // check crop break requirements - if (!RequirementManager.isRequirementMet(state, crop.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - if (!RequirementManager.isRequirementMet(state, stage.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(cropLocation); - Optional optionalWorldCrop = plugin.getWorldManager().getCropAt(simpleLocation); - if (optionalWorldCrop.isEmpty()) { - WorldCrop worldCrop = new MemoryCrop(simpleLocation, crop.getKey(), stage.getPoint()); - plugin.getWorldManager().addCropAt(worldCrop, simpleLocation); - optionalWorldCrop = Optional.of(worldCrop); - plugin.debug("Found a crop without data broken by " + player.getName() + " at " + cropLocation + ". " + - "You can safely ignore this if the crop is spawned in the wild."); - } else { - if (!optionalWorldCrop.get().getKey().equals(crop.getKey())) { - LogUtils.warn("Found a crop having inconsistent data broken by " + player.getName() + " at " + cropLocation + "."); - plugin.getWorldManager().removeCropAt(simpleLocation); - return FunctionResult.RETURN; - } - } - // fire event - CropBreakEvent breakEvent = new CropBreakEvent(player, cropLocation, optionalWorldCrop.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(breakEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - // trigger actions - stage.trigger(ActionTrigger.BREAK, state); - crop.trigger(ActionTrigger.BREAK, state); - plugin.getWorldManager().removeCropAt(simpleLocation); - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - } - } - } - - @SuppressWarnings("DuplicatedCode") - private void loadPot(String key, ConfigurationSection section) { - int storage = section.getInt("max-water-storage", 1); - String dryModel = Preconditions.checkNotNull(section.getString("base.dry"), "base.dry should not be null"); - String wetModel = Preconditions.checkNotNull(section.getString("base.wet"), "base.wet should not be null"); - boolean enableFertilizedAppearance = section.getBoolean("fertilized-pots.enable", false); - - PotConfig pot = new PotConfig( - key, storage, section.getBoolean("absorb-rainwater", false), section.getBoolean("absorb-nearby-water", false), - dryModel, wetModel, - enableFertilizedAppearance, - enableFertilizedAppearance ? ConfigUtils.getFertilizedPotMap(section.getConfigurationSection("fertilized-pots")) : new HashMap<>(), - section.contains("water-bar") ? WaterBar.of( - section.getString("water-bar.left", ""), - section.getString("water-bar.empty", ""), - section.getString("water-bar.full", ""), - section.getString("water-bar.right", "") - ) : null, - ConfigUtils.getPassiveFillMethods(section.getConfigurationSection("fill-method")), - ConfigUtils.getActionMap(section.getConfigurationSection("events")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.place")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.break")), - ConfigUtils.getRequirements(section.getConfigurationSection("requirements.use")) - ); - - if (!this.registerPot(pot)) { - LogUtils.warn("Failed to register new pot: " + key + " due to duplicated entries."); - return; - } - - for (String potItemID : pot.getPotBlocks()) { - this.registerItemFunction(potItemID, FunctionTrigger.BE_INTERACTED, - /* - * Interact the pot - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper interactBlockWrapper)) { - return FunctionResult.PASS; - } - - ItemStack itemInHand = interactBlockWrapper.getItemInHand(); - Player player = interactBlockWrapper.getPlayer(); - Location location = interactBlockWrapper.getClickedBlock().getLocation(); - // check pot use requirement - State state = new State(player, itemInHand, location); - if (!RequirementManager.isRequirementMet(state, pot.getUseRequirements())) { - return FunctionResult.RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalPot = plugin.getWorldManager().getPotAt(simpleLocation); - if (optionalPot.isEmpty()) { - plugin.debug("Found a pot without data interacted by " + player.getName() + " at " + location); - WorldPot newPot = new MemoryPot(simpleLocation, pot.getKey()); - if (pot.isWetPot(potItemID)) { - newPot.setWater(1); - } - plugin.getWorldManager().addPotAt(newPot, simpleLocation); - optionalPot = Optional.of(newPot); - } else { - if (!optionalPot.get().getKey().equals(pot.getKey())) { - LogUtils.warn("Found a pot having inconsistent data interacted by " + player.getName() + " at " + location + "."); - plugin.getWorldManager().addPotAt(new MemoryPot(simpleLocation, pot.getKey()), simpleLocation); - return FunctionResult.RETURN; - } - } - - // fire the event - PotInteractEvent interactEvent = new PotInteractEvent(player, itemInHand, location, optionalPot.get()); - if (EventUtils.fireAndCheckCancel(interactEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - String itemID = getItemID(itemInHand); - int itemAmount = itemInHand.getAmount(); - // get water in pot - int waterInPot = plugin.getWorldManager().getPotAt(simpleLocation).map(WorldPot::getWater).orElse(0); - - for (PassiveFillMethod method : pot.getPassiveFillMethods()) { - if (method.getUsed().equals(itemID) && itemAmount >= method.getUsedAmount()) { - if (method.canFill(state)) { - if (waterInPot < pot.getStorage()) { - // fire the event - PotFillEvent waterEvent = new PotFillEvent(player, itemInHand, location, method, optionalPot.get()); - if (EventUtils.fireAndCheckCancel(waterEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - - if (player.getGameMode() != GameMode.CREATIVE) { - itemInHand.setAmount(itemAmount - method.getUsedAmount()); - if (method.getReturned() != null) { - ItemStack returned = getItemStack(player, method.getReturned()); - ItemUtils.giveItem(player, returned, method.getReturnedAmount()); - } - } - method.trigger(state); - pot.trigger(ActionTrigger.ADD_WATER, state); - plugin.getWorldManager().addWaterToPot(pot, method.getAmount(), simpleLocation); - } else { - pot.trigger(ActionTrigger.FULL, state); - return FunctionResult.RETURN; - } - } - return FunctionResult.RETURN; - } - } - - return FunctionResult.PASS; - }, CFunction.FunctionPriority.NORMAL) - ); - - this.registerItemFunction(potItemID, FunctionTrigger.BE_INTERACTED, - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof InteractBlockWrapper interactBlockWrapper)) { - return FunctionResult.PASS; - } - - Location location = interactBlockWrapper.getClickedBlock().getLocation(); - plugin.getScheduler().runTaskSyncLater(() -> { - Optional worldPot = plugin.getWorldManager().getPotAt(SimpleLocation.of(location)); - if (worldPot.isEmpty()) { - return; - } - State state = new State(interactBlockWrapper.getPlayer(), interactBlockWrapper.getItemInHand(), location); - state.setArg("{current}", String.valueOf(worldPot.get().getWater())); - state.setArg("{storage}", String.valueOf(pot.getStorage())); - state.setArg("{water_bar}", pot.getWaterBar() == null ? "" : pot.getWaterBar().getWaterBar(worldPot.get().getWater(), pot.getStorage())); - state.setArg("{left_times}", String.valueOf(worldPot.get().getFertilizerTimes())); - state.setArg("{max_times}", String.valueOf(Optional.ofNullable(worldPot.get().getFertilizer()).map(fertilizer -> fertilizer.getTimes()).orElse(0))); - state.setArg("{icon}", Optional.ofNullable(worldPot.get().getFertilizer()).map(fertilizer -> fertilizer.getIcon()).orElse("")); - - // trigger actions - pot.trigger(ActionTrigger.INTERACT, state); - }, location, 1); - return FunctionResult.PASS; - }, CFunction.FunctionPriority.LOWEST) - ); - - this.registerItemFunction(potItemID, FunctionTrigger.BREAK, - /* - * Break the pot - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof BreakBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - // check break requirements - Location location = blockWrapper.getBrokenBlock().getLocation(); - Player player = blockWrapper.getPlayer(); - State state = new State(player, blockWrapper.getItemInHand(), location); - if (!RequirementManager.isRequirementMet(state, pot.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - Location cropLocation = location.clone().add(0,1,0); - String cropStageID = customProvider.getSomethingAt(cropLocation); - // remove crops - Crop.Stage stage = stage2CropStageMap.get(cropStageID); - if (stage != null) { - // if crops are above, check the break requirements for crops - Crop crop = getCropByStageID(cropStageID); - if (crop != null) { - State cropState = new State(player, blockWrapper.getItemInHand(), cropLocation); - if (!RequirementManager.isRequirementMet(cropState, crop.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - if (!RequirementManager.isRequirementMet(cropState, stage.getBreakRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - SimpleLocation simpleLocation = SimpleLocation.of(cropLocation); - Optional optionalWorldCrop = plugin.getWorldManager().getCropAt(simpleLocation); - if (optionalWorldCrop.isPresent()) { - if (!optionalWorldCrop.get().getKey().equals(crop.getKey())) { - LogUtils.warn("Found a crop having inconsistent data broken by " + player.getName() + " at " + cropLocation + "."); - } - } else { - WorldCrop worldCrop = new MemoryCrop(simpleLocation, crop.getKey(), stage.getPoint()); - optionalWorldCrop = Optional.of(worldCrop); - plugin.getWorldManager().addCropAt(worldCrop, simpleLocation); - plugin.debug("Found a crop without data broken by " + player.getName() + " at " + cropLocation + ". " + - "You can safely ignore this if the crop is spawned in the wild."); - } - // fire event - CropBreakEvent breakEvent = new CropBreakEvent(player, cropLocation, optionalWorldCrop.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(breakEvent)) - return FunctionResult.CANCEL_EVENT_AND_RETURN; - // trigger actions - stage.trigger(ActionTrigger.BREAK, cropState); - crop.trigger(ActionTrigger.BREAK, cropState); - plugin.getWorldManager().removeCropAt(simpleLocation); - customProvider.removeAnythingAt(cropLocation); - } else { - LogUtils.warn("Invalid crop stage: " + cropStageID); - customProvider.removeAnythingAt(cropLocation); - } - } - // remove dead crops - if (deadCrops.contains(cropStageID)) { - customProvider.removeAnythingAt(cropLocation); - } - - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalPot = plugin.getWorldManager().getPotAt(simpleLocation); - if (optionalPot.isEmpty()) { - plugin.debug("Found a pot without data broken by " + state.getPlayer().getName() + " at " + simpleLocation); - return FunctionResult.RETURN; - } - if (!optionalPot.get().getKey().equals(pot.getKey())) { - plugin.debug("Found a pot having inconsistent data broken by " + state.getPlayer().getName() + " at " + simpleLocation + "."); - plugin.getWorldManager().removePotAt(simpleLocation); - return FunctionResult.RETURN; - } - // fire event - PotBreakEvent breakEvent = new PotBreakEvent(blockWrapper.getPlayer(), location, optionalPot.get(), Reason.BREAK); - if (EventUtils.fireAndCheckCancel(breakEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // remove data - plugin.getWorldManager().removePotAt(simpleLocation); - pot.trigger(ActionTrigger.BREAK, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL) - ); - - this.registerItemFunction(potItemID, FunctionTrigger.PLACE, - /* - * Place the pot - */ - new CFunction(conditionWrapper -> { - if (!(conditionWrapper instanceof PlaceBlockWrapper blockWrapper)) { - return FunctionResult.PASS; - } - Location location = blockWrapper.getPlacedBlock().getLocation(); - Player player = blockWrapper.getPlayer(); - // check place requirements - State state = new State(player, blockWrapper.getItemInHand(), location); - if (!RequirementManager.isRequirementMet(state, pot.getPlaceRequirements())) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // check limitation - SimpleLocation simpleLocation = SimpleLocation.of(location); - if (plugin.getWorldManager().isReachLimit(simpleLocation, ItemType.POT)) { - pot.trigger(ActionTrigger.REACH_LIMIT, new State(player, blockWrapper.getItemInHand(), location)); - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // fire event - PotPlaceEvent potPlaceEvent = new PotPlaceEvent(player, location, pot); - if (EventUtils.fireAndCheckCancel(potPlaceEvent)) { - return FunctionResult.CANCEL_EVENT_AND_RETURN; - } - // add data - plugin.getWorldManager().addPotAt(new MemoryPot(simpleLocation, pot.getKey()), simpleLocation); - pot.trigger(ActionTrigger.PLACE, state); - return FunctionResult.RETURN; - }, CFunction.FunctionPriority.NORMAL)); - } - } - - private void registerItemFunction(String[] items, FunctionTrigger trigger, CFunction... function) { - for (String item : items) { - if (item != null) { - registerItemFunction(item, trigger, function); - } - } - } - - private void registerItemFunction(String item, FunctionTrigger trigger, CFunction... function) { - if (item == null) return; - if (itemID2FunctionMap.containsKey(item)) { - var previous = itemID2FunctionMap.get(item); - TreeSet previousFunctions = previous.get(trigger); - if (previousFunctions == null) { - previous.put(trigger, new TreeSet<>(List.of(function))); - } else { - previousFunctions.addAll(List.of(function)); - } - } else { - TreeSet list = new TreeSet<>(List.of(function)); - itemID2FunctionMap.put(item, new HashMap<>(Map.of(trigger, list))); - } - } - - @Override - @SuppressWarnings("DuplicatedCode") - public void handlePlayerInteractBlock( - Player player, - Block clickedBlock, - BlockFace clickedFace, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canInteract(player, clickedBlock.getLocation())) - return; - - // check pot firstly because events might be cancelled - var condition = new InteractBlockWrapper(player, clickedBlock, clickedFace); - String blockID = customProvider.getBlockID(clickedBlock); - TreeSet blockFunctions = Optional.ofNullable(itemID2FunctionMap.get(blockID)) - .map(map -> map.get(FunctionTrigger.BE_INTERACTED)) - .orElse(null); - if (handleFunctions(blockFunctions, condition, event)) { - return; - } - // Then check item in hand - String itemID = getItemID(condition.getItemInHand()); - Optional.ofNullable(itemID2FunctionMap.get(itemID)) - .map(map -> map.get(FunctionTrigger.INTERACT_AT)) - .ifPresent(itemFunctions -> handleFunctions(itemFunctions, condition, event)); - } - - @Override - public void handlePlayerInteractAir( - Player player, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canInteract(player, player.getLocation())) - return; - - var condition = new InteractWrapper(player, null); - // check item in hand - String itemID = getItemID(condition.getItemInHand()); - Optional.ofNullable(itemID2FunctionMap.get(itemID)) - .map(map -> map.get(FunctionTrigger.INTERACT_AIR)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, condition, event)); - } - - @Override - public void handlePlayerBreakBlock( - Player player, - Block brokenBlock, - String blockID, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canBreak(player, brokenBlock.getLocation())) - return; - - Optional.ofNullable(itemID2FunctionMap.get(blockID)) - .map(map -> map.get(FunctionTrigger.BREAK)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, new BreakBlockWrapper(player, brokenBlock), event)); - } - - @Override - @SuppressWarnings("DuplicatedCode") - public void handlePlayerInteractFurniture( - Player player, - Location location, - String id, - Entity baseEntity, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canInteract(player, location)) - return; - - var condition = new InteractFurnitureWrapper(player, location, id, baseEntity); - // check furniture firstly - TreeSet functions = Optional.ofNullable(itemID2FunctionMap.get(id)).map(map -> map.get(FunctionTrigger.BE_INTERACTED)).orElse(null); - if (handleFunctions(functions, condition, event)) { - return; - } - // Then check item in hand - String itemID = getItemID(condition.getItemInHand()); - Optional.ofNullable(itemID2FunctionMap.get(itemID)) - .map(map -> map.get(FunctionTrigger.INTERACT_AT)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, condition, event)); - } - - @Override - public void handlePlayerPlaceFurniture( - Player player, - Location location, - String id, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canPlace(player, location)) - return; - - // check furniture, no need to check item in hand - Optional.ofNullable(itemID2FunctionMap.get(id)) - .map(map -> map.get(FunctionTrigger.PLACE)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, new PlaceFurnitureWrapper(player, location, id), event)); - } - - @Override - public void handlePlayerBreakFurniture( - Player player, - Location location, - String id, - Cancellable event - ) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canBreak(player, location)) - return; - - // check furniture, no need to check item in hand - Optional.ofNullable(itemID2FunctionMap.get(id)) - .map(map -> map.get(FunctionTrigger.BREAK)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, new BreakFurnitureWrapper(player, location, id), event)); - } - - @Override - public void handlePlayerPlaceBlock(Player player, Block block, String blockID, Cancellable event) { - if (!plugin.getWorldManager().isMechanicEnabled(player.getWorld())) - return; - - // check anti-grief - if (!antiGrief.canPlace(player, block.getLocation())) - return; - - // check furniture, no need to check item in hand - Optional.ofNullable(itemID2FunctionMap.get(blockID)) - .map(map -> map.get(FunctionTrigger.PLACE)) - .ifPresent(cFunctions -> handleFunctions(cFunctions, new PlaceBlockWrapper(player, block, blockID), event)); - } - - @Override - public void handleEntityTramplingBlock(Entity entity, Block block, Cancellable event) { - if (entity instanceof Player player) { - handlePlayerBreakBlock(player, block, "FARMLAND", event); - } else { - // if the block is a pot - Pot pot = getPotByBlock(block); - if (pot != null) { - Location potLocation = block.getLocation(); - // get or fix pot - SimpleLocation potSimpleLocation = SimpleLocation.of(potLocation); - Optional worldPot = plugin.getWorldManager().getPotAt(potSimpleLocation); - if (worldPot.isEmpty()) { - plugin.debug("Found pot data not exists at " + potSimpleLocation + ". Fixing it."); - MemoryPot memoryPot = new MemoryPot(potSimpleLocation, pot.getKey()); - plugin.getWorldManager().addPotAt(memoryPot, potSimpleLocation); - worldPot = Optional.of(memoryPot); - } - // fire the event - PotBreakEvent potBreakEvent = new PotBreakEvent(entity, potLocation, worldPot.get(), Reason.TRAMPLE); - if (EventUtils.fireAndCheckCancel(potBreakEvent)) { - event.setCancelled(true); - return; - } - - plugin.getWorldManager().removePotAt(SimpleLocation.of(block.getLocation())); - pot.trigger(ActionTrigger.BREAK, new State(null, new ItemStack(Material.AIR), block.getLocation())); - - Location cropLocation = block.getLocation().clone().add(0,1,0); - String cropStageID = customProvider.getSomethingAt(cropLocation); - Crop.Stage stage = stage2CropStageMap.get(cropStageID); - if (stage != null) { - Crop crop = getCropByStageID(cropStageID); - SimpleLocation simpleLocation = SimpleLocation.of(cropLocation); - Optional optionalWorldCrop = plugin.getWorldManager().getCropAt(simpleLocation); - if (optionalWorldCrop.isPresent()) { - if (!optionalWorldCrop.get().getKey().equals(crop.getKey())) { - LogUtils.warn("Found a crop having inconsistent data broken by " + entity.getType() + " at " + cropLocation + "."); - } - } else { - WorldCrop worldCrop = new MemoryCrop(simpleLocation, crop.getKey(), stage.getPoint()); - plugin.getWorldManager().addCropAt(worldCrop, simpleLocation); - optionalWorldCrop = Optional.of(worldCrop); - plugin.debug("Found a crop without data broken by " + entity.getType() + " at " + cropLocation + ". " + - "You can safely ignore this if the crop is spawned in the wild."); - } - // fire the event - CropBreakEvent breakEvent = new CropBreakEvent(entity, cropLocation, optionalWorldCrop.get(), Reason.TRAMPLE); - if (EventUtils.fireAndCheckCancel(breakEvent)) { - event.setCancelled(true); - return; - } - - State state = new State(null, new ItemStack(Material.AIR), cropLocation); - // trigger actions - stage.trigger(ActionTrigger.BREAK, state); - crop.trigger(ActionTrigger.BREAK, state); - plugin.getWorldManager().removeCropAt(simpleLocation); - customProvider.removeAnythingAt(cropLocation); - } - - if (deadCrops.contains(cropStageID)) { - customProvider.removeAnythingAt(cropLocation); - } - } - } - } - - @Override - public void handleExplosion(Entity entity, List blocks, Cancellable event) { - List locationsToRemove = new ArrayList<>(); - List locationsToRemoveBlock = new ArrayList<>(); - HashSet blockLocations = new HashSet<>(blocks.stream().map(Block::getLocation).toList()); - List aboveLocations = new ArrayList<>(); - for (Location location : blockLocations) { - Optional optionalCustomCropsBlock = plugin.getWorldManager().getBlockAt(SimpleLocation.of(location)); - if (optionalCustomCropsBlock.isPresent()) { - Event customEvent = null; - CustomCropsBlock customCropsBlock = optionalCustomCropsBlock.get(); - switch (customCropsBlock.getType()) { - case POT -> { - customEvent = new PotBreakEvent(entity, location, (WorldPot) customCropsBlock, Reason.EXPLODE); - Location above = location.clone().add(0,1,0); - if (!blockLocations.contains(above)) { - aboveLocations.add(above); - } - } - case SPRINKLER -> customEvent = new SprinklerBreakEvent(entity, location, (WorldSprinkler) customCropsBlock, Reason.EXPLODE); - case CROP -> customEvent = new CropBreakEvent(entity, location, (WorldCrop) customCropsBlock, Reason.EXPLODE); - case GREENHOUSE -> customEvent = new GreenhouseGlassBreakEvent(entity, location, (WorldGlass) customCropsBlock, Reason.EXPLODE); - case SCARECROW -> customEvent = new ScarecrowBreakEvent(entity, location, (WorldScarecrow) customCropsBlock, Reason.EXPLODE); - } - if (customEvent != null && EventUtils.fireAndCheckCancel(customEvent)) { - event.setCancelled(true); - return; - } - locationsToRemove.add(location); - } - } - - for (Location location : aboveLocations) { - Optional optionalCustomCropsBlock = plugin.getWorldManager().getBlockAt(SimpleLocation.of(location)); - if (optionalCustomCropsBlock.isPresent()) { - CustomCropsBlock customCropsBlock = optionalCustomCropsBlock.get(); - if (customCropsBlock.getType() == ItemType.CROP) { - if (EventUtils.fireAndCheckCancel(new CropBreakEvent(entity, location, (WorldCrop) customCropsBlock, Reason.EXPLODE))) { - event.setCancelled(true); - return; - } - locationsToRemove.add(location); - locationsToRemoveBlock.add(location); - } - } - } - - for (Location location : locationsToRemoveBlock) { - removeAnythingAt(location); - } - - for (Location location : locationsToRemove) { - CustomCropsBlock customCropsBlock = plugin.getWorldManager().removeAnythingAt(SimpleLocation.of(location)); - if (customCropsBlock != null) { - State state = new State(null, new ItemStack(Material.AIR), location); - switch (customCropsBlock.getType()) { - case POT -> { - Pot pot = ((WorldPot) customCropsBlock).getConfig(); - pot.trigger(ActionTrigger.BREAK, state); - } - case CROP -> { - Crop crop = ((WorldCrop) customCropsBlock).getConfig(); - Crop.Stage stage = crop.getStageByItemID(crop.getStageItemByPoint(((WorldCrop) customCropsBlock).getPoint())); - crop.trigger(ActionTrigger.BREAK, state); - stage.trigger(ActionTrigger.BREAK, state); - } - case SPRINKLER -> { - Sprinkler sprinkler = ((WorldSprinkler) customCropsBlock).getConfig(); - sprinkler.trigger(ActionTrigger.BREAK, state); - } - } - } - } - } - - private boolean handleFunctions(Collection functions, ConditionWrapper wrapper, @Nullable Cancellable event) { - if (functions == null) return false; - for (CFunction function : functions) { - FunctionResult result = function.apply(wrapper); - if (result == FunctionResult.CANCEL_EVENT_AND_RETURN) { - if (event != null) event.setCancelled(true); - return true; - } - if (result == FunctionResult.RETURN) - return true; - } - return false; - } - - @Override - public void updatePotState(Location location, Pot pot, boolean hasWater, Fertilizer fertilizer) { - Block block = location.getBlock(); - Pot currentPot = getPotByBlock(block); - if (currentPot != pot) { - plugin.getWorldManager().removePotAt(SimpleLocation.of(location)); - if (currentPot != null) { - plugin.getWorldManager().addPotAt(new MemoryPot(SimpleLocation.of(location), currentPot.getKey()), SimpleLocation.of(location)); - } - return; - } - if (pot.isVanillaBlock()) { - if (block.getBlockData() instanceof Farmland farmland) { - farmland.setMoisture(hasWater ? 7 : 0); - block.setBlockData(farmland); - return; - } - } - this.customProvider.placeBlock( - location, - pot.getBlockState( - hasWater, - Optional.ofNullable(fertilizer) - .map(Fertilizer::getFertilizerType) - .orElse(null) - ) - ); - } - - @NotNull - @Override - public Collection getPotInRange(Location baseLocation, int width, int length, float yaw, String potID) { - ArrayList potLocations = new ArrayList<>(); - int extend = (width-1) / 2; - int extra = (width-1) % 2; - switch ((int) ((yaw + 180) / 45)) { - case 0 -> { - // -180 ~ -135 - for (int i = -extend; i <= extend + extra; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(i, 0, -j)); - } - } - } - case 1 -> { - // -135 ~ -90 - for (int i = -extend - extra; i <= extend; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(j, 0, i)); - } - } - } - case 2 -> { - // -90 ~ -45 - for (int i = -extend; i <= extend + extra; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(j, 0, i)); - } - } - } - case 3 -> { - // -45 ~ 0 - for (int i = -extend; i <= extend + extra; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(i, 0, j)); - } - } - } - case 4 -> { - // 0 ~ 45 - for (int i = -extend - extra; i <= extend; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(i, 0, j)); - } - } - } - case 5 -> { - // 45 ~ 90 - for (int i = -extend; i <= extend + extra; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(-j, 0, i)); - } - } - } - case 6 -> { - // 90 ~ 135 - for (int i = -extend - extra; i <= extend; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(-j, 0, i)); - } - } - } - case 7 -> { - // 135 ~ 180 - for (int i = -extend - extra; i <= extend; i++) { - for (int j = 0; j < length; j++) { - potLocations.add(baseLocation.clone().add(i, 0, -j)); - } - } - } - default -> potLocations.add(baseLocation); - } - return potLocations.stream().filter(it -> { - Pot pot = getPotByBlock(it.getBlock()); - if (pot == null) return false; - return pot.getKey().equals(potID); - }).toList(); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleListener.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleListener.java deleted file mode 100644 index 5f6020d..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleListener.java +++ /dev/null @@ -1,49 +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.mechanic.item.custom.crucible; -// -//import net.momirealms.customcrops.mechanic.item.ItemManagerImpl; -//import net.momirealms.customcrops.api.mechanic.item.custom.AbstractCustomListener; -//import org.bukkit.event.EventHandler; -// -//public class CrucibleListener extends AbstractCustomListener { -// -// public CrucibleListener(ItemManagerImpl itemManager) { -// super(itemManager); -// } -// -// @EventHandler (ignoreCancelled = true) -// public void onBreakCustomBlock() { -// } -// -// @EventHandler (ignoreCancelled = true) -// public void onPlaceCustomBlock() { -// } -// -// @EventHandler (ignoreCancelled = true) -// public void onPlaceFurniture() { -// } -// -// @EventHandler (ignoreCancelled = true) -// public void onBreakFurniture() { -// } -// -// @EventHandler (ignoreCancelled = true) -// public void onInteractFurniture() { -// } -//} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleProvider.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleProvider.java deleted file mode 100644 index af4d7db..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/crucible/CrucibleProvider.java +++ /dev/null @@ -1,124 +0,0 @@ -///* -// * Copyright (C) <2022> -// * -// * This program is free software: you can redistribute it and/or modify -// * it under the terms of the GNU General Public License as published by -// * the Free Software Foundation, either version 3 of the License, or -// * any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// * GNU General Public License for more details. -// * -// * You should have received a copy of the GNU General Public License -// * along with this program. If not, see . -// */ -// -//package net.momirealms.customcrops.mechanic.item.custom.crucible; -// -//import io.lumine.mythic.bukkit.BukkitAdapter; -//import io.lumine.mythic.bukkit.adapters.BukkitEntity; -//import io.lumine.mythiccrucible.MythicCrucible; -//import io.lumine.mythiccrucible.items.CrucibleItem; -//import io.lumine.mythiccrucible.items.ItemManager; -//import io.lumine.mythiccrucible.items.blocks.CustomBlockItemContext; -//import io.lumine.mythiccrucible.items.blocks.CustomBlockManager; -//import io.lumine.mythiccrucible.items.furniture.Furniture; -//import io.lumine.mythiccrucible.items.furniture.FurnitureManager; -//import net.momirealms.customcrops.api.util.LogUtils; -//import net.momirealms.customcrops.api.mechanic.item.custom.CustomProvider; -//import org.bukkit.Location; -//import org.bukkit.Material; -//import org.bukkit.block.Block; -//import org.bukkit.block.BlockFace; -//import org.bukkit.entity.Entity; -//import org.bukkit.inventory.ItemStack; -// -//import java.util.Optional; -// -//public class CrucibleProvider implements CustomProvider { -// -// private final ItemManager itemManager; -// private final CustomBlockManager blockManager; -// private final FurnitureManager furnitureManager; -// -// public CrucibleProvider() { -// this.itemManager = MythicCrucible.inst().getItemManager(); -// this.blockManager = itemManager.getCustomBlockManager(); -// this.furnitureManager = itemManager.getFurnitureManager(); -// } -// -// @Override -// public boolean removeBlock(Location location) { -// Block block = location.getBlock(); -// if (block.getType() == Material.AIR) { -// return false; -// } -// Optional optional = blockManager.getBlockFromBlock(block); -// if (optional.isPresent()) { -// optional.get().remove(block, null, false); -// } else { -// block.setType(Material.AIR); -// } -// return true; -// } -// -// @Override -// public void placeCustomBlock(Location location, String id) { -// Optional optionalCI = itemManager.getItem(id); -// if (optionalCI.isPresent()) { -// location.getBlock().setBlockData(optionalCI.get().getBlockData().getBlockData()); -// } else { -// LogUtils.warn("Custom block(" + id +") doesn't exist in Crucible configs. Please double check if that block exists."); -// } -// } -// -// @Override -// public Entity placeFurniture(Location location, String id) { -// Optional optionalCI = itemManager.getItem(id); -// if (optionalCI.isPresent()) { -// return optionalCI.get().getFurnitureData().placeFrame(location.getBlock(), BlockFace.UP, 0f, null); -// } else { -// LogUtils.warn("Furniture(" + id +") doesn't exist in Crucible configs. Please double check if that furniture exists."); -// return null; -// } -// } -// -// @Override -// public void removeFurniture(Entity entity) { -// Optional optional = furnitureManager.getFurniture(entity.getUniqueId()); -// optional.ifPresent(furniture -> furniture.getFurnitureData().remove(furniture, null, false, false)); -// } -// -// @Override -// public String getBlockID(Block block) { -// Optional optionalCB = blockManager.getBlockFromBlock(block); -// return optionalCB.map(customBlockItemContext -> customBlockItemContext.getCrucibleItem().getInternalName()).orElse(block.getType().name()); -// } -// -// @Override -// public String getItemID(ItemStack itemStack) { -// return itemManager.getItem(itemStack).map(CrucibleItem::getInternalName).orElse(null); -// } -// -// @Override -// public ItemStack getItemStack(String id) { -// Optional optionalCI = itemManager.getItem(id); -// return optionalCI.map(crucibleItem -> BukkitAdapter.adapt(crucibleItem.getMythicItem().generateItemStack(1))).orElse(null); -// } -// -// @Override -// public String getEntityID(Entity entity) { -// Optional optionalCI = furnitureManager.getItemFromEntity(entity); -// if (optionalCI.isPresent()) { -// return optionalCI.get().getInternalName(); -// } -// return entity.getType().name(); -// } -// -// @Override -// public boolean isFurniture(Entity entity) { -// return furnitureManager.isFurniture(new BukkitEntity(entity)); -// } -//} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderListener.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderListener.java deleted file mode 100644 index 86040ff..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderListener.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.custom.itemsadder; - -import dev.lone.itemsadder.api.CustomFurniture; -import dev.lone.itemsadder.api.Events.*; -import net.momirealms.customcrops.api.mechanic.item.custom.AbstractCustomListener; -import net.momirealms.customcrops.mechanic.item.ItemManagerImpl; -import org.bukkit.entity.Entity; -import org.bukkit.event.EventHandler; - -public class ItemsAdderListener extends AbstractCustomListener { - - public ItemsAdderListener(ItemManagerImpl itemManager) { - super(itemManager); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakCustomBlock(CustomBlockBreakEvent event) { - this.itemManager.handlePlayerBreakBlock( - event.getPlayer(), - event.getBlock(), - event.getNamespacedID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceCustomBlock(CustomBlockPlaceEvent event) { - super.onPlaceBlock( - event.getPlayer(), - event.getBlock(), - event.getNamespacedID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onPlaceFurniture(FurniturePlaceSuccessEvent event) { - Entity entity = event.getBukkitEntity(); - if (entity == null) return; - // player would be null if furniture is placed with API - if (event.getPlayer() == null) return; - super.onPlaceFurniture( - event.getPlayer(), - entity.getLocation(), - event.getNamespacedID(), - null - ); - } - - @EventHandler (ignoreCancelled = true) - public void onBreakFurniture(FurnitureBreakEvent event) { - CustomFurniture customFurniture = event.getFurniture(); - if (customFurniture == null) return; - Entity entity = customFurniture.getEntity(); - if (entity == null) return; - super.onBreakFurniture( - event.getPlayer(), - entity.getLocation(), - event.getNamespacedID(), - event - ); - } - - @EventHandler (ignoreCancelled = true) - public void onInteractFurniture(FurnitureInteractEvent event) { - CustomFurniture customFurniture = event.getFurniture(); - if (customFurniture == null) return; - Entity entity = customFurniture.getEntity(); - if (entity == null) return; - super.onInteractFurniture(event.getPlayer(), - entity.getLocation(), - event.getNamespacedID(), - entity, - event - ); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderProvider.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderProvider.java deleted file mode 100644 index bc184d3..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/custom/itemsadder/ItemsAdderProvider.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.custom.itemsadder; - -import dev.lone.itemsadder.api.CustomBlock; -import dev.lone.itemsadder.api.CustomFurniture; -import dev.lone.itemsadder.api.CustomStack; -import net.momirealms.customcrops.api.mechanic.item.custom.CustomProvider; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; -import org.bukkit.inventory.ItemStack; - -public class ItemsAdderProvider implements CustomProvider { - - @Override - public boolean removeBlock(Location location) { - Block block = location.getBlock(); - if (block.getType() == Material.AIR) - return false; - if (!CustomBlock.remove(location)) { - block.setType(Material.AIR); - } - return true; - } - - @Override - public void placeCustomBlock(Location location, String id) { - CustomBlock block = CustomBlock.place(id, location); - if (block == null) { - LogUtils.warn("Detected that custom block(" + id + ") doesn't exist in ItemsAdder configs. Please double check if that block exists."); - } - } - - @Override - public Entity placeFurniture(Location location, String id) { - try { - CustomFurniture furniture = CustomFurniture.spawnPreciseNonSolid(id, location); - if (furniture == null) return null; - return furniture.getEntity(); - } catch (RuntimeException e) { - LogUtils.warn("Failed to place ItemsAdder furniture. If this is not a problem caused by furniture not existing, consider increasing max-furniture-vehicles-per-chunk in ItemsAdder config.yml."); - e.printStackTrace(); - return null; - } - } - - @Override - public void removeFurniture(Entity entity) { - CustomFurniture.remove(entity, false); - } - - @Override - public String getBlockID(Block block) { - CustomBlock customBlock = CustomBlock.byAlreadyPlaced(block); - if (customBlock == null) { - return block.getType().name(); - } - return customBlock.getNamespacedID(); - } - - @Override - public String getItemID(ItemStack itemInHand) { - CustomStack customStack = CustomStack.byItemStack(itemInHand); - if (customStack == null) { - return null; - } - return customStack.getNamespacedID(); - } - - @Override - public ItemStack getItemStack(String id) { - if (id == null) return new ItemStack(Material.AIR); - CustomStack customStack = CustomStack.getInstance(id); - if (customStack == null) { - return null; - } - return customStack.getItemStack().clone(); - } - - @Override - public String getEntityID(Entity entity) { - CustomFurniture customFurniture = CustomFurniture.byAlreadySpawned(entity); - if (customFurniture == null) { - return entity.getType().name(); - } - return customFurniture.getNamespacedID(); - } - - @Override - public boolean isFurniture(Entity entity) { - try { - return CustomFurniture.byAlreadySpawned(entity) != null; - } catch (Exception e) { - return false; - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ComponentKeys.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ComponentKeys.java deleted file mode 100644 index 32eaad2..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/ComponentKeys.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.momirealms.customcrops.mechanic.item.factory; - -import net.kyori.adventure.key.Key; - -public class ComponentKeys { - - public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString(); - public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString(); - public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString(); - public static final String LORE = Key.key("minecraft", "lore").asString(); - public static final String DAMAGE = Key.key("minecraft", "damage").asString(); - public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString(); - public static final String HIDE_TOOLTIP = Key.key("minecraft", "hide_tooltip").asString(); - public static final String MAX_STACK_SIZE = Key.key("minecraft", "max_stack_size").asString(); - public static final String PROFILE = Key.key("minecraft", "profile").asString(); - public static final String UNBREAKABLE = Key.key("minecraft", "unbreakable").asString(); -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/Item.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/Item.java deleted file mode 100644 index 35e2233..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/factory/Item.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.momirealms.customcrops.mechanic.item.factory; - -import java.util.List; -import java.util.Optional; - -public interface Item { - - Item customModelData(Integer data); - - Optional customModelData(); - - Item damage(Integer data); - - Optional damage(); - - Item maxDamage(Integer data); - - Optional maxDamage(); - - Item lore(List lore); - - Optional> lore(); - - Optional getTag(Object... path); - - Item setTag(Object value, Object... path); - - boolean hasTag(Object... path); - - boolean removeTag(Object... path); - - I getItem(); - - I load(); - - I loadCopy(); - - void update(); -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/CFunction.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/CFunction.java deleted file mode 100644 index 23a83db..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/CFunction.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.function; - -import net.momirealms.customcrops.mechanic.item.function.wrapper.ConditionWrapper; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -public class CFunction implements Comparable { - - private static int functionID = 0; - private final Function function; - private final FunctionPriority priority; - private final int id; - - public CFunction(Function function, FunctionPriority priority) { - this.function = function; - this.priority = priority; - this.id = functionID++; - } - - public FunctionResult apply(ConditionWrapper wrapper) { - return function.apply(wrapper); - } - - public Function getFunction() { - return function; - } - - public FunctionPriority getPriority() { - return priority; - } - - @Override - public int compareTo(@NotNull CFunction o) { - if (this.priority.ordinal() > o.priority.ordinal()) { - return 1; - } else if (this.priority.ordinal() < o.priority.ordinal()) { - return -1; - } - return Integer.compare(this.id, o.id); - } - - public static void resetID() { - functionID = 0; - } - - public enum FunctionPriority { - - HIGHEST, - HIGH, - NORMAL, - LOW, - LOWEST; - - public boolean isHigherThan(FunctionPriority priority) { - return this.ordinal() < priority.ordinal(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakWrapper.java deleted file mode 100644 index 90c0d75..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/BreakWrapper.java +++ /dev/null @@ -1,35 +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.mechanic.item.function.wrapper; - -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class BreakWrapper extends ConditionWrapper { - - private final Location location; - - public BreakWrapper(Player player, Location location) { - super(player); - this.location = location; - } - - public Location getLocation() { - return location; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/ConditionWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/ConditionWrapper.java deleted file mode 100644 index 2cada63..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/ConditionWrapper.java +++ /dev/null @@ -1,40 +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.mechanic.item.function.wrapper; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -public abstract class ConditionWrapper { - - private final Player player; - private final ItemStack itemInHand; - - public ConditionWrapper(Player player) { - this.player = player; - this.itemInHand = player.getInventory().getItemInMainHand(); - } - - public Player getPlayer() { - return player; - } - - public ItemStack getItemInHand() { - return itemInHand; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractBlockWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractBlockWrapper.java deleted file mode 100644 index 77f9a64..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractBlockWrapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.function.wrapper; - -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; - -public class InteractBlockWrapper extends InteractWrapper { - - private final Block clickedBlock; - private final BlockFace clickedFace; - - public InteractBlockWrapper(Player player, Block clickedBlock, BlockFace clickedFace) { - super(player, clickedBlock.getLocation()); - this.clickedBlock = clickedBlock; - this.clickedFace = clickedFace; - } - - public Block getClickedBlock() { - return clickedBlock; - } - - public BlockFace getClickedFace() { - return clickedFace; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractFurnitureWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractFurnitureWrapper.java deleted file mode 100644 index f01e487..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractFurnitureWrapper.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.function.wrapper; - -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class InteractFurnitureWrapper extends InteractWrapper { - - private final String id; - private final Entity entity; - - public InteractFurnitureWrapper(Player player, Location location, String id, @Nullable Entity baseEntity) { - super(player, location); - this.entity = baseEntity; - this.id = id; - } - - @Nullable - public Entity getEntity() { - return entity; - } - - @NotNull - public String getID() { - return id; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractWrapper.java deleted file mode 100644 index f14201f..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/InteractWrapper.java +++ /dev/null @@ -1,35 +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.mechanic.item.function.wrapper; - -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class InteractWrapper extends ConditionWrapper { - - private final Location location; - - public InteractWrapper(Player player, Location location) { - super(player); - this.location = location; - } - - public Location getLocation() { - return location; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceBlockWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceBlockWrapper.java deleted file mode 100644 index ab66813..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceBlockWrapper.java +++ /dev/null @@ -1,36 +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.mechanic.item.function.wrapper; - -import org.bukkit.block.Block; -import org.bukkit.entity.Player; - -public class PlaceBlockWrapper extends PlaceWrapper { - - private final Block placedBlock; - - public PlaceBlockWrapper(Player player, Block placedBlock, String blockID) { - super(player, placedBlock.getLocation(), blockID); - this.placedBlock = placedBlock; - } - - public Block getPlacedBlock() { - return placedBlock; - } -} - diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceWrapper.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceWrapper.java deleted file mode 100644 index 668f31d..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/function/wrapper/PlaceWrapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.function.wrapper; - -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class PlaceWrapper extends ConditionWrapper { - - private final Location location; - private final String id; - - public PlaceWrapper(Player player, Location location, String id) { - super(player); - this.location = location; - this.id = id; - } - - public Location getLocation() { - return location; - } - - public String getId() { - return id; - } -} - diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/AbstractFertilizer.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/AbstractFertilizer.java deleted file mode 100644 index c7e6402..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/AbstractFertilizer.java +++ /dev/null @@ -1,102 +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.mechanic.item.impl; - -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.AbstractEventItem; - -import java.util.HashMap; -import java.util.HashSet; - -public class AbstractFertilizer extends AbstractEventItem implements Fertilizer { - - private final String key; - private final String itemID; - private final int times; - private final FertilizerType fertilizerType; - private final HashSet potWhitelist; - private final boolean beforePlant; - private final String icon; - private final Requirement[] requirements; - - public AbstractFertilizer( - String key, - String itemID, - int times, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - HashMap actionMap - ) { - super(actionMap); - this.key = key; - this.itemID = itemID; - this.times = times; - this.fertilizerType = fertilizerType; - this.potWhitelist = potWhitelist; - this.beforePlant = beforePlant; - this.icon = icon; - this.requirements = requirements; - } - - @Override - public String getKey() { - return key; - } - - @Override - public String getItemID() { - return itemID; - } - - @Override - public int getTimes() { - return times; - } - - @Override - public FertilizerType getFertilizerType() { - return fertilizerType; - } - - @Override - public HashSet getPotWhitelist() { - return potWhitelist; - } - - @Override - public boolean isBeforePlant() { - return beforePlant; - } - - @Override - public String getIcon() { - return icon; - } - - @Override - public Requirement[] getRequirements() { - return requirements; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/CropConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/CropConfig.java deleted file mode 100644 index 2618d5a..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/CropConfig.java +++ /dev/null @@ -1,241 +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.mechanic.item.impl; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.condition.Conditions; -import net.momirealms.customcrops.api.mechanic.condition.DeathConditions; -import net.momirealms.customcrops.api.mechanic.item.BoneMeal; -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.AbstractEventItem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; - -public class CropConfig extends AbstractEventItem implements Crop { - - private final String key; - private final String seedID; - private final int maxPoints; - private final boolean rotation; - private final ItemCarrier carrier; - private final Requirement[] plantRequirements; - private final Requirement[] breakRequirements; - private final Requirement[] interactRequirements; - private final BoneMeal[] boneMeals; - private final Conditions growConditions; - private final HashSet whitelistPots; - private final DeathConditions[] deathConditions; - private final HashMap point2StageConfigMap; - private final HashMap item2StageConfigMap; - - public CropConfig( - String key, - String seedID, - ItemCarrier carrier, - HashSet whitelistPots, - boolean rotation, - int maxPoints, - BoneMeal[] boneMeals, - Conditions growConditions, - DeathConditions[] deathConditions, - HashMap actionMap, - HashMap point2StageConfigMap, - Requirement[] plantRequirements, - Requirement[] breakRequirements, - Requirement[] interactRequirements - ) { - super(actionMap); - this.key = key; - this.seedID = seedID; - this.boneMeals = boneMeals; - this.rotation = rotation; - this.maxPoints = maxPoints; - this.growConditions = growConditions; - this.deathConditions = deathConditions; - this.plantRequirements = plantRequirements; - this.breakRequirements = breakRequirements; - this.interactRequirements = interactRequirements; - this.point2StageConfigMap = point2StageConfigMap; - this.whitelistPots = whitelistPots; - this.carrier = carrier; - this.item2StageConfigMap = new HashMap<>(); - for (CropStageConfig cropStageConfig : point2StageConfigMap.values()) { - if (cropStageConfig.getStageID() != null) { - this.item2StageConfigMap.put(cropStageConfig.getStageID(), cropStageConfig); - } - } - } - - @Override - public String getKey() { - return key; - } - - @Override - public String getSeedItemID() { - return seedID; - } - - @Override - public int getMaxPoints() { - return maxPoints; - } - - @Override - public Requirement[] getPlantRequirements() { - return plantRequirements; - } - - @Override - public Requirement[] getBreakRequirements() { - return breakRequirements; - } - - @Override - public Requirement[] getInteractRequirements() { - return interactRequirements; - } - - @Override - public Conditions getGrowConditions() { - return growConditions; - } - - @Override - public DeathConditions[] getDeathConditions() { - return deathConditions; - } - - @Override - public BoneMeal[] getBoneMeals() { - return boneMeals; - } - - @Override - public boolean hasRotation() { - return rotation; - } - - @Override - public Stage getStageByPoint(int point) { - return point2StageConfigMap.get(point); - } - - @NotNull - @Override - public String getStageItemByPoint(int point) { - if (point >= 0) { - Stage stage = point2StageConfigMap.get(point); - if (stage != null) { - String id = stage.getStageID(); - if (id == null) return getStageItemByPoint(point-1); - return id; - } else { - return getStageItemByPoint(point-1); - } - } - return null; - } - - @Override - public Stage getStageByItemID(String itemID) { - return item2StageConfigMap.get(itemID); - } - - @Override - public Collection getStages() { - return new ArrayList<>(point2StageConfigMap.values()); - } - - @Override - public HashSet getPotWhitelist() { - return whitelistPots; - } - - @Override - public ItemCarrier getItemCarrier() { - return carrier; - } - - public static class CropStageConfig extends AbstractEventItem implements Crop.Stage { - - @Nullable - private final String stageID; - - private final int point; - private final double hologramOffset; - private final Requirement[] interactRequirements; - private final Requirement[] breakRequirements; - - public CropStageConfig( - @Nullable String stageID, - int point, - double hologramOffset, - HashMap actionMap, - Requirement[] interactRequirements, - Requirement[] breakRequirements - ) { - super(actionMap); - this.stageID = stageID; - this.point = point; - this.hologramOffset = hologramOffset; - this.interactRequirements = interactRequirements; - this.breakRequirements = breakRequirements; - } - - @Override - public Crop getCrop() { - return CustomCropsPlugin.get().getItemManager().getCropByStageID(stageID); - } - - @Override - public double getHologramOffset() { - return hologramOffset; - } - - @Nullable - @Override - public String getStageID() { - return stageID; - } - - @Override - public int getPoint() { - return point; - } - - @Override - public Requirement[] getInteractRequirements() { - return interactRequirements; - } - - @Override - public Requirement[] getBreakRequirements() { - return breakRequirements; - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java deleted file mode 100644 index ecda936..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/PotConfig.java +++ /dev/null @@ -1,177 +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.mechanic.item.impl; - -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.AbstractEventItem; -import net.momirealms.customcrops.util.ConfigUtils; - -import java.util.HashMap; -import java.util.HashSet; - -public class PotConfig extends AbstractEventItem implements Pot { - - private final String key; - private final int storage; - private final String dryModel; - private final String wetModel; - private final boolean enableFertilizedAppearance; - private final HashMap> fertilizedPotMap; - private final PassiveFillMethod[] passiveFillMethods; - private final WaterBar waterBar; - private final Requirement[] placeRequirements; - private final Requirement[] breakRequirements; - private final Requirement[] useRequirements; - private final boolean acceptRainDrop; - private final boolean acceptNearbyWater; - private final boolean isVanillaBlock; - - public PotConfig( - String key, - int storage, - boolean acceptRainDrop, - boolean acceptNearbyWater, - String dryModel, - String wetModel, - boolean enableFertilizedAppearance, - HashMap> fertilizedPotMap, - WaterBar waterBar, - PassiveFillMethod[] passiveFillMethods, - HashMap actionMap, - Requirement[] placeRequirements, - Requirement[] breakRequirements, - Requirement[] useRequirements - ) { - super(actionMap); - this.key = key; - this.storage = storage; - this.acceptRainDrop = acceptRainDrop; - this.acceptNearbyWater = acceptNearbyWater; - this.enableFertilizedAppearance = enableFertilizedAppearance; - this.fertilizedPotMap = fertilizedPotMap; - this.passiveFillMethods = passiveFillMethods; - this.dryModel = dryModel; - this.wetModel = wetModel; - this.waterBar = waterBar; - this.placeRequirements = placeRequirements; - this.breakRequirements = breakRequirements; - this.useRequirements = useRequirements; - this.isVanillaBlock = ConfigUtils.isVanillaItem(dryModel) && ConfigUtils.isVanillaItem(wetModel); - } - - @Override - public int getStorage() { - return storage; - } - - @Override - public String getKey() { - return key; - } - - @Override - public HashSet getPotBlocks() { - HashSet set = new HashSet<>(); - set.add(wetModel); - set.add(dryModel); - for (Pair pair : fertilizedPotMap.values()) { - set.add(pair.left()); - set.add(pair.right()); - } - return set; - } - - @Override - public PassiveFillMethod[] getPassiveFillMethods() { - return passiveFillMethods; - } - - @Override - public String getDryItem() { - return dryModel; - } - - @Override - public String getWetItem() { - return wetModel; - } - - @Override - public Requirement[] getPlaceRequirements() { - return placeRequirements; - } - - @Override - public Requirement[] getBreakRequirements() { - return breakRequirements; - } - - @Override - public Requirement[] getUseRequirements() { - return useRequirements; - } - - @Override - public WaterBar getWaterBar() { - return waterBar; - } - - @Override - public boolean isRainDropAccepted() { - return acceptRainDrop; - } - - @Override - public boolean isNearbyWaterAccepted() { - return acceptNearbyWater; - } - - @Override - public String getBlockState(boolean water, FertilizerType type) { - if (type != null && enableFertilizedAppearance) { - return water ? fertilizedPotMap.get(type).right() : fertilizedPotMap.get(type).left(); - } else { - return water ? wetModel : dryModel; - } - } - - @Override - public boolean isVanillaBlock() { - return isVanillaBlock; - } - - @Override - public boolean isWetPot(String id) { - if (id.equals(getWetItem())) { - return true; - } - for (Pair pair : fertilizedPotMap.values()) { - if (pair.right().equals(id)) { - return true; - } - } - return false; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/SprinklerConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/SprinklerConfig.java deleted file mode 100644 index 0470f3b..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/SprinklerConfig.java +++ /dev/null @@ -1,165 +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.mechanic.item.impl; - -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.AbstractEventItem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.HashSet; - -public class SprinklerConfig extends AbstractEventItem implements Sprinkler { - - private final String key; - private final int range; - private final int storage; - private final int water; - private final boolean infinite; - private final String twoDItem; - private final String threeDItem; - private final String threeDItemWithWater; - private final WaterBar waterBar; - private final HashSet potWhitelist; - private final ItemCarrier itemCarrier; - private final PassiveFillMethod[] passiveFillMethods; - private final Requirement[] placeRequirements; - private final Requirement[] breakRequirements; - private final Requirement[] useRequirements; - - public SprinklerConfig( - String key, - ItemCarrier itemCarrier, - String twoDItem, - String threeDItem, - String threeDItemWithWater, - int range, - int storage, - int water, - boolean infinite, - WaterBar waterBar, - HashSet potWhitelist, - PassiveFillMethod[] passiveFillMethods, - HashMap actionMap, - Requirement[] placeRequirements, - Requirement[] breakRequirements, - Requirement[] useRequirements - ) { - super(actionMap); - this.key = key; - this.itemCarrier = itemCarrier; - this.twoDItem = twoDItem; - this.threeDItem = threeDItem; - this.threeDItemWithWater = threeDItemWithWater; - this.range = range; - this.storage = storage; - this.infinite = infinite; - this.water = water; - this.waterBar = waterBar; - this.potWhitelist = potWhitelist; - this.passiveFillMethods = passiveFillMethods; - this.placeRequirements = placeRequirements; - this.breakRequirements = breakRequirements; - this.useRequirements = useRequirements; - } - - @Nullable - @Override - public String get2DItemID() { - return twoDItem; - } - - @NotNull - @Override - public String get3DItemID() { - return threeDItem; - } - - @Override - @Nullable - public String get3DItemWithWater() { - return threeDItemWithWater; - } - - @Override - public int getStorage() { - return storage; - } - - @Override - public int getRange() { - return range; - } - - @Override - public String getKey() { - return key; - } - - @Override - public boolean isInfinite() { - return infinite; - } - - @Override - public int getWater() { - return water; - } - - @Override - public HashSet getPotWhitelist() { - return potWhitelist; - } - - @Override - public ItemCarrier getItemCarrier() { - return itemCarrier; - } - - @Override - public PassiveFillMethod[] getPassiveFillMethods() { - return passiveFillMethods; - } - - @Override - public Requirement[] getPlaceRequirements() { - return placeRequirements; - } - - @Override - public Requirement[] getBreakRequirements() { - return breakRequirements; - } - - @Override - public Requirement[] getUseRequirements() { - return useRequirements; - } - - @Override - public WaterBar getWaterBar() { - return waterBar; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/WateringCanConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/WateringCanConfig.java deleted file mode 100644 index 537f05f..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/WateringCanConfig.java +++ /dev/null @@ -1,212 +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.mechanic.item.impl; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ScoreComponent; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.momirealms.customcrops.api.manager.AdventureManager; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.PlaceholderManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.WateringCan; -import net.momirealms.customcrops.api.mechanic.item.water.PositiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.image.WaterBar; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.AbstractEventItem; -import net.momirealms.customcrops.mechanic.item.factory.BukkitItemFactory; -import net.momirealms.customcrops.mechanic.item.factory.Item; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -public class WateringCanConfig extends AbstractEventItem implements WateringCan { - - private final String key; - private final String itemID; - private final boolean infinite; - private final int width; - private final int length; - private final int storage; - private final int water; - private final HashSet potWhitelist; - private final HashSet sprinklerWhitelist; - private final boolean hasDynamicLore; - private final List lore; - private final PositiveFillMethod[] positiveFillMethods; - private final HashMap appearanceMap; - private final Requirement[] requirements; - private final WaterBar waterBar; - - public WateringCanConfig( - String key, - String itemID, - boolean infinite, - int width, - int length, - int storage, - int water, - boolean hasDynamicLore, - List lore, - HashSet potWhitelist, - HashSet sprinklerWhitelist, - PositiveFillMethod[] positiveFillMethods, - HashMap appearanceMap, - Requirement[] requirements, - HashMap actionMap, - WaterBar waterBar - ) { - super(actionMap); - this.itemID = itemID; - this.width = width; - this.length = length; - this.storage = storage; - this.hasDynamicLore = hasDynamicLore; - this.lore = lore; - this.potWhitelist = potWhitelist; - this.sprinklerWhitelist = sprinklerWhitelist; - this.positiveFillMethods = positiveFillMethods; - this.appearanceMap = appearanceMap; - this.requirements = requirements; - this.waterBar = waterBar; - this.key = key; - this.infinite = infinite; - this.water = water; - } - - @Override - public String getItemID() { - return itemID; - } - - @Override - public int getWidth() { - return width; - } - - @Override - public int getLength() { - return length; - } - - @Override - public int getStorage() { - return storage; - } - - @Override - public int getWater() { - return water; - } - - @Override - public boolean hasDynamicLore() { - return hasDynamicLore; - } - - @NotNull - public PositiveFillMethod[] getPositiveFillMethods() { - return positiveFillMethods; - } - - @Override - public void updateItem(Player player, ItemStack itemStack, int water, Map args) { - Item item = BukkitItemFactory.getInstance().wrap(itemStack); - int maxDurability = item.maxDamage().orElse((int) itemStack.getType().getMaxDurability()); - - if (isInfinite()) water = storage; - item.setTag(water, "WaterAmount"); - if (maxDurability != 0) { - item.setTag((int) (maxDurability * (((double) storage - water) / storage)), "Damage"); - } - if (appearanceMap.containsKey(water)) { - item.customModelData(appearanceMap.get(water)); - } - if (hasDynamicLore()) { - List lore = new ArrayList<>(item.lore().orElse(List.of())); - if (ConfigManager.protectLore()) { - lore.removeIf(line -> { - Component component = GsonComponentSerializer.gson().deserialize(line); - return component instanceof ScoreComponent scoreComponent - && scoreComponent.objective().equals("water") - && scoreComponent.name().equals("cc"); - }); - } else { - lore.clear(); - } - for (String newLore : getLore()) { - ScoreComponent.Builder builder = Component.score().name("cc").objective("water"); - builder.append(AdventureManager.getInstance().getComponentFromMiniMessage( - PlaceholderManager.getInstance().parse(player, newLore, args) - )); - lore.add(GsonComponentSerializer.gson().serialize(builder.build())); - } - item.lore(lore); - } - itemStack.setItemMeta(item.loadCopy().getItemMeta()); - } - - @Override - public int getCurrentWater(ItemStack itemStack) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return 0; - Item item = BukkitItemFactory.getInstance().wrap(itemStack); - return (int) item.getTag("WaterAmount").orElse(0); - } - - @Override - public HashSet getPotWhitelist() { - return potWhitelist; - } - - @Override - public HashSet getSprinklerWhitelist() { - return sprinklerWhitelist; - } - - @Override - public String getKey() { - return key; - } - - @Override - public List getLore() { - return lore; - } - - @Override - @Nullable - public WaterBar getWaterBar() { - return waterBar; - } - - @Override - public Requirement[] getRequirements() { - return requirements; - } - - @Override - public boolean isInfinite() { - return infinite; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/QualityCropConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/QualityCropConfig.java deleted file mode 100644 index 93b18f3..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/QualityCropConfig.java +++ /dev/null @@ -1,62 +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.mechanic.item.impl.fertilizer; - -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.QualityCrop; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.impl.AbstractFertilizer; - -import java.util.HashMap; -import java.util.HashSet; - -public class QualityCropConfig extends AbstractFertilizer implements QualityCrop { - - private final double[] ratio; - private final double chance; - - public QualityCropConfig( - String key, - String itemID, - int times, - double chance, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - double[] ratio, - HashMap events - ) { - super(key, itemID, times, fertilizerType, potWhitelist, beforePlant, icon, requirements, events); - this.ratio = ratio; - this.chance = chance; - } - - @Override - public double getChance() { - return chance; - } - - @Override - public double[] getRatio() { - return ratio; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SoilRetainConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SoilRetainConfig.java deleted file mode 100644 index 0f1f482..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SoilRetainConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.impl.fertilizer; - -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.SoilRetain; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.impl.AbstractFertilizer; - -import java.util.HashMap; -import java.util.HashSet; - -public class SoilRetainConfig extends AbstractFertilizer implements SoilRetain { - - private final double chance; - - public SoilRetainConfig( - String key, - String itemID, - int times, - double chance, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - HashMap events) { - super(key, itemID, times, fertilizerType, potWhitelist, beforePlant, icon, requirements, events); - this.chance = chance; - } - - @Override - public double getChance() { - return chance; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SpeedGrowConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SpeedGrowConfig.java deleted file mode 100644 index b211a80..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/SpeedGrowConfig.java +++ /dev/null @@ -1,60 +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.mechanic.item.impl.fertilizer; - -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.SpeedGrow; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.impl.AbstractFertilizer; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -public class SpeedGrowConfig extends AbstractFertilizer implements SpeedGrow { - - private final List> pairs; - - public SpeedGrowConfig( - String key, - String itemID, - int times, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - List> pairs, - HashMap events) { - super(key, itemID, times, fertilizerType, potWhitelist, beforePlant, icon, requirements, events); - this.pairs = pairs; - } - - @Override - public int getPointBonus() { - for (Pair pair : pairs) { - if (Math.random() < pair.left()) { - return pair.right(); - } - } - return 0; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/VariationConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/VariationConfig.java deleted file mode 100644 index 1323d23..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/VariationConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.item.impl.fertilizer; - -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.Variation; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.impl.AbstractFertilizer; - -import java.util.HashMap; -import java.util.HashSet; - -public class VariationConfig extends AbstractFertilizer implements Variation { - - private final double chanceBonus; - - public VariationConfig( - String key, - String itemID, - int times, - double chance, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - HashMap events) { - super(key, itemID, times, fertilizerType, potWhitelist, beforePlant, icon, requirements, events); - this.chanceBonus = chance; - } - - @Override - public double getChanceBonus() { - return chanceBonus; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/YieldIncreaseConfig.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/YieldIncreaseConfig.java deleted file mode 100644 index f47af9e..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/item/impl/fertilizer/YieldIncreaseConfig.java +++ /dev/null @@ -1,60 +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.mechanic.item.impl.fertilizer; - -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.YieldIncrease; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.mechanic.item.impl.AbstractFertilizer; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -public class YieldIncreaseConfig extends AbstractFertilizer implements YieldIncrease { - - private final List> pairs; - - public YieldIncreaseConfig( - String key, - String itemID, - int times, - FertilizerType fertilizerType, - HashSet potWhitelist, - boolean beforePlant, - String icon, - Requirement[] requirements, - List> pairs, - HashMap events) { - super(key, itemID, times, fertilizerType, potWhitelist, beforePlant, icon, requirements, events); - this.pairs = pairs; - } - - @Override - public int getAmountBonus() { - for (Pair pair : pairs) { - if (Math.random() < pair.left()) { - return pair.right(); - } - } - return 0; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/CrowAttackAnimation.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/CrowAttackAnimation.java deleted file mode 100644 index 72b1b19..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/CrowAttackAnimation.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.misc; - -import com.comphenix.protocol.events.PacketContainer; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import net.momirealms.customcrops.manager.PacketManager; -import net.momirealms.customcrops.util.FakeEntityUtils; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class CrowAttackAnimation { - - private CancellableTask task; - private final Location fromLocation; - private final Vector vectorDown; - private final Vector vectorUp; - private final Player[] viewers; - private int timer; - private final ItemStack flyModel; - private final ItemStack standModel; - private final int entityID = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - private final float yaw = ThreadLocalRandom.current().nextInt(361) - 180; - - public CrowAttackAnimation(SimpleLocation cropSimpleLocation, String flyModel, String standModel) { - ArrayList viewers = new ArrayList<>(); - for (Player player : Bukkit.getOnlinePlayers()) { - if (cropSimpleLocation.isNear(SimpleLocation.of(player.getLocation()), 48)) { - viewers.add(player); - } - } - this.viewers = viewers.toArray(new Player[0]); - Location cropLocation = cropSimpleLocation.getBukkitLocation().add(0.25 + Math.random() * 0.5, 0, 0.25 + Math.random() * 0.5); - this.flyModel = CustomCropsPlugin.get().getItemManager().getItemStack(null, flyModel); - this.standModel = CustomCropsPlugin.get().getItemManager().getItemStack(null, standModel); - this.fromLocation = cropLocation.clone().add((10 * Math.sin((Math.PI * yaw)/180)), 10, (- 10 * Math.cos((Math.PI * yaw)/180))); - Location relative = cropLocation.clone().subtract(fromLocation); - this.vectorDown = new Vector(relative.getX() / 100, -0.1, relative.getZ() / 100); - this.vectorUp = new Vector(relative.getX() / 100, 0.1, relative.getZ() / 100); - } - - public void start() { - if (this.viewers.length == 0) return; - sendPacketToViewers( - FakeEntityUtils.getSpawnPacket(entityID, fromLocation, EntityType.ARMOR_STAND), - FakeEntityUtils.getVanishArmorStandMetaPacket(entityID), - FakeEntityUtils.getEquipPacket(entityID, flyModel) - ); - this.task = CustomCropsPlugin.get().getScheduler().runTaskAsyncTimer(() -> { - timer++; - if (timer < 100) { - sendPacketToViewers(FakeEntityUtils.getTeleportPacket(entityID, fromLocation.add(vectorDown), yaw)); - } else if (timer == 100){ - sendPacketToViewers(FakeEntityUtils.getEquipPacket(entityID, standModel)); - } else if (timer == 150) { - sendPacketToViewers(FakeEntityUtils.getEquipPacket(entityID, flyModel)); - } else if (timer > 150) { - sendPacketToViewers(FakeEntityUtils.getTeleportPacket(entityID, fromLocation.add(vectorUp), yaw)); - } - if (timer > 300) { - sendPacketToViewers(FakeEntityUtils.getDestroyPacket(entityID)); - task.cancel(); - } - }, 50, 50, TimeUnit.MILLISECONDS); - } - - private void sendPacketToViewers(PacketContainer... packet) { - for (Player viewer : viewers) { - PacketManager.getInstance().send(viewer, packet); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/TempFakeItem.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/TempFakeItem.java deleted file mode 100644 index b5dbd41..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/TempFakeItem.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.misc; - -import com.comphenix.protocol.events.PacketContainer; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.manager.PacketManager; -import net.momirealms.customcrops.util.FakeEntityUtils; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -public class TempFakeItem { - - private final Player[] viewers; - private final int duration; - private final int entityID = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); - private final Location location; - private final String item; - - public TempFakeItem(Location location, String item, int duration, Player viewer) { - SimpleLocation simpleLocation = SimpleLocation.of(location); - ArrayList viewers = new ArrayList<>(); - if (viewer == null) - for (Player player : location.getWorld().getPlayers()) { - if (simpleLocation.isNear(SimpleLocation.of(player.getLocation()), 48)) { - viewers.add(player); - } - } - else { - viewers.add(viewer); - } - this.viewers = viewers.toArray(new Player[0]); - this.location = location; - this.item = item; - this.duration = duration; - } - - public void start() { - if (this.viewers.length == 0) return; - sendPacketToViewers( - FakeEntityUtils.getSpawnPacket(entityID, location, EntityType.ARMOR_STAND), - FakeEntityUtils.getVanishArmorStandMetaPacket(entityID), - FakeEntityUtils.getEquipPacket(entityID, CustomCropsPlugin.get().getItemManager().getItemStack(null, item)) - ); - CustomCropsPlugin.get().getScheduler().runTaskAsyncLater(() -> { - sendPacketToViewers(FakeEntityUtils.getDestroyPacket(entityID)); - }, duration * 50L, TimeUnit.MILLISECONDS); - } - - private void sendPacketToViewers(PacketContainer... packet) { - for (Player viewer : viewers) { - PacketManager.getInstance().send(viewer, packet); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java deleted file mode 100644 index dac5ecb..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/misc/migrator/Migration.java +++ /dev/null @@ -1,472 +0,0 @@ -package net.momirealms.customcrops.mechanic.misc.migrator; - -import dev.dejvokep.boostedyaml.YamlDocument; -import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning; -import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings; -import dev.dejvokep.boostedyaml.settings.general.GeneralSettings; -import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings; -import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSetting; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.world.CWorld; -import net.momirealms.customcrops.mechanic.world.adaptor.BukkitWorldAdaptor; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - -public class Migration { - - public static void tryUpdating() { - File configFile = new File(CustomCropsPlugin.getInstance().getDataFolder(), "config.yml"); - // If not config file found, do nothing - if (!configFile.exists()) return; - - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - String version = config.getString("config-version"); - if (version == null) return; - - int versionNumber = Integer.parseInt(version); - if (versionNumber >= 25 && versionNumber <= 34) { - doV33Migration(config); - return; - } - if (versionNumber == 35) { - doV343Migration(); - } - } - - private static void doV343Migration() { - if (CustomCropsPlugin.get().getWorldManager().getWorldAdaptor() instanceof BukkitWorldAdaptor adaptor) { - for (World world : Bukkit.getWorlds()) { - CWorld temp = new CWorld(CustomCropsPlugin.getInstance().getWorldManager(), world); - temp.setWorldSetting(WorldSetting.of(false,300,true, 1,true,2,true,2,false,1200, false,false,28,-1,-1,-1, 0)); - adaptor.convertWorldFromV342toV343(temp, world); - } - } - } - - private static void doV33Migration(YamlConfiguration config) { - // do migration - if (config.contains("mechanics.season.sync-season")) { - config.set("mechanics.sync-season.enable", config.getBoolean("mechanics.season.sync-season.enable")); - config.set("mechanics.sync-season.reference", config.getString("mechanics.season.sync-season.reference")); - } - if (config.contains("mechanics.season.greenhouse")) { - config.set("mechanics.greenhouse.enable", config.getBoolean("mechanics.season.greenhouse.enable")); - config.set("mechanics.greenhouse.id", config.getString("mechanics.season.greenhouse.block")); - config.set("mechanics.greenhouse.range", config.getInt("mechanics.season.greenhouse.range")); - } - if (config.contains("mechanics.scarecrow")) { - config.set("mechanics.scarecrow.id", config.getString("mechanics.scarecrow")); - } - - try { - config.save(new File(CustomCropsPlugin.getInstance().getDataFolder(), "config.yml")); - } catch (IOException e) { - e.printStackTrace(); - } - - try { - YamlDocument.create( - new File(CustomCropsPlugin.getInstance().getDataFolder(), "config.yml"), - Objects.requireNonNull(CustomCropsPlugin.getInstance().getResource("config.yml")), - GeneralSettings.DEFAULT, - LoaderSettings - .builder() - .setAutoUpdate(true) - .build(), - DumperSettings.DEFAULT, - UpdaterSettings - .builder() - .setVersioning(new BasicVersioning("config-version")) - .build() - ); - } catch (IOException e) { - LogUtils.warn(e.getMessage()); - } - - updateWateringCans(); - updatePots(); - updateFertilizers(); - updateSprinklers(); - updateCrops(); - - if (CustomCropsPlugin.get().getWorldManager().getWorldAdaptor() instanceof BukkitWorldAdaptor adaptor) { - for (World world : Bukkit.getWorlds()) { - CWorld temp = new CWorld(CustomCropsPlugin.getInstance().getWorldManager(), world); - temp.setWorldSetting(WorldSetting.of(false,300,true, 1,true,2,true,2,false, 1200, false,false,28,-1,-1,-1, 0)); - adaptor.convertWorldFromV33toV34(temp, world); - } - } - } - - private static void updateWateringCans() { - var files = ConfigUtils.getFilesRecursively(new File(CustomCropsPlugin.getInstance().getDataFolder(), "contents" + File.separator + "watering-cans")); - for (File file : files) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry sections : yaml.getValues(false).entrySet()) { - if (sections.getValue() instanceof ConfigurationSection section) { - ConfigurationSection fillSection = section.getConfigurationSection("fill-method"); - if (fillSection != null) { - for (String key : fillSection.getKeys(false)) { - fillSection.set(key + ".particle", null); - fillSection.set(key + ".sound", null); - } - } - if (section.contains("sound")) { - section.set("events.consume_water.sound_action.type", "sound"); - section.set("events.consume_water.sound_action.value.key", section.getString("sound")); - section.set("events.consume_water.sound_action.value.source", "player"); - section.set("events.consume_water.sound_action.value.volume", 1); - section.set("events.consume_water.sound_action.value.pitch", 1); - section.set("sound", null); - } - if (section.contains("particle")) { - section.set("events.add_water.particle_action.type", "particle"); - section.set("events.add_water.particle_action.value.particle", section.getString("particle")); - section.set("events.add_water.particle_action.value.x", 0.5); - section.set("events.add_water.particle_action.value.z", 0.5); - section.set("events.add_water.particle_action.value.y", 1.3); - section.set("events.add_water.particle_action.value.count", 5); - section.set("particle", null); - } - if (section.contains("actionbar")) { - if (section.getBoolean("actionbar.enable")) { - section.set("events.consume_water.actionbar_action.type", "actionbar"); - section.set("events.consume_water.actionbar_action.value", section.getString("actionbar.content")); - section.set("events.add_water.actionbar_action.type", "actionbar"); - section.set("events.add_water.actionbar_action.value", section.getString("actionbar.content")); - } - section.set("actionbar", null); - } - section.set("events.add_water.sound_action.type", "sound"); - section.set("events.add_water.sound_action.value.key", "minecraft:item.bucket.empty"); - section.set("events.add_water.sound_action.value.source", "player"); - section.set("events.add_water.sound_action.value.volume", 1); - section.set("events.add_water.sound_action.value.pitch", 1); - } - } - try { - yaml.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void updatePots() { - var files = ConfigUtils.getFilesRecursively(new File(CustomCropsPlugin.getInstance().getDataFolder(), "contents" + File.separator + "pots")); - for (File file : files) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry sections : yaml.getValues(false).entrySet()) { - if (sections.getValue() instanceof ConfigurationSection section) { - section.set("absorb-rainwater", true); - section.set("absorb-nearby-water", false); - ConfigurationSection fillSection = section.getConfigurationSection("fill-method"); - if (fillSection != null) { - for (String key : fillSection.getKeys(false)) { - fillSection.set(key + ".particle", null); - fillSection.set(key + ".sound", null); - } - } - if (section.contains("hologram.water.water-bar")) { - section.set("water-bar.left", section.getString("hologram.water.water-bar.left")); - section.set("water-bar.right", section.getString("hologram.water.water-bar.right")); - section.set("water-bar.empty", section.getString("hologram.water.water-bar.empty")); - section.set("water-bar.full", section.getString("hologram.water.water-bar.full")); - } - section.set("events.add_water.particle_action.type", "particle"); - section.set("events.add_water.particle_action.value.particle", "WATER_SPLASH"); - section.set("events.add_water.particle_action.value.x", 0.5); - section.set("events.add_water.particle_action.value.z", 0.5); - section.set("events.add_water.particle_action.value.y", 1.3); - section.set("events.add_water.particle_action.value.count", 5); - section.set("events.add_water.particle_action.value.offset-x", 0.3); - section.set("events.add_water.particle_action.value.offset-z", 0.3); - - ConfigurationSection holoSection = section.getConfigurationSection("hologram"); - if (holoSection != null) { - int duration = holoSection.getInt("duration") * 20; - String requireItem = holoSection.getString("require-item", "*"); - section.set("events.interact.conditional_action.type", "conditional"); - section.set("events.interact.conditional_action.value.conditions.requirement_1.type", "item-in-hand"); - section.set("events.interact.conditional_action.value.conditions.requirement_1.value.amount", 1); - section.set("events.interact.conditional_action.value.conditions.requirement_1.value.item", requireItem); - if (holoSection.getBoolean("water.enable")) { - String waterText = holoSection.getString("water.content"); - section.set("events.interact.conditional_action.value.actions.water_hologram.type", "hologram"); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.duration", duration); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.text", waterText); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.apply-correction", true); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.x", 0.5); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.y", 0.6); - section.set("events.interact.conditional_action.value.actions.water_hologram.value.z", 0.5); - } - if (holoSection.getBoolean("fertilizer.enable")) { - String fertilizerText = holoSection.getString("fertilizer.content"); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.type", "conditional"); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.conditions.requirement_1.type", "fertilizer"); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.conditions.requirement_1.value.has", true); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.type", "hologram"); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.text", fertilizerText); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.duration", duration); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.apply-correction", true); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.x", 0.5); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.y", 0.83); - section.set("events.interact.conditional_action.value.actions.conditional_fertilizer_action.value.actions.fertilizer_hologram.value.z", 0.5); - } - section.set("hologram", null); - } - } - } - try { - yaml.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void updateFertilizers() { - var files = ConfigUtils.getFilesRecursively(new File(CustomCropsPlugin.getInstance().getDataFolder(), "contents" + File.separator + "fertilizers")); - for (File file : files) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry sections : yaml.getValues(false).entrySet()) { - if (sections.getValue() instanceof ConfigurationSection section) { - if (section.contains("particle")) { - section.set("events.use.particle_action.type", "particle"); - section.set("events.use.particle_action.value.particle", section.getString("particle")); - section.set("events.use.particle_action.value.x", 0.5); - section.set("events.use.particle_action.value.y", 1.3); - section.set("events.use.particle_action.value.z", 0.5); - section.set("events.use.particle_action.value.count", 5); - section.set("events.use.particle_action.value.offset-x", 0.3); - section.set("events.use.particle_action.value.offset-z", 0.3); - section.set("particle", null); - } - if (section.contains("sound")) { - section.set("events.use.sound_action.type", "sound"); - section.set("events.use.sound_action.value.source", "player"); - section.set("events.use.sound_action.value.key", section.getString("sound")); - section.set("events.use.sound_action.value.volume", 1); - section.set("events.use.sound_action.value.pitch", 1); - section.set("sound", null); - } - if (section.contains("pot-whitelist")) { - section.set("events.wrong_pot.sound_action.type", "sound"); - section.set("events.wrong_pot.sound_action.value.source", "player"); - section.set("events.wrong_pot.sound_action.value.key", "minecraft:item.bundle.insert"); - section.set("events.wrong_pot.sound_action.value.volume", 1); - section.set("events.wrong_pot.sound_action.value.pitch", 1); - section.set("events.wrong_pot.actionbar_action.type", "actionbar"); - section.set("events.wrong_pot.actionbar_action.value", "[X] This fertilizer can only be used in pots."); - } - if (section.getBoolean("before-plant")) { - section.set("events.before_plant.sound_action.type", "sound"); - section.set("events.before_plant.sound_action.value.source", "player"); - section.set("events.before_plant.sound_action.value.key", "minecraft:item.bundle.insert"); - section.set("events.before_plant.sound_action.value.volume", 1); - section.set("events.before_plant.sound_action.value.pitch", 1); - section.set("events.before_plant.actionbar_action.type", "actionbar"); - section.set("events.before_plant.actionbar_action.value", "[X] You can only use this fertilizer before planting the crop."); - } - } - } - try { - yaml.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void updateSprinklers() { - var files = ConfigUtils.getFilesRecursively(new File(CustomCropsPlugin.getInstance().getDataFolder(), "contents" + File.separator + "sprinklers")); - for (File file : files) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry sections : yaml.getValues(false).entrySet()) { - if (sections.getValue() instanceof ConfigurationSection section) { - section.set("infinite", false); - ConfigurationSection fillSection = section.getConfigurationSection("fill-method"); - if (fillSection != null) { - for (String key : fillSection.getKeys(false)) { - fillSection.set(key + ".particle", null); - fillSection.set(key + ".sound", null); - } - } - if (section.contains("place-sound")) { - section.set("events.place.sound_action.type", "sound"); - section.set("events.place.sound_action.value.key", section.getString("place-sound")); - section.set("events.place.sound_action.value.source", "player"); - section.set("events.place.sound_action.value.volume", 1); - section.set("events.place.sound_action.value.pitch", 1); - section.set("place-sound", null); - } - section.set("events.interact.force_work_action.type", "conditional"); - section.set("events.interact.force_work_action.value.conditions.requirement_1.type", "sneak"); - section.set("events.interact.force_work_action.value.conditions.requirement_1.value", true); - section.set("events.interact.force_work_action.value.actions.action_1.type", "force-tick"); - if (section.contains("hologram")) { - if (section.getBoolean("hologram.enable")) { - int duration = section.getInt("hologram.duration") * 20; - String text = section.getString("hologram.content"); - if (section.contains("hologram.water-bar")) { - section.set("water-bar.left", section.getString("hologram.water-bar.left")); - section.set("water-bar.right", section.getString("hologram.water-bar.right")); - section.set("water-bar.empty", section.getString("hologram.water-bar.empty")); - section.set("water-bar.full", section.getString("hologram.water-bar.full")); - } - section.set("events.interact.hologram_action.type", "hologram"); - section.set("events.interact.hologram_action.value.duration", duration); - section.set("events.interact.hologram_action.value.text", text); - section.set("events.interact.hologram_action.value.x", 0.5); - section.set("events.interact.hologram_action.value.y", -0.3); - section.set("events.interact.hologram_action.value.z", 0.5); - section.set("events.interact.hologram_action.value.visible-to-all", false); - } - section.set("hologram", null); - } - if (section.contains("animation")) { - if (section.getBoolean("animation.enable")) { - String item = section.getString("animation.item"); - int duration = section.getInt("animation.duration") * 20; - section.set("events.work.fake_item_action.type", "fake-item"); - section.set("events.work.fake_item_action.value.item", item); - section.set("events.work.fake_item_action.value.duration", duration); - section.set("events.work.fake_item_action.value.x", 0.5); - section.set("events.work.fake_item_action.value.y", 0.4); - section.set("events.work.fake_item_action.value.z", 0.5); - section.set("events.work.fake_item_action.value.visible-to-all", true); - } - section.set("animation", null); - } - section.set("events.add_water.particle_action.type", "particle"); - section.set("events.add_water.particle_action.value.particle", "WATER_SPLASH"); - section.set("events.add_water.particle_action.value.x", 0.5); - section.set("events.add_water.particle_action.value.z", 0.5); - section.set("events.add_water.particle_action.value.y", 0.7); - section.set("events.add_water.particle_action.value.count", 5); - section.set("events.add_water.sound_action.type", "sound"); - section.set("events.add_water.sound_action.value.key", "minecraft:item.bucket.empty"); - section.set("events.add_water.sound_action.value.source", "player"); - section.set("events.add_water.sound_action.value.pitch", 1); - section.set("events.add_water.sound_action.value.volume", 1); - } - } - try { - yaml.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static void updateCrops() { - var files = ConfigUtils.getFilesRecursively(new File(CustomCropsPlugin.getInstance().getDataFolder(), "contents" + File.separator + "crops")); - for (File file : files) { - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (Map.Entry sections : yaml.getValues(false).entrySet()) { - if (sections.getValue() instanceof ConfigurationSection section) { - if (section.contains("plant-actions")) { - section.set("events.plant", section.getConfigurationSection("plant-actions")); - section.set("plant-actions", null); - } - ConfigurationSection boneMeal = section.getConfigurationSection("custom-bone-meal"); - if (boneMeal != null) { - for (Map.Entry entry : boneMeal.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - inner.set("actions.swing_action.type", "swing-hand"); - inner.set("actions.swing_action.value", true); - if (inner.contains("particle")) { - inner.set("actions.particle_action.type", "particle"); - inner.set("actions.particle_action.value.particle", inner.getString("particle")); - inner.set("actions.particle_action.value.count",5); - inner.set("actions.particle_action.value.x", 0.5); - inner.set("actions.particle_action.value.y", 0.5); - inner.set("actions.particle_action.value.z", 0.5); - inner.set("actions.particle_action.value.offset-x", 0.3); - inner.set("actions.particle_action.value.offset-y", 0.3); - inner.set("actions.particle_action.value.offset-z", 0.3); - inner.set("particle", null); - } - if (inner.contains("sound")) { - inner.set("actions.sound_action.type", "sound"); - inner.set("actions.sound_action.value.key", inner.getString("sound")); - inner.set("actions.sound_action.value.source", "player"); - inner.set("actions.sound_action.value.volume", 1); - inner.set("actions.sound_action.value.pitch", 1); - inner.set("sound", null); - } - } - } - } - - ConfigurationSection pointSection = section.getConfigurationSection("points"); - if (pointSection == null) continue; - for (Map.Entry entry1 : pointSection.getValues(false).entrySet()) { - if (entry1.getValue() instanceof ConfigurationSection pointS1) { - ConfigurationSection eventSection = pointS1.getConfigurationSection("events"); - if (eventSection != null) { - if (eventSection.contains("interact-by-hand")) { - eventSection.set("interact.empty_hand_action.type", "conditional"); - eventSection.set("interact.empty_hand_action.value.conditions", eventSection.getConfigurationSection("interact-by-hand.requirements")); - eventSection.set("interact.empty_hand_action.value.conditions.requirement_empty_hand.type", "item-in-hand"); - eventSection.set("interact.empty_hand_action.value.conditions.requirement_empty_hand.value.item", "AIR"); - eventSection.set("interact.empty_hand_action.value.actions", eventSection.getConfigurationSection("interact-by-hand")); - eventSection.set("interact.empty_hand_action.value.actions.requirements", null); - eventSection.set("interact-by-hand", null); - } - if (eventSection.contains("interact-with-item")) { - ConfigurationSection interactWithItem = eventSection.getConfigurationSection("interact-with-item"); - if (interactWithItem != null) { - int amount = 0; - for (Map.Entry entry : interactWithItem.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - amount++; - String requiredItem = inner.getString("item"); - boolean consume = inner.getBoolean("reduce-amount"); - String returned = inner.getString("return"); - ConfigurationSection actions = inner.getConfigurationSection("actions"); - eventSection.set("interact.action_" + amount + ".type", "conditional"); - eventSection.set("interact.action_" + amount + ".type", "conditional"); - eventSection.set("interact.action_" + amount + ".value.conditions", inner.getConfigurationSection("requirements")); - eventSection.set("interact.action_" + amount + ".value.conditions.requirement_item.type", "item-in-hand"); - eventSection.set("interact.action_" + amount + ".value.conditions.requirement_item.value.item", requiredItem); - eventSection.set("interact.action_" + amount + ".value.actions", actions); - if (consume) { - eventSection.set("interact.action_" + amount + ".value.actions.consume_item.type", "item-amount"); - eventSection.set("interact.action_" + amount + ".value.actions.consume_item.value", -1); - } - if (returned != null) { - eventSection.set("interact.action_" + amount + ".value.actions.return_item.type", "give-item"); - eventSection.set("interact.action_" + amount + ".value.actions.return_item.value.id", returned); - eventSection.set("interact.action_" + amount + ".value.actions.return_item.value.amount", 1); - } - } - } - } - eventSection.set("interact-with-item", null); - } - } - - } - } - - } - } - try { - yaml.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/RequirementManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/RequirementManagerImpl.java deleted file mode 100644 index cb8d289..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/requirement/RequirementManagerImpl.java +++ /dev/null @@ -1,1041 +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.mechanic.requirement; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.integration.LevelInterface; -import net.momirealms.customcrops.api.integration.SeasonInterface; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.RequirementManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.requirement.RequirementExpansion; -import net.momirealms.customcrops.api.mechanic.requirement.RequirementFactory; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.compatibility.VaultHook; -import net.momirealms.customcrops.compatibility.papi.ParseUtils; -import net.momirealms.customcrops.util.ClassUtils; -import net.momirealms.customcrops.util.ConfigUtils; -import net.momirealms.sparrow.heart.SparrowHeart; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.MemorySection; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.*; - -public class RequirementManagerImpl implements RequirementManager { - - private final CustomCropsPlugin plugin; - private final HashMap requirementBuilderMap; - private final String EXPANSION_FOLDER = "expansions/requirement"; - - public RequirementManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.requirementBuilderMap = new HashMap<>(); - this.registerInbuiltRequirements(); - } - - @Override - public void load() { - this.loadExpansions(); - } - - @Override - public void unload() { - } - - @Override - public void disable() { - this.requirementBuilderMap.clear(); - } - - @Override - public boolean registerRequirement(String type, RequirementFactory requirementFactory) { - if (this.requirementBuilderMap.containsKey(type)) return false; - this.requirementBuilderMap.put(type, requirementFactory); - return true; - } - - @Override - public boolean unregisterRequirement(String type) { - return this.requirementBuilderMap.remove(type) != null; - } - - private void registerInbuiltRequirements() { - this.registerTimeRequirement(); - this.registerYRequirement(); - this.registerContainRequirement(); - this.registerStartWithRequirement(); - this.registerEndWithRequirement(); - this.registerEqualsRequirement(); - this.registerBiomeRequirement(); - this.registerDateRequirement(); - this.registerPluginLevelRequirement(); - this.registerPermissionRequirement(); - this.registerWorldRequirement(); - this.registerWeatherRequirement(); - this.registerSeasonRequirement(); - this.registerGreaterThanRequirement(); - this.registerAndRequirement(); - this.registerOrRequirement(); - this.registerLevelRequirement(); - this.registerRandomRequirement(); - this.registerCoolDownRequirement(); - this.registerLessThanRequirement(); - this.registerNumberEqualRequirement(); - this.registerRegexRequirement(); - this.registerMoneyRequirement(); - this.registerEnvironmentRequirement(); - this.registerPotionEffectRequirement(); - this.registerInListRequirement(); - this.registerItemInHandRequirement(); - this.registerSneakRequirement(); - this.registerTemperatureRequirement(); - this.registerFertilizerRequirement(); - this.registerLightRequirement(); - this.registerGameModeRequirement(); - } - - @NotNull - @Override - public Requirement[] getRequirements(ConfigurationSection section, boolean advanced) { - List requirements = new ArrayList<>(); - if (section == null) { - return requirements.toArray(new Requirement[0]); - } - for (Map.Entry entry : section.getValues(false).entrySet()) { - String typeOrName = entry.getKey(); - if (hasRequirement(typeOrName)) { - requirements.add(getRequirement(typeOrName, entry.getValue())); - } else { - requirements.add(getRequirement(section.getConfigurationSection(typeOrName), advanced)); - } - } - return requirements.toArray(new Requirement[0]); - } - - @NotNull - @Override - public Requirement getRequirement(ConfigurationSection section, boolean advanced) { - if (section == null) return EmptyRequirement.instance; - List actionList = null; - if (advanced) { - actionList = new ArrayList<>(); - if (section.contains("not-met-actions")) { - for (Map.Entry entry : Objects.requireNonNull(section.getConfigurationSection("not-met-actions")).getValues(false).entrySet()) { - if (entry.getValue() instanceof MemorySection inner) { - actionList.add(plugin.getActionManager().getAction(inner)); - } - } - } - if (section.contains("message")) { - List messages = ConfigUtils.stringListArgs(section.get("message")); - actionList.add(plugin.getActionManager().getActionFactory("message").build(messages, 1)); - } - if (actionList.size() == 0) - actionList = null; - } - String type = section.getString("type"); - if (type == null) { - LogUtils.warn("No requirement type found at " + section.getCurrentPath()); - return EmptyRequirement.instance; - } - var builder = getRequirementFactory(type); - if (builder == null) { - return EmptyRequirement.instance; - } - return builder.build(section.get("value"), actionList, advanced); - } - - @Override - @NotNull - public Requirement getRequirement(String type, Object value) { - RequirementFactory factory = getRequirementFactory(type); - if (factory == null) { - LogUtils.warn("Requirement type: " + type + " doesn't exist."); - return EmptyRequirement.instance; - } - return factory.build(value); - } - - @Override - @Nullable - public RequirementFactory getRequirementFactory(String type) { - return requirementBuilderMap.get(type); - } - - private void registerTimeRequirement() { - registerRequirement("time", (args, actions, advanced) -> { - List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return state -> { - long time = state.getLocation().getWorld().getTime(); - for (Pair pair : timePairs) - if (time >= pair.left() && time <= pair.right()) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerYRequirement() { - registerRequirement("ypos", (args, actions, advanced) -> { - List> timePairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return state -> { - int y = state.getLocation().getBlockY(); - for (Pair pair : timePairs) - if (y >= pair.left() && y <= pair.right()) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerGameModeRequirement() { - registerRequirement("gamemode", (args, actions, advanced) -> { - List modes = ConfigUtils.stringListArgs(args); - return condition -> { - if (condition.getPlayer() == null) return true; - var name = condition.getPlayer().getGameMode().name().toLowerCase(Locale.ENGLISH); - if (modes.contains(name)) { - return true; - } - if (advanced) triggerActions(actions, condition); - return false; - }; - }); - } - - private void registerTemperatureRequirement() { - registerRequirement("temperature", (args, actions, advanced) -> { - List> tempPairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return state -> { - Location location = state.getLocation(); - double temp = location.getWorld().getTemperature(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - for (Pair pair : tempPairs) - if (temp >= pair.left() && temp <= pair.right()) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerLightRequirement() { - registerRequirement("light", (args, actions, advanced) -> { - List> tempPairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return state -> { - Location location = state.getLocation(); - int temp = location.getBlock().getLightLevel(); - for (Pair pair : tempPairs) - if (temp >= pair.left() && temp <= pair.right()) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - registerRequirement("natural-light", (args, actions, advanced) -> { - List> tempPairs = ConfigUtils.stringListArgs(args).stream().map(it -> ConfigUtils.splitStringIntegerArgs(it, "~")).toList(); - return state -> { - Location location = state.getLocation(); - int temp = location.getBlock().getLightFromSky(); - for (Pair pair : tempPairs) - if (temp >= pair.left() && temp <= pair.right()) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerOrRequirement() { - registerRequirement("||", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - Requirement[] requirements = getRequirements(section, advanced); - return state -> { - for (Requirement requirement : requirements) { - if (requirement.isStateMet(state)) { - return true; - } - } - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at || requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerAndRequirement() { - registerRequirement("&&", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - Requirement[] requirements = getRequirements(section, advanced); - return state -> { - outer: { - for (Requirement requirement : requirements) { - if (!requirement.isStateMet(state)) { - break outer; - } - } - return true; - } - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at && requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerLevelRequirement() { - registerRequirement("level", (args, actions, advanced) -> { - int level = (int) args; - return state -> { - if (state.getPlayer() == null) return true; - int current = state.getPlayer().getLevel(); - if (current >= level) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerMoneyRequirement() { - registerRequirement("money", (args, actions, advanced) -> { - double money = ConfigUtils.getDoubleValue(args); - return state -> { - if (state.getPlayer() == null) return true; - double current = VaultHook.getEconomy().getBalance(state.getPlayer()); - if (current >= money) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerRandomRequirement() { - registerRequirement("random", (args, actions, advanced) -> { - double random = ConfigUtils.getDoubleValue(args); - return state -> { - if (Math.random() < random) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerBiomeRequirement() { - registerRequirement("biome", (args, actions, advanced) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return state -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(state.getLocation()); - if (biomes.contains(currentBiome)) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - registerRequirement("!biome", (args, actions, advanced) -> { - HashSet biomes = new HashSet<>(ConfigUtils.stringListArgs(args)); - return state -> { - String currentBiome = SparrowHeart.getInstance().getBiomeResourceLocation(state.getLocation()); - if (!biomes.contains(currentBiome)) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerWorldRequirement() { - registerRequirement("world", (args, actions, advanced) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return state -> { - if (worlds.contains(state.getLocation().getWorld().getName())) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - registerRequirement("!world", (args, actions, advanced) -> { - HashSet worlds = new HashSet<>(ConfigUtils.stringListArgs(args)); - return state -> { - if (!worlds.contains(state.getLocation().getWorld().getName())) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerWeatherRequirement() { - registerRequirement("weather", (args, actions, advanced) -> { - List weathers = ConfigUtils.stringListArgs(args); - return state -> { - String currentWeather; - World world = state.getLocation().getWorld(); - if (world.isClearWeather()) currentWeather = "clear"; - else if (world.isThundering()) currentWeather = "thunder"; - else currentWeather = "rain"; - for (String weather : weathers) - if (weather.equalsIgnoreCase(currentWeather)) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerCoolDownRequirement() { - registerRequirement("cooldown", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String key = section.getString("key"); - int time = section.getInt("time"); - return state -> { - if (state.getPlayer() == null) return true; - if (!plugin.getCoolDownManager().isCoolDown(state.getPlayer().getUniqueId(), key, time)) { - return true; - } - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at cooldown requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerDateRequirement() { - registerRequirement("date", (args, actions, advanced) -> { - HashSet dates = new HashSet<>(ConfigUtils.stringListArgs(args)); - return state -> { - Calendar calendar = Calendar.getInstance(); - String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE); - if (dates.contains(current)) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerSneakRequirement() { - registerRequirement("sneak", (args, actions, advanced) -> { - boolean sneak = (boolean) args; - return state -> { - if (state.getPlayer() == null) return true; - if (sneak) { - if (state.getPlayer().isSneaking()) - return true; - } else { - if (!state.getPlayer().isSneaking()) - return true; - } - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerPermissionRequirement() { - registerRequirement("permission", (args, actions, advanced) -> { - List perms = ConfigUtils.stringListArgs(args); - return state -> { - if (state.getPlayer() == null) return true; - for (String perm : perms) - if (state.getPlayer().hasPermission(perm)) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - registerRequirement("!permission", (args, actions, advanced) -> { - List perms = ConfigUtils.stringListArgs(args); - return state -> { - if (state.getPlayer() == null) return true; - for (String perm : perms) - if (state.getPlayer().hasPermission(perm)) { - if (advanced) triggerActions(actions, state); - return false; - } - return true; - }; - }); - } - - private void registerSeasonRequirement() { - registerRequirement("season", (args, actions, advanced) -> { - HashSet seasons = new HashSet<>(ConfigUtils.stringListArgs(args).stream().map(str -> str.toUpperCase(Locale.ENGLISH)).toList()); - return state -> { - Location location = state.getLocation(); - SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface(); - if (seasonInterface == null) return true; - Season season = seasonInterface.getSeason(location.getWorld()); - if (season == null) return true; - if (seasons.contains(season.name())) return true; - if (ConfigManager.enableGreenhouse()) { - for (int i = 1; i <= ConfigManager.greenhouseRange(); i++) { - if (plugin.getWorldManager().getGlassAt(SimpleLocation.of(location.clone().add(0,i,0))).isPresent()) { - return true; - } - } - } - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerGreaterThanRequirement() { - registerRequirement(">=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) >= Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at >= requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement(">", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) > Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at > requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerRegexRequirement() { - registerRequirement("regex", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("papi", ""); - String v2 = section.getString("regex", ""); - return state -> { - if (ParseUtils.setPlaceholders(state.getPlayer(), v1).matches(v2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at regex requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerNumberEqualRequirement() { - registerRequirement("=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) == Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at = requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("==", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) == Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at == requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) != Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at != requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerFertilizerRequirement() { - registerRequirement("fertilizer", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - boolean has = section.getBoolean("has"); - HashSet keys = new HashSet<>(ConfigUtils.stringListArgs(section.get("key"))); - int y = section.getInt("y", 0); - return condition -> { - Location location = condition.getLocation().clone().add(0,y,0); - SimpleLocation simpleLocation = SimpleLocation.of(location); - Optional optionalCustomCropsBlock = plugin.getWorldManager().getBlockAt(simpleLocation); - if (optionalCustomCropsBlock.isPresent()) { - if (optionalCustomCropsBlock.get() instanceof WorldPot pot) { - Fertilizer fertilizer = pot.getFertilizer(); - if (fertilizer == null) { - if (!has && keys.size() == 0) { - return true; - } - } else { - String key = fertilizer.getKey(); - if (has) { - if (keys.size() == 0) { - return true; - } - if (keys.contains(key)) { - return true; - } - } else { - if (!keys.contains(key)) { - return true; - } - } - } - } - } - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at fertilizer requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerItemInHandRequirement() { - registerRequirement("item-in-hand", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - int amount = section.getInt("amount", 0); - List items = ConfigUtils.stringListArgs(section.get("item")); - return condition -> { - ItemStack itemStack = condition.getItemInHand(); - if (itemStack == null) itemStack = new ItemStack(Material.AIR); - String id; - if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0) { - id = "AIR"; - } else { - id = plugin.getItemManager().getItemID(itemStack); - } - if ((items.contains(id) || items.contains("*")) && itemStack.getAmount() >= amount) return true; - if (advanced) triggerActions(actions, condition); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at item-in-hand requirement."); - return EmptyRequirement.instance; - } - }); - } - - @SuppressWarnings("DuplicatedCode") - private void registerLessThanRequirement() { - registerRequirement("<", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) < Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at < requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("<=", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (Double.parseDouble(p1) <= Double.parseDouble(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at <= requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerStartWithRequirement() { - registerRequirement("startsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (p1.startsWith(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at startsWith requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!startsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (!p1.startsWith(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !startsWith requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerEndWithRequirement() { - registerRequirement("endsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (p1.endsWith(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at endsWith requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!endsWith", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (!p1.endsWith(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !endsWith requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerContainRequirement() { - registerRequirement("contains", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (p1.contains(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at contains requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!contains", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (!p1.contains(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !contains requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerInListRequirement() { - registerRequirement("in-list", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - HashSet values = new HashSet<>(ConfigUtils.stringListArgs(section.get("values"))); - return state -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), papi) : papi; - if (values.contains(p1)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at in-list requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!in-list", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String papi = section.getString("papi", ""); - HashSet values = new HashSet<>(ConfigUtils.stringListArgs(section.get("values"))); - return state -> { - String p1 = papi.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), papi) : papi; - if (!values.contains(p1)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !in-list requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerEqualsRequirement() { - registerRequirement("equals", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (p1.equals(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at equals requirement."); - return EmptyRequirement.instance; - } - }); - registerRequirement("!equals", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String v1 = section.getString("value1", ""); - String v2 = section.getString("value2", ""); - return state -> { - String p1 = v1.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v1) : v1; - String p2 = v2.startsWith("%") ? ParseUtils.setPlaceholders(state.getPlayer(), v2) : v2; - if (!p1.equals(p2)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at !equals requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerEnvironmentRequirement() { - registerRequirement("environment", (args, actions, advanced) -> { - List environments = ConfigUtils.stringListArgs(args); - return state -> { - var name = state.getLocation().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); - if (environments.contains(name)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - registerRequirement("!environment", (args, actions, advanced) -> { - List environments = ConfigUtils.stringListArgs(args); - return state -> { - var name = state.getLocation().getWorld().getEnvironment().name().toLowerCase(Locale.ENGLISH); - if (!environments.contains(name)) return true; - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void registerPluginLevelRequirement() { - registerRequirement("plugin-level", (args, actions, advanced) -> { - if (args instanceof ConfigurationSection section) { - String pluginName = section.getString("plugin"); - int level = section.getInt("level"); - String target = section.getString("target"); - return state -> { - LevelInterface levelInterface = plugin.getIntegrationManager().getLevelPlugin(pluginName); - if (levelInterface == null) { - LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."); - return true; - } - if (levelInterface.getLevel(state.getPlayer(), target) >= level) - return true; - if (advanced) triggerActions(actions, state); - return false; - }; - } else { - LogUtils.warn("Wrong value format found at plugin-level requirement."); - return EmptyRequirement.instance; - } - }); - } - - private void registerPotionEffectRequirement() { - registerRequirement("potion-effect", (args, actions, advanced) -> { - String potions = (String) args; - String[] split = potions.split("(<=|>=|<|>|==|=)", 2); - PotionEffectType type = PotionEffectType.getByName(split[0]); - if (type == null) { - LogUtils.warn("Potion effect doesn't exist: " + split[0]); - return EmptyRequirement.instance; - } - int required = Integer.parseInt(split[1]); - String operator = potions.substring(split[0].length(), potions.length() - split[1].length()); - return state -> { - if (state.getPlayer() == null) return true; - int level = -1; - PotionEffect potionEffect = state.getPlayer().getPotionEffect(type); - if (potionEffect != null) { - level = potionEffect.getAmplifier(); - } - boolean result = false; - switch (operator) { - case ">=" -> { - if (level >= required) result = true; - } - case ">" -> { - if (level > required) result = true; - } - case "==", "=" -> { - if (level == required) result = true; - } - case "!=" -> { - if (level != required) result = true; - } - case "<=" -> { - if (level <= required) result = true; - } - case "<" -> { - if (level < required) result = true; - } - } - if (result) { - return true; - } - if (advanced) triggerActions(actions, state); - return false; - }; - }); - } - - private void triggerActions(List actions, State state) { - if (actions != null) - for (Action action : actions) - action.trigger(state); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private void loadExpansions() { - File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER); - if (!expansionFolder.exists()) - expansionFolder.mkdirs(); - - List> classes = new ArrayList<>(); - File[] expansionJars = expansionFolder.listFiles(); - if (expansionJars == null) return; - for (File expansionJar : expansionJars) { - if (expansionJar.getName().endsWith(".jar")) { - try { - Class expansionClass = ClassUtils.findClass(expansionJar, RequirementExpansion.class); - classes.add(expansionClass); - } catch (IOException | ClassNotFoundException e) { - LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e); - } - } - } - try { - for (Class expansionClass : classes) { - RequirementExpansion expansion = expansionClass.getDeclaredConstructor().newInstance(); - unregisterRequirement(expansion.getRequirementType()); - registerRequirement(expansion.getRequirementType(), expansion.getRequirementFactory()); - LogUtils.info("Loaded requirement expansion: " + expansion.getRequirementType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor()); - } - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { - LogUtils.warn("Error occurred when creating expansion instance.", e); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java deleted file mode 100644 index a3cf277..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CChunk.java +++ /dev/null @@ -1,624 +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.mechanic.world; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.BlockPos; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.*; -import net.momirealms.customcrops.mechanic.world.block.MemoryPot; -import net.momirealms.customcrops.mechanic.world.block.MemorySprinkler; -import net.momirealms.customcrops.scheduler.task.TickTask; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadLocalRandom; - -public class CChunk implements CustomCropsChunk { - - private transient CWorld cWorld; - private final ChunkPos chunkPos; - private final ConcurrentHashMap loadedSections; - private final PriorityQueue queue; - private final Set tickedBlocks; - private long lastLoadedTime; - private int loadedSeconds; - private int unloadedSeconds; - private boolean notified; - - public CChunk(CWorld cWorld, ChunkPos chunkPos) { - this.cWorld = cWorld; - this.chunkPos = chunkPos; - this.loadedSections = new ConcurrentHashMap<>(64); - this.queue = new PriorityQueue<>(); - this.unloadedSeconds = 0; - this.tickedBlocks = Collections.synchronizedSet(new HashSet<>()); - this.updateLastLoadedTime(); - this.notified = true; - } - - public CChunk( - CWorld cWorld, - ChunkPos chunkPos, - int loadedSeconds, - long lastLoadedTime, - ConcurrentHashMap loadedSections, - PriorityQueue queue, - HashSet tickedBlocks - ) { - this.cWorld = cWorld; - this.chunkPos = chunkPos; - this.loadedSections = loadedSections; - this.lastLoadedTime = lastLoadedTime; - this.loadedSeconds = loadedSeconds; - this.queue = queue; - this.unloadedSeconds = 0; - this.tickedBlocks = Collections.synchronizedSet(tickedBlocks); - } - - @Override - public void updateLastLoadedTime() { - this.lastLoadedTime = System.currentTimeMillis(); - } - - @Override - public void notifyOfflineUpdates() { - this.notified = true; - long current = System.currentTimeMillis(); - int offlineTimeInSeconds = (int) (current - this.lastLoadedTime) / 1000; - CustomCropsPlugin.get().debug(chunkPos.toString() + " Offline seconds: " + offlineTimeInSeconds + "s."); - offlineTimeInSeconds = Math.min(offlineTimeInSeconds, cWorld.getWorldSetting().getMaxOfflineTime()); - this.lastLoadedTime = current; - var setting = cWorld.getWorldSetting(); - int minTickUnit = setting.getMinTickUnit(); - for (int i = 0; i < offlineTimeInSeconds; i++) { - this.loadedSeconds++; - if (this.loadedSeconds >= minTickUnit) { - this.loadedSeconds = 0; - this.tickedBlocks.clear(); - this.queue.clear(); - if (setting.isScheduledTick()) { - this.arrangeTasks(minTickUnit); - } - } - scheduledTick(setting, true); - randomTick(setting, true); - } - } - - public void setWorld(CWorld cWorld) { - this.cWorld = cWorld; - } - - @Override - public CustomCropsWorld getCustomCropsWorld() { - return cWorld; - } - - @Override - public CustomCropsRegion getCustomCropsRegion() { - return cWorld.getLoadedRegionAt(chunkPos.getRegionPos()).orElse(null); - } - - @Override - public ChunkPos getChunkPos() { - return chunkPos; - } - - @Override - public void secondTimer() { - WorldSetting setting = cWorld.getWorldSetting(); - int interval = setting.getMinTickUnit(); - this.loadedSeconds++; - // if loadedSeconds reach another recycle, rearrange the tasks - if (this.loadedSeconds >= interval) { - this.loadedSeconds = 0; - this.tickedBlocks.clear(); - this.queue.clear(); - if (setting.isScheduledTick()) { - this.arrangeTasks(interval); - } - } - - scheduledTick(setting, false); - randomTick(setting, false); - } - - private void scheduledTick(WorldSetting setting, boolean offline) { - // scheduled tick - while (!queue.isEmpty() && queue.peek().getTime() <= loadedSeconds) { - TickTask task = queue.poll(); - if (task != null) { - BlockPos pos = task.getChunkPos(); - CSection section = loadedSections.get(pos.getSectionID()); - if (section != null) { - CustomCropsBlock block = section.getBlockAt(pos); - if (block == null) continue; - switch (block.getType()) { - case SCARECROW, GREENHOUSE -> {} - case POT -> { - if (!setting.randomTickPot()) { - block.tick(setting.getTickPotInterval(), offline); - } - } - case CROP -> { - if (!setting.randomTickCrop()) { - block.tick(setting.getTickCropInterval(), offline); - } - } - case SPRINKLER -> { - if (!setting.randomTickSprinkler()) { - block.tick(setting.getTickSprinklerInterval(), offline); - } - } - } - } - } - } - } - - private void randomTick(WorldSetting setting, boolean offline) { - // random tick - ThreadLocalRandom random = ThreadLocalRandom.current(); - int randomTicks = setting.getRandomTickSpeed(); - for (CustomCropsSection section : getSections()) { - int sectionID = section.getSectionID(); - int baseY = sectionID * 16; - for (int i = 0; i < randomTicks; i++) { - int x = random.nextInt(16); - int y = random.nextInt(16) + baseY; - int z = random.nextInt(16); - CustomCropsBlock block = section.getBlockAt(new BlockPos(x,y,z)); - if (block != null) { - switch (block.getType()) { - case CROP -> { - if (setting.randomTickCrop()) { - block.tick(setting.getTickCropInterval(), offline); - } - } - case SPRINKLER -> { - if (setting.randomTickSprinkler()) { - block.tick(setting.getTickSprinklerInterval(), offline); - } - } - case POT -> { - ((WorldPot) block).tickWater(); - if (setting.randomTickPot()) { - block.tick(setting.getTickPotInterval(), offline); - } - } - } - } - } - } - } - - @Override - public long getLastLoadedTime() { - return lastLoadedTime; - } - - @Override - public int getLoadedSeconds() { - return this.loadedSeconds; - } - - public void arrangeTasks(int unit) { - ThreadLocalRandom random = ThreadLocalRandom.current(); - for (CustomCropsSection section : getSections()) { - for (Map.Entry entry : section.getBlockMap().entrySet()) { - this.queue.add(new TickTask( - random.nextInt(0, unit), - entry.getKey() - )); - this.tickedBlocks.add(entry.getKey()); - } - } - } - - public void tryCreatingTaskForNewBlock(BlockPos pos) { - WorldSetting setting = cWorld.getWorldSetting(); - if (setting.isScheduledTick() && !tickedBlocks.contains(pos)) { - tickedBlocks.add(pos); - int random = ThreadLocalRandom.current().nextInt(0, setting.getMinTickUnit()); - if (random > loadedSeconds) { - queue.add(new TickTask(random, pos)); - } - } - } - - @Override - public Optional getCropAt(SimpleLocation simpleLocation) { - return getBlockAt(simpleLocation).map(customCropsBlock -> customCropsBlock instanceof WorldCrop worldCrop ? worldCrop : null); - } - - @Override - public Optional getSprinklerAt(SimpleLocation simpleLocation) { - return getBlockAt(simpleLocation).map(customCropsBlock -> customCropsBlock instanceof WorldSprinkler worldSprinkler ? worldSprinkler : null); - } - - @Override - public Optional getPotAt(SimpleLocation simpleLocation) { - return getBlockAt(simpleLocation).map(customCropsBlock -> customCropsBlock instanceof WorldPot worldPot ? worldPot : null); - } - - @Override - public Optional getGlassAt(SimpleLocation simpleLocation) { - return getBlockAt(simpleLocation).map(customCropsBlock -> customCropsBlock instanceof WorldGlass worldGlass ? worldGlass : null); - } - - @Override - public Optional getScarecrowAt(SimpleLocation simpleLocation) { - return getBlockAt(simpleLocation).map(customCropsBlock -> customCropsBlock instanceof WorldScarecrow worldScarecrow ? worldScarecrow : null); - } - - @Override - public void addWaterToSprinkler(Sprinkler sprinkler, int amount, SimpleLocation location) { - Optional optionalSprinkler = getSprinklerAt(location); - if (optionalSprinkler.isEmpty()) { - addBlockAt(new MemorySprinkler(location, sprinkler.getKey(), amount), location); - CustomCropsPlugin.get().debug("When adding water to sprinkler at " + location + ", the sprinkler data doesn't exist."); - if (sprinkler.get3DItemWithWater() != null) { - CustomCropsPlugin.get().getItemManager().removeAnythingAt(location.getBukkitLocation()); - CustomCropsPlugin.get().getItemManager().placeItem(location.getBukkitLocation(), sprinkler.getItemCarrier(), sprinkler.get3DItemWithWater()); - } - } else { - int current = optionalSprinkler.get().getWater(); - if (current == 0) { - if (sprinkler.get3DItemWithWater() != null) { - CustomCropsPlugin.get().getItemManager().removeAnythingAt(location.getBukkitLocation()); - CustomCropsPlugin.get().getItemManager().placeItem(location.getBukkitLocation(), sprinkler.getItemCarrier(), sprinkler.get3DItemWithWater()); - } - } - optionalSprinkler.get().setWater(current + amount); - } - } - - @Override - public void addFertilizerToPot(Pot pot, Fertilizer fertilizer, SimpleLocation location) { - Optional optionalWorldPot = getPotAt(location); - if (optionalWorldPot.isEmpty()) { - MemoryPot memoryPot = new MemoryPot(location, pot.getKey()); - memoryPot.setFertilizer(fertilizer); - addBlockAt(memoryPot, location); - CustomCropsPlugin.get().debug("When adding fertilizer to pot at " + location + ", the pot data doesn't exist."); - CustomCropsPlugin.get().getItemManager().updatePotState(location.getBukkitLocation(), pot, false, fertilizer); - } else { - optionalWorldPot.get().setFertilizer(fertilizer); - CustomCropsPlugin.get().getItemManager().updatePotState(location.getBukkitLocation(), pot, optionalWorldPot.get().getWater() > 0, fertilizer); - } - } - - @Override - public void addWaterToPot(Pot pot, int amount, SimpleLocation location) { - Optional optionalWorldPot = getPotAt(location); - if (optionalWorldPot.isEmpty()) { - MemoryPot memoryPot = new MemoryPot(location, pot.getKey()); - memoryPot.setWater(amount); - addBlockAt(memoryPot, location); - CustomCropsPlugin.get().getItemManager().updatePotState(location.getBukkitLocation(), pot, true, null); - CustomCropsPlugin.get().debug("When adding water to pot at " + location + ", the pot data doesn't exist."); - } else { - optionalWorldPot.get().setWater(optionalWorldPot.get().getWater() + amount); - CustomCropsPlugin.get().getItemManager().updatePotState(location.getBukkitLocation(), pot, true, optionalWorldPot.get().getFertilizer()); - } - } - - @Override - public void addPotAt(WorldPot pot, SimpleLocation location) { - CustomCropsBlock previous = addBlockAt(pot, location); - if (previous != null) { - if (previous instanceof WorldPot) { - CustomCropsPlugin.get().debug("Found duplicated pot data when adding pot at " + location); - } else { - CustomCropsPlugin.get().debug("Found unremoved data when adding crop at " + location + ". Previous type is " + previous.getType().name()); - } - } - } - - @Override - public void addSprinklerAt(WorldSprinkler sprinkler, SimpleLocation location) { - CustomCropsBlock previous = addBlockAt(sprinkler, location); - if (previous != null) { - if (previous instanceof WorldSprinkler) { - CustomCropsPlugin.get().debug("Found duplicated sprinkler data when adding sprinkler at " + location); - } else { - CustomCropsPlugin.get().debug("Found unremoved data when adding crop at " + location + ". Previous type is " + previous.getType().name()); - } - } - } - - @Override - public void addCropAt(WorldCrop crop, SimpleLocation location) { - CustomCropsBlock previous = addBlockAt(crop, location); - if (previous != null) { - if (previous instanceof WorldCrop) { - CustomCropsPlugin.get().debug("Found duplicated crop data when adding crop at " + location); - } else { - CustomCropsPlugin.get().debug("Found unremoved data when adding crop at " + location + ". Previous type is " + previous.getType().name()); - } - } - } - - @Override - public void addPointToCrop(Crop crop, int points, SimpleLocation location) { - if (points <= 0) return; - Optional cropData = getCropAt(location); - if (cropData.isEmpty()) { - return; - } - WorldCrop worldCrop = cropData.get(); - int previousPoint = worldCrop.getPoint(); - int x = Math.min(previousPoint + points, crop.getMaxPoints()); - worldCrop.setPoint(x); - Location bkLoc = location.getBukkitLocation(); - if (bkLoc == null) return; - for (int i = previousPoint + 1; i <= x; i++) { - Crop.Stage stage = crop.getStageByPoint(i); - if (stage != null) { - stage.trigger(ActionTrigger.GROW, new State(null, new ItemStack(Material.AIR), bkLoc)); - } - } - String pre = crop.getStageItemByPoint(previousPoint); - String after = crop.getStageItemByPoint(x); - if (pre.equals(after)) return; - CRotation CRotation = CustomCropsPlugin.get().getItemManager().removeAnythingAt(bkLoc); - CustomCropsPlugin.get().getItemManager().placeItem(bkLoc, crop.getItemCarrier(), after, CRotation); - } - - @Override - public void addGlassAt(WorldGlass glass, SimpleLocation location) { - CustomCropsBlock previous = addBlockAt(glass, location); - if (previous != null) { - if (previous instanceof WorldGlass) { - CustomCropsPlugin.get().debug("Found duplicated glass data when adding crop at " + location); - } else { - CustomCropsPlugin.get().debug("Found unremoved data when adding glass at " + location + ". Previous type is " + previous.getType().name()); - } - } - } - - @Override - public void addScarecrowAt(WorldScarecrow scarecrow, SimpleLocation location) { - CustomCropsBlock previous = addBlockAt(scarecrow, location); - if (previous != null) { - if (previous instanceof WorldScarecrow) { - CustomCropsPlugin.get().debug("Found duplicated scarecrow data when adding scarecrow at " + location); - } else { - CustomCropsPlugin.get().debug("Found unremoved data when adding scarecrow at " + location + ". Previous type is " + previous.getType().name()); - } - } - } - - @Override - public WorldSprinkler removeSprinklerAt(SimpleLocation location) { - CustomCropsBlock removed = removeBlockAt(location); - if (removed == null) { - CustomCropsPlugin.get().debug("Failed to remove sprinkler from " + location + " because sprinkler doesn't exist."); - return null; - } else if (!(removed instanceof WorldSprinkler worldSprinkler)) { - CustomCropsPlugin.get().debug("Removed sprinkler from " + location + " but the previous block type is " + removed.getType().name()); - return null; - } else { - return worldSprinkler; - } - } - - @Override - public WorldPot removePotAt(SimpleLocation location) { - CustomCropsBlock removed = removeBlockAt(location); - if (removed == null) { - CustomCropsPlugin.get().debug("Failed to remove pot from " + location + " because pot doesn't exist."); - return null; - } else if (!(removed instanceof WorldPot worldPot)) { - CustomCropsPlugin.get().debug("Removed pot from " + location + " but the previous block type is " + removed.getType().name()); - return null; - } else { - return worldPot; - } - } - - @Override - public WorldCrop removeCropAt(SimpleLocation location) { - CustomCropsBlock removed = removeBlockAt(location); - if (removed == null) { - CustomCropsPlugin.get().debug("Failed to remove crop from " + location + " because crop doesn't exist."); - return null; - } else if (!(removed instanceof WorldCrop worldCrop)) { - CustomCropsPlugin.get().debug("Removed crop from " + location + " but the previous block type is " + removed.getType().name()); - return null; - } else { - return worldCrop; - } - } - - @Override - public WorldGlass removeGlassAt(SimpleLocation location) { - CustomCropsBlock removed = removeBlockAt(location); - if (removed == null) { - CustomCropsPlugin.get().debug("Failed to remove glass from " + location + " because glass doesn't exist."); - return null; - } else if (!(removed instanceof WorldGlass worldGlass)) { - CustomCropsPlugin.get().debug("Removed glass from " + location + " but the previous block type is " + removed.getType().name()); - return null; - } else { - return worldGlass; - } - } - - @Override - public WorldScarecrow removeScarecrowAt(SimpleLocation location) { - CustomCropsBlock removed = removeBlockAt(location); - if (removed == null) { - CustomCropsPlugin.get().debug("Failed to remove scarecrow from " + location + " because scarecrow doesn't exist."); - return null; - } else if (!(removed instanceof WorldScarecrow worldScarecrow)) { - CustomCropsPlugin.get().debug("Removed scarecrow from " + location + " but the previous block type is " + removed.getType().name()); - return null; - } else { - return worldScarecrow; - } - } - - @Override - public CustomCropsBlock removeBlockAt(SimpleLocation location) { - BlockPos pos = BlockPos.getByLocation(location); - CSection section = loadedSections.get(pos.getSectionID()); - if (section == null) return null; - return section.removeBlockAt(pos); - } - - @Override - public CustomCropsBlock addBlockAt(CustomCropsBlock block, SimpleLocation location) { - BlockPos pos = BlockPos.getByLocation(location); - CSection section = loadedSections.get(pos.getSectionID()); - if (section == null) { - section = new CSection(pos.getSectionID()); - loadedSections.put(pos.getSectionID(), section); - } - this.tryCreatingTaskForNewBlock(pos); - return section.addBlockAt(pos, block); - } - - @Override - public Optional getBlockAt(SimpleLocation location) { - BlockPos pos = BlockPos.getByLocation(location); - CSection section = loadedSections.get(pos.getSectionID()); - if (section == null) { - return Optional.empty(); - } - return Optional.ofNullable(section.getBlockAt(pos)); - } - - @Override - public int getCropAmount() { - int amount = 0; - for (CustomCropsSection section : getSections()) { - for (CustomCropsBlock block : section.getBlocks()) { - if (block instanceof WorldCrop) { - amount++; - } - } - } - return amount; - } - - @Override - public int getPotAmount() { - int amount = 0; - for (CustomCropsSection section : getSections()) { - for (CustomCropsBlock block : section.getBlocks()) { - if (block instanceof WorldPot) { - amount++; - } - } - } - return amount; - } - - @Override - public int getSprinklerAmount() { - int amount = 0; - for (CustomCropsSection section : getSections()) { - for (CustomCropsBlock block : section.getBlocks()) { - if (block instanceof WorldSprinkler) { - amount++; - } - } - } - return amount; - } - - @Override - public boolean hasScarecrow() { - for (CustomCropsSection section : getSections()) { - for (CustomCropsBlock block : section.getBlocks()) { - if (block instanceof WorldScarecrow) { - return true; - } - } - } - return false; - } - - public CSection[] getSectionsForSerialization() { - ArrayList sections = new ArrayList<>(); - for (Map.Entry entry : loadedSections.entrySet()) { - if (!entry.getValue().canPrune()) { - sections.add(entry.getValue()); - } - } - return sections.toArray(new CSection[0]); - } - - @Override - public CustomCropsSection[] getSections() { - return loadedSections.values().toArray(new CustomCropsSection[0]); - } - - @Override - public CustomCropsSection getSection(int sectionID) { - return loadedSections.get(sectionID); - } - - @Override - public int getUnloadedSeconds() { - return unloadedSeconds; - } - - @Override - public void setUnloadedSeconds(int unloadedSeconds) { - this.unloadedSeconds = unloadedSeconds; - } - - @Override - public void resetUnloadedSeconds() { - this.unloadedSeconds = 0; - this.notified = false; - } - - @Override - public boolean canPrune() { - return loadedSections.isEmpty(); - } - - public PriorityQueue getQueue() { - return queue; - } - - public Set getTickedBlocks() { - return tickedBlocks; - } - - @Override - public boolean isOfflineTaskNotified() { - return notified; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java deleted file mode 100644 index 5a6808e..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CRegion.java +++ /dev/null @@ -1,83 +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.mechanic.world; - -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.RegionPos; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class CRegion implements CustomCropsRegion { - - private final CWorld cWorld; - private final RegionPos regionPos; - private final ConcurrentHashMap cachedChunkBytes; - - public CRegion(CWorld cWorld, RegionPos regionPos) { - this.cWorld = cWorld; - this.regionPos = regionPos; - this.cachedChunkBytes = new ConcurrentHashMap<>(); - } - - public CRegion(CWorld cWorld, RegionPos regionPos, ConcurrentHashMap cachedChunkBytes) { - this.cWorld = cWorld; - this.regionPos = regionPos; - this.cachedChunkBytes = cachedChunkBytes; - } - - @Override - public CustomCropsWorld getCustomCropsWorld() { - return cWorld; - } - - @Nullable - @Override - public byte[] getChunkBytes(ChunkPos pos) { - return cachedChunkBytes.get(pos); - } - - @Override - public RegionPos getRegionPos() { - return regionPos; - } - - @Override - public void removeChunk(ChunkPos pos) { - cachedChunkBytes.remove(pos); - } - - @Override - public void saveChunk(ChunkPos pos, byte[] data) { - cachedChunkBytes.put(pos, data); - } - - @Override - public Map getRegionDataToSave() { - return new HashMap<>(cachedChunkBytes); - } - - @Override - public boolean canPrune() { - return cachedChunkBytes.size() == 0; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CSection.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CSection.java deleted file mode 100644 index 454ed95..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CSection.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.world; - -import net.momirealms.customcrops.api.mechanic.world.BlockPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsSection; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class CSection implements CustomCropsSection { - - private final int sectionID; - private final ConcurrentHashMap blocks; - - public CSection(int sectionID) { - this.sectionID = sectionID; - this.blocks = new ConcurrentHashMap<>(); - } - - public CSection(int sectionID, ConcurrentHashMap blocks) { - this.blocks = blocks; - this.sectionID = sectionID; - } - - @Override - public int getSectionID() { - return sectionID; - } - - @Override - public CustomCropsBlock getBlockAt(BlockPos pos) { - return blocks.get(pos); - } - - @Override - public CustomCropsBlock removeBlockAt(BlockPos pos) { - return blocks.remove(pos); - } - - @Override - public CustomCropsBlock addBlockAt(BlockPos pos, CustomCropsBlock block) { - return blocks.put(pos, block); - } - - @Override - public boolean canPrune() { - return blocks.size() == 0; - } - - @Override - public CustomCropsBlock[] getBlocks() { - return blocks.values().toArray(new CustomCropsBlock[0]); - } - - @Override - public Map getBlockMap() { - return blocks; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java deleted file mode 100644 index 04dbc52..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/CWorld.java +++ /dev/null @@ -1,590 +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.mechanic.world; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.event.SeasonChangeEvent; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.RegionPos; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.*; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import net.momirealms.customcrops.api.scheduler.Scheduler; -import net.momirealms.customcrops.api.util.EventUtils; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -public class CWorld implements CustomCropsWorld { - - private final WorldManager worldManager; - private WeakReference world; - private final ConcurrentHashMap loadedChunks; - private final ConcurrentHashMap lazyChunks; - private final ConcurrentHashMap loadedRegions; - private WorldSetting setting; - private WorldInfoData infoData; - private final String worldName; - private CancellableTask worldTask; - private int currentMinecraftDay; - private int regionTimer; - - public CWorld(WorldManager worldManager, World world) { - this.world = new WeakReference<>(world); - this.worldManager = worldManager; - this.loadedChunks = new ConcurrentHashMap<>(); - this.lazyChunks = new ConcurrentHashMap<>(); - this.loadedRegions = new ConcurrentHashMap<>(); - this.worldName = world.getName(); - this.currentMinecraftDay = (int) (world.getFullTime() / 24000); - this.regionTimer = 0; - } - - @Override - public void save() { - long time1 = System.currentTimeMillis(); - worldManager.saveInfoData(this); - for (CChunk chunk : loadedChunks.values()) { - worldManager.saveChunkToCachedRegion(chunk); - } - for (CChunk chunk : lazyChunks.values()) { - worldManager.saveChunkToCachedRegion(chunk); - } - for (CRegion region : loadedRegions.values()) { - worldManager.saveRegionToFile(region); - } - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to save world " + worldName + ". Saved " + (lazyChunks.size() + loadedChunks.size()) + " chunks."); - } - - @Override - public void startTick() { - if (this.worldTask == null || this.worldTask.isCancelled()) - this.worldTask = CustomCropsPlugin.get().getScheduler().runTaskAsyncTimer(this::timer, 1, 1, TimeUnit.SECONDS); - } - - @Override - public void cancelTick() { - if (!this.worldTask.isCancelled()) - this.worldTask.cancel(); - } - - private void timer() { - ArrayList> chunksToSave = new ArrayList<>(); - for (Map.Entry lazyEntry : lazyChunks.entrySet()) { - CChunk chunk = lazyEntry.getValue(); - int sec = chunk.getUnloadedSeconds() + 1; - if (sec >= 30) { - chunksToSave.add(Pair.of(lazyEntry.getKey(), chunk)); - } else { - chunk.setUnloadedSeconds(sec); - } - } - for (Pair pair : chunksToSave) { - lazyChunks.remove(pair.left()); - worldManager.saveChunkToCachedRegion(pair.right()); - } - if (setting.isAutoSeasonChange()) { - this.updateSeasonAndDate(); - } - if (setting.isSchedulerEnabled()) { - Scheduler scheduler = CustomCropsPlugin.get().getScheduler(); - if (VersionManager.folia()) { - for (CChunk chunk : loadedChunks.values()) { - if (unloadIfNotLoaded(chunk.getChunkPos())) { - continue; - } - scheduler.runTaskSync(chunk::secondTimer, getWorld(), chunk.getChunkPos().x(), chunk.getChunkPos().z()); - } - } else { - for (CChunk chunk : loadedChunks.values()) { - if (unloadIfNotLoaded(chunk.getChunkPos())) { - continue; - } - chunk.secondTimer(); - } - } - } - - // timer check to unload regions - this.regionTimer++; - if (this.regionTimer >= 600) { - this.regionTimer = 0; - ArrayList removed = new ArrayList<>(); - for (Map.Entry entry : loadedRegions.entrySet()) { - if (isRegionNoLongerLoaded(entry.getKey())) { - worldManager.saveRegionToFile(entry.getValue()); - removed.add(entry.getKey()); - } - } - for (RegionPos pos : removed) { - loadedRegions.remove(pos); - } - } - } - - private void updateSeasonAndDate() { - World bukkitWorld = getWorld(); - if (bukkitWorld == null) { - LogUtils.severe(String.format("World %s unloaded unexpectedly. Stop ticking task...", worldName)); - this.cancelTick(); - return; - } - - long ticks = bukkitWorld.getFullTime(); - int days = (int) (ticks / 24000); - if (days == this.currentMinecraftDay) { - return; - } - - if (days > this.currentMinecraftDay) { - int date = infoData.getDate(); - date++; - if (date > setting.getSeasonDuration()) { - date = 1; - Season next = infoData.getSeason().getNextSeason(); - infoData.setSeason(next); - EventUtils.fireAndForget(new SeasonChangeEvent(bukkitWorld, next)); - } - infoData.setDate(date); - } - this.currentMinecraftDay = days; - } - - @Override - public CustomCropsChunk removeLazyChunkAt(ChunkPos chunkPos) { - return lazyChunks.remove(chunkPos); - } - - @Override - public WorldSetting getWorldSetting() { - return setting; - } - - @Override - public void setWorldSetting(WorldSetting setting) { - this.setting = setting; - } - - @Override - public Collection getChunkStorage() { - return loadedChunks.values(); - } - - @Nullable - @Override - public World getWorld() { - return Optional.ofNullable(world.get()).orElseGet(() -> { - World bukkitWorld = Bukkit.getWorld(worldName); - if (bukkitWorld != null) { - this.world = new WeakReference<>(bukkitWorld); - } - return bukkitWorld; - }); - } - - @NotNull - @Override - public String getWorldName() { - return worldName; - } - - @Override - public boolean isChunkLoaded(ChunkPos chunkPos) { - return loadedChunks.containsKey(chunkPos); - } - - @Override - public boolean isRegionLoaded(RegionPos regionPos) { - return loadedRegions.containsKey(regionPos); - } - - @Override - public Optional getOrCreateLoadedChunkAt(ChunkPos chunkPos) { - return Optional.ofNullable(createOrGetChunk(chunkPos)); - } - - @Override - public Optional getLoadedChunkAt(ChunkPos chunkPos) { - return Optional.ofNullable(loadedChunks.get(chunkPos)); - } - - @Override - public Optional getLoadedRegionAt(RegionPos regionPos) { - return Optional.ofNullable(loadedRegions.get(regionPos)); - } - - @Override - public void loadRegion(CustomCropsRegion region) { - RegionPos regionPos = region.getRegionPos(); - if (loadedRegions.containsKey(regionPos)) { - LogUtils.warn("Invalid operation: Loaded region is loaded again." + regionPos); - return; - } - loadedRegions.put(regionPos, (CRegion) region); - } - - @Override - public void loadChunk(CustomCropsChunk chunk) { - ChunkPos chunkPos = chunk.getChunkPos(); - if (loadedChunks.containsKey(chunkPos)) { - LogUtils.warn("Invalid operation: Loaded chunk is loaded again." + chunkPos); - return; - } - loadedChunks.put(chunkPos, (CChunk) chunk); - } - - @Override - public void unloadChunk(ChunkPos chunkPos) { - CChunk chunk = loadedChunks.remove(chunkPos); - if (chunk != null) { - chunk.updateLastLoadedTime(); - lazyChunks.put(chunkPos, chunk); - } - } - - @Override - public void deleteChunk(ChunkPos chunkPos) { - CChunk chunk = loadedChunks.remove(chunkPos); - } - - @Override - public void setInfoData(WorldInfoData infoData) { - this.infoData = infoData; - } - - @Override - public WorldInfoData getInfoData() { - return infoData; - } - - @Override - public int getDate() { - if (setting.isEnableSeason()) { - if (ConfigManager.syncSeasons() && ConfigManager.referenceWorld() != world) { - return worldManager.getCustomCropsWorld(ConfigManager.referenceWorld()).map(customCropsWorld -> customCropsWorld.getInfoData().getDate()).orElse(0); - } - return infoData.getDate(); - } else { - return 0; - } - } - - @Override - @Nullable - public Season getSeason() { - if (setting.isEnableSeason()) { - if (ConfigManager.syncSeasons() && ConfigManager.referenceWorld() != world) { - return worldManager.getCustomCropsWorld(ConfigManager.referenceWorld()).map(customCropsWorld -> customCropsWorld.getInfoData().getSeason()).orElse(null); - } - return infoData.getSeason(); - } else { - return null; - } - } - - @Override - public Optional getSprinklerAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getSprinklerAt(location); - } - - @Override - public Optional getPotAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getPotAt(location); - } - - @Override - public Optional getCropAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getCropAt(location); - } - - @Override - public Optional getGlassAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getGlassAt(location); - } - - @Override - public Optional getScarecrowAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getScarecrowAt(location); - } - - @Override - public Optional getBlockAt(SimpleLocation location) { - CChunk chunk = loadedChunks.get(location.getChunkPos()); - if (chunk == null) return Optional.empty(); - return chunk.getBlockAt(location); - } - - @Override - public void addWaterToSprinkler(Sprinkler sprinkler, int amount, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addWaterToSprinkler(sprinkler, amount, location); - } else { - LogUtils.warn("Invalid operation: Adding water to sprinkler in a not generated chunk"); - } - } - - @Override - public void addFertilizerToPot(Pot pot, Fertilizer fertilizer, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addFertilizerToPot(pot, fertilizer, location); - } else { - LogUtils.warn("Invalid operation: Adding fertilizer to pot in a not generated chunk"); - } - } - - @Override - public void addWaterToPot(Pot pot, int amount, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addWaterToPot(pot, amount, location); - } else { - LogUtils.warn("Invalid operation: Adding water to pot in a not generated chunk"); - } - } - - @Override - public void addPotAt(WorldPot pot, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addPotAt(pot, location); - } else { - LogUtils.warn("Invalid operation: Adding pot in a not generated chunk"); - } - } - - @Override - public void addSprinklerAt(WorldSprinkler sprinkler, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addSprinklerAt(sprinkler, location); - } else { - LogUtils.warn("Invalid operation: Adding sprinkler in a not generated chunk"); - } - } - - @Override - public void addCropAt(WorldCrop crop, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addCropAt(crop, location); - } else { - LogUtils.warn("Invalid operation: Adding crop in a not generated chunk"); - } - } - - @Override - public void addPointToCrop(Crop crop, int points, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addPointToCrop(crop, points, location); - } else { - LogUtils.warn("Invalid operation: Adding point to crop in a not generated chunk"); - } - } - - @Override - public void addGlassAt(WorldGlass glass, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addGlassAt(glass, location); - } else { - LogUtils.warn("Invalid operation: Adding glass in a not generated chunk"); - } - } - - @Override - public void addScarecrowAt(WorldScarecrow scarecrow, SimpleLocation location) { - Optional chunk = getOrCreateLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - chunk.get().addScarecrowAt(scarecrow, location); - } else { - LogUtils.warn("Invalid operation: Adding scarecrow in a not generated chunk"); - } - } - - @Override - public WorldSprinkler removeSprinklerAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removeSprinklerAt(location); - } else { - LogUtils.warn("Invalid operation: Removing sprinkler from an unloaded/empty chunk"); - return null; - } - } - - @Override - public WorldPot removePotAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removePotAt(location); - } else { - LogUtils.warn("Invalid operation: Removing pot from an unloaded/empty chunk"); - return null; - } - } - - @Override - public WorldCrop removeCropAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removeCropAt(location); - } else { - LogUtils.warn("Invalid operation: Removing crop from an unloaded/empty chunk"); - return null; - } - } - - @Override - public WorldGlass removeGlassAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removeGlassAt(location); - } else { - LogUtils.warn("Invalid operation: Removing glass from an unloaded/empty chunk"); - return null; - } - } - - @Override - public WorldScarecrow removeScarecrowAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removeScarecrowAt(location); - } else { - LogUtils.warn("Invalid operation: Removing scarecrow from an unloaded/empty chunk"); - return null; - } - } - - @Override - public CustomCropsBlock removeAnythingAt(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isPresent()) { - return chunk.get().removeBlockAt(location); - } else { - LogUtils.warn("Invalid operation: Removing anything from an unloaded/empty chunk"); - return null; - } - } - - @Nullable - private CustomCropsChunk createOrGetChunk(ChunkPos chunkPos) { - World bukkitWorld = world.get(); - if (bukkitWorld == null) - return null; - CChunk chunk = loadedChunks.get(chunkPos); - if (chunk != null) { - return chunk; - } - // is a loaded chunk, but it doesn't have CustomCrops data - if (bukkitWorld.isChunkLoaded(chunkPos.x(), chunkPos.z())) { - chunk = new CChunk(this, chunkPos); - loadChunk(chunk); - return chunk; - } else { - return null; - } - } - - @Override - public boolean doesChunkHaveScarecrow(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - return chunk.map(CustomCropsChunk::hasScarecrow).orElse(false); - } - - @Override - public boolean isPotReachLimit(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isEmpty()) return false; - if (setting.getPotPerChunk() < 0) return false; - return chunk.get().getPotAmount() >= setting.getPotPerChunk(); - } - - @Override - public boolean isCropReachLimit(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isEmpty()) return false; - if (setting.getCropPerChunk() < 0) return false; - return chunk.get().getCropAmount() >= setting.getCropPerChunk(); - } - - @Override - public boolean isSprinklerReachLimit(SimpleLocation location) { - Optional chunk = getLoadedChunkAt(location.getChunkPos()); - if (chunk.isEmpty()) return false; - if (setting.getSprinklerPerChunk() < 0) return false; - return chunk.get().getSprinklerAmount() >= setting.getSprinklerPerChunk(); - } - - private boolean isRegionNoLongerLoaded(RegionPos region) { - World w = world.get(); - if (w != null) { - for (int chunkX = region.x() * 32; chunkX < region.x() * 32 + 32; chunkX++) { - for (int chunkZ = region.z() * 32; chunkZ < region.z() * 32 + 32; chunkZ++) { - // if a chunk is unloaded, then it should not be in the loaded chunks map - if (w.isChunkLoaded(chunkX, chunkZ) || lazyChunks.containsKey(ChunkPos.of(chunkX, chunkZ))) { - return false; - } - } - } - } - return true; - } - - private boolean unloadIfNotLoaded(ChunkPos pos) { - if (!world.get().isChunkLoaded(pos.x(), pos.z())) { - unloadChunk(pos); - return true; - } - return false; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java deleted file mode 100644 index b18e223..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableChunk.java +++ /dev/null @@ -1,73 +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.mechanic.world; - -import java.util.List; - -public class SerializableChunk { - - private final int x; - private final int z; - private final int loadedSeconds; - private final long lastLoadedTime; - private final List sections; - private final int[] queuedTasks; - private final int[] ticked; - - public SerializableChunk(int x, int z, int loadedSeconds, long lastLoadedTime, List sections, int[] queuedTasks, int[] ticked) { - this.x = x; - this.z = z; - this.lastLoadedTime = lastLoadedTime; - this.loadedSeconds = loadedSeconds; - this.sections = sections; - this.queuedTasks = queuedTasks; - this.ticked = ticked; - } - - public int getLoadedSeconds() { - return loadedSeconds; - } - - public int getX() { - return x; - } - - public int getZ() { - return z; - } - - public long getLastLoadedTime() { - return lastLoadedTime; - } - - public List getSections() { - return sections; - } - - public int[] getQueuedTasks() { - return queuedTasks; - } - - public int[] getTicked() { - return ticked; - } - - public boolean canPrune() { - return sections.size() == 0; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableSection.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableSection.java deleted file mode 100644 index 9184cd9..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/SerializableSection.java +++ /dev/null @@ -1,41 +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.mechanic.world; - -import com.flowpowered.nbt.CompoundTag; - -import java.util.List; - -public class SerializableSection { - - private final int sectionID; - private final List blocks; - - public SerializableSection(int sectionID, List blocks) { - this.sectionID = sectionID; - this.blocks = blocks; - } - - public int getSectionID() { - return sectionID; - } - - public List getBlocks() { - return blocks; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java deleted file mode 100644 index fa1d9d8..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/WorldManagerImpl.java +++ /dev/null @@ -1,599 +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.mechanic.world; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.item.*; -import net.momirealms.customcrops.api.mechanic.misc.MatchRule; -import net.momirealms.customcrops.api.mechanic.world.AbstractWorldAdaptor; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.*; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.world.adaptor.BukkitWorldAdaptor; -import net.momirealms.customcrops.mechanic.world.adaptor.SlimeWorldAdaptor; -import net.momirealms.customcrops.mechanic.world.block.*; -import net.momirealms.customcrops.util.ConfigUtils; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.World; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.event.world.ChunkUnloadEvent; -import org.bukkit.event.world.EntitiesLoadEvent; -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class WorldManagerImpl implements WorldManager, Listener { - - private final CustomCropsPlugin plugin; - private final ConcurrentHashMap loadedWorlds; - private final HashMap worldSettingMap; - private WorldSetting defaultWorldSetting; - private MatchRule matchRule; - private HashSet worldList; - private AbstractWorldAdaptor worldAdaptor; - private String absoluteWorldFolder; - - public WorldManagerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.loadedWorlds = new ConcurrentHashMap<>(); - this.worldSettingMap = new HashMap<>(); - try { - // to ignore the warning from asp - Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); - this.worldAdaptor = new SlimeWorldAdaptor(this, 1); - } catch (ClassNotFoundException e) { - if (Bukkit.getPluginManager().isPluginEnabled("SlimeWorldPlugin")) { - this.worldAdaptor = new SlimeWorldAdaptor(this, 2); - } else { - this.worldAdaptor = new BukkitWorldAdaptor(this); - } - } - } - - @Override - public void load() { - this.registerListener(); - this.loadConfig(); - if (this.worldAdaptor instanceof BukkitWorldAdaptor adaptor) { - adaptor.setWorldFolder(absoluteWorldFolder); - } - for (World world : Bukkit.getWorlds()) { - if (isMechanicEnabled(world)) { - loadWorld(world); - } else { - unloadWorld(world); - } - } - } - - @Override - public void unload() { - this.unregisterListener(); - this.worldSettingMap.clear(); - } - - @Override - public void disable() { - this.unload(); - for (World world : Bukkit.getWorlds()) { - unloadWorld(world); - } - if (!this.loadedWorlds.isEmpty()) { - LogUtils.severe("Detected that some worlds are not properly unloaded. " + - "You can safely ignore this if you are editing \"worlds.list\" and restarting to apply it"); - for (String world : this.loadedWorlds.keySet()) { - LogUtils.severe(" - " + world); - } - for (CustomCropsWorld world : this.loadedWorlds.values()) { - worldAdaptor.unload(world); - } - this.loadedWorlds.clear(); - } - } - - private void loadConfig() { - YamlConfiguration config = ConfigUtils.getConfig("config.yml"); - - ConfigurationSection section = config.getConfigurationSection("worlds"); - if (section == null) { - LogUtils.severe("worlds section should not be null"); - return; - } - - this.absoluteWorldFolder = section.getString("absolute-world-folder-path",""); - this.matchRule = MatchRule.valueOf(section.getString("mode", "blacklist").toUpperCase(Locale.ENGLISH)); - this.worldList = new HashSet<>(section.getStringList("list")); - - // limitation - ConfigurationSection settingSection = section.getConfigurationSection("settings"); - if (settingSection == null) { - LogUtils.severe("worlds.settings section should not be null"); - return; - } - - ConfigurationSection defaultSection = settingSection.getConfigurationSection("_DEFAULT_"); - if (defaultSection == null) { - LogUtils.severe("worlds.settings._DEFAULT_ section should not be null"); - return; - } - - this.defaultWorldSetting = ConfigUtils.getWorldSettingFromSection(defaultSection); - ConfigurationSection worldSection = settingSection.getConfigurationSection("_WORLDS_"); - if (worldSection != null) { - for (Map.Entry entry : worldSection.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - this.worldSettingMap.put(entry.getKey(), ConfigUtils.getWorldSettingFromSection(inner)); - } - } - } - } - - private void registerListener() { - Bukkit.getPluginManager().registerEvents(this, plugin); - if (worldAdaptor != null) - Bukkit.getPluginManager().registerEvents(worldAdaptor, plugin); - } - - private void unregisterListener() { - HandlerList.unregisterAll(this); - if (worldAdaptor != null) - HandlerList.unregisterAll(worldAdaptor); - } - - @NotNull - @Override - public CustomCropsWorld loadWorld(@NotNull World world) { - String worldName = world.getName(); - if (loadedWorlds.containsKey(worldName)) { - CWorld cWorld = loadedWorlds.get(worldName); - cWorld.setWorldSetting(getInitWorldSetting(world)); - return cWorld; - } - CWorld cWorld = new CWorld(this, world); - worldAdaptor.init(cWorld); - loadedWorlds.put(worldName, cWorld); - cWorld.setWorldSetting(getInitWorldSetting(world)); - cWorld.startTick(); - for (Chunk chunk : world.getLoadedChunks()) { - handleChunkLoad(chunk); - } - return cWorld; - } - - @Override - public boolean unloadWorld(@NotNull World world) { - CustomCropsWorld customCropsWorld = loadedWorlds.remove(world.getName()); - if (customCropsWorld != null) { - customCropsWorld.cancelTick(); - worldAdaptor.unload(customCropsWorld); - return true; - } - return false; - } - - private WorldSetting getInitWorldSetting(World world) { - if (worldSettingMap.containsKey(world.getName())) - return worldSettingMap.get(world.getName()).clone(); - return defaultWorldSetting.clone(); - } - - @Override - public boolean isMechanicEnabled(@NotNull World world) { - if (matchRule == MatchRule.WHITELIST) { - return worldList.contains(world.getName()); - } else if (matchRule == MatchRule.BLACKLIST) { - return !worldList.contains(world.getName()); - } else { - for (String regex : worldList) { - if (world.getName().matches(regex)) { - return true; - } - } - return false; - } - } - - @NotNull - @Override - public Collection getWorldNames() { - return loadedWorlds.keySet(); - } - - @NotNull - @Override - public Collection getBukkitWorlds() { - return loadedWorlds.keySet().stream().map(Bukkit::getWorld).toList(); - } - - @NotNull - @Override - public Collection getCustomCropsWorlds() { - return loadedWorlds.values(); - } - - @NotNull - @Override - public Optional getCustomCropsWorld(@NotNull String name) { - return Optional.ofNullable(loadedWorlds.get(name)); - } - - @NotNull - @Override - public Optional getCustomCropsWorld(@NotNull World world) { - return Optional.ofNullable(loadedWorlds.get(world.getName())); - } - - @NotNull - @Override - public Optional getSprinklerAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getSprinklerAt(location); - } - - @NotNull - @Override - public Optional getPotAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getPotAt(location); - } - - @NotNull - @Override - public Optional getCropAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getCropAt(location); - } - - @NotNull - @Override - public Optional getGlassAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getGlassAt(location); - } - - @NotNull - @Override - public Optional getScarecrowAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getScarecrowAt(location); - } - - @Override - public Optional getBlockAt(SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) return Optional.empty(); - return cWorld.getBlockAt(location); - } - - @Override - public WorldCrop createCropData(SimpleLocation location, Crop crop, int point) { - return new MemoryCrop(location, crop.getKey(), point); - } - - @Override - public WorldSprinkler createSprinklerData(SimpleLocation location, Sprinkler sprinkler, int water) { - return new MemorySprinkler(location, sprinkler.getKey(), water); - } - - @Override - public WorldPot createPotData(SimpleLocation location, Pot pot, int water, Fertilizer fertilizer, int fertilizerTimes) { - return new MemoryPot(location, pot.getKey(), water, fertilizer == null ? "" : fertilizer.getKey(), fertilizerTimes); - } - - @Override - public WorldGlass createGreenhouseGlassData(SimpleLocation location) { - return new MemoryGlass(location); - } - - @Override - public WorldScarecrow createScarecrowData(SimpleLocation location) { - return new MemoryScarecrow(location); - } - - @Override - public void addWaterToSprinkler(@NotNull Sprinkler sprinkler, @NotNull SimpleLocation location, int amount) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding water to sprinkler in unloaded world " + location); - return; - } - cWorld.addWaterToSprinkler(sprinkler, amount, location); - } - - @Override - public void addFertilizerToPot(@NotNull Pot pot, @NotNull Fertilizer fertilizer, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding fertilizer to pot in unloaded world " + location); - return; - } - cWorld.addFertilizerToPot(pot, fertilizer, location); - } - - @Override - public void addWaterToPot(@NotNull Pot pot, int amount, @NotNull SimpleLocation location) { - if (amount <= 0) return; - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding water to pot in unloaded world " + location); - return; - } - cWorld.addWaterToPot(pot, amount, location); - } - - @Override - public void addPotAt(@NotNull WorldPot pot, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding pot in unloaded world " + location); - return; - } - cWorld.addPotAt(pot, location); - } - - @Override - public void addSprinklerAt(@NotNull WorldSprinkler sprinkler, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding sprinkler in unloaded world " + location); - return; - } - cWorld.addSprinklerAt(sprinkler, location); - } - - @Override - public void addCropAt(@NotNull WorldCrop crop, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding crop in unloaded world " + location); - return; - } - cWorld.addCropAt(crop, location); - } - - @Override - public void addPointToCrop(@NotNull Crop crop, int points, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding point to crop in unloaded world " + location); - return; - } - cWorld.addPointToCrop(crop, points, location); - } - - @Override - public void addGlassAt(@NotNull WorldGlass glass, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding glass in unloaded world " + location); - return; - } - cWorld.addGlassAt(glass, location); - } - - @Override - public void addScarecrowAt(@NotNull WorldScarecrow scarecrow, @NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Adding scarecrow in unloaded world " + location); - return; - } - cWorld.addScarecrowAt(scarecrow, location); - } - - @Override - public WorldSprinkler removeSprinklerAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing sprinkler from unloaded world " + location); - return null; - } - return cWorld.removeSprinklerAt(location); - } - - @Override - public WorldPot removePotAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing pot from unloaded world " + location); - return null; - } - return cWorld.removePotAt(location); - } - - @Override - public WorldCrop removeCropAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing crop from unloaded world " + location); - return null; - } - return cWorld.removeCropAt(location); - } - - @Override - public WorldGlass removeGlassAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing glass from unloaded world " + location); - return null; - } - return cWorld.removeGlassAt(location); - } - - @Override - public WorldScarecrow removeScarecrowAt(@NotNull SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing scarecrow from unloaded world " + location); - return null; - } - return cWorld.removeScarecrowAt(location); - } - - @Override - public CustomCropsBlock removeAnythingAt(SimpleLocation location) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Removing anything from unloaded world " + location); - return null; - } - return cWorld.removeAnythingAt(location); - } - - @Override - public boolean isReachLimit(SimpleLocation location, ItemType itemType) { - CWorld cWorld = loadedWorlds.get(location.getWorldName()); - if (cWorld == null) { - LogUtils.warn("Unsupported operation: Querying amount in an unloaded world " + location); - return true; - } - switch (itemType) { - case CROP -> { - return cWorld.isCropReachLimit(location); - } - case SPRINKLER -> { - return cWorld.isSprinklerReachLimit(location); - } - case POT -> { - return cWorld.isPotReachLimit(location); - } - default -> { - return false; - } - } - } - - /** - * Still need further investigations into why chunk load event is called twice - */ - @Override - public void handleChunkLoad(Chunk bukkitChunk) { - Optional optional = getCustomCropsWorld(bukkitChunk.getWorld()); - if (optional.isEmpty()) - return; - - CustomCropsWorld customCropsWorld = optional.get(); - ChunkPos chunkPos = ChunkPos.getByBukkitChunk(bukkitChunk); - - if (customCropsWorld.isChunkLoaded(chunkPos)) { - return; - } - - // load chunks - this.worldAdaptor.loadChunkData(customCropsWorld, chunkPos); - - // offline grow part - if (!customCropsWorld.getWorldSetting().isOfflineTick()) return; - - // If chunk data not exists, return - Optional optionalChunk = customCropsWorld.getLoadedChunkAt(chunkPos); - if (optionalChunk.isEmpty()) { - return; - } - - CustomCropsChunk chunk = optionalChunk.get(); - - // load the entities if not loaded - bukkitChunk.getEntities(); - if (bukkitChunk.isEntitiesLoaded()) { - chunk.notifyOfflineUpdates(); - } - } - - @Override - public void handleChunkUnload(Chunk bukkitChunk) { - Optional optional = getCustomCropsWorld(bukkitChunk.getWorld()); - if (optional.isEmpty()) - return; - - CustomCropsWorld customCropsWorld = optional.get(); - ChunkPos chunkPos = ChunkPos.getByBukkitChunk(bukkitChunk); - - this.worldAdaptor.unloadChunkData(customCropsWorld, chunkPos); - } - - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - handleChunkLoad(event.getChunk()); - } - - @EventHandler - public void onChunkUnLoad(ChunkUnloadEvent event) { - handleChunkUnload(event.getChunk()); - } - - @EventHandler - public void onEntitiesLoad(EntitiesLoadEvent event) { - - Chunk bukkitChunk = event.getChunk(); - Optional optional = getCustomCropsWorld(bukkitChunk.getWorld()); - if (optional.isEmpty()) - return; - - CustomCropsWorld customCropsWorld = optional.get(); - // offline grow part - if (!customCropsWorld.getWorldSetting().isOfflineTick()) return; - - ChunkPos chunkPos = ChunkPos.getByBukkitChunk(bukkitChunk); - - Optional optionalChunk = customCropsWorld.getLoadedChunkAt(chunkPos); - if (optionalChunk.isEmpty()) { - return; - } - - if (!optionalChunk.get().isOfflineTaskNotified()) { - optionalChunk.get().notifyOfflineUpdates(); - } - } - - @Override - public void saveChunkToCachedRegion(CustomCropsChunk chunk) { - this.worldAdaptor.saveChunkToCachedRegion(chunk); - } - - @Override - public void saveRegionToFile(CustomCropsRegion region) { - this.worldAdaptor.saveRegion(region); - } - - @Override - public AbstractWorldAdaptor getWorldAdaptor() { - return worldAdaptor; - } - - @Override - public void saveInfoData(CustomCropsWorld customCropsWorld) { - this.worldAdaptor.saveInfoData(customCropsWorld); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java deleted file mode 100644 index be37945..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/BukkitWorldAdaptor.java +++ /dev/null @@ -1,729 +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.mechanic.world.adaptor; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.CompoundTag; -import com.flowpowered.nbt.IntArrayTag; -import com.flowpowered.nbt.StringTag; -import com.flowpowered.nbt.stream.NBTInputStream; -import com.flowpowered.nbt.stream.NBTOutputStream; -import com.github.luben.zstd.Zstd; -import com.google.gson.Gson; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.manager.VersionManager; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.world.*; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.level.WorldInfoData; -import net.momirealms.customcrops.api.mechanic.world.season.Season; -import net.momirealms.customcrops.api.object.crop.GrowingCrop; -import net.momirealms.customcrops.api.object.fertilizer.Fertilizer; -import net.momirealms.customcrops.api.object.pot.Pot; -import net.momirealms.customcrops.api.object.sprinkler.Sprinkler; -import net.momirealms.customcrops.api.object.world.CCChunk; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.world.*; -import net.momirealms.customcrops.mechanic.world.block.*; -import net.momirealms.customcrops.scheduler.task.TickTask; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldSaveEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.persistence.PersistentDataType; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class BukkitWorldAdaptor extends AbstractWorldAdaptor { - - private static final NamespacedKey key = new NamespacedKey(CustomCropsPlugin.get(), "data"); - protected final Gson gson; - private String worldFolder; - - public BukkitWorldAdaptor(WorldManager worldManager) { - super(worldManager); - this.gson = new Gson(); - this.worldFolder = ""; - } - - @Override - public void unload(CustomCropsWorld customCropsWorld) { - World world = customCropsWorld.getWorld(); - if (world != null) { - getWorldFolder(world).mkdir(); - customCropsWorld.save(); - } - } - - @Override - public void saveInfoData(CustomCropsWorld customCropsWorld) { - CWorld cWorld = (CWorld) customCropsWorld; - World world = cWorld.getWorld(); - if (world == null) { - LogUtils.severe("Unexpected issue: World " + cWorld.getWorldName() + " unloaded before data saved"); - return; - } - - if (VersionManager.isHigherThan1_18()) { - world.getPersistentDataContainer().set(key, PersistentDataType.STRING, - gson.toJson(cWorld.getInfoData())); - } else { - try (FileWriter file = new FileWriter(new File(getWorldFolder(world), "cworld.dat"))) { - gson.toJson(cWorld.getInfoData(), file); - } catch (IOException ioException) { - ioException.printStackTrace(); - } - } - } - - @Override - public void init(CustomCropsWorld customCropsWorld) { - CWorld cWorld = (CWorld) customCropsWorld; - World world = cWorld.getWorld(); - if (world == null) { - LogUtils.severe("Unexpected issue: World " + cWorld.getWorldName() + " unloaded before data loaded"); - return; - } - - // create directory - new File(world.getWorldFolder(), "customcrops").mkdir(); - - // try converting legacy worlds - if (ConfigManager.convertWorldOnLoad()) { - if (convertWorldFromV33toV34(cWorld, world)) { - return; - } - } - - if (VersionManager.isHigherThan1_18()) { - // init world basic info - String json = world.getPersistentDataContainer().get(key, PersistentDataType.STRING); - WorldInfoData data = (json == null || json.equals("null")) ? WorldInfoData.empty() : gson.fromJson(json, WorldInfoData.class); - if (data == null) data = WorldInfoData.empty(); - cWorld.setInfoData(data); - } else { - File cWorldFile = new File(getWorldFolder(world), "cworld.dat"); - if (cWorldFile.exists()) { - byte[] fileBytes = new byte[(int) cWorldFile.length()]; - try (FileInputStream fis = new FileInputStream(cWorldFile)) { - fis.read(fileBytes); - } catch (IOException ioException) { - ioException.printStackTrace(); - } - String jsonContent = new String(fileBytes, StandardCharsets.UTF_8); - cWorld.setInfoData(gson.fromJson(jsonContent, WorldInfoData.class)); - } else { - cWorld.setInfoData(WorldInfoData.empty()); - } - } - } - - @Override - public void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos) { - CWorld cWorld = (CWorld) customCropsWorld; - World world = cWorld.getWorld(); - if (world == null) { - LogUtils.severe("Unexpected issue: World " + cWorld.getWorldName() + " unloaded before data loaded"); - return; - } - - long time1 = System.currentTimeMillis(); - // load lazy chunks firstly - CustomCropsChunk lazyChunk = cWorld.removeLazyChunkAt(chunkPos); - if (lazyChunk != null) { - CChunk cChunk = (CChunk) lazyChunk; - cChunk.resetUnloadedSeconds(); - cWorld.loadChunk(cChunk); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from lazy chunks"); - return; - } - - // check if region is loaded, load if not loaded - RegionPos regionPos = RegionPos.getByChunkPos(chunkPos); - Optional optionalRegion = cWorld.getLoadedRegionAt(regionPos); - if (optionalRegion.isPresent()) { - CustomCropsRegion region = optionalRegion.get(); - byte[] bytes = region.getChunkBytes(chunkPos); - if (bytes != null) { - try { - DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(bytes)); - CChunk chunk = deserializeChunk(cWorld, dataStream); - dataStream.close(); - cWorld.loadChunk(chunk); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from cached region"); - } catch (IOException e) { - LogUtils.severe("Failed to load CustomCrops data at " + chunkPos); - e.printStackTrace(); - region.removeChunk(chunkPos); - } - } - return; - } - - // if region file not exist, create one - File data = getRegionDataFilePath(world, regionPos); - if (!data.exists()) { - var region = new CRegion(cWorld, regionPos); - cWorld.loadRegion(region); - return; - } - - // load region from local files - try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(data))) { - DataInputStream dataStream = new DataInputStream(bis); - CRegion region = deserializeRegion(cWorld, dataStream, regionPos); - dataStream.close(); - cWorld.loadRegion(region); - byte[] bytes = region.getChunkBytes(chunkPos); - if (bytes != null) { - try { - DataInputStream chunkStream = new DataInputStream(new ByteArrayInputStream(bytes)); - CChunk chunk = deserializeChunk(cWorld, chunkStream); - chunkStream.close(); - cWorld.loadChunk(chunk); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos); - } catch (IOException e) { - LogUtils.severe("Failed to load CustomCrops data at " + chunkPos + ". Deleting corrupted chunk."); - e.printStackTrace(); - region.removeChunk(chunkPos); - } - } else { - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load region " + regionPos); - } - } catch (Exception e) { - LogUtils.severe("Failed to load CustomCrops region data at " + chunkPos + ". Deleting corrupted region."); - e.printStackTrace(); - data.delete(); - } - } - - @Override - public void unloadChunkData(CustomCropsWorld ccWorld, ChunkPos chunkPos) { - CWorld cWorld = (CWorld) ccWorld; - World world = cWorld.getWorld(); - if (world == null) { - LogUtils.severe("Unexpected issue: World " + cWorld.getWorldName() + " unloaded before data loaded"); - return; - } - - cWorld.unloadChunk(chunkPos); - } - - @Override - public void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk) { - CustomCropsRegion customCropsRegion = customCropsChunk.getCustomCropsRegion(); - if (customCropsRegion == null) { - LogUtils.severe(customCropsChunk.getChunkPos().toString() + " unloaded before chunk saving"); - return; - } - SerializableChunk serializableChunk = toSerializableChunk((CChunk) customCropsChunk); - if (serializableChunk.canPrune()) { - customCropsRegion.removeChunk(customCropsChunk.getChunkPos()); - } else { - customCropsRegion.saveChunk(customCropsChunk.getChunkPos(), serialize(serializableChunk)); - } - } - - @Override - public void saveRegion(CustomCropsRegion customCropsRegion) { - File file = getRegionDataFilePath(customCropsRegion.getCustomCropsWorld().getWorld(), customCropsRegion.getRegionPos()); - long time1 = System.currentTimeMillis(); - try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) { - bos.write(serialize(customCropsRegion)); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to save region " + customCropsRegion.getRegionPos()); - } catch (IOException e) { - LogUtils.severe("Failed to save CustomCrops region data." + customCropsRegion.getRegionPos()); - e.printStackTrace(); - } - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) - public void onWorldLoad(WorldLoadEvent event) { - if (worldManager.isMechanicEnabled(event.getWorld())) { - worldManager.loadWorld(event.getWorld()); - } - } - - @EventHandler (ignoreCancelled = true, priority = EventPriority.LOW) - public void onWorldUnload(WorldUnloadEvent event) { - if (worldManager.isMechanicEnabled(event.getWorld())) - worldManager.unloadWorld(event.getWorld()); - } - - @EventHandler (ignoreCancelled = true) - public void onWorldSave(WorldSaveEvent event) { - World world = event.getWorld(); - worldManager.getCustomCropsWorld(world).ifPresent(CustomCropsWorld::save); - } - - @Deprecated - private String getChunkDataFile(ChunkPos chunkPos) { - return chunkPos.x() + "," + chunkPos.z() + ".ccd"; - } - - private String getRegionDataFile(RegionPos regionPos) { - return "r." + regionPos.x() + "." + regionPos.z() + ".mcc"; - } - - @Deprecated - private File getChunkDataFilePath(World world, ChunkPos chunkPos) { - if (worldFolder.isEmpty()) { - return new File(world.getWorldFolder(), "customcrops" + File.separator + getChunkDataFile(chunkPos)); - } else { - return new File(worldFolder, world.getName() + File.separator + "customcrops" + File.separator + getChunkDataFile(chunkPos)); - } - } - - private File getRegionDataFilePath(World world, RegionPos regionPos) { - if (worldFolder.isEmpty()) { - return new File(world.getWorldFolder(), "customcrops" + File.separator + getRegionDataFile(regionPos)); - } else { - return new File(worldFolder, world.getName() + File.separator + "customcrops" + File.separator + getRegionDataFile(regionPos)); - } - } - - private File getWorldFolder(World world) { - if (worldFolder.isEmpty()) { - return new File(world.getWorldFolder(), "customcrops"); - } else { - return new File(worldFolder, world.getName() + File.separator + "customcrops"); - } - } - - public void setWorldFolder(String folder) { - this.worldFolder = folder; - } - - public byte[] serialize(CustomCropsRegion region) { - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); - DataOutputStream outStream = new DataOutputStream(outByteStream); - try { - outStream.writeByte(regionVersion); - outStream.writeInt(region.getRegionPos().x()); - outStream.writeInt(region.getRegionPos().z()); - Map map = region.getRegionDataToSave(); - outStream.writeInt(map.size()); - for (Map.Entry entry : map.entrySet()) { - outStream.writeInt(entry.getKey().x()); - outStream.writeInt(entry.getKey().z()); - byte[] dataArray = entry.getValue(); - outStream.writeInt(dataArray.length); - outStream.write(dataArray); - } - } catch (IOException e) { - e.printStackTrace(); - } - return outByteStream.toByteArray(); - } - - public CRegion deserializeRegion(CWorld world, DataInputStream dataStream, RegionPos pos) throws IOException { - int regionVersion = dataStream.readByte(); - int regionX = dataStream.readInt(); - int regionZ = dataStream.readInt(); - RegionPos regionPos = RegionPos.of(regionX, regionZ); - ConcurrentHashMap map = new ConcurrentHashMap<>(); - int chunkAmount = dataStream.readInt(); - for (int i = 0; i < chunkAmount; i++) { - int chunkX = dataStream.readInt(); - int chunkZ = dataStream.readInt(); - ChunkPos chunkPos = ChunkPos.of(chunkX, chunkZ); - byte[] chunkData = new byte[dataStream.readInt()]; - dataStream.read(chunkData); - map.put(chunkPos, chunkData); - } - return new CRegion(world, pos, map); - } - - public byte[] serialize(SerializableChunk serializableChunk) { - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); - DataOutputStream outStream = new DataOutputStream(outByteStream); - try { - outStream.writeByte(chunkVersion); - byte[] serializedSections = serializeChunk(serializableChunk); - byte[] compressed = Zstd.compress(serializedSections); - outStream.writeInt(compressed.length); - outStream.writeInt(serializedSections.length); - outStream.write(compressed); - } catch (IOException e) { - e.printStackTrace(); - } - return outByteStream.toByteArray(); - } - - public CChunk deserializeChunk(CWorld world, DataInputStream dataStream) throws IOException { - int chunkVersion = dataStream.readByte(); - byte[] blockData = readCompressedBytes(dataStream); - return deserializeChunk(world, blockData); - } - - private CChunk deserializeChunk(CWorld cWorld, byte[] bytes) throws IOException { - String world = cWorld.getWorldName(); - DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(bytes)); - // read coordinate - int x = chunkData.readInt(); - int z = chunkData.readInt(); - ChunkPos coordinate = new ChunkPos(x, z); - // read loading info - int loadedSeconds = chunkData.readInt(); - long lastLoadedTime = chunkData.readLong(); - // read task queue - int tasksSize = chunkData.readInt(); - PriorityQueue queue = new PriorityQueue<>(Math.max(11, tasksSize)); - for (int i = 0; i < tasksSize; i++) { - int time = chunkData.readInt(); - BlockPos pos = new BlockPos(chunkData.readInt()); - queue.add(new TickTask(time, pos)); - } - // read ticked blocks - int tickedSize = chunkData.readInt(); - HashSet tickedSet = new HashSet<>(Math.max(11, tickedSize)); - for (int i = 0; i < tickedSize; i++) { - tickedSet.add(new BlockPos(chunkData.readInt())); - } - // read block data - ConcurrentHashMap sectionMap = new ConcurrentHashMap<>(); - int sections = chunkData.readInt(); - // read sections - for (int i = 0; i < sections; i++) { - ConcurrentHashMap blockMap = new ConcurrentHashMap<>(); - int sectionID = chunkData.readInt(); - byte[] sectionBytes = new byte[chunkData.readInt()]; - chunkData.read(sectionBytes); - DataInputStream sectionData = new DataInputStream(new ByteArrayInputStream(sectionBytes)); - int blockAmount = sectionData.readInt(); - // read blocks - for (int j = 0; j < blockAmount; j++){ - byte[] blockData = new byte[sectionData.readInt()]; - sectionData.read(blockData); - CompoundMap block = readCompound(blockData).getValue(); - String type = (String) block.get("type").getValue(); - CompoundMap data = (CompoundMap) block.get("data").getValue(); - switch (type) { - case "CROP" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryCrop(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "POT" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryPot(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "SPRINKLER" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemorySprinkler(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "SCARECROW" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryScarecrow(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "GREENHOUSE" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryGlass(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - } - } - CSection cSection = new CSection(sectionID, blockMap); - sectionMap.put(sectionID, cSection); - } - - return new CChunk(cWorld, coordinate, loadedSeconds, lastLoadedTime, sectionMap, queue, tickedSet); - } - - private byte[] serializeChunk(SerializableChunk chunk) throws IOException { - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(16384); - DataOutputStream outStream = new DataOutputStream(outByteStream); - outStream.writeInt(chunk.getX()); - outStream.writeInt(chunk.getZ()); - outStream.writeInt(chunk.getLoadedSeconds()); - outStream.writeLong(chunk.getLastLoadedTime()); - // write queue - int[] queue = chunk.getQueuedTasks(); - outStream.writeInt(queue.length / 2); - for (int i : queue) { - outStream.writeInt(i); - } - // write ticked blocks - int[] tickedSet = chunk.getTicked(); - outStream.writeInt(tickedSet.length); - for (int i : tickedSet) { - outStream.writeInt(i); - } - // write block data - List sectionsToSave = chunk.getSections(); - outStream.writeInt(sectionsToSave.size()); - for (SerializableSection section : sectionsToSave) { - outStream.writeInt(section.getSectionID()); - byte[] blockData = serializeBlocks(section.getBlocks()); - outStream.writeInt(blockData.length); - outStream.write(blockData); - } - return outByteStream.toByteArray(); - } - - private byte[] serializeBlocks(Collection blocks) throws IOException { - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(16384); - DataOutputStream outStream = new DataOutputStream(outByteStream); - outStream.writeInt(blocks.size()); - for (CompoundTag block : blocks) { - byte[] blockData = serializeCompoundTag(block); - outStream.writeInt(blockData.length); - outStream.write(blockData); - } - return outByteStream.toByteArray(); - } - - private byte[] readCompressedBytes(DataInputStream dataStream) throws IOException { - int compressedLength = dataStream.readInt(); - int decompressedLength = dataStream.readInt(); - byte[] compressedData = new byte[compressedLength]; - byte[] decompressedData = new byte[decompressedLength]; - - dataStream.read(compressedData); - Zstd.decompress(decompressedData, compressedData); - return decompressedData; - } - - public SerializableChunk toSerializableChunk(CChunk chunk) { - ChunkPos chunkPos = chunk.getChunkPos(); - return new SerializableChunk( - chunkPos.x(), - chunkPos.z(), - chunk.getLoadedSeconds(), - chunk.getLastLoadedTime(), - Arrays.stream(chunk.getSectionsForSerialization()).map(this::toSerializableSection).toList(), - queueToIntArray(chunk.getQueue()), - tickedBlocksToArray(chunk.getTickedBlocks()) - ); - } - - private int[] tickedBlocksToArray(Set set) { - int[] ticked = new int[set.size()]; - int i = 0; - for (BlockPos pos : set) { - ticked[i] = pos.getPosition(); - i++; - } - return ticked; - } - - private int[] queueToIntArray(PriorityQueue queue) { - int size = queue.size() * 2; - int[] tasks = new int[size]; - int i = 0; - for (TickTask task : queue) { - tasks[i * 2] = task.getTime(); - tasks[i * 2 + 1] = task.getChunkPos().getPosition(); - i++; - } - return tasks; - } - - private SerializableSection toSerializableSection(CSection section) { - return new SerializableSection(section.getSectionID(), toCompoundTags(section.getBlockMap())); - } - - private List toCompoundTags(Map blocks) { - ArrayList tags = new ArrayList<>(blocks.size()); - Map> blockToPosMap = new HashMap<>(); - for (Map.Entry entry : blocks.entrySet()) { - BlockPos coordinate = entry.getKey(); - CustomCropsBlock block = entry.getValue(); - List coordinates = blockToPosMap.computeIfAbsent(block, k -> new ArrayList<>()); - coordinates.add(coordinate.getPosition()); - } - for (Map.Entry> entry : blockToPosMap.entrySet()) { - tags.add(new CompoundTag("", toCompoundMap(entry.getKey(), entry.getValue()))); - } - return tags; - } - - private CompoundMap toCompoundMap(CustomCropsBlock block, List pos) { - CompoundMap map = new CompoundMap(); - int[] result = new int[pos.size()]; - for (int i = 0; i < pos.size(); i++) { - result[i] = pos.get(i); - } - map.put(new StringTag("type", block.getType().name())); - map.put(new IntArrayTag("pos", result)); - map.put(new CompoundTag("data", block.getCompoundMap().getOriginalMap())); - return map; - } - - private CompoundTag readCompound(byte[] bytes) throws IOException { - if (bytes.length == 0) - return null; - NBTInputStream nbtInputStream = new NBTInputStream( - new ByteArrayInputStream(bytes), - NBTInputStream.NO_COMPRESSION, - ByteOrder.BIG_ENDIAN - ); - return (CompoundTag) nbtInputStream.readTag(); - } - - private byte[] serializeCompoundTag(CompoundTag tag) throws IOException { - if (tag == null || tag.getValue().isEmpty()) - return new byte[0]; - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); - NBTOutputStream outStream = new NBTOutputStream( - outByteStream, - NBTInputStream.NO_COMPRESSION, - ByteOrder.BIG_ENDIAN - ); - outStream.writeTag(tag); - return outByteStream.toByteArray(); - } - - public boolean convertWorldFromV33toV34(@Nullable CWorld cWorld, World world) { - // handle legacy files - File leagcyFile = new File(world.getWorldFolder(), "customcrops" + File.separator + "data.yml"); - if (leagcyFile.exists()) { - // read date and season - YamlConfiguration data = YamlConfiguration.loadConfiguration(leagcyFile); - try { - Season season = Season.valueOf(data.getString("season", "SPRING")); - if (cWorld != null) - cWorld.setInfoData(new WorldInfoData(season, data.getInt("date", 1))); - world.getPersistentDataContainer().set(key, PersistentDataType.STRING, - gson.toJson(new WorldInfoData(season, data.getInt("date", 1)))); - } catch (Exception e) { - if (cWorld != null) - cWorld.setInfoData(WorldInfoData.empty()); - } - // delete the file - leagcyFile.delete(); - new File(world.getWorldFolder(), "customcrops" + File.separator + "corrupted.yml").delete(); - - // read chunks - File folder = new File(world.getWorldFolder(), "customcrops" + File.separator + "chunks"); - if (!folder.exists()) return false; - LogUtils.warn("Converting chunks for world " + world.getName() + " from 3.3 to 3.4... This might take some time."); - File[] data_files = folder.listFiles(); - if (data_files == null) return false; - - HashMap regionHashMap = new HashMap<>(); - - for (File file : data_files) { - ChunkPos chunkPos = ChunkPos.getByString(file.getName().substring(0, file.getName().length() - 7)); - try (FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis)) { - CCChunk chunk = (CCChunk) ois.readObject(); - CChunk cChunk = new CChunk(cWorld, chunkPos); - for (net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation : chunk.getGreenhouseSet()) { - SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); - cChunk.addGlassAt(new MemoryGlass(simpleLocation), simpleLocation); - } - for (net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation : chunk.getScarecrowSet()) { - SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); - cChunk.addScarecrowAt(new MemoryScarecrow(simpleLocation), simpleLocation); - } - for (Map.Entry entry : chunk.getGrowingCropMap().entrySet()) { - net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation = entry.getKey(); - SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); - cChunk.addCropAt(new MemoryCrop(simpleLocation, entry.getValue().getKey(), entry.getValue().getPoints()), simpleLocation); - } - for (Map.Entry entry : chunk.getSprinklerMap().entrySet()) { - net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation = entry.getKey(); - SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); - cChunk.addSprinklerAt(new MemorySprinkler(simpleLocation, entry.getValue().getKey(), entry.getValue().getWater()), simpleLocation); - } - for (Map.Entry entry : chunk.getPotMap().entrySet()) { - net.momirealms.customcrops.api.object.world.SimpleLocation legacyLocation = entry.getKey(); - SimpleLocation simpleLocation = new SimpleLocation(legacyLocation.getWorldName(), legacyLocation.getX(), legacyLocation.getY(), legacyLocation.getZ()); - Fertilizer fertilizer = entry.getValue().getFertilizer(); - cChunk.addPotAt(new MemoryPot(simpleLocation, entry.getValue().getKey(), entry.getValue().getWater(), fertilizer == null ? "" : fertilizer.getKey(), fertilizer == null ? 0 : fertilizer.getTimes()), simpleLocation); - } - CustomCropsRegion region = regionHashMap.get(chunkPos.getRegionPos()); - if (region == null) { - region = new CRegion(cWorld, chunkPos.getRegionPos()); - regionHashMap.put(chunkPos.getRegionPos(), region); - } - region.saveChunk(chunkPos, serialize(toSerializableChunk(cChunk))); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - LogUtils.info("Error at " + file.getAbsolutePath()); - } - } - - for (CustomCropsRegion region : regionHashMap.values()) { - saveRegion(region); - } - LogUtils.info("Successfully converted chunks for world: " + world.getName()); - return true; - } - return false; - } - - public void convertWorldFromV342toV343(@Nullable CWorld cWorld, World world) { - File folder = new File(world.getWorldFolder(), "customcrops"); - if (!folder.exists()) return; - LogUtils.warn("Converting chunks for world " + world.getName() + " from 3.4.2 to 3.4.3... This might take some time."); - File[] data_files = folder.listFiles(); - if (data_files == null) return; - HashMap regionHashMap = new HashMap<>(); - for (File file : data_files) { - String fileName = file.getName(); - if (fileName.endsWith(".ccd")) { - String chunkStr = file.getName().substring(0, fileName.length()-4); - ChunkPos chunkPos = ChunkPos.getByString(chunkStr); - try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { - DataInputStream dataStream = new DataInputStream(bis); - byte[] chunkData = dataStream.readAllBytes(); - dataStream.close(); - CustomCropsRegion region = regionHashMap.get(chunkPos.getRegionPos()); - if (region == null) { - region = new CRegion(cWorld, chunkPos.getRegionPos()); - regionHashMap.put(chunkPos.getRegionPos(), region); - } - region.saveChunk(chunkPos, chunkData); - } catch (IOException e) { - e.printStackTrace(); - } - file.delete(); - } - } - for (CustomCropsRegion region : regionHashMap.values()) { - saveRegion(region); - } - LogUtils.info("Successfully converted chunks for world: " + world.getName()); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java deleted file mode 100644 index 679d859..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/adaptor/SlimeWorldAdaptor.java +++ /dev/null @@ -1,323 +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.mechanic.world.adaptor; - -import com.flowpowered.nbt.*; -import com.infernalsuite.aswm.api.events.LoadSlimeWorldEvent; -import com.infernalsuite.aswm.api.world.SlimeWorld; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.WorldManager; -import net.momirealms.customcrops.api.mechanic.world.BlockPos; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.CustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsChunk; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsRegion; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.level.WorldInfoData; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.world.*; -import net.momirealms.customcrops.mechanic.world.block.*; -import net.momirealms.customcrops.scheduler.task.TickTask; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.plugin.Plugin; - -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.PriorityQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -public class SlimeWorldAdaptor extends BukkitWorldAdaptor { - - private final Function getSlimeWorldFunction; - - public SlimeWorldAdaptor(WorldManager worldManager, int version) { - super(worldManager); - try { - if (version == 1) { - Plugin plugin = Bukkit.getPluginManager().getPlugin("SlimeWorldManager"); - Class slimeClass = Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); - Method method = slimeClass.getMethod("getWorld", String.class); - this.getSlimeWorldFunction = (name) -> { - try { - return (SlimeWorld) method.invoke(plugin, name); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - }; - } else if (version == 2) { - Class apiClass = Class.forName("com.infernalsuite.aswm.api.AdvancedSlimePaperAPI"); - Object apiInstance = apiClass.getMethod("instance").invoke(null); - Method method = apiClass.getMethod("getLoadedWorld", String.class); - this.getSlimeWorldFunction = (name) -> { - try { - return (SlimeWorld) method.invoke(apiInstance, name); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - }; - } else { - throw new IllegalArgumentException("Unsupported version: " + version); - } - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - - private CompoundMap createOrGetDataMap(SlimeWorld world) { - Optional optionalCompoundTag = world.getExtraData().getAsCompoundTag("customcrops"); - CompoundMap ccDataMap; - if (optionalCompoundTag.isEmpty()) { - ccDataMap = new CompoundMap(); - world.getExtraData().getValue().put(new CompoundTag("customcrops", ccDataMap)); - } else { - ccDataMap = optionalCompoundTag.get().getValue(); - } - return ccDataMap; - } - - @EventHandler (ignoreCancelled = true) - public void onSlimeWorldLoad(LoadSlimeWorldEvent event) { - World world = Bukkit.getWorld(event.getSlimeWorld().getName()); - if (world == null) { - LogUtils.warn("Failed to load slime world because the bukkit world not loaded"); - return; - } - if (worldManager.isMechanicEnabled(world)) { - worldManager.loadWorld(world); - } - } - - @Override - public void saveInfoData(CustomCropsWorld customCropsWorld) { - SlimeWorld slimeWorld = getSlimeWorldFunction.apply(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.saveInfoData(customCropsWorld); - return; - } - CompoundMap ccDataMap = createOrGetDataMap(slimeWorld); - ccDataMap.put(new StringTag("world-info", gson.toJson(customCropsWorld.getInfoData()))); - } - - @Override - public void unload(CustomCropsWorld customCropsWorld) { - SlimeWorld slimeWorld = getSlimeWorldFunction.apply(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.unload(customCropsWorld); - return; - } - customCropsWorld.save(); - } - - @Override - public void init(CustomCropsWorld customCropsWorld) { - SlimeWorld slimeWorld = getSlimeWorldFunction.apply(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.init(customCropsWorld); - return; - } - - CompoundMap ccDataMap = createOrGetDataMap(slimeWorld); - String json = Optional.ofNullable(ccDataMap.get("world-info")).map(tag -> tag.getAsStringTag().get().getValue()).orElse(null); - WorldInfoData data = (json == null || json.equals("null")) ? WorldInfoData.empty() : gson.fromJson(json, WorldInfoData.class); - customCropsWorld.setInfoData(data); - } - - @Override - public void loadChunkData(CustomCropsWorld customCropsWorld, ChunkPos chunkPos) { - SlimeWorld slimeWorld = getSlimeWorldFunction.apply(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.loadChunkData(customCropsWorld, chunkPos); - return; - } - - long time1 = System.currentTimeMillis(); - CWorld cWorld = (CWorld) customCropsWorld; - // load lazy chunks firstly - CustomCropsChunk lazyChunk = customCropsWorld.removeLazyChunkAt(chunkPos); - if (lazyChunk != null) { - CChunk cChunk = (CChunk) lazyChunk; - cChunk.resetUnloadedSeconds(); - cWorld.loadChunk(cChunk); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos + " from lazy chunks"); - return; - } - - CompoundMap ccDataMap = createOrGetDataMap(slimeWorld); - Tag chunkTag = ccDataMap.get(chunkPos.getAsString()); - if (chunkTag == null) { - return; - } - Optional chunkCompoundTag = chunkTag.getAsCompoundTag(); - if (chunkCompoundTag.isEmpty()) { - return; - } - - // load chunk from slime world - cWorld.loadChunk(tagToChunk(cWorld, chunkCompoundTag.get())); - long time2 = System.currentTimeMillis(); - CustomCropsPlugin.get().debug("Took " + (time2-time1) + "ms to load chunk " + chunkPos); - } - - @Override - public void saveChunkToCachedRegion(CustomCropsChunk customCropsChunk) { - CustomCropsWorld customCropsWorld = customCropsChunk.getCustomCropsWorld(); - SlimeWorld slimeWorld = getSlimeWorld(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.saveChunkToCachedRegion(customCropsChunk); - return; - } - - CompoundMap ccDataMap = createOrGetDataMap(slimeWorld); - - SerializableChunk serializableChunk = toSerializableChunk((CChunk) customCropsChunk); - if (Bukkit.isPrimaryThread()) { - if (serializableChunk.canPrune()) { - ccDataMap.remove(customCropsChunk.getChunkPos().getAsString()); - } else { - ccDataMap.put(chunkToTag(serializableChunk)); - } - } else { - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> { - if (serializableChunk.canPrune()) { - ccDataMap.remove(customCropsChunk.getChunkPos().getAsString()); - } else { - ccDataMap.put(chunkToTag(serializableChunk)); - } - }, null); - } - } - - @Override - public void saveRegion(CustomCropsRegion customCropsRegion) { - CustomCropsWorld customCropsWorld = customCropsRegion.getCustomCropsWorld(); - SlimeWorld slimeWorld = getSlimeWorld(customCropsWorld.getWorldName()); - if (slimeWorld == null) { - super.saveRegion(customCropsRegion); - return; - } - - // don't need to save region to slime world - } - - private SlimeWorld getSlimeWorld(String name) { - return getSlimeWorldFunction.apply(name); - } - - private CompoundTag chunkToTag(SerializableChunk serializableChunk) { - CompoundMap map = new CompoundMap(); - map.put(new IntTag("x", serializableChunk.getX())); - map.put(new IntTag("z", serializableChunk.getZ())); - map.put(new IntTag("loadedSeconds", serializableChunk.getLoadedSeconds())); - map.put(new LongTag("lastLoadedTime", serializableChunk.getLastLoadedTime())); - map.put(new IntArrayTag("queued", serializableChunk.getQueuedTasks())); - map.put(new IntArrayTag("ticked", serializableChunk.getTicked())); - CompoundMap sectionMap = new CompoundMap(); - for (SerializableSection section : serializableChunk.getSections()) { - sectionMap.put(new ListTag<>(String.valueOf(section.getSectionID()), TagType.TAG_COMPOUND, section.getBlocks())); - } - map.put(new CompoundTag("sections", sectionMap)); - return new CompoundTag(serializableChunk.getX() + "," + serializableChunk.getZ(), map); - } - - private CChunk tagToChunk(CWorld cWorld, CompoundTag tag) { - String world = cWorld.getWorldName(); - CompoundMap map = tag.getValue(); - int x = map.get("x").getAsIntTag().get().getValue(); - int z = map.get("z").getAsIntTag().get().getValue(); - ChunkPos coordinate = new ChunkPos(x, z); - int loadedSeconds = map.get("loadedSeconds").getAsIntTag().get().getValue(); - long lastLoadedTime = map.get("lastLoadedTime").getAsLongTag().get().getValue(); - int[] queued = map.get("queued").getAsIntArrayTag().get().getValue(); - int[] ticked = map.get("ticked").getAsIntArrayTag().get().getValue(); - - PriorityQueue queue = new PriorityQueue<>(Math.max(11, queued.length / 2)); - for (int i = 0, size = queued.length / 2; i < size; i++) { - BlockPos pos = new BlockPos(queued[2*i+1]); - queue.add(new TickTask(queued[2*i], pos)); - } - - HashSet tickedSet = new HashSet<>(Math.max(11, ticked.length)); - for (int tick : ticked) { - tickedSet.add(new BlockPos(tick)); - } - - ConcurrentHashMap sectionMap = new ConcurrentHashMap<>(); - CompoundMap sectionCompoundMap = map.get("sections").getAsCompoundTag().get().getValue(); - for (Map.Entry> entry : sectionCompoundMap.entrySet()) { - if (entry.getValue() instanceof ListTag listTag) { - int id = Integer.parseInt(entry.getKey()); - ConcurrentHashMap blockMap = new ConcurrentHashMap<>(); - ListTag blocks = (ListTag) listTag; - for (CompoundTag blockTag : blocks.getValue()) { - CompoundMap block = blockTag.getValue(); - String type = (String) block.get("type").getValue(); - CompoundMap data = (CompoundMap) block.get("data").getValue(); - switch (type) { - case "CROP" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryCrop(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "POT" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryPot(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "SPRINKLER" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemorySprinkler(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "SCARECROW" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryScarecrow(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - case "GLASS" -> { - for (int pos : (int[]) block.get("pos").getValue()) { - BlockPos blockPos = new BlockPos(pos); - blockMap.put(blockPos, new MemoryGlass(blockPos.getLocation(world, coordinate), new CompoundMap(data))); - } - } - } - } - sectionMap.put(id, new CSection(id, blockMap)); - } - } - - return new CChunk( - cWorld, - coordinate, - loadedSeconds, - lastLoadedTime, - sectionMap, - queue, - tickedSet - ); - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryCrop.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryCrop.java deleted file mode 100644 index 7a3dec9..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryCrop.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.mechanic.world.block; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.IntTag; -import com.flowpowered.nbt.StringTag; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.condition.Condition; -import net.momirealms.customcrops.api.mechanic.condition.DeathConditions; -import net.momirealms.customcrops.api.mechanic.item.Crop; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.item.fertilizer.SpeedGrow; -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.AbstractCustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.WorldCrop; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.util.Objects; -import java.util.Optional; - -public class MemoryCrop extends AbstractCustomCropsBlock implements WorldCrop { - - public MemoryCrop(SimpleLocation location, String key, int point) { - super(location, new CompoundMap()); - setData("point", new IntTag("point", point)); - setData("key", new StringTag("key", key)); - setData("tick", new IntTag("tick", 0)); - } - - public MemoryCrop(SimpleLocation location, CompoundMap properties) { - super(location, properties); - } - - @Override - public String getKey() { - return getData("key").getAsStringTag() - .map(StringTag::getValue) - .orElse(""); - } - - @Override - public int getPoint() { - return getData("point").getAsIntTag().map(IntTag::getValue).orElse(0); - } - - @Override - public void setPoint(int point) { - if (point < 0) return; - int max = getConfig().getMaxPoints(); - if (point > max) { - point = max; - } - setData("point", new IntTag("point", point)); - } - - @Override - public Crop getConfig() { - return CustomCropsPlugin.get().getItemManager().getCropByID(getKey()); - } - - @Override - public ItemType getType() { - return ItemType.CROP; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractCustomCropsBlock that = (AbstractCustomCropsBlock) o; - return Objects.equals(getCompoundMap(), that.getCompoundMap()); - } - - @Override - public int hashCode() { - return getKey().hashCode() + getPoint() * 17; - } - - @Override - public void tick(int interval, boolean offline) { - if (canTick(interval)) { - tick(offline); - } - } - - private void tick(boolean offline) { - Crop crop = getConfig(); - if (crop == null) { - LogUtils.warn("Found a crop without config at " + getLocation() + ". Try removing the data."); - CustomCropsPlugin.get().getWorldManager().removeCropAt(getLocation()); - return; - } - - SimpleLocation location = getLocation(); - Location bukkitLocation = location.getBukkitLocation(); - if (bukkitLocation == null) return; - - int previous = getPoint(); - - // check death conditions - for (DeathConditions deathConditions : crop.getDeathConditions()) { - for (Condition condition : deathConditions.getConditions()) { - if (condition.isConditionMet(this, offline)) { - CustomCropsPlugin.get().getScheduler().runTaskSyncLater(() -> { - CustomCropsPlugin.get().getWorldManager().removeCropAt(location); - CustomCropsPlugin.get().getItemManager().removeAnythingAt(bukkitLocation); - if (deathConditions.getDeathItem() != null) { - CustomCropsPlugin.get().getItemManager().placeItem(bukkitLocation, deathConditions.getItemCarrier(), deathConditions.getDeathItem()); - } - }, bukkitLocation, offline ? 0 : deathConditions.getDeathDelay()); - return; - } - } - } - - // don't check grow conditions if it's already ripe - if (previous >= crop.getMaxPoints()) { - return; - } - - // check grow conditions - for (Condition condition : crop.getGrowConditions().getConditions()) { - if (!condition.isConditionMet(this, offline)) { - return; - } - } - - // check pot & fertilizer - Optional pot = CustomCropsPlugin.get().getWorldManager().getPotAt(location.copy().add(0,-1,0)); - if (pot.isEmpty()) { - return; - } - - int point = 1; - Fertilizer fertilizer = pot.get().getFertilizer(); - if (fertilizer instanceof SpeedGrow speedGrow) { - point += speedGrow.getPointBonus(); - } - - int x = Math.min(previous + point, crop.getMaxPoints()); - setPoint(x); - String pre = crop.getStageItemByPoint(previous); - String after = crop.getStageItemByPoint(x); - - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> { - for (int i = previous + 1; i <= x; i++) { - Crop.Stage stage = crop.getStageByPoint(i); - if (stage != null) { - stage.trigger(ActionTrigger.GROW, new State(null, new ItemStack(Material.AIR), bukkitLocation)); - } - } - if (pre.equals(after)) return; - CRotation CRotation = CustomCropsPlugin.get().getItemManager().removeAnythingAt(bukkitLocation); - CustomCropsPlugin.get().getItemManager().placeItem(bukkitLocation, crop.getItemCarrier(), after, CRotation); - }, bukkitLocation); - } -} \ No newline at end of file diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryGlass.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryGlass.java deleted file mode 100644 index 0d21d70..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryGlass.java +++ /dev/null @@ -1,60 +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.mechanic.world.block; - -import com.flowpowered.nbt.CompoundMap; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.AbstractCustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.WorldGlass; - -import java.util.Objects; - -public class MemoryGlass extends AbstractCustomCropsBlock implements WorldGlass { - - public MemoryGlass(SimpleLocation location) { - super(location, new CompoundMap()); - } - - public MemoryGlass(SimpleLocation location, CompoundMap properties) { - super(location, properties); - } - - @Override - public ItemType getType() { - return ItemType.GREENHOUSE; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractCustomCropsBlock that = (AbstractCustomCropsBlock) o; - return Objects.equals(getCompoundMap(), that.getCompoundMap()); - } - - @Override - public int hashCode() { - return 1821739123; - } - - @Override - public void tick(int interval, boolean offline) { - - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryPot.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryPot.java deleted file mode 100644 index 1bc3130..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryPot.java +++ /dev/null @@ -1,272 +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.mechanic.world.block; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.IntTag; -import com.flowpowered.nbt.StringTag; -import com.flowpowered.nbt.Tag; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.item.Fertilizer; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.AbstractCustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.Waterlogged; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class MemoryPot extends AbstractCustomCropsBlock implements WorldPot { - - public MemoryPot(SimpleLocation location, CompoundMap compoundMap) { - super(location, compoundMap); - } - - public MemoryPot(SimpleLocation location, String key) { - super(location, new CompoundMap()); - setData("key", new StringTag("key", key)); - setData("water", new IntTag("water", 0)); - setData("fertilizer", new StringTag("fertilizer", "")); - setData("fertilizer-times", new IntTag("fertilizer-times", 0)); - } - - public MemoryPot(SimpleLocation location, String key, int water, String fertilizer, int fertilizerTimes) { - super(location, new CompoundMap()); - setData("key", new StringTag("key", key)); - setData("water", new IntTag("water", water)); - setData("fertilizer", new StringTag("fertilizer", fertilizer)); - setData("fertilizer-times", new IntTag("fertilizer-times", fertilizerTimes)); - } - - @Override - public String getKey() { - return getData("key").getAsStringTag() - .map(StringTag::getValue) - .orElse(""); - } - - @Override - public int getWater() { - return getData("water").getAsIntTag().map(IntTag::getValue).orElse(0); - } - - @Override - public void setWater(int water) { - if (water < 0) return; - int max = getConfig().getStorage(); - if (water > max) { - water = max; - } - setData("water", new IntTag("water", water)); - } - - @Override - public Fertilizer getFertilizer() { - Tag tag = getData("fertilizer"); - if (tag == null) return null; - return tag.getAsStringTag() - .map(strTag -> { - String key = strTag.getValue(); - return CustomCropsPlugin.get().getItemManager().getFertilizerByID(key); - }) - .orElse(null); - } - - @Override - public void setFertilizer(@NotNull Fertilizer fertilizer) { - setData("fertilizer", new StringTag("fertilizer", fertilizer.getKey())); - setData("fertilizer-times", new IntTag("fertilizer-times", fertilizer.getTimes())); - } - - @Override - public void removeFertilizer() { - setData("fertilizer", new StringTag("fertilizer", "")); - setData("fertilizer-times", new IntTag("fertilizer-times", 0)); - } - - @Override - public int getFertilizerTimes() { - return getData("fertilizer-times").getAsIntTag().map(IntTag::getValue).orElse(0); - } - - @Override - public void setFertilizerTimes(int times) { - setData("fertilizer-times", new IntTag("fertilizer-times", times)); - } - - @Override - public Pot getConfig() { - return CustomCropsPlugin.get().getItemManager().getPotByID(getKey()); - } - - @Override - public void tickWater() { - Pot pot = getConfig(); - if (pot == null) { - LogUtils.warn("Found a pot without config at " + getLocation() + ". Try removing the data."); - CustomCropsPlugin.get().getWorldManager().removePotAt(getLocation()); - return; - } - SimpleLocation location = getLocation(); - Location bukkitLocation = location.getBukkitLocation(); - World world = location.getBukkitWorld(); - if (world == null || bukkitLocation == null) return; - - if (pot.isRainDropAccepted()) { - if (world.hasStorm() || (!world.isClearWeather() && !world.isThundering())) { - double temperature = world.getTemperature(location.getX(), location.getY(), location.getZ()); - if (temperature > 0.15 && temperature < 0.85) { - Block highest = world.getHighestBlockAt(location.getX(), location.getZ()); - if (highest.getLocation().getY() == location.getY()) { - addWaterToPot(pot, location); - return; - } - } - } - } - if (pot.isNearbyWaterAccepted()) { - for (int i = -4; i <= 4; i++) { - for (int j = -4; j <= 4; j++) { - for (int k : new int[]{0, 1}) { - Block block = bukkitLocation.clone().add(i,k,j).getBlock(); - if (block.getType() == Material.WATER || (block.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged())) { - addWaterToPot(pot, location); - return; - } - } - } - } - } - } - - private void addWaterToPot(Pot pot, SimpleLocation location) { - int previous = getWater(); - if (previous >= pot.getStorage()) return; - setWater(Math.min(previous + 1, pot.getStorage())); - if (previous == 0) { - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> CustomCropsPlugin.get().getItemManager().updatePotState(location.getBukkitLocation(), pot, true, getFertilizer()), location.getBukkitLocation()); - } - } - - @Override - public ItemType getType() { - return ItemType.POT; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractCustomCropsBlock that = (AbstractCustomCropsBlock) o; - return Objects.equals(getCompoundMap(), that.getCompoundMap()); - } - - @Override - public int hashCode() { - return getKey().hashCode() + getWater() * 17; - } - - @Override - public void tick(int interval, boolean offline) { - if (canTick(interval)) { - tick(); - } - } - - // if the tick is triggered by offline growth - private void tick() { - Pot pot = getConfig(); - if (pot == null) { - LogUtils.warn("Found a pot without config at " + getLocation() + ". Try removing the data."); - CustomCropsPlugin.get().getWorldManager().removePotAt(getLocation()); - return; - } - - boolean loseFertilizer = false; - boolean loseWater = false; - int times = getFertilizerTimes(); - if (times > 0) { - if (--times <= 0) { - removeFertilizer(); - loseFertilizer = true; - } else { - setFertilizerTimes(times); - } - } - - int water = getWater(); - - SimpleLocation location = getLocation(); - Location bukkitLocation = location.getBukkitLocation(); - if (bukkitLocation == null) return; - World world = bukkitLocation.getWorld(); - - outer: { - if (water > 0) { - if (pot.isRainDropAccepted()) { - if (world.hasStorm() || (!world.isClearWeather() && !world.isThundering())) { - double temperature = world.getTemperature(location.getX(), location.getY(), location.getZ()); - if (temperature > 0.15 && temperature < 0.85) { - Block highest = world.getHighestBlockAt(location.getX(), location.getZ()); - if (highest.getLocation().getY() == location.getY()) { - break outer; - } - } - } - } - - if (pot.isNearbyWaterAccepted()) { - for (int i = -4; i <= 4; i++) { - for (int j = -4; j <= 4; j++) { - for (int k : new int[]{0, 1}) { - Block block = bukkitLocation.clone().add(i,k,j).getBlock(); - if (block.getType() == Material.WATER || (block.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged())) { - break outer; - } - } - } - } - } - - if (--water <= 0) { - loseWater = true; - } - setWater(water); - } - } - - if (loseFertilizer || loseWater) { - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> - CustomCropsPlugin.get().getItemManager() - .updatePotState( - bukkitLocation, - pot, - getWater() > 0, - getFertilizer() - ), bukkitLocation - ); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryScarecrow.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryScarecrow.java deleted file mode 100644 index 0011922..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemoryScarecrow.java +++ /dev/null @@ -1,60 +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.mechanic.world.block; - -import com.flowpowered.nbt.CompoundMap; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.AbstractCustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.WorldScarecrow; - -import java.util.Objects; - -public class MemoryScarecrow extends AbstractCustomCropsBlock implements WorldScarecrow { - - public MemoryScarecrow(SimpleLocation location) { - super(location, new CompoundMap()); - } - - public MemoryScarecrow(SimpleLocation location, CompoundMap properties) { - super(location, properties); - } - - @Override - public ItemType getType() { - return ItemType.SCARECROW; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractCustomCropsBlock that = (AbstractCustomCropsBlock) o; - return Objects.equals(getCompoundMap(), that.getCompoundMap()); - } - - @Override - public int hashCode() { - return 28371283; - } - - @Override - public void tick(int interval, boolean offline) { - - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemorySprinkler.java b/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemorySprinkler.java deleted file mode 100644 index ba9cac0..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/mechanic/world/block/MemorySprinkler.java +++ /dev/null @@ -1,183 +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.mechanic.world.block; - -import com.flowpowered.nbt.CompoundMap; -import com.flowpowered.nbt.IntTag; -import com.flowpowered.nbt.StringTag; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.item.ItemType; -import net.momirealms.customcrops.api.mechanic.item.Pot; -import net.momirealms.customcrops.api.mechanic.item.Sprinkler; -import net.momirealms.customcrops.api.mechanic.requirement.State; -import net.momirealms.customcrops.api.mechanic.world.ChunkPos; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; -import net.momirealms.customcrops.api.mechanic.world.level.AbstractCustomCropsBlock; -import net.momirealms.customcrops.api.mechanic.world.level.CustomCropsWorld; -import net.momirealms.customcrops.api.mechanic.world.level.WorldPot; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSprinkler; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.inventory.ItemStack; - -import java.util.*; - -public class MemorySprinkler extends AbstractCustomCropsBlock implements WorldSprinkler { - - public MemorySprinkler(SimpleLocation location, CompoundMap compoundMap) { - super(location, compoundMap); - } - - public MemorySprinkler(SimpleLocation location, String key, int water) { - super(location, new CompoundMap()); - setData("water", new IntTag("water", water)); - setData("key", new StringTag("key", key)); - } - - @Override - public int getWater() { - return getData("water").getAsIntTag().map(IntTag::getValue).orElse(0); - } - - @Override - public void setWater(int water) { - if (water < 0) return; - int max = getConfig().getStorage(); - if (water > max) { - water = max; - } - setData("water", new IntTag("water", water)); - } - - @Override - public String getKey() { - return getData("key").getAsStringTag() - .map(StringTag::getValue) - .orElse(""); - } - - @Override - public Sprinkler getConfig() { - return CustomCropsPlugin.get().getItemManager().getSprinklerByID(getKey()); - } - - @Override - public ItemType getType() { - return ItemType.SPRINKLER; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractCustomCropsBlock that = (AbstractCustomCropsBlock) o; - return Objects.equals(getCompoundMap(), that.getCompoundMap()); - } - - @Override - public int hashCode() { - return getKey().hashCode() + getWater() * 17; - } - - @Override - public void tick(int interval, boolean offline) { - if (canTick(interval)) { - tick(offline); - } - } - - // if the tick is triggered by offline growth - private void tick(boolean offline) { - Sprinkler sprinkler = getConfig(); - if (sprinkler == null) { - LogUtils.warn("Found a sprinkler without config at " + getLocation() + ". Try removing the data."); - CustomCropsPlugin.get().getWorldManager().removeSprinklerAt(getLocation()); - return; - } - - SimpleLocation location = getLocation(); - boolean updateState; - if (!sprinkler.isInfinite()) { - int water = getWater(); - if (water <= 0) { - return; - } - setWater(--water); - updateState = water == 0; - } else { - updateState = false; - } - - Location bukkitLocation = location.getBukkitLocation(); - if (bukkitLocation == null) return; - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> { - State state = new State(null, new ItemStack(Material.AIR), bukkitLocation); - if (offline) state.setArg("{offline}", "true"); - sprinkler.trigger(ActionTrigger.WORK, state); - if (updateState && sprinkler.get3DItemWithWater() != null) { - CustomCropsPlugin.get().getItemManager().removeAnythingAt(bukkitLocation); - CustomCropsPlugin.get().getItemManager().placeItem(bukkitLocation, sprinkler.getItemCarrier(), sprinkler.get3DItemID()); - } - }, bukkitLocation); - - int range = sprinkler.getRange(); - HashMap> map = new HashMap<>(); - for (int i = -range; i <= range; i++) { - for (int j = -range; j <= range; j++) { - for (int k : new int[]{-1,0}) { - SimpleLocation potLocation = location.copy().add(i,k,j); - var cPos = potLocation.getChunkPos(); - var list = map.computeIfAbsent(cPos, key -> new ArrayList<>()); - list.add(potLocation); - } - } - } - - CustomCropsWorld world = CustomCropsPlugin.get().getWorldManager().getCustomCropsWorld(location.getWorldName()).get(); - World bkWorld = world.getWorld(); - for (Map.Entry> entry : map.entrySet()) { - var chunkPos = entry.getKey(); - CustomCropsPlugin.get().getScheduler().runTaskSync(() -> { - // load the chunk firstly to load CustomCrops data - bkWorld.getChunkAt(chunkPos.x(), chunkPos.z()); - for (SimpleLocation potLocation : entry.getValue()) { - Optional pot = world.getPotAt(potLocation); - if (pot.isPresent()) { - WorldPot worldPot = pot.get(); - if (sprinkler.getPotWhitelist().contains(worldPot.getKey())) { - Pot potConfig = worldPot.getConfig(); - if (potConfig != null) { - int current = worldPot.getWater(); - if (current >= potConfig.getStorage()) { - continue; - } - worldPot.setWater(current + sprinkler.getWater()); - if (current == 0) { - CustomCropsPlugin.get().getItemManager().updatePotState(potLocation.getBukkitLocation(), potConfig, true, worldPot.getFertilizer()); - } - } - } - } - } - }, bkWorld, chunkPos.x(), chunkPos.z()); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/BukkitSchedulerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/scheduler/BukkitSchedulerImpl.java deleted file mode 100644 index 76774f3..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/BukkitSchedulerImpl.java +++ /dev/null @@ -1,83 +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.scheduler; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.scheduler.BukkitTask; - -public class BukkitSchedulerImpl implements SyncScheduler { - - private final CustomCropsPlugin plugin; - - public BukkitSchedulerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void runSyncTask(Runnable runnable, Location location) { - if (Bukkit.isPrimaryThread()) - runnable.run(); - else - Bukkit.getScheduler().runTask(plugin, runnable); - } - - @Override - public void runSyncTask(Runnable runnable, World world, int x, int z) { - runSyncTask(runnable, null); - } - - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) { - return new BukkitCancellableTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period)); - } - - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) { - if (delay == 0) { - if (Bukkit.isPrimaryThread()) runnable.run(); - else Bukkit.getScheduler().runTask(plugin, runnable); - return new BukkitCancellableTask(null); - } - return new BukkitCancellableTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay)); - } - - public static class BukkitCancellableTask implements CancellableTask { - - private final BukkitTask bukkitTask; - - public BukkitCancellableTask(BukkitTask bukkitTask) { - this.bukkitTask = bukkitTask; - } - - @Override - public void cancel() { - if (this.bukkitTask != null) - this.bukkitTask.cancel(); - } - - @Override - public boolean isCancelled() { - if (this.bukkitTask == null) return true; - return this.bukkitTask.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/FoliaSchedulerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/scheduler/FoliaSchedulerImpl.java deleted file mode 100644 index 61c787e..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/FoliaSchedulerImpl.java +++ /dev/null @@ -1,87 +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.scheduler; - -import io.papermc.paper.threadedregions.scheduler.ScheduledTask; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; - -public class FoliaSchedulerImpl implements SyncScheduler { - - private final CustomCropsPlugin plugin; - - public FoliaSchedulerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void runSyncTask(Runnable runnable, Location location) { - if (location == null) { - Bukkit.getGlobalRegionScheduler().execute(plugin, runnable); - } else { - Bukkit.getRegionScheduler().execute(plugin, location, runnable); - } - } - - @Override - public void runSyncTask(Runnable runnable, World world, int x, int z) { - Bukkit.getRegionScheduler().execute(plugin, world, x, z, runnable); - } - - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) { - if (location == null) { - return new FoliaCancellableTask(Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, (scheduledTask -> runnable.run()), delay, period)); - } - return new FoliaCancellableTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, (scheduledTask -> runnable.run()), delay, period)); - } - - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) { - if (delay == 0) { - runSyncTask(runnable, location); - return new FoliaCancellableTask(null); - } - if (location == null) { - return new FoliaCancellableTask(Bukkit.getGlobalRegionScheduler().runDelayed(plugin, (scheduledTask -> runnable.run()), delay)); - } - return new FoliaCancellableTask(Bukkit.getRegionScheduler().runDelayed(plugin, location, (scheduledTask -> runnable.run()), delay)); - } - - public static class FoliaCancellableTask implements CancellableTask { - - private final ScheduledTask scheduledTask; - - public FoliaCancellableTask(ScheduledTask scheduledTask) { - this.scheduledTask = scheduledTask; - } - - @Override - public void cancel() { - this.scheduledTask.cancel(); - } - - @Override - public boolean isCancelled() { - return this.scheduledTask.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/SchedulerImpl.java b/plugin/src/main/java/net/momirealms/customcrops/scheduler/SchedulerImpl.java deleted file mode 100644 index a2022f5..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/SchedulerImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.scheduler; - -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.manager.ConfigManager; -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import net.momirealms.customcrops.api.scheduler.Scheduler; -import net.momirealms.customcrops.api.util.LogUtils; -import org.bukkit.Location; -import org.bukkit.World; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * A scheduler implementation responsible for scheduling and managing tasks in a multi-threaded environment. - */ -public class SchedulerImpl implements Scheduler { - - private final SyncScheduler syncScheduler; - private final ScheduledThreadPoolExecutor schedule; - private final CustomCropsPlugin plugin; - - public SchedulerImpl(CustomCropsPlugin plugin) { - this.plugin = plugin; - this.syncScheduler = plugin.getVersionManager().hasRegionScheduler() ? - new FoliaSchedulerImpl(plugin) : new BukkitSchedulerImpl(plugin); - this.schedule = new ScheduledThreadPoolExecutor(1); - this.schedule.setMaximumPoolSize(1); - this.schedule.setKeepAliveTime(30, TimeUnit.SECONDS); - this.schedule.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); - } - - public void reload() { - try { - this.schedule.setMaximumPoolSize(ConfigManager.maximumPoolSize()); - this.schedule.setCorePoolSize(ConfigManager.corePoolSize()); - this.schedule.setKeepAliveTime(ConfigManager.keepAliveTime(), TimeUnit.SECONDS); - } catch (IllegalArgumentException e) { - LogUtils.warn("Failed to create thread pool. Please lower the corePoolSize in config.yml.", e); - } - } - - public void shutdown() { - if (this.schedule != null && !this.schedule.isShutdown()) - this.schedule.shutdown(); - } - - @Override - public void runTaskSync(Runnable runnable, Location location) { - this.syncScheduler.runSyncTask(runnable, location); - } - - @Override - public void runTaskSync(Runnable runnable, World world, int x, int z) { - this.syncScheduler.runSyncTask(runnable, world, x, z); - } - - @Override - public void runTaskAsync(Runnable runnable) { - try { - this.schedule.execute(runnable); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override - public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks) { - return this.syncScheduler.runTaskSyncTimer(runnable, location, delayTicks, periodTicks); - } - - @Override - public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) { - return new ScheduledTask(schedule.schedule(() -> { - try { - runnable.run(); - } catch (Exception e) { - e.printStackTrace(); - } - }, delay, timeUnit)); - } - - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit) { - return new ScheduledTask(schedule.schedule(() -> runTaskSync(runnable, location), delay, timeUnit)); - } - - @Override - public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks) { - return this.syncScheduler.runTaskSyncLater(runnable, location, delayTicks); - } - - @Override - public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) { - return new ScheduledTask(schedule.scheduleAtFixedRate(() -> { - try { - runnable.run(); - } catch (Exception e) { - e.printStackTrace(); - } - }, delay, period, timeUnit)); - } - - public static class ScheduledTask implements CancellableTask { - - private final ScheduledFuture scheduledFuture; - - public ScheduledTask(ScheduledFuture scheduledFuture) { - this.scheduledFuture = scheduledFuture; - } - - @Override - public void cancel() { - this.scheduledFuture.cancel(false); - } - - @Override - public boolean isCancelled() { - return this.scheduledFuture.isCancelled(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/SyncScheduler.java b/plugin/src/main/java/net/momirealms/customcrops/scheduler/SyncScheduler.java deleted file mode 100644 index f08621c..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/SyncScheduler.java +++ /dev/null @@ -1,33 +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.scheduler; - -import net.momirealms.customcrops.api.scheduler.CancellableTask; -import org.bukkit.Location; -import org.bukkit.World; - -public interface SyncScheduler { - - void runSyncTask(Runnable runnable, Location location); - - void runSyncTask(Runnable runnable, World world, int x, int z); - - CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks); - - CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks); -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/scheduler/task/ReplaceTask.java b/plugin/src/main/java/net/momirealms/customcrops/scheduler/task/ReplaceTask.java deleted file mode 100644 index 163fa77..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/scheduler/task/ReplaceTask.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.scheduler.task; - -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import net.momirealms.customcrops.api.mechanic.world.SimpleLocation; - -public class ReplaceTask { - - private final SimpleLocation simpleLocation; - private final ItemCarrier carrier; - private final String id; - - public ReplaceTask(SimpleLocation simpleLocation, ItemCarrier carrier, String id) { - this.simpleLocation = simpleLocation; - this.carrier = carrier; - this.id = id; - } - - public SimpleLocation getSimpleLocation() { - return simpleLocation; - } - - public ItemCarrier getCarrier() { - return carrier; - } - - public String getID() { - return id; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/ConfigUtils.java b/plugin/src/main/java/net/momirealms/customcrops/util/ConfigUtils.java deleted file mode 100644 index f25b5ce..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/util/ConfigUtils.java +++ /dev/null @@ -1,415 +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.util; - -import com.google.common.base.Preconditions; -import net.momirealms.customcrops.api.CustomCropsPlugin; -import net.momirealms.customcrops.api.common.Pair; -import net.momirealms.customcrops.api.manager.PlaceholderManager; -import net.momirealms.customcrops.api.mechanic.action.Action; -import net.momirealms.customcrops.api.mechanic.action.ActionTrigger; -import net.momirealms.customcrops.api.mechanic.condition.Condition; -import net.momirealms.customcrops.api.mechanic.condition.DeathConditions; -import net.momirealms.customcrops.api.mechanic.item.BoneMeal; -import net.momirealms.customcrops.api.mechanic.item.FertilizerType; -import net.momirealms.customcrops.api.mechanic.item.ItemCarrier; -import net.momirealms.customcrops.api.mechanic.item.water.PassiveFillMethod; -import net.momirealms.customcrops.api.mechanic.item.water.PositiveFillMethod; -import net.momirealms.customcrops.api.mechanic.misc.Value; -import net.momirealms.customcrops.api.mechanic.requirement.Requirement; -import net.momirealms.customcrops.api.mechanic.world.level.WorldSetting; -import net.momirealms.customcrops.api.util.LogUtils; -import net.momirealms.customcrops.mechanic.item.impl.CropConfig; -import net.momirealms.customcrops.mechanic.misc.value.ExpressionValue; -import net.momirealms.customcrops.mechanic.misc.value.PlainValue; -import net.objecthunter.exp4j.ExpressionBuilder; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -public class ConfigUtils { - - private ConfigUtils() {} - - public static YamlConfiguration getConfig(String file) { - File config = new File(CustomCropsPlugin.get().getDataFolder(), file); - if (!config.exists()) { - CustomCropsPlugin.get().saveResource(file, false); - addDefaultNamespace(config); - } - return YamlConfiguration.loadConfiguration(config); - } - - public static WorldSetting getWorldSettingFromSection(ConfigurationSection section) { - return WorldSetting.of( - section.getBoolean("enable", true), - section.getInt("min-tick-unit", 300), - getRandomTickModeByString(section.getString("crop.mode")), - section.getInt("crop.tick-interval", 1), - getRandomTickModeByString(section.getString("pot.mode")), - section.getInt("pot.tick-interval", 2), - getRandomTickModeByString(section.getString("sprinkler.mode")), - section.getInt("sprinkler.tick-interval", 2), - section.getBoolean("offline-tick.enable", false), - section.getInt("offline-tick.max-offline-seconds", 1200), - section.getBoolean("season.enable", false), - section.getBoolean("season.auto-alternation", false), - section.getInt("season.duration", 28), - section.getInt("crop.max-per-chunk", 128), - section.getInt("pot.max-per-chunk", -1), - section.getInt("sprinkler.max-per-chunk", 32), - section.getInt("random-tick-speed", 0) - ); - } - - public static boolean getRandomTickModeByString(String str) { - if (str == null) { - return false; - } - if (str.equalsIgnoreCase("RANDOM_TICK")) { - return true; - } - if (str.equalsIgnoreCase("SCHEDULED_TICK")) { - return false; - } - throw new IllegalArgumentException("Invalid mode found when loading world settings: " + str); - } - - public static boolean isVanillaItem(String item) { - char[] chars = item.toCharArray(); - for (char character : chars) { - if ((character < 65 || character > 90) && character != 95) { - return false; - } - } - return true; - } - - /** - * Converts an object into an ArrayList of strings. - * - * @param object The input object - * @return An ArrayList of strings - */ - @SuppressWarnings("unchecked") - public static ArrayList stringListArgs(Object object) { - ArrayList list = new ArrayList<>(); - if (object instanceof String member) { - list.add(member); - } else if (object instanceof List members) { - list.addAll((Collection) members); - } else if (object instanceof String[] strings) { - list.addAll(List.of(strings)); - } - return list; - } - - /** - * Converts an object into a double value. - * - * @param arg The input object - * @return A double value - */ - public static double getDoubleValue(Object arg) { - if (arg instanceof Double d) { - return d; - } else if (arg instanceof Integer i) { - return Double.valueOf(i); - } - return 0; - } - - /** - * Converts an object into a "value". - * - * @param arg int / double / expression - * @return Value - */ - public static Value getValue(Object arg) { - if (arg instanceof Integer i) { - return new PlainValue(i); - } else if (arg instanceof Double d) { - return new PlainValue(d); - } else if (arg instanceof String s) { - return new ExpressionValue(s); - } - throw new IllegalArgumentException("Illegal value type"); - } - - public static double getExpressionValue(Player player, String formula, Map vars) { - formula = PlaceholderManager.getInstance().parse(player, formula, vars); - return new ExpressionBuilder(formula).build().evaluate(); - } - - /** - * Splits a string into a pair of integers using the "~" delimiter. - * - * @param value The input string - * @return A Pair of integers - */ - public static Pair splitStringIntegerArgs(String value, String regex) { - String[] split = value.split(regex); - return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1])); - } - - public static List getFilesRecursively(File folder) { - List ymlFiles = new ArrayList<>(); - if (folder != null && folder.isDirectory()) { - File[] files = folder.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - ymlFiles.addAll(getFilesRecursively(file)); - } else if (file.getName().endsWith(".yml")) { - ymlFiles.add(file); - } - } - } - } - return ymlFiles; - } - - public static Action[] getActions(ConfigurationSection section) { - return CustomCropsPlugin.get().getActionManager().getActions(section); - } - - - public static HashMap getActionMap(ConfigurationSection section) { - HashMap map = new HashMap<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - try { - ActionTrigger trigger = ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)); - map.put(trigger, getActions(innerSection)); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } - } - } - return map; - } - - public static PositiveFillMethod[] getPositiveFillMethods(ConfigurationSection section) { - ArrayList methods = new ArrayList<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - PositiveFillMethod fillMethod = new PositiveFillMethod( - Preconditions.checkNotNull(innerSection.getString("target"), "fill-method target should not be null"), - innerSection.getInt("amount", 1), - getActions(innerSection.getConfigurationSection("actions")), - getRequirements(innerSection.getConfigurationSection("requirements")) - ); - methods.add(fillMethod); - } - } - } - return methods.toArray(new PositiveFillMethod[0]); - } - - public static PassiveFillMethod[] getPassiveFillMethods(ConfigurationSection section) { - ArrayList methods = new ArrayList<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - PassiveFillMethod fillMethod = new PassiveFillMethod( - 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), - getActions(innerSection.getConfigurationSection("actions")), - getRequirements(innerSection.getConfigurationSection("requirements")) - ); - methods.add(fillMethod); - } - } - } - return methods.toArray(new PassiveFillMethod[0]); - } - - public static HashMap getInt2IntMap(ConfigurationSection section) { - HashMap map = new HashMap<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - try { - int i1 = Integer.parseInt(entry.getKey()); - if (entry.getValue() instanceof Integer i2) { - map.put(i1, i2); - } - } catch (NumberFormatException e) { - e.printStackTrace(); - } - } - } - return map; - } - - public static Requirement[] getRequirements(ConfigurationSection section) { - return CustomCropsPlugin.get().getRequirementManager().getRequirements(section, true); - } - - public static HashMap> getFertilizedPotMap(ConfigurationSection section) { - HashMap> map = new HashMap<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection innerSection) { - FertilizerType type = switch (entry.getKey()) { - case "quality" -> FertilizerType.QUALITY; - case "yield-increase" -> FertilizerType.YIELD_INCREASE; - case "variation" -> FertilizerType.VARIATION; - case "soil-retain" -> FertilizerType.SOIL_RETAIN; - case "speed-grow" -> FertilizerType.SPEED_GROW; - default -> null; - }; - 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 static double[] getQualityRatio(String str) { - String[] split = str.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 static List> getIntChancePair(ConfigurationSection section) { - ArrayList> pairs = new ArrayList<>(); - if (section != null) { - for (String point : section.getKeys(false)) { - Pair pair = new Pair<>(section.getDouble(point), Integer.parseInt(point)); - pairs.add(pair); - } - } - return pairs; - } - - public static BoneMeal[] getBoneMeals(ConfigurationSection section) { - ArrayList boneMeals = new ArrayList<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection 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.getConfigurationSection("chance")), - getActions(innerSection.getConfigurationSection("actions")) - ); - boneMeals.add(boneMeal); - } - } - } - return boneMeals.toArray(new BoneMeal[0]); - } - - public static DeathConditions[] getDeathConditions(ConfigurationSection section, ItemCarrier original) { - ArrayList conditions = new ArrayList<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - DeathConditions deathConditions = new DeathConditions( - getConditions(inner.getConfigurationSection("conditions")), - inner.getString("model"), - Optional.ofNullable(inner.getString("type")).map(ItemCarrier::valueOf).orElse(original), - inner.getInt("delay", 0) - ); - conditions.add(deathConditions); - } - } - } - return conditions.toArray(new DeathConditions[0]); - } - - public static Condition[] getConditions(ConfigurationSection section) { - return CustomCropsPlugin.get().getConditionManager().getConditions(section); - } - - public static HashMap getStageConfigs(ConfigurationSection section) { - HashMap map = new HashMap<>(); - if (section != null) { - for (Map.Entry entry : section.getValues(false).entrySet()) { - if (entry.getValue() instanceof ConfigurationSection inner) { - try { - int point = Integer.parseInt(entry.getKey()); - if (point < 0) { - LogUtils.warn(entry.getKey() + " is not a valid number"); - } else { - map.put(point, new CropConfig.CropStageConfig( - inner.getString("model"), - point, - inner.getDouble("hologram-offset-correction", 0d), - getActionMap(inner.getConfigurationSection("events")), - getRequirements(inner.getConfigurationSection("requirements.interact")), - getRequirements(inner.getConfigurationSection("requirements.break")) - )); - } - } catch (NumberFormatException e) { - LogUtils.warn(entry.getKey() + " is not a valid number"); - } - } - } - } - return map; - } - - public static void addDefaultNamespace(File file) { - boolean has = Bukkit.getPluginManager().getPlugin("ItemsAdder") != null; - 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) { - e.printStackTrace(); - } - try (BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - String finalStr = sb.toString(); - if (!has) { - finalStr = finalStr.replace("CHORUS", "TRIPWIRE").replace("", ""); - } - writer.write(finalStr.replace("{0}", has ? "customcrops:" : "")); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/FakeEntityUtils.java b/plugin/src/main/java/net/momirealms/customcrops/util/FakeEntityUtils.java deleted file mode 100644 index 030c2a9..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/util/FakeEntityUtils.java +++ /dev/null @@ -1,147 +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.util; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.*; -import com.google.common.collect.Lists; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.momirealms.customcrops.api.manager.AdventureManager; -import net.momirealms.customcrops.api.manager.VersionManager; -import org.bukkit.Location; -import org.bukkit.entity.EntityType; -import org.bukkit.inventory.ItemStack; - -import java.util.*; - -public class FakeEntityUtils { - - public static WrappedDataWatcher createInvisibleDataWatcher() { - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - //wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, WrappedDataWatcher.Registry.get(Boolean.class)), false); - byte flag = 0x20; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, WrappedDataWatcher.Registry.get(Byte.class)), flag); - return wrappedDataWatcher; - } - - public static PacketContainer getDestroyPacket(int id) { - PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY); - destroyPacket.getIntLists().write(0, List.of(id)); - return destroyPacket; - } - - public static PacketContainer getSpawnPacket(int id, Location location, EntityType entityType) { - PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY); - entityPacket.getModifier().write(0, id); - entityPacket.getModifier().write(1, UUID.randomUUID()); - entityPacket.getEntityTypeModifier().write(0, entityType); - entityPacket.getDoubles().write(0, location.getX()); - entityPacket.getDoubles().write(1, location.getY()); - entityPacket.getDoubles().write(2, location.getZ()); - return entityPacket; - } - - public static PacketContainer getVanishArmorStandMetaPacket(int id) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getIntegers().write(0, id); - if (VersionManager.isHigherThan1_19_R2()) { - WrappedDataWatcher wrappedDataWatcher = createInvisibleDataWatcher(); - setWrappedDataValue(metaPacket, wrappedDataWatcher); - } else { - metaPacket.getWatchableCollectionModifier().write(0, createInvisibleDataWatcher().getWatchableObjects()); - } - return metaPacket; - } - - public static PacketContainer getEquipPacket(int id, ItemStack itemStack) { - PacketContainer equipPacket = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); - equipPacket.getIntegers().write(0, id); - List> pairs = new ArrayList<>(); - pairs.add(new Pair<>(EnumWrappers.ItemSlot.HEAD, itemStack)); - equipPacket.getSlotStackPairLists().write(0, pairs); - return equipPacket; - } - - public static PacketContainer getTeleportPacket(int id, Location location, float yaw) { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT); - packet.getIntegers().write(0, id); - packet.getDoubles().write(0, location.getX()); - packet.getDoubles().write(1, location.getY()); - packet.getDoubles().write(2, location.getZ()); - packet.getBytes().write(0, (byte) (yaw * (128.0 / 180))); - return packet; - } - - public static PacketContainer getVanishArmorStandMetaPacket(int id, Component component) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(2, WrappedDataWatcher.Registry.getChatComponentSerializer(true)), Optional.of(WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(component)).getHandle())); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(3, WrappedDataWatcher.Registry.get(Boolean.class)), true); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(5, WrappedDataWatcher.Registry.get(Boolean.class)), true); - byte mask1 = 0x20; - byte mask2 = 0x01; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, WrappedDataWatcher.Registry.get(Byte.class)), mask1); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(15, WrappedDataWatcher.Registry.get(Byte.class)), mask2); - metaPacket.getModifier().write(0, id); - if (VersionManager.isHigherThan1_19_R2()) { - setWrappedDataValue(metaPacket, wrappedDataWatcher); - } else { - metaPacket.getWatchableCollectionModifier().write(0, wrappedDataWatcher.getWatchableObjects()); - } - return metaPacket; - } - - private static void setWrappedDataValue(PacketContainer metaPacket, WrappedDataWatcher wrappedDataWatcher) { - List wrappedDataValueList = Lists.newArrayList(); - wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> { - final WrappedDataWatcher.WrappedDataWatcherObject dataWatcherObject = entry.getWatcherObject(); - wrappedDataValueList.add(new WrappedDataValue(dataWatcherObject.getIndex(), dataWatcherObject.getSerializer(), entry.getRawValue())); - }); - metaPacket.getDataValueCollectionModifier().write(0, wrappedDataValueList); - } - - public static PacketContainer get1_19_4TextDisplayMetaPacket(int id, Component component) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getModifier().write(0, id); - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(22, WrappedDataWatcher.Registry.getChatComponentSerializer(false)), WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(component))); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(24, WrappedDataWatcher.Registry.get(Integer.class)), AdventureManager.getInstance().rgbaToDecimal("0,0,0,0")); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(14, WrappedDataWatcher.Registry.get(Byte.class)), (byte) 3); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(25, WrappedDataWatcher.Registry.get(Byte.class)), (byte) -1); - int mask = 0; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(26, WrappedDataWatcher.Registry.get(Byte.class)), (byte) mask); - setWrappedDataValue(metaPacket, wrappedDataWatcher); - return metaPacket; - } - - public static PacketContainer get1_20_2TextDisplayMetaPacket(int id, Component component) { - PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - metaPacket.getModifier().write(0, id); - WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher(); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(23, WrappedDataWatcher.Registry.getChatComponentSerializer(false)), WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(component))); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(25, WrappedDataWatcher.Registry.get(Integer.class)), AdventureManager.getInstance().rgbaToDecimal("0,0,0,0")); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(15, WrappedDataWatcher.Registry.get(Byte.class)), (byte) 3); - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(26, WrappedDataWatcher.Registry.get(Byte.class)), (byte) -1); - int mask = 0; - wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(27, WrappedDataWatcher.Registry.get(Byte.class)), (byte) mask); - setWrappedDataValue(metaPacket, wrappedDataWatcher); - return metaPacket; - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/ItemUtils.java b/plugin/src/main/java/net/momirealms/customcrops/util/ItemUtils.java deleted file mode 100644 index eb8e504..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/util/ItemUtils.java +++ /dev/null @@ -1,116 +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.util; - -import net.momirealms.customcrops.mechanic.item.factory.BukkitItemFactory; -import net.momirealms.customcrops.mechanic.item.factory.Item; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerItemDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; - -public class ItemUtils { - - public static void giveItem(Player player, ItemStack itemStack, int amount) { - PlayerInventory inventory = player.getInventory(); - ItemMeta meta = itemStack.getItemMeta(); - int maxStackSize = itemStack.getMaxStackSize(); - for (ItemStack other : inventory.getStorageContents()) { - if (other != null) { - if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) { - if (other.getAmount() < maxStackSize) { - int delta = maxStackSize - other.getAmount(); - if (amount > delta) { - other.setAmount(maxStackSize); - amount -= delta; - } else { - other.setAmount(amount + other.getAmount()); - return; - } - } - } - } - } - if (amount > 0) { - for (ItemStack other : inventory.getStorageContents()) { - if (other == null) { - if (amount > maxStackSize) { - amount -= maxStackSize; - ItemStack cloned = itemStack.clone(); - cloned.setAmount(maxStackSize); - inventory.addItem(cloned); - } else { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(amount); - inventory.addItem(cloned); - return; - } - } - } - } - if (amount > 0) { - for (int i = 0; i < amount / maxStackSize; i++) { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(maxStackSize); - player.getWorld().dropItem(player.getLocation(), cloned); - } - int left = amount % maxStackSize; - if (left != 0) { - ItemStack cloned = itemStack.clone(); - cloned.setAmount(left); - player.getWorld().dropItem(player.getLocation(), cloned); - } - } - } - - public static void increaseDurability(ItemStack itemStack, int amount) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - Item item = BukkitItemFactory.getInstance().wrap(itemStack.clone()); - int damage = Math.max(item.damage().orElse(0) - amount, 0); - item.damage(damage); - itemStack.setItemMeta(item.load().getItemMeta()); - } - - public static void decreaseDurability(Player player, ItemStack itemStack, int amount) { - if (itemStack == null || itemStack.getType() == Material.AIR) - return; - ItemMeta previousMeta = itemStack.getItemMeta().clone(); - PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(player, itemStack, amount, amount); - Bukkit.getPluginManager().callEvent(itemDamageEvent); - if (!itemStack.getItemMeta().equals(previousMeta) || itemDamageEvent.isCancelled()) { - return; - } - int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY); - if (Math.random() > (double) 1 / (unBreakingLevel + 1)) { - return; - } - Item item = BukkitItemFactory.getInstance().wrap(itemStack.clone()); - int damage = item.damage().orElse(0) + amount; - if (damage > item.maxDamage().orElse((int) itemStack.getType().getMaxDurability())) { - itemStack.setAmount(0); - } else { - item.damage(damage); - itemStack.setItemMeta(item.load().getItemMeta()); - } - } -} diff --git a/plugin/src/main/java/net/momirealms/customcrops/util/RotationUtils.java b/plugin/src/main/java/net/momirealms/customcrops/util/RotationUtils.java deleted file mode 100644 index 630876c..0000000 --- a/plugin/src/main/java/net/momirealms/customcrops/util/RotationUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) <2022> - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.momirealms.customcrops.util; - -import net.momirealms.customcrops.api.mechanic.misc.CRotation; -import org.bukkit.Rotation; - -import java.util.Random; - -public class RotationUtils { - - private static final Rotation[] rotationsI = {Rotation.NONE, Rotation.FLIPPED, Rotation.CLOCKWISE, Rotation.COUNTER_CLOCKWISE}; - private static final float[] rotationsF = {0f, 90f, 180f, -90f}; - - public static Rotation getRandomBukkitRotation() { - return rotationsI[new Random().nextInt(4)]; - } - - public static float getRandomFloatRotation() { - return rotationsF[new Random().nextInt(4)]; - } - - public static float getFloatRotation(CRotation cRotation) { - if (cRotation == CRotation.RANDOM) { - return getRandomFloatRotation(); - } - return cRotation.getYaw(); - } - - public static Rotation getBukkitRotation(CRotation cRotation) { - switch (cRotation) { - case RANDOM -> { - return getRandomBukkitRotation(); - } - case EAST -> { - return Rotation.COUNTER_CLOCKWISE; - } - case WEST -> { - return Rotation.CLOCKWISE; - } - case NORTH -> { - return Rotation.FLIPPED; - } - default -> { - return Rotation.NONE; - } - } - } -} diff --git a/plugin/src/main/resources/commands.yml b/plugin/src/main/resources/commands.yml new file mode 100644 index 0000000..4c1eba9 --- /dev/null +++ b/plugin/src/main/resources/commands.yml @@ -0,0 +1,38 @@ +# +# Don't change this +# +config-version: "${config_version}" + +# +# For safety reasons, editing this file requires a restart to apply +# + +# A command to reload the plugin +# Usage: [COMMAND] +reload: + enable: true + permission: customcrops.command.reload + usage: + - /customcrops reload + - /ccrops reload + +get_season: + enable: true + permission: customcrops.command.get_season + usage: + - /customcrops season get + - /ccrops season get + +set_season: + enable: true + permission: customcrops.command.set_season + usage: + - /customcrops season set + - /ccrops season set + +debug_data: + enable: true + permission: customcrops.command.debug + usage: + - /customcrops debug data + - /ccrops debug data diff --git a/plugin/src/main/resources/config.yml b/plugin/src/main/resources/config.yml index 9460b5e..cc585c9 100644 --- a/plugin/src/main/resources/config.yml +++ b/plugin/src/main/resources/config.yml @@ -1,30 +1,26 @@ # Don't change config-version: '37' - # Debug debug: false - # BStats metrics: true - # Check updates update-checker: true - -# Language -# https://github.com/Xiao-MoMi/Custom-Crops/tree/main/plugin/src/main/resources/messages -lang: en - +# Force locale, for instance zh_cn +force-locale: '' # World settings worlds: - # This is designed for servers that using an independent folder for worlds - # Especially for realm systems + # Some servers use separate folders to store player worlds, and these world directories + # are often not located in the server's root directory. This option allows users to read + # world data from a custom directory. It is only applicable to Bukkit worlds. absolute-world-folder-path: '' # A list of worlds that would decide where the plugin mechanisms take effect # Mode: whitelist/blacklist/regex mode: blacklist list: - - blacklist_world + - blacklist_world # A non-existent world settings: + # Default settings that apply to all worlds _DEFAULT_: # Whether to enable the plugin's pre-made system # Disabling this option will make all mechanisms stop counting ticks unless you have full control over it using the API. @@ -37,10 +33,9 @@ worlds: auto-alternation: true # Game days of each season duration: 28 - # Random tick speed # It's different from the vanilla RandomTickSpeed. - # CustomCrops' random tick speed has a larger base value - # So we can have more precise control on crops' growth speed + # "Random tick" here refers to the process of randomly selecting n blocks within a 16x16x16 section every second to perform a tick while Minecraft do that every tick. + # Therefore, the random tick of CustomCrops has little impact on the server, and it is performed on multiple threads. random-tick-speed: 20 # The smallest tick unit in seconds used in scheduled tick mode # 300s means that a crop would be certainly ticked once in 300s @@ -66,6 +61,7 @@ worlds: # Scheduler mode provides more reliable growth schedule management # which allows crops to grow at almost the same speed mode: RANDOM_TICK + # The tick-interval determines how many times a block is ticked before its tick logic is actually executed. tick-interval: 1 # Limit the max amount of crops in one chunk (-1 = no limit) max-per-chunk: -1 @@ -74,14 +70,12 @@ worlds: # RANDOM_TICK / SCHEDULED_TICK mode: SCHEDULED_TICK tick-interval: 3 - # Limit the max amount of pots in one chunk (-1 = no limit) max-per-chunk: -1 # Settings for sprinklers sprinkler: # RANDOM_TICK / SCHEDULED_TICK mode: SCHEDULED_TICK tick-interval: 2 - # Limit the max amount of sprinklers in one chunk (-1 = no limit) max-per-chunk: -1 # You can override the default settings for worlds here _WORLDS_: @@ -89,7 +83,6 @@ worlds: enable: false world_the_end: enable: false - # Mechanics settings mechanics: # You can create more ranks by adding more "/" for instance x/x/x/x/x @@ -97,29 +90,26 @@ mechanics: # 17/2/1 = 85%/10%/5% # 85% = 17/(17+2+1) * 100% default-quality-ratio: 17/2/1 - # Scarecrow prevents crops from being attacked by crows scarecrow: enable: true id: '{0}scarecrow' type: ITEM_FRAME range: 7 - # If this option is enabled, the range above would not longer take effect - # This option would make the scarecrow protect all the crops in a chunk instead of crops in certain range + # If this option is enabled, the range would no longer take effect + # This option would make it protect all the crops in the same chunk protect-chunk: false - # Greenhouse glass prevents crops from withering from season changing greenhouse: enable: true + # You can use a list of ids here, vanilla blocks are supported too id: '{0}greenhouse_glass' type: CHORUS range: 5 - # Sync seasons sync-season: enable: false reference: world - # Vanilla farmland settings vanilla-farmland: # Disable vanilla farmland moisture mechanics @@ -127,29 +117,19 @@ mechanics: disable-moisture-mechanic: false # Prevent entities from trampling the farmland prevent-trampling: false - other-settings: # It's recommended to use MiniMessage format. If you insist on using legacy color code "&", enable the support below. # Disable this would improve performance legacy-color-code-support: true - # Requires PlaceholderAPI to work placeholder-register: '{skill-level}': '%levelplugin_farming%' - - # Thread pool settings - thread-pool-settings: - corePoolSize: 10 - maximumPoolSize: 10 - keepAliveTime: 30 - # Using items from other plugins item-detection-order: [] - # Whether to protect the original lore of the item # This uses the scoreboard component to identify the plugin's lore, # which may conflict with some plugins that still use SpigotAPI#ItemMeta. protect-original-lore: false - - # Should the plugin try to convert worlds from 3.3 to 3.4 when WorldLoadEvent is triggered - convert-on-world-load: false \ No newline at end of file + # Whether to check if the block/furniture corresponds with the one in CustomCrops data + # You can enable this if you are using Oraxen as its API is more reliable + double-check: false \ No newline at end of file diff --git a/plugin/src/main/resources/contents/crops/default.yml b/plugin/src/main/resources/contents/crops/default.yml index 1bc252b..b8ff752 100644 --- a/plugin/src/main/resources/contents/crops/default.yml +++ b/plugin/src/main/resources/contents/crops/default.yml @@ -215,14 +215,17 @@ tomato: crop: tomato # Custom grow conditions grow-conditions: - season_condition: - type: suitable_season - value: - - Spring - - Autumn - water_condition: - type: water_more_than - value: 0 + default: + point: 1 + conditions: + season_condition: + type: suitable_season + value: + - Spring + - Autumn + water_condition: + type: water_more_than + value: 0 # Custom death conditions death-conditions: no_water: diff --git a/plugin/src/main/resources/contents/pots/default.yml b/plugin/src/main/resources/contents/pots/default.yml index aef4cdf..8b95198 100644 --- a/plugin/src/main/resources/contents/pots/default.yml +++ b/plugin/src/main/resources/contents/pots/default.yml @@ -13,20 +13,19 @@ default: absorb-nearby-water: false # Set unique looks for pots with different fertilizer statuses fertilized-pots: - enable: false quality: dry: {0}dry_pot wet: {0}wet_pot - yield-increase: + yield_increase: dry: {0}dry_pot wet: {0}wet_pot variation: dry: {0}dry_pot wet: {0}wet_pot - soil-retain: + soil_retain: dry: {0}dry_pot wet: {0}wet_pot - speed-grow: + speed_grow: dry: {0}dry_pot wet: {0}wet_pot # Methods to fill the watering can diff --git a/plugin/src/main/resources/messages/en.yml b/plugin/src/main/resources/messages/en.yml deleted file mode 100644 index 6f4ce57..0000000 --- a/plugin/src/main/resources/messages/en.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: 'Reloaded! Took {time}ms.' - spring: 'Spring' - summer: 'Summer' - autumn: 'Autumn' - winter: 'Winter' - no-season: 'Season Disabled' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/es.yml b/plugin/src/main/resources/messages/es.yml deleted file mode 100644 index a638c34..0000000 --- a/plugin/src/main/resources/messages/es.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: '¡Recargado! Tardó {time}ms.' - spring: 'Primavera' - summer: 'Verano' - autumn: 'Otoño' - winter: 'Invierno' - no-season: 'LAS ESTACIONES ESTÁN DESACTIVADAS EN ESTE MUNDO' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/fr.yml b/plugin/src/main/resources/messages/fr.yml deleted file mode 100644 index e7dc40c..0000000 --- a/plugin/src/main/resources/messages/fr.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: 'Rechargé ! A pris {time}ms.' - spring: 'Printemps' - summer: 'Été' - autumn: 'Automne' - winter: 'Hiver' - no-season: 'SAISON DÉSACTIVÉE DANS CE MONDE' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/pt.yml b/plugin/src/main/resources/messages/pt.yml deleted file mode 100644 index 7353c8b..0000000 --- a/plugin/src/main/resources/messages/pt.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: 'Recarregado! Levou {time}ms.' - spring: 'Primavera' - summer: 'Verão' - autumn: 'Outono' - winter: 'Inverno' - no-season: 'Estação Desativada' diff --git a/plugin/src/main/resources/messages/ru.yml b/plugin/src/main/resources/messages/ru.yml deleted file mode 100644 index c15e60c..0000000 --- a/plugin/src/main/resources/messages/ru.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: 'Перезагружено! Заняло {time}мс.' - spring: 'Весна' - summer: 'Лето' - autumn: 'Осень' - winter: 'Зима' - no-season: 'СЕЗОНЫ ОТКЛЮЧЕНЫ В ЭТОМ МИРЕ' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/zh_cn.yml b/plugin/src/main/resources/messages/zh_cn.yml deleted file mode 100644 index 3fef4d4..0000000 --- a/plugin/src/main/resources/messages/zh_cn.yml +++ /dev/null @@ -1,8 +0,0 @@ -messages: - prefix: '[CustomCrops] ' - reload: '重载完成! 耗时 {time}ms.' - spring: '春季' - summer: '夏季' - autumn: '秋季' - winter: '冬季' - no-season: '未启用季节' \ No newline at end of file diff --git a/plugin/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml index d16b5ca..7ff6f5e 100644 --- a/plugin/src/main/resources/plugin.yml +++ b/plugin/src/main/resources/plugin.yml @@ -1,12 +1,10 @@ name: CustomCrops -version: '${version}' -main: net.momirealms.customcrops.CustomCropsPluginImpl +version: '${project_version}' +main: net.momirealms.customcrops.bukkit.BukkitBootstrap api-version: 1.17 load: POSTWORLD authors: [ XiaoMoMi ] folia-supported: true -depend: - - ProtocolLib softdepend: - Vault - ItemsAdder @@ -18,7 +16,7 @@ softdepend: - RealisticSeasons - AdvancedSeasons - SlimeWorldManager - - SlimeWorldPlugin + - MythicMobs - HuskClaims - HuskTowns - Residence @@ -29,7 +27,6 @@ softdepend: - GriefPrevention - BentoBox - IridiumSkyblock - - MythicMobs - KingdomsX - Landlord - Lands diff --git a/plugin/src/main/resources/translations/en.yml b/plugin/src/main/resources/translations/en.yml new file mode 100644 index 0000000..5d09ab1 --- /dev/null +++ b/plugin/src/main/resources/translations/en.yml @@ -0,0 +1,46 @@ +# Don"t change this +config-version: "37" + +exception.invalid_syntax: "Invalid syntax. Correct syntax: " +exception.invalid_argument: "Invalid argument. Reason: " +exception.invalid_sender: " is not allowed to execute that command. Must be of type " +exception.unexpected: "An internal error occurred while attempting to perform this command" +exception.no_permission: "I'm sorry, but you do not have permission to perform this command" +exception.no_such_command: "Unknown command." +argument.entity.notfound.player: "" +argument.entity.notfound.entity: "" +argument.parse.failure.time: "'' is not a valid time format" +argument.parse.failure.material: "'' is not a valid material name" +argument.parse.failure.enchantment: "'' is not a valid enchantment" +argument.parse.failure.offlineplayer: "No player found for input ''" +argument.parse.failure.player: "No player found for input ''" +argument.parse.failure.world: "'' is not a valid Minecraft world" +argument.parse.failure.location.invalid_format: "'' is not a valid location. Required format is ' " +argument.parse.failure.location.mixed_local_absolute: "Cannot mix local and absolute coordinates. (either all coordinates use '^' or none do)" +argument.parse.failure.namespacedkey.namespace: "Invalid namespace ''. Must be [a-z0-9._-]" +argument.parse.failure.namespacedkey.key: "Invalid key ''. Must be [a-z0-9/._-]" +argument.parse.failure.namespacedkey.need_namespace: "Invalid input '', requires an explicit namespace" +argument.parse.failure.boolean: "Could not parse boolean from ''" +argument.parse.failure.number: "'' is not a valid number in the range to " +argument.parse.failure.char: "'' is not a valid character" +argument.parse.failure.string: "'' is not a valid string of type " +argument.parse.failure.uuid: "'' is not a valid UUID" +argument.parse.failure.enum: "'' is not one of the following: " +argument.parse.failure.regex: "'' does not match ''" +argument.parse.failure.flag.unknown: "Unknown flag ''" +argument.parse.failure.flag.duplicate_flag: "Duplicate flag ''" +argument.parse.failure.flag.no_flag_started: "No flag started. Don't know what to do with ''" +argument.parse.failure.flag.missing_argument: "Missing argument for ''" +argument.parse.failure.flag.no_permission: "You don't have permission to use ''" +argument.parse.failure.color: "'' is not a valid color" +argument.parse.failure.duration: "'' is not a duration format" +argument.parse.failure.aggregate.missing: "Missing component ''" +argument.parse.failure.aggregate.failure: "Invalid component '': " +argument.parse.failure.either: "Could not resolve or from ''" +argument.parse.failure.namedtextcolor: "'' is not a named text color" +command.reload.success: "Reloaded. Took ms." +season.spring: "Spring" +season.summer: "Summer" +season.autumn: "Autumn" +season.winter: "Winter" +season.disable: "Disable" \ No newline at end of file diff --git a/plugin/src/main/resources/translations/zh_cn.yml b/plugin/src/main/resources/translations/zh_cn.yml new file mode 100644 index 0000000..acb9446 --- /dev/null +++ b/plugin/src/main/resources/translations/zh_cn.yml @@ -0,0 +1,46 @@ +# 别动这个 +config-version: "37" + +exception.invalid_syntax: "无效语法. 正确语法:" +exception.invalid_argument: "无效参数. 原因:" +exception.invalid_sender: " 不允许执行该命令. 执行者必须是 " +exception.unexpected: "执行该命令时发生内部错误" +exception.no_permission: "抱歉, 您没有权限执行该命令" +exception.no_such_command: "未知命令" +argument.entity.notfound.player: "找不到玩家 ''" +argument.entity.notfound.entity: "找不到实体 ''" +argument.parse.failure.time: "'' 不是有效的时间格式" +argument.parse.failure.material: "'' 不是有效的材料" +argument.parse.failure.enchantment: "'' 不是有效的魔咒" +argument.parse.failure.offlineplayer: "输入的玩家 '' 已离线" +argument.parse.failure.player: "找不到输入的玩家 ''" +argument.parse.failure.world: "'' 不是有效的 Minecraft 世界名称" +argument.parse.failure.location.invalid_format: "'' 不是有效的位置格式.必须格式为 ' '" +argument.parse.failure.location.mixed_local_absolute: "不能混用相对和绝对坐标.坐标要么全部使用 '^',要么全部不用" +argument.parse.failure.namespacedkey.namespace: "无效的命名空间 ''.必须为 [a-z0-9._-]" +argument.parse.failure.namespacedkey.key: "无效的键 ''.必须为 [a-z0-9/._-]" +argument.parse.failure.namespacedkey.need_namespace: "无效的输入 '', 需要显式指定命名空间" +argument.parse.failure.boolean: "无法解析布尔值 ''" +argument.parse.failure.number: "'' 不是从 范围内的有效数字" +argument.parse.failure.char: "'' 不是有效的字符" +argument.parse.failure.string: "'' 不是类型为 的有效字符串" +argument.parse.failure.uuid: "'' 不是有效的 UUID" +argument.parse.failure.enum: "'' 不是以下任何一种情况之一: " +argument.parse.failure.regex: "'' 不匹配 ''" +argument.parse.failure.flag.unknown: "未知标志 ''" +argument.parse.failure.flag.duplicate_flag: "重复的标志 ''" +argument.parse.failure.flag.no_flag_started: "没有开始标志. 不知道如何处理 ''" +argument.parse.failure.flag.missing_argument: "缺少 '' 参数" +argument.parse.failure.flag.no_permission: "您没有权限使用 ''" +argument.parse.failure.color: "'' 不是有效的颜色" +argument.parse.failure.duration: "'' 不是有效的持续时间格式" +argument.parse.failure.aggregate.missing: "缺少组件 ''" +argument.parse.failure.aggregate.failure: "无效的组件 '': " +argument.parse.failure.either: "无法从 '' 解析 " +argument.parse.failure.namedtextcolor: "'' 不是颜色代码" +command.reload.success: "重新加载完成.耗时 毫秒译者:jhqwqmc" +season.spring: "春" +season.summer: "夏" +season.autumn: "秋" +season.winter: "冬" +season.disable: "未启用" \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..760b8fd --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +rootProject.name = "CustomCrops" +include(":common") +include(":api") +include(":plugin") +include(":compatibility") +include(":compatibility-asp-r1") +include(":compatibility-oraxen-r1") +include(":compatibility-oraxen-r2") +include(":compatibility-itemsadder-r1") + + +