mirror of
https://github.com/Xiao-MoMi/Custom-Crops.git
synced 2025-12-19 15:09:25 +00:00
3.6
This commit is contained in:
50
README.md
50
README.md
@@ -1,42 +1,38 @@
|
||||
# Note: This project is undergoing a major refactoring. Please switch to the 3.6-dev branch for the new API.
|
||||
|
||||
# Custom-Crops
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/Xiao-MoMi/Custom-Crops/)
|
||||

|
||||
[](https://jitpack.io/#Xiao-MoMi/Custom-Crops)
|
||||
<a href="https://mo-mi.gitbook.io/xiaomomi-plugins/plugin-wiki/customcrops" alt="GitBook">
|
||||
<img src="https://img.shields.io/badge/docs-gitbook-brightgreen" alt="Gitbook"/>
|
||||
</a>
|
||||
[](https://github.com/Xiao-MoMi/Custom-Crops/)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Ultra-customizable planting experience for Minecraft servers
|
||||
|
||||
### Support the developer
|
||||
|
||||
https://afdian.net/@xiaomomi
|
||||
|
||||
https://polymart.org/resource/customcrops.2625
|
||||
CustomCrops is a Paper plugin crafted to deliver an exceptional planting experience for Minecraft servers, with a strong emphasis on customization and performance. It employs Zstd compression for data serialization, ensuring high efficiency comparable to Minecraft's own serialization techniques. The plugin optimizes server performance by running its tick system across multiple threads, reverting to the main thread only when required. Additionally, CustomCrops offers a comprehensive API that enables developers to create custom block mechanics with specific interaction and tick behaviors, such as a fish trap block that periodically provides players with fish.
|
||||
|
||||
## How to build
|
||||
|
||||
### Windows
|
||||
|
||||
#### Command Line
|
||||
Install JDK 17 and set the JDK installation path to JAVA_HOME as an environment variable.\
|
||||
Start powershell and change directory to the project folder.\
|
||||
Execute ".\gradlew build" and get the jar at /target/CustomCrops-plugin-version.jar.
|
||||
Install JDK 17 & 21. \
|
||||
Start terminal and change directory to the project folder.\
|
||||
Execute ".\gradlew build" and get the artifact under /target folder
|
||||
|
||||
#### IDE
|
||||
Import the project and execute gradle build action.
|
||||
Import the project and execute gradle build action. \
|
||||
Get the artifact under /target folder
|
||||
|
||||
## Use CustomCrops API
|
||||
## Support the developer
|
||||
|
||||
Polymart: https://polymart.org/resource/customfishing.2723 \
|
||||
Afdian: https://afdian.net/@xiaomomi
|
||||
|
||||
## CustomCrops API
|
||||
|
||||
### Maven
|
||||
|
||||
```
|
||||
```html
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack</id>
|
||||
@@ -44,7 +40,7 @@ Import the project and execute gradle build action.
|
||||
</repository>
|
||||
</repositories>
|
||||
```
|
||||
```
|
||||
```html
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.Xiao-MoMi</groupId>
|
||||
@@ -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}")
|
||||
}
|
||||
|
||||
@@ -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<JavaCompile> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
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<MavenPublication>("mavenJava") {
|
||||
groupId = "net.momirealms"
|
||||
artifactId = "CustomCrops"
|
||||
version = rootProject.version.toString()
|
||||
artifact(tasks.shadowJar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Location, World> 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<BukkitCustomCropsPlugin, CommandSender> senderFactory;
|
||||
|
||||
protected final Map<Class<?>, ActionManager<?>> actionManagers = new HashMap<>();
|
||||
protected final Map<Class<?>, 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<Location, World> getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public SenderFactory<BukkitCustomCropsPlugin, CommandSender> 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 <T> ActionManager<T> getActionManager(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type cannot be null");
|
||||
}
|
||||
return (ActionManager<T>) actionManagers.get(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> RequirementManager<T> getRequirementManager(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type cannot be null");
|
||||
}
|
||||
return (RequirementManager<T>) instance.requirementManagers.get(type);
|
||||
}
|
||||
|
||||
public CoolDownManager getCoolDownManager() {
|
||||
return coolDownManager;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -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<T> implements ActionManager<T> {
|
||||
|
||||
protected final BukkitCustomCropsPlugin plugin;
|
||||
private final HashMap<String, ActionFactory<T>> 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<T> 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<T> getActionFactory(@NotNull String type) {
|
||||
return actionFactoryMap.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<T> parseAction(Section section) {
|
||||
if (section == null) return Action.empty();
|
||||
ActionFactory<T> 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<T>[] parseActions(Section section) {
|
||||
ArrayList<Action<T>> actionList = new ArrayList<>();
|
||||
if (section != null)
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
Action<T> action = parseAction(innerSection);
|
||||
if (action != null)
|
||||
actionList.add(action);
|
||||
}
|
||||
}
|
||||
return actionList.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<T> parseAction(@NotNull String type, @NotNull Object args) {
|
||||
ActionFactory<T> 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<T> tClass) {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
if (!expansionFolder.exists())
|
||||
expansionFolder.mkdirs();
|
||||
|
||||
List<Class<? extends ActionExpansion<T>>> classes = new ArrayList<>();
|
||||
File[] expansionJars = expansionFolder.listFiles();
|
||||
if (expansionJars == null) return;
|
||||
for (File expansionJar : expansionJars) {
|
||||
if (expansionJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
Class<? extends ActionExpansion<T>> expansionClass = (Class<? extends ActionExpansion<T>>) ClassUtils.findClass(expansionJar, ActionExpansion.class, tClass);
|
||||
classes.add(expansionClass);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (Class<? extends ActionExpansion<T>> expansionClass : classes) {
|
||||
ActionExpansion<T> 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<String> messages = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer offlinePlayer = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
offlinePlayer = player;
|
||||
}
|
||||
List<String> 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<String> messages = ListUtils.toList(section.get("message"));
|
||||
MathValue<T> 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<String> 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<T> 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<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
List<String> 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<String> 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<String> cmd = ListUtils.toList(section.get("command"));
|
||||
MathValue<T> 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<String> 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<T> tClass) {
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> actions = new ArrayList<>();
|
||||
if (args instanceof Section section) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
actions.add(parseAction(innerSection));
|
||||
}
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
};
|
||||
}, "chain");
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> 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<String, Object> 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<T> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
plugin.getScheduler().sync().runLater(() -> {
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay, location);
|
||||
}
|
||||
};
|
||||
}, "delay");
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> 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<String, Object> 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<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
}, delay * 50L, period * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
task = plugin.getScheduler().sync().runRepeating(() -> {
|
||||
for (Action<T> 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<T>[] actions = parseActions(section.getSection("actions"));
|
||||
Requirement<T>[] requirements = plugin.getRequirementManager(tClass).parseRequirements(section.getSection("conditions"), true);
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Requirement<T> requirement : requirements) {
|
||||
if (!requirement.isSatisfied(condition)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Action<T> 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<Pair<Requirement<T>[], Action<T>[]>> conditionActionPairList = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
Action<T>[] actions = parseActions(inner.getSection("actions"));
|
||||
Requirement<T>[] 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<Requirement<T>[], Action<T>[]> pair : conditionActionPairList) {
|
||||
if (pair.left() != null)
|
||||
for (Requirement<T> requirement : pair.left()) {
|
||||
if (!requirement.isSatisfied(context)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (pair.right() != null)
|
||||
for (Action<T> 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<T> title = TextValue.auto(section.getString("title"));
|
||||
TextValue<T> 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<T> min = MathValue.auto(section.get("min"));
|
||||
MathValue<T> 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<CustomCropsWorld<?>> 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<CustomCropsChunk> chunk = world.get().getChunk(potLocation.toChunkPos());
|
||||
if (chunk.isPresent()) {
|
||||
Optional<CustomCropsBlockState> state = chunk.get().getBlockState(potLocation);
|
||||
if (state.isPresent()) {
|
||||
if (state.get().type() instanceof PotBlock potBlock) {
|
||||
fertilizers = potBlock.fertilizers(state.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayList<FertilizerConfig> 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<T> min = MathValue.auto(section.get("min"));
|
||||
MathValue<T> 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<CustomCropsWorld<?>> 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<CustomCropsChunk> chunk = world.get().getChunk(potLocation.toChunkPos());
|
||||
if (chunk.isPresent()) {
|
||||
Optional<CustomCropsBlockState> state = chunk.get().getBlockState(potLocation);
|
||||
if (state.isPresent()) {
|
||||
if (state.get().type() instanceof PotBlock potBlock) {
|
||||
fertilizers = potBlock.fertilizers(state.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayList<FertilizerConfig> 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<Action<T>> actions = new ArrayList<>();
|
||||
Section otherItemSection = section.getSection("other-items");
|
||||
if (otherItemSection != null) {
|
||||
for (Map.Entry<String, Object> 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<T> 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<T> duration = MathValue.auto(section.get("duration", 20));
|
||||
boolean position = !section.getString("position", "player").equals("player");
|
||||
MathValue<T> x = MathValue.auto(section.get("x", 0));
|
||||
MathValue<T> y = MathValue.auto(section.get("y", 0));
|
||||
MathValue<T> z = MathValue.auto(section.get("z", 0));
|
||||
MathValue<T> 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<Player> 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<CustomCropsWorld<?>> 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<Player>) 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<CustomCropsWorld<?>> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld());
|
||||
if (optionalWorld.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
CustomCropsWorld<?> world = optionalWorld.get();
|
||||
Optional<CustomCropsBlockState> 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");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type of the object that is used in the context for triggering the action.
|
||||
*/
|
||||
public interface Action<T> {
|
||||
|
||||
/**
|
||||
* 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<T> context);
|
||||
|
||||
static <T> Action<T> empty() {
|
||||
return EmptyAction.instance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type parameter for the action factory
|
||||
*/
|
||||
public abstract class ActionExpansion<T> {
|
||||
|
||||
/**
|
||||
* 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<T> getActionFactory();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type of object that the action will operate on
|
||||
*/
|
||||
public interface ActionFactory<T> {
|
||||
|
||||
/**
|
||||
* 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<T> process(Object args, double chance);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type of the context in which the actions are triggered.
|
||||
*/
|
||||
public interface ActionManager<T> 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<T> 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<T> 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<T> 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<T>[] 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<T> 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<ActionTrigger, Action<T>[]> parseEventActions(Section section) {
|
||||
HashMap<ActionTrigger, Action<T>[]> actionMap = new HashMap<>();
|
||||
for (Map.Entry<String, Object> 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<Integer, Action<T>[]> parseTimesActions(Section section) {
|
||||
TreeMap<Integer, Action<T>[]> actionMap = new TreeMap<>();
|
||||
for (Map.Entry<String, Object> 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 <T> void trigger(@NotNull Context<T> context, @Nullable List<Action<T>> actions) {
|
||||
if (actions != null)
|
||||
for (Action<T> 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 <T> void trigger(@NotNull Context<T> context, @Nullable Action<T>[] actions) {
|
||||
if (actions != null)
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<T> implements Action<T> {
|
||||
|
||||
public static EmptyCondition instance = new EmptyCondition();
|
||||
public static <T> EmptyAction<T> instance() {
|
||||
return new EmptyAction<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConditionMet(CustomCropsBlock block, boolean offline) {
|
||||
return true;
|
||||
public void trigger(Context<T> context) {
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.common;
|
||||
|
||||
public class Tuple<L, M, R> {
|
||||
|
||||
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 <L, M, R> Tuple<L, M, R> 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;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.common.item;
|
||||
|
||||
public interface KeyItem {
|
||||
|
||||
/**
|
||||
* Get item's key
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
String getKey();
|
||||
}
|
||||
@@ -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<T> implements Context<T> {
|
||||
|
||||
private final T holder;
|
||||
private final Map<ContextKeys<?>, Object> args;
|
||||
private final Map<String, String> 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<ContextKeys<?>, Object> args() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> placeholderMap() {
|
||||
return placeholderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> AbstractContext<T> arg(ContextKeys<C> 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<T> combine(Context<T> other) {
|
||||
this.args.putAll(other.args());
|
||||
this.placeholderMap.putAll(other.placeholderMap());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C> C arg(ContextKeys<C> key) {
|
||||
return (C) args.get(key);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <C> C remove(ContextKeys<C> key) {
|
||||
placeholderMap.remove("{" + key.key() + "}");
|
||||
return (C) args.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T holder() {
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
@@ -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<CustomCropsBlockState> {
|
||||
|
||||
public BlockContextImpl(@NotNull CustomCropsBlockState block, boolean sync) {
|
||||
super(block, sync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlockContext{" +
|
||||
"args=" + args() +
|
||||
", block=" + holder() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type of the holder object for this context
|
||||
*/
|
||||
public interface Context<T> {
|
||||
|
||||
/**
|
||||
* 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<ContextKeys<?>, Object> args();
|
||||
|
||||
/**
|
||||
* Converts the context to a map of placeholders
|
||||
*
|
||||
* @return a map of placeholders
|
||||
*/
|
||||
Map<String, String> 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 <C> 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.
|
||||
*/
|
||||
<C> Context<T> arg(ContextKeys<C> key, C value);
|
||||
|
||||
/**
|
||||
* Combines one context with another
|
||||
*
|
||||
* @param other other
|
||||
* @return this context
|
||||
*/
|
||||
Context<T> combine(Context<T> other);
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specific argument from the context.
|
||||
* This method fetches the value associated with the specified ContextKeys key.
|
||||
*
|
||||
* @param <C> 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> C arg(ContextKeys<C> key);
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specific argument from the context.
|
||||
* This method fetches the value associated with the specified ContextKeys key.
|
||||
*
|
||||
* @param <C> 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> C argOrDefault(ContextKeys<C> 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 <C> the type of the value being removed.
|
||||
*/
|
||||
@Nullable
|
||||
<C> C remove(ContextKeys<C> 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> 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<CustomCropsBlockState> 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> 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<CustomCropsBlockState> block(@NotNull CustomCropsBlockState block, boolean threadSafe) {
|
||||
return new BlockContextImpl(block, threadSafe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <T> the type of the value associated with the context key.
|
||||
*/
|
||||
public class ContextKeys<T> {
|
||||
|
||||
public static final ContextKeys<Location> LOCATION = of("location", Location.class);
|
||||
public static final ContextKeys<Integer> X = of("x", Integer.class);
|
||||
public static final ContextKeys<Integer> Y = of("y", Integer.class);
|
||||
public static final ContextKeys<Integer> Z = of("z", Integer.class);
|
||||
public static final ContextKeys<String> WORLD = of("world", String.class);
|
||||
public static final ContextKeys<String> PLAYER = of("player", String.class);
|
||||
public static final ContextKeys<EquipmentSlot> SLOT = of("slot", EquipmentSlot.class);
|
||||
public static final ContextKeys<String> TEMP_NEAR_PLAYER = of("near", String.class);
|
||||
public static final ContextKeys<Boolean> OFFLINE = of("offline", Boolean.class);
|
||||
|
||||
private final String key;
|
||||
private final Class<T> type;
|
||||
|
||||
protected ContextKeys(String key, Class<T> 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<T> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context key.
|
||||
*
|
||||
* @param key the key.
|
||||
* @param type the type.
|
||||
* @param <T> the type of the value.
|
||||
* @return a new ContextKeys instance.
|
||||
*/
|
||||
public static <T> ContextKeys<T> of(String key, Class<T> type) {
|
||||
return new ContextKeys<T>(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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player> {
|
||||
|
||||
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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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<EntityType> entities = new HashSet<>();
|
||||
private final HashSet<Material> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
|
||||
public class ClearableMappedRegistry<K, T> extends MappedRegistry<K, T> implements ClearableRegistry<K, T> {
|
||||
|
||||
public ClearableMappedRegistry(Key key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.byID.clear();
|
||||
super.byKey.clear();
|
||||
super.byValue.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public interface ClearableRegistry<K, T> extends WriteableRegistry<K, T> {
|
||||
|
||||
void clear();
|
||||
}
|
||||
@@ -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<String> scarecrow;
|
||||
protected ExistenceForm scarecrowExistenceForm;
|
||||
protected boolean enableScarecrow;
|
||||
protected boolean protectOriginalLore;
|
||||
protected int scarecrowRange;
|
||||
protected boolean scarecrowProtectChunk;
|
||||
|
||||
protected Set<String> 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<String> greenhouse() {
|
||||
return instance.greenhouse;
|
||||
}
|
||||
|
||||
public static ExistenceForm scarecrowExistenceForm() {
|
||||
return instance.scarecrowExistenceForm;
|
||||
}
|
||||
|
||||
public static boolean doubleCheck() {
|
||||
return instance.doubleCheck;
|
||||
}
|
||||
|
||||
public static Set<String> 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<WateringMethod> methods = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<FillMethod> methods = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<Integer, Integer> getInt2IntMap(Section section) {
|
||||
HashMap<Integer, Integer> map = new HashMap<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<Pair<Double, Integer>> getIntChancePair(Section section) {
|
||||
ArrayList<Pair<Double, Integer>> pairs = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
int point = Integer.parseInt(entry.getKey());
|
||||
if (entry.getValue() instanceof Number n) {
|
||||
Pair<Double, Integer> pair = new Pair<>(n.doubleValue(), point);
|
||||
pairs.add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
public HashMap<FertilizerType, Pair<String, String>> getFertilizedPotMap(Section section) {
|
||||
HashMap<FertilizerType, Pair<String, String>> map = new HashMap<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<BoneMeal> boneMeals = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<DeathCondition> conditions = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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<GrowCondition> conditions = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> 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("<font:customcrops:default>", "<font:minecraft:customcrops>");
|
||||
}
|
||||
writer.write(finalStr.replace("{0}", hasNamespace ? "customcrops:" : ""));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public enum ExistenceForm {
|
||||
|
||||
BLOCK,
|
||||
FURNITURE,
|
||||
ANY
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IdMap<T> extends Iterable<T> {
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public enum InteractionResult {
|
||||
|
||||
SUCCESS,
|
||||
FAIL,
|
||||
PASS
|
||||
}
|
||||
@@ -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<ItemStack> wrap(ItemStack itemStack);
|
||||
|
||||
void decreaseDamage(Player player, ItemStack itemStack, int amount);
|
||||
|
||||
void increaseDamage(Player holder, ItemStack itemStack, int amount);
|
||||
}
|
||||
@@ -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<K, T> implements WriteableRegistry<K, T> {
|
||||
|
||||
protected final Map<K, T> byKey = new HashMap<>(1024);
|
||||
protected final Map<T, K> byValue = new IdentityHashMap<>(1024);
|
||||
protected final ArrayList<T> 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<T> iterator() {
|
||||
return this.byKey.values().iterator();
|
||||
}
|
||||
}
|
||||
@@ -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<Key, CustomCropsBlock> BLOCK = new MappedRegistry<>(Key.key("mechanic", "block"));
|
||||
@DoNotUse
|
||||
public static final WriteableRegistry<Key, CustomCropsItem> ITEM = new MappedRegistry<>(Key.key("mechanic", "item"));
|
||||
@DoNotUse
|
||||
public static final WriteableRegistry<String, FertilizerType> FERTILIZER_TYPE = new ClearableMappedRegistry<>(Key.key("mechanic", "fertilizer_type"));
|
||||
@DoNotUse
|
||||
public static final ClearableRegistry<String, CustomCropsBlock> BLOCKS = new ClearableMappedRegistry<>(Key.key("internal", "blocks"));
|
||||
@DoNotUse
|
||||
public static final ClearableRegistry<String, CustomCropsItem> ITEMS = new ClearableMappedRegistry<>(Key.key("internal", "items"));
|
||||
|
||||
public static final ClearableRegistry<String, SprinklerConfig> SPRINKLER = new ClearableMappedRegistry<>(Key.key("config", "sprinkler"));
|
||||
public static final ClearableRegistry<String, PotConfig> POT = new ClearableMappedRegistry<>(Key.key("config", "pot"));
|
||||
public static final ClearableRegistry<String, CropConfig> CROP = new ClearableMappedRegistry<>(Key.key("config", "crop"));
|
||||
public static final ClearableRegistry<String, FertilizerConfig> FERTILIZER = new ClearableMappedRegistry<>(Key.key("config", "fertilizer"));
|
||||
public static final ClearableRegistry<String, WateringCanConfig> WATERING_CAN = new ClearableMappedRegistry<>(Key.key("config", "watering_can"));
|
||||
|
||||
public static final ClearableRegistry<String, CropConfig> SEED_TO_CROP = new ClearableMappedRegistry<>(Key.key("fast_lookup", "seed_to_crop"));
|
||||
public static final ClearableRegistry<String, List<CropConfig>> STAGE_TO_CROP_UNSAFE = new ClearableMappedRegistry<>(Key.key("fast_lookup", "stage_to_crop"));
|
||||
public static final ClearableRegistry<String, FertilizerConfig> ITEM_TO_FERTILIZER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_fertilizer"));
|
||||
public static final ClearableRegistry<String, PotConfig> ITEM_TO_POT = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_pot"));
|
||||
public static final ClearableRegistry<String, SprinklerConfig> ITEM_TO_SPRINKLER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_sprinkler"));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface Registry<K, T> extends IdMap<T> {
|
||||
|
||||
Key key();
|
||||
|
||||
@Override
|
||||
int getId(@Nullable T value);
|
||||
|
||||
@Nullable
|
||||
T get(@Nullable K key);
|
||||
}
|
||||
@@ -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<Key, CustomCropsBlock> getBlockRegistry();
|
||||
|
||||
Registry<Key, CustomCropsItem> getItemRegistry();
|
||||
|
||||
Registry<String, FertilizerType> getFertilizerTypeRegistry();
|
||||
}
|
||||
@@ -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<Key, CustomCropsBlock> getBlockRegistry() {
|
||||
return Registries.BLOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<Key, CustomCropsItem> getItemRegistry() {
|
||||
return Registries.ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<String, FertilizerType> getFertilizerTypeRegistry() {
|
||||
return Registries.FERTILIZER_TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StatedItem<T> {
|
||||
|
||||
String currentState(Context<T> context);
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public interface WriteableRegistry<K, T> extends Registry<K, T> {
|
||||
|
||||
void register(K key, T value);
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -15,67 +15,53 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Pair<Double, Integer>> pointGainList;
|
||||
private final Action[] actions;
|
||||
private final Action<Player>[] actions;
|
||||
private final boolean dispenserAllowed;
|
||||
|
||||
public BoneMeal(
|
||||
String item,
|
||||
int usedAmount,
|
||||
int requiredAmount,
|
||||
String returned,
|
||||
int returnedAmount,
|
||||
boolean dispenserAllowed,
|
||||
List<Pair<Double, Integer>> pointGainList,
|
||||
Action[] actions
|
||||
Action<Player>[] 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<Double, Integer> 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<Player> 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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<CropConfig> 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<Player> 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<CropConfig> configs = Registries.STAGE_TO_CROP_UNSAFE.get(event.placedID());
|
||||
if (configs == null || configs.size() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
CropConfig cropConfig = configs.get(0);
|
||||
Context<Player> 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<Player> 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<Integer, CropStageConfig> 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<CustomCropsBlockState> 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<CropConfig> configList = Registries.STAGE_TO_CROP_UNSAFE.get(stageID);
|
||||
if (configList == null) return null;
|
||||
|
||||
Optional<CustomCropsBlockState> 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<Integer, CropStageConfig> 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<CustomCropsBlockState> 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<CustomCropsBlockState> 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<Integer, CropStageConfig> 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<Integer, CropStageConfig> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<Player>[] plantRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Requirement<Player>[] interactRequirements();
|
||||
|
||||
GrowCondition[] growConditions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<Player>[] plantActions();
|
||||
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
Action<CustomCropsBlockState>[] deathActions();
|
||||
|
||||
DeathCondition[] deathConditions();
|
||||
|
||||
BoneMeal[] boneMeals();
|
||||
|
||||
boolean rotation();
|
||||
|
||||
Set<String> potWhitelist();
|
||||
|
||||
CropStageConfig stageByPoint(int point);
|
||||
|
||||
@Nullable
|
||||
CropStageConfig stageByID(String stageModel);
|
||||
|
||||
CropStageConfig stageWithModelByPoint(int point);
|
||||
|
||||
Collection<CropStageConfig> stages();
|
||||
|
||||
Collection<String> stageIDs();
|
||||
|
||||
Map.Entry<Integer, CropStageConfig> 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<Player>[] wrongPotActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder plantActions(Action<Player>[] plantActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder plantRequirements(Requirement<Player>[] plantRequirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] breakRequirements);
|
||||
|
||||
Builder interactRequirements(Requirement<Player>[] interactRequirements);
|
||||
|
||||
Builder growConditions(GrowCondition[] growConditions);
|
||||
|
||||
Builder deathConditions(DeathCondition[] deathConditions);
|
||||
|
||||
Builder boneMeals(BoneMeal[] boneMeals);
|
||||
|
||||
Builder rotation(boolean rotation);
|
||||
|
||||
Builder potWhitelist(Set<String> whitelist);
|
||||
|
||||
Builder stages(Collection<CropStageConfig.Builder> stages);
|
||||
}
|
||||
}
|
||||
@@ -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<Player>[] wrongPotActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] plantActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<CustomCropsBlockState>[] deathActions;
|
||||
private final Requirement<Player>[] plantRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] interactRequirements;
|
||||
private final GrowCondition[] growConditions;
|
||||
private final DeathCondition[] deathConditions;
|
||||
private final BoneMeal[] boneMeals;
|
||||
private final boolean rotation;
|
||||
private final Set<String> potWhitelist;
|
||||
private final HashMap<Integer, CropStageConfig> point2Stages = new HashMap<>();
|
||||
private final NavigableMap<Integer, CropStageConfig> navigablePoint2Stages = new TreeMap<>();
|
||||
private final HashMap<String, CropStageConfig> id2Stages = new HashMap<>();
|
||||
private final Set<String> stageIDs = new HashSet<>();
|
||||
private final HashMap<Integer, CropStageConfig> cropStageWithModelMap = new HashMap<>();
|
||||
|
||||
public CropConfigImpl(
|
||||
String id,
|
||||
String seed,
|
||||
int maxPoints,
|
||||
Action<Player>[] wrongPotActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] plantActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<CustomCropsBlockState>[] deathActions,
|
||||
Requirement<Player>[] plantRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] interactRequirements,
|
||||
GrowCondition[] growConditions,
|
||||
DeathCondition[] deathConditions,
|
||||
BoneMeal[] boneMeals,
|
||||
boolean rotation,
|
||||
Set<String> potWhitelist,
|
||||
Collection<CropStageConfig.Builder> 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<Player>[] plantRequirements() {
|
||||
return plantRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] interactRequirements() {
|
||||
return interactRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GrowCondition[] growConditions() {
|
||||
return growConditions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] plantActions() {
|
||||
return plantActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] deathActions() {
|
||||
return deathActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeathCondition[] deathConditions() {
|
||||
return deathConditions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoneMeal[] boneMeals() {
|
||||
return boneMeals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> 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<CropStageConfig> stages() {
|
||||
return point2Stages.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> stageIDs() {
|
||||
return stageIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Integer, CropStageConfig> 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<Player>[] wrongPotActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] plantActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<CustomCropsBlockState>[] deathActions;
|
||||
private Requirement<Player>[] plantRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] interactRequirements;
|
||||
private GrowCondition[] growConditions;
|
||||
private DeathCondition[] deathConditions;
|
||||
private BoneMeal[] boneMeals;
|
||||
private boolean rotation;
|
||||
private Set<String> potWhitelist;
|
||||
private Collection<CropStageConfig.Builder> 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<Player>[] wrongPotActions) {
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder plantActions(Action<Player>[] plantActions) {
|
||||
this.plantActions = plantActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder deathActions(Action<CustomCropsBlockState>[] deathActions) {
|
||||
this.deathActions = deathActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder plantRequirements(Requirement<Player>[] plantRequirements) {
|
||||
this.plantRequirements = plantRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] breakRequirements) {
|
||||
this.breakRequirements = breakRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactRequirements(Requirement<Player>[] 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<String> potWhitelist) {
|
||||
this.potWhitelist = new HashSet<>(potWhitelist);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stages(Collection<CropStageConfig.Builder> stages) {
|
||||
this.stages = new HashSet<>(stages);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Player>[] interactRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<CustomCropsBlockState>[] 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<Player>[] requirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder interactActions(Action<Player>[] actions);
|
||||
|
||||
Builder breakActions(Action<Player>[] actions);
|
||||
|
||||
Builder growActions(Action<CustomCropsBlockState>[] actions);
|
||||
|
||||
Builder existenceForm(ExistenceForm existenceForm);
|
||||
}
|
||||
}
|
||||
@@ -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<Player>[] interactRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<CustomCropsBlockState>[] growActions;
|
||||
|
||||
public CropStageConfigImpl(
|
||||
CropConfig crop,
|
||||
ExistenceForm existenceForm,
|
||||
double offset,
|
||||
String stageID,
|
||||
int point,
|
||||
Requirement<Player>[] interactRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<CustomCropsBlockState>[] 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<Player>[] interactRequirements() {
|
||||
return interactRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] 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<Player>[] interactRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<CustomCropsBlockState>[] 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<Player>[] requirements) {
|
||||
this.interactRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] requirements) {
|
||||
this.breakRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] actions) {
|
||||
this.interactActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] actions) {
|
||||
this.breakActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder growActions(Action<CustomCropsBlockState>[] actions) {
|
||||
this.growActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder existenceForm(ExistenceForm existenceForm) {
|
||||
this.existenceForm = existenceForm;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player> 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<CustomCropsBlockState>[] requirements;
|
||||
private final String deathStage;
|
||||
private final ExistenceForm existenceForm;
|
||||
private final int deathDelay;
|
||||
|
||||
public DeathCondition(Requirement<CustomCropsBlockState>[] 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<CustomCropsBlockState> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
|
||||
public ExistenceForm existenceForm() {
|
||||
return existenceForm;
|
||||
}
|
||||
}
|
||||
@@ -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<CustomCropsBlockState> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<CustomCropsBlockState>[] requirements;
|
||||
private final int pointToAdd;
|
||||
|
||||
public GrowCondition(Requirement<CustomCropsBlockState>[] requirements, int pointToAdd) {
|
||||
this.requirements = requirements;
|
||||
this.pointToAdd = pointToAdd;
|
||||
}
|
||||
|
||||
public int pointToAdd() {
|
||||
return pointToAdd;
|
||||
}
|
||||
|
||||
public boolean isMet(Context<CustomCropsBlockState> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
}
|
||||
@@ -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<Player> 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<CropConfig> 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<Player> 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<Player> 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<Player> 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<CustomCropsBlockState> 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<CompoundTag> tags = ((ListTag<CompoundTag>) 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<CompoundTag>("", TagType.TAG_COMPOUND, new ArrayList<>());
|
||||
state.set("fertilizers", fertilizerTag);
|
||||
}
|
||||
List<CompoundTag> tags = ((ListTag<CompoundTag>) 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<CompoundTag> tags = ((ListTag<CompoundTag>) fertilizerTag.getValue()).getValue();
|
||||
if (tags.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
List<Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> blocks();
|
||||
|
||||
boolean isWet(String blockID);
|
||||
|
||||
WaterBar waterBar();
|
||||
|
||||
int maxFertilizers();
|
||||
|
||||
String getPotAppearance(boolean watered, FertilizerType type);
|
||||
|
||||
Requirement<Player>[] placeRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Requirement<Player>[] useRequirements();
|
||||
|
||||
Action<CustomCropsBlockState>[] tickActions();
|
||||
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] placeActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
Action<Player>[] 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<Player>[] requirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder useRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder tickActions(Action<CustomCropsBlockState>[] tickActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder placeActions(Action<Player>[] placeActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder fullWaterActions(Action<Player>[] fullWaterActions);
|
||||
|
||||
Builder basicAppearance(Pair<String, String> basicAppearance);
|
||||
|
||||
Builder potAppearanceMap(HashMap<FertilizerType, Pair<String, String>> potAppearanceMap);
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> basicAppearance;
|
||||
private final HashMap<FertilizerType, Pair<String, String>> potAppearanceMap;
|
||||
private final Set<String> blocks = new HashSet<>();
|
||||
private final Set<String> 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<Player>[] placeRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] useRequirements;
|
||||
private final Action<CustomCropsBlockState>[] tickActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] placeActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] fullWaterActions;
|
||||
|
||||
public PotConfigImpl(
|
||||
String id,
|
||||
Pair<String, String> basicAppearance,
|
||||
HashMap<FertilizerType, Pair<String, String>> potAppearanceMap,
|
||||
int storage,
|
||||
boolean isRainDropAccepted,
|
||||
boolean isNearbyWaterAccepted,
|
||||
WateringMethod[] wateringMethods,
|
||||
WaterBar waterBar,
|
||||
int maxFertilizers,
|
||||
Requirement<Player>[] placeRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] useRequirements,
|
||||
Action<CustomCropsBlockState>[] tickActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] placeActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] 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<String, String> 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<String> 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<String, String> appearance = potAppearanceMap.get(type);
|
||||
if (appearance != null) {
|
||||
return watered ? appearance.right() : appearance.left();
|
||||
}
|
||||
}
|
||||
return watered ? basicAppearance.right() : basicAppearance.left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] placeRequirements() {
|
||||
return placeRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] useRequirements() {
|
||||
return useRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] tickActions() {
|
||||
return tickActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] placeActions() {
|
||||
return placeActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] fullWaterActions() {
|
||||
return fullWaterActions;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
|
||||
private String id;
|
||||
private ExistenceForm existenceForm;
|
||||
private Pair<String, String> basicAppearance;
|
||||
private HashMap<FertilizerType, Pair<String, String>> potAppearanceMap;
|
||||
private int storage;
|
||||
private boolean isRainDropAccepted;
|
||||
private boolean isNearbyWaterAccepted;
|
||||
private WateringMethod[] wateringMethods;
|
||||
private WaterBar waterBar;
|
||||
private int maxFertilizers;
|
||||
private Requirement<Player>[] placeRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] useRequirements;
|
||||
private Action<CustomCropsBlockState>[] tickActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] placeActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] 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<Player>[] requirements) {
|
||||
this.placeRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] requirements) {
|
||||
this.breakRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder useRequirements(Requirement<Player>[] requirements) {
|
||||
this.useRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder tickActions(Action<CustomCropsBlockState>[] tickActions) {
|
||||
this.tickActions = tickActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeActions(Action<Player>[] placeActions) {
|
||||
this.placeActions = placeActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullWaterActions(Action<Player>[] fullWaterActions) {
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder basicAppearance(Pair<String, String> basicAppearance) {
|
||||
this.basicAppearance = basicAppearance;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder potAppearanceMap(HashMap<FertilizerType, Pair<String, String>> potAppearanceMap) {
|
||||
this.potAppearanceMap = potAppearanceMap;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CustomCropsBlockState> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Player> 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<Player> 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<Player> 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<CustomCropsBlockState> 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<CustomCropsBlockState> context = Context.block(state);
|
||||
World bukkitWorld = world.bukkitWorld();
|
||||
Location bukkitLocation = location.toLocation(bukkitWorld);
|
||||
|
||||
CompletableFuture<Boolean> 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<CustomCropsBlockState> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> potWhitelist();
|
||||
|
||||
@NotNull
|
||||
Set<String> modelIDs();
|
||||
|
||||
@Nullable
|
||||
WaterBar waterBar();
|
||||
|
||||
@NotNull
|
||||
ExistenceForm existenceForm();
|
||||
|
||||
/**
|
||||
* Get the requirements for placement
|
||||
*
|
||||
* @return requirements for placement
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] placeRequirements();
|
||||
|
||||
/**
|
||||
* Get the requirements for breaking
|
||||
*
|
||||
* @return requirements for breaking
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
/**
|
||||
* Get the requirements for using
|
||||
*
|
||||
* @return requirements for using
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] useRequirements();
|
||||
|
||||
@Nullable
|
||||
Action<CustomCropsBlockState>[] workActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] placeActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] 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<String> potWhitelist);
|
||||
|
||||
Builder waterBar(WaterBar waterBar);
|
||||
|
||||
Builder twoDItem(@Nullable String twoDItem);
|
||||
|
||||
Builder threeDItem(String threeDItem);
|
||||
|
||||
Builder threeDItemWithWater(String threeDItemWithWater);
|
||||
|
||||
Builder placeRequirements(Requirement<Player>[] placeRequirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] breakRequirements);
|
||||
|
||||
Builder useRequirements(Requirement<Player>[] useRequirements);
|
||||
|
||||
Builder workActions(Action<CustomCropsBlockState>[] workActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder placeActions(Action<Player>[] placeActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder fullWaterActions(Action<Player>[] fullWaterActions);
|
||||
|
||||
Builder wateringMethods(WateringMethod[] wateringMethods);
|
||||
}
|
||||
}
|
||||
@@ -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<String> potWhitelist;
|
||||
private final WaterBar waterBar;
|
||||
private final String twoDItem;
|
||||
private final String threeDItem;
|
||||
private final String threeDItemWithWater;
|
||||
private final Requirement<Player>[] placeRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] useRequirements;
|
||||
private final Action<CustomCropsBlockState>[] workActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] placeActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] fullWaterActions;
|
||||
private final WateringMethod[] wateringMethods;
|
||||
private final Set<String> modelIDs = new HashSet<>();
|
||||
|
||||
public SprinklerConfigImpl(
|
||||
String id,
|
||||
ExistenceForm existenceForm,
|
||||
int storage,
|
||||
int[][] range,
|
||||
boolean infinite,
|
||||
int sprinklingAmount,
|
||||
Set<String> potWhitelist,
|
||||
WaterBar waterBar,
|
||||
String twoDItem,
|
||||
String threeDItem,
|
||||
String threeDItemWithWater,
|
||||
Requirement<Player>[] placeRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] useRequirements,
|
||||
Action<CustomCropsBlockState>[] workActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] placeActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] 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<String> potWhitelist() {
|
||||
return potWhitelist;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> modelIDs() {
|
||||
return modelIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterBar waterBar() {
|
||||
return waterBar;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ExistenceForm existenceForm() {
|
||||
return existenceForm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] placeRequirements() {
|
||||
return placeRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] useRequirements() {
|
||||
return useRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] workActions() {
|
||||
return workActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] placeActions() {
|
||||
return placeActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] 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<String> potWhitelist;
|
||||
private WaterBar waterBar;
|
||||
private Requirement<Player>[] placeRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] useRequirements;
|
||||
private Action<CustomCropsBlockState>[] workActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] placeActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] 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<String> 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<Player>[] placeRequirements) {
|
||||
this.placeRequirements = placeRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] breakRequirements) {
|
||||
this.breakRequirements = breakRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder useRequirements(Requirement<Player>[] useRequirements) {
|
||||
this.useRequirements = useRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder workActions(Action<CustomCropsBlockState>[] workActions) {
|
||||
this.workActions = workActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeActions(Action<Player>[] placeActions) {
|
||||
this.placeActions = placeActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullWaterActions(Action<Player>[] fullWaterActions) {
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wateringMethods(WateringMethod[] wateringMethods) {
|
||||
this.wateringMethods = wateringMethods;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,31 +15,31 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots;
|
||||
protected Requirement<Player>[] requirements;
|
||||
protected Action<Player>[] beforePlantActions;
|
||||
protected Action<Player>[] useActions;
|
||||
protected Action<Player>[] wrongPotActions;
|
||||
|
||||
public AbstractFertilizerConfig(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] 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<Player>[] beforePlantActions() {
|
||||
return beforePlantActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] useActions() {
|
||||
return useActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforePlant() {
|
||||
return beforePlant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> whitelistPots() {
|
||||
return whitelistPots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String icon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int times() {
|
||||
return times;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Player>[] requirements();
|
||||
|
||||
String itemID();
|
||||
|
||||
int times();
|
||||
|
||||
Set<String> whitelistPots();
|
||||
|
||||
Action<Player>[] beforePlantActions();
|
||||
|
||||
Action<Player>[] useActions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
int processGainPoints(int previousPoints);
|
||||
|
||||
int processWaterToLose(int waterToLose);
|
||||
|
||||
double processVariationChance(double previousChance);
|
||||
|
||||
int processDroppedItemAmount(int amount);
|
||||
|
||||
@Nullable
|
||||
double[] overrideQualityRatio();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Player> 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<CropConfig> 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<CustomCropsBlockState> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player> 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<Player> 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<Player> 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<Player> 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<Player> 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<ConfigManager, String, Section, FertilizerConfig> argumentConsumer;
|
||||
|
||||
public FertilizerType(String id, TriFunction<ConfigManager, String, Section, FertilizerConfig> argumentConsumer) {
|
||||
this.id = id;
|
||||
this.argumentConsumer = argumentConsumer;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static FertilizerType of(String id, TriFunction<ConfigManager, String, Section, FertilizerConfig> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance,
|
||||
double[] ratio
|
||||
) {
|
||||
return new QualityImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance, ratio);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] 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();
|
||||
}
|
||||
}
|
||||
@@ -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<Player> 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<Integer, CropStageConfig> 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<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5);
|
||||
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
|
||||
return entities.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance
|
||||
) {
|
||||
return new SoilRetainImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
return new SpeedGrowImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances);
|
||||
}
|
||||
}
|
||||
@@ -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<Pair<Double, Integer>> chances;
|
||||
|
||||
public SpeedGrowImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chances = chances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int pointBonus() {
|
||||
for (Pair<Double, Integer> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Player> 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<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5);
|
||||
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
|
||||
return entities.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
boolean addOrMultiply,
|
||||
double chance
|
||||
) {
|
||||
return new VariationImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, addOrMultiply, chance);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots();
|
||||
|
||||
Set<String> whitelistSprinklers();
|
||||
|
||||
List<TextValue<Player>> lore();
|
||||
|
||||
WaterBar waterBar();
|
||||
|
||||
Requirement<Player>[] requirements();
|
||||
|
||||
boolean infinite();
|
||||
|
||||
Integer appearance(int water);
|
||||
|
||||
Action<Player>[] fullActions();
|
||||
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
Action<Player>[] consumeWaterActions();
|
||||
|
||||
Action<Player>[] runOutOfWaterActions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
Action<Player>[] 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<String> whitelistPots);
|
||||
|
||||
Builder sprinklerWhitelist(Set<String> whitelistSprinklers);
|
||||
|
||||
Builder lore(List<TextValue<Player>> lore);
|
||||
|
||||
Builder waterBar(WaterBar waterBar);
|
||||
|
||||
Builder requirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder infinite(boolean infinite);
|
||||
|
||||
Builder appearances(Map<Integer, Integer> appearances);
|
||||
|
||||
Builder fullActions(Action<Player>[] fullActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder consumeWaterActions(Action<Player>[] consumeWaterActions);
|
||||
|
||||
Builder runOutOfWaterActions(Action<Player>[] runOutOfWaterActions);
|
||||
|
||||
Builder wrongPotActions(Action<Player>[] wrongPotActions);
|
||||
|
||||
Builder wrongSprinklerActions(Action<Player>[] wrongSprinklerActions);
|
||||
|
||||
Builder fillMethods(FillMethod[] fillMethods);
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots;
|
||||
private final Set<String> whitelistSprinklers;
|
||||
private final List<TextValue<Player>> lore;
|
||||
private final WaterBar waterBar;
|
||||
private final Requirement<Player>[] requirements;
|
||||
private final boolean infinite;
|
||||
private final HashMap<Integer, Integer> appearances;
|
||||
private final Action<Player>[] fullActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] consumeWaterActions;
|
||||
private final Action<Player>[] runOutOfWaterActions;
|
||||
private final Action<Player>[] wrongPotActions;
|
||||
private final Action<Player>[] wrongSprinklerActions;
|
||||
private final FillMethod[] fillMethods;
|
||||
|
||||
public WateringCanConfigImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int width,
|
||||
int length,
|
||||
int storage,
|
||||
int wateringAmount,
|
||||
boolean dynamicLore,
|
||||
Set<String> whitelistPots,
|
||||
Set<String> whitelistSprinklers,
|
||||
List<TextValue<Player>> lore,
|
||||
WaterBar waterBar,
|
||||
Requirement<Player>[] requirements,
|
||||
boolean infinite,
|
||||
HashMap<Integer, Integer> appearances,
|
||||
Action<Player>[] fullActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] consumeWaterActions,
|
||||
Action<Player>[] runOutOfWaterActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
Action<Player>[] 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<String> whitelistPots() {
|
||||
return whitelistPots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> whitelistSprinklers() {
|
||||
return whitelistSprinklers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TextValue<Player>> lore() {
|
||||
return lore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterBar waterBar() {
|
||||
return waterBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] requirements() {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean infinite() {
|
||||
return infinite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer appearance(int water) {
|
||||
return appearances.get(water);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] fullActions() {
|
||||
return fullActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] consumeWaterActions() {
|
||||
return consumeWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] runOutOfWaterActions() {
|
||||
return runOutOfWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] 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<String> whitelistPots;
|
||||
private Set<String> whitelistSprinklers;
|
||||
private List<TextValue<Player>> lore;
|
||||
private WaterBar waterBar;
|
||||
private Requirement<Player>[] requirements;
|
||||
private boolean infinite;
|
||||
private HashMap<Integer, Integer> appearances;
|
||||
private Action<Player>[] fullActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] consumeWaterActions;
|
||||
private Action<Player>[] runOutOfWaterActions;
|
||||
private Action<Player>[] wrongPotActions;
|
||||
private Action<Player>[] 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<String> whitelistPots) {
|
||||
this.whitelistPots = new HashSet<>(whitelistPots);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder sprinklerWhitelist(Set<String> whitelistSprinklers) {
|
||||
this.whitelistSprinklers = new HashSet<>(whitelistSprinklers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder lore(List<TextValue<Player>> lore) {
|
||||
this.lore = new ArrayList<>(lore);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder waterBar(WaterBar waterBar) {
|
||||
this.waterBar = waterBar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder requirements(Requirement<Player>[] requirements) {
|
||||
this.requirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder infinite(boolean infinite) {
|
||||
this.infinite = infinite;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder appearances(Map<Integer, Integer> appearances) {
|
||||
this.appearances = new HashMap<>(appearances);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullActions(Action<Player>[] fullActions) {
|
||||
this.fullActions = fullActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder consumeWaterActions(Action<Player>[] consumeWaterActions) {
|
||||
this.consumeWaterActions = consumeWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder runOutOfWaterActions(Action<Player>[] runOutOfWaterActions) {
|
||||
this.runOutOfWaterActions = runOutOfWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wrongPotActions(Action<Player>[] wrongPotActions) {
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wrongSprinklerActions(Action<Player>[] wrongSprinklerActions) {
|
||||
this.wrongSprinklerActions = wrongSprinklerActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fillMethods(FillMethod[] fillMethods) {
|
||||
this.fillMethods = fillMethods;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ItemStack> wrapped = BukkitCustomCropsPlugin.getInstance().getItemManager().wrap(itemStack);
|
||||
return (int) wrapped.getTag("CustomCrops", "water").orElse(0);
|
||||
}
|
||||
|
||||
public void setCurrentWater(ItemStack itemStack, WateringCanConfig config, int water, Context<Player> context) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) return;
|
||||
Item<ItemStack> 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<String> 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<Player> 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<Player> 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<Player> 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<CropConfig> 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<Pair<Pos3, String>> 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<Pos3, String> 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<Pair<Pos3, String>> potInRange(World world, Pos3 pos3, int width, int length, float yaw, PotConfig config) {
|
||||
ArrayList<Pos3> 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<Pair<Pos3, String>> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
return new YieldIncreaseImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances);
|
||||
}
|
||||
}
|
||||
@@ -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<Pair<Double, Integer>> chances;
|
||||
|
||||
public YieldIncreaseImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chances = chances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int amountBonus() {
|
||||
for (Pair<Double, Integer> 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player>[] actions;
|
||||
private final Requirement<Player>[] requirements;
|
||||
|
||||
protected AbstractMethod(int amount, Action<Player>[] actions, Requirement<Player>[] requirements) {
|
||||
this.amount = amount;
|
||||
this.actions = actions;
|
||||
this.requirements = requirements;
|
||||
}
|
||||
|
||||
public int amountOfWater() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void triggerActions(Context<Player> context) {
|
||||
ActionManager.trigger(context, actions);
|
||||
}
|
||||
|
||||
public boolean checkRequirements(Context<Player> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player>[] actions, Requirement<Player>[] requirements) {
|
||||
super(amount, actions, requirements);
|
||||
this.id = id;
|
||||
}
|
||||
@@ -15,20 +15,29 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Player>[] actions,
|
||||
Requirement<Player>[] requirements
|
||||
) {
|
||||
super(amount, actions, requirements);
|
||||
this.used = used;
|
||||
this.returned = returned;
|
||||
@@ -15,9 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<CustomCropsBlockState> getBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Remove any block data from a certain location
|
||||
*
|
||||
* @param location location
|
||||
* @return block data
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> removeBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Add a custom block data at a certain location
|
||||
*
|
||||
* @param block block to add
|
||||
* @return the previous block data
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> addBlockState(Pos3 location, CustomCropsBlockState block);
|
||||
|
||||
/**
|
||||
* Get CustomCrops sections
|
||||
*
|
||||
* @return sections
|
||||
*/
|
||||
@NotNull
|
||||
Stream<CustomCropsSection> sectionsToSave();
|
||||
|
||||
/**
|
||||
* Get section by ID
|
||||
*
|
||||
* @param sectionID id
|
||||
* @return section
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsSection> getLoadedSection(int sectionID);
|
||||
|
||||
CustomCropsSection getSection(int sectionID);
|
||||
|
||||
Collection<CustomCropsSection> sections();
|
||||
|
||||
Optional<CustomCropsSection> removeSection(int sectionID);
|
||||
|
||||
void resetUnloadedSeconds();
|
||||
|
||||
boolean canPrune();
|
||||
|
||||
boolean isOfflineTaskNotified();
|
||||
|
||||
PriorityQueue<DelayedTickTask> tickTaskQueue();
|
||||
|
||||
Set<BlockPos> tickedBlocks();
|
||||
}
|
||||
@@ -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<Integer, CustomCropsSection> loadedSections;
|
||||
private final PriorityQueue<DelayedTickTask> queue;
|
||||
private final Set<BlockPos> 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<Integer, CustomCropsSection> loadedSections,
|
||||
PriorityQueue<DelayedTickTask> queue,
|
||||
HashSet<BlockPos> 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<BlockPos, CustomCropsBlockState> 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<CustomCropsBlockState> 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<CustomCropsBlockState> 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<CustomCropsBlockState> getBlockState(Pos3 location) {
|
||||
BlockPos pos = BlockPos.fromPos3(location);
|
||||
return getLoadedSection(pos.sectionID()).flatMap(section -> section.getBlockState(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> removeBlockState(Pos3 location) {
|
||||
BlockPos pos = BlockPos.fromPos3(location);
|
||||
return getLoadedSection(pos.sectionID()).flatMap(section -> section.removeBlockState(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> 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<CustomCropsSection> sectionsToSave() {
|
||||
return loadedSections.values().stream().filter(section -> !section.canPrune());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsSection> 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<CustomCropsSection> sections() {
|
||||
return loadedSections.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomCropsSection> 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<DelayedTickTask> tickTaskQueue() {
|
||||
return queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BlockPos> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,32 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<ChunkPos, byte[]> dataToSave();
|
||||
|
||||
boolean canPrune();
|
||||
}
|
||||
|
||||
@@ -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<ChunkPos, byte[]> 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<ChunkPos, byte[]> 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<ChunkPos, byte[]> dataToSave() {
|
||||
return new HashMap<>(cachedChunks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPrune() {
|
||||
return cachedChunks.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<BlockPos, CustomCropsBlockState> blocks) {
|
||||
return new CustomCropsSectionImpl(sectionID, blocks);
|
||||
}
|
||||
|
||||
int getSectionID();
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> getBlockState(BlockPos pos);
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> removeBlockState(BlockPos pos);
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> addBlockState(BlockPos pos, CustomCropsBlockState block);
|
||||
|
||||
boolean canPrune();
|
||||
|
||||
CustomCropsBlockState[] blocks();
|
||||
|
||||
Map<BlockPos, CustomCropsBlockState> blockMap();
|
||||
}
|
||||
@@ -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<BlockPos, CustomCropsBlockState> blocks;
|
||||
|
||||
protected CustomCropsSectionImpl(int sectionID) {
|
||||
this.sectionID = sectionID;
|
||||
this.blocks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
protected CustomCropsSectionImpl(int sectionID, ConcurrentHashMap<BlockPos, CustomCropsBlockState> blocks) {
|
||||
this.sectionID = sectionID;
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionID() {
|
||||
return sectionID;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> getBlockState(BlockPos pos) {
|
||||
return Optional.ofNullable(blocks.get(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> removeBlockState(BlockPos pos) {
|
||||
return Optional.ofNullable(blocks.remove(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> 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<BlockPos, CustomCropsBlockState> blockMap() {
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<W> {
|
||||
|
||||
/**
|
||||
* Create a CustomCrops world
|
||||
*
|
||||
* @param world world instance
|
||||
* @param adaptor the world adaptor
|
||||
* @return CustomCrops world
|
||||
* @param <W> world type
|
||||
*/
|
||||
static <W> CustomCropsWorld<W> create(W world, WorldAdaptor<W> 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<Integer, CustomCropsSection> loadedSections,
|
||||
PriorityQueue<DelayedTickTask> queue,
|
||||
HashSet<BlockPos> 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<ChunkPos, byte[]> cachedChunks) {
|
||||
return new CustomCropsRegionImpl(this, pos, cachedChunks);
|
||||
}
|
||||
|
||||
WorldAdaptor<W> adaptor();
|
||||
|
||||
WorldExtraData extraData();
|
||||
|
||||
boolean testChunkLimitation(Pos3 pos3, Class<? extends CustomCropsBlock> clazz, int amount);
|
||||
|
||||
boolean doesChunkHaveBlock(Pos3 pos3, Class<? extends CustomCropsBlock> clazz);
|
||||
|
||||
int getChunkBlockAmount(Pos3 pos3, Class<? extends CustomCropsBlock> 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<CustomCropsBlockState> 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<CustomCropsBlockState> 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<CustomCropsBlockState> 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<CustomCropsChunk> getLoadedChunk(ChunkPos chunkPos);
|
||||
|
||||
/**
|
||||
* Get chunk from cache or file
|
||||
*
|
||||
* @param chunkPos the position of the chunk
|
||||
* @return the optional chunk
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsChunk> 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<CustomCropsRegion> getLoadedRegion(RegionPos regionPos);
|
||||
|
||||
/**
|
||||
* Get the region from cache or file
|
||||
*
|
||||
* @param regionPos position of the region
|
||||
* @return the optional region
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsRegion> 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);
|
||||
}
|
||||
@@ -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<W> implements CustomCropsWorld<W> {
|
||||
|
||||
private final ConcurrentHashMap<ChunkPos, CustomCropsChunk> loadedChunks = new ConcurrentHashMap<>(512);
|
||||
private final ConcurrentHashMap<ChunkPos, CustomCropsChunk> lazyChunks = new ConcurrentHashMap<>(128);
|
||||
private final ConcurrentHashMap<RegionPos, CustomCropsRegion> loadedRegions = new ConcurrentHashMap<>(128);
|
||||
private final WeakReference<W> world;
|
||||
private final WeakReference<World> bukkitWorld;
|
||||
private final String worldName;
|
||||
private long currentMinecraftDay;
|
||||
private int regionTimer;
|
||||
private SchedulerTask tickTask;
|
||||
private WorldSetting setting;
|
||||
private final WorldAdaptor<W> adaptor;
|
||||
private final WorldExtraData extraData;
|
||||
|
||||
public CustomCropsWorldImpl(W world, WorldAdaptor<W> 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<W> adaptor() {
|
||||
return adaptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WorldExtraData extraData() {
|
||||
return extraData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testChunkLimitation(Pos3 pos3, Class<? extends CustomCropsBlock> clazz, int amount) {
|
||||
Optional<CustomCropsChunk> 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<? extends CustomCropsBlock> clazz) {
|
||||
Optional<CustomCropsChunk> 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<? extends CustomCropsBlock> clazz) {
|
||||
Optional<CustomCropsChunk> 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<CustomCropsBlockState> getBlockState(Pos3 location) {
|
||||
ChunkPos pos = location.toChunkPos();
|
||||
Optional<CustomCropsChunk> 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<CustomCropsBlockState> removeBlockState(Pos3 location) {
|
||||
ChunkPos pos = location.toChunkPos();
|
||||
Optional<CustomCropsChunk> 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<CustomCropsBlockState> 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<Location, World> 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<CustomCropsRegion> removed = new ArrayList<>();
|
||||
for (Map.Entry<RegionPos, CustomCropsRegion> entry : loadedRegions.entrySet()) {
|
||||
if (shouldUnloadRegion(entry.getKey())) {
|
||||
removed.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
for (CustomCropsRegion region : removed) {
|
||||
region.unload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLazyChunks() {
|
||||
ArrayList<CustomCropsChunk> chunksToSave = new ArrayList<>();
|
||||
for (Map.Entry<ChunkPos, CustomCropsChunk> 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<CustomCropsChunk> 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<CustomCropsChunk> 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<CustomCropsChunk> getLoadedChunk(ChunkPos chunkPos) {
|
||||
return Optional.ofNullable(this.loadedChunks.get(chunkPos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsChunk> 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<CustomCropsRegion> 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<CustomCropsRegion> getLoadedRegion(RegionPos regionPos) {
|
||||
return Optional.ofNullable(loadedRegions.get(regionPos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsRegion> 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<CustomCropsRegion> 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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user