9
0
mirror of https://github.com/Xiao-MoMi/Custom-Crops.git synced 2025-12-19 15:09:25 +00:00
This commit is contained in:
XiaoMoMi
2024-08-31 22:57:45 +08:00
parent 3cbd1f65a6
commit bbde4ebd47
475 changed files with 24733 additions and 24341 deletions

View File

@@ -1,42 +1,38 @@
# Note: This project is undergoing a major refactoring. Please switch to the 3.6-dev branch for the new API.
# Custom-Crops
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/Xiao-MoMi/Custom-Crops)
![Code Size](https://img.shields.io/github/languages/code-size/Xiao-MoMi/Custom-Crops)
![bStats Servers](https://img.shields.io/bstats/servers/16593)
![bStats Players](https://img.shields.io/bstats/players/16593)
[![Scc Count Badge](https://sloc.xyz/github/Xiao-MoMi/Custom-Crops/?category=codes)](https://github.com/Xiao-MoMi/Custom-Crops/)
![GitHub](https://img.shields.io/github/license/Xiao-MoMi/Custom-Crops)
[![](https://jitpack.io/v/Xiao-MoMi/Custom-Crops.svg)](https://jitpack.io/#Xiao-MoMi/Custom-Crops)
<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>
[![Scc Count Badge](https://sloc.xyz/github/Xiao-MoMi/Custom-Crops/?category=codes)](https://github.com/Xiao-MoMi/Custom-Crops/)
![Code Size](https://img.shields.io/github/languages/code-size/Xiao-MoMi/Custom-Crops)
![bStats Servers](https://img.shields.io/bstats/servers/16593)
![bStats Players](https://img.shields.io/bstats/players/16593)
![GitHub](https://img.shields.io/github/license/Xiao-MoMi/Custom-Crops)
Ultra-customizable planting experience for Minecraft servers
### Support the developer
https://afdian.net/@xiaomomi
https://polymart.org/resource/customcrops.2625
CustomCrops is a Paper plugin crafted to deliver an exceptional planting experience for Minecraft servers, with a strong emphasis on customization and performance. It employs Zstd compression for data serialization, ensuring high efficiency comparable to Minecraft's own serialization techniques. The plugin optimizes server performance by running its tick system across multiple threads, reverting to the main thread only when required. Additionally, CustomCrops offers a comprehensive API that enables developers to create custom block mechanics with specific interaction and tick behaviors, such as a fish trap block that periodically provides players with fish.
## How to build
### Windows
#### Command Line
Install JDK 17 and set the JDK installation path to JAVA_HOME as an environment variable.\
Start powershell and change directory to the project folder.\
Execute ".\gradlew build" and get the jar at /target/CustomCrops-plugin-version.jar.
Install JDK 17 & 21. \
Start terminal and change directory to the project folder.\
Execute ".\gradlew build" and get the artifact under /target folder
#### IDE
Import the project and execute gradle build action.
Import the project and execute gradle build action. \
Get the artifact under /target folder
## Use CustomCrops API
## Support the developer
Polymart: https://polymart.org/resource/customfishing.2723 \
Afdian: https://afdian.net/@xiaomomi
## CustomCrops API
### Maven
```
```html
<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}")
}

View File

@@ -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 {
@@ -15,3 +32,29 @@ java {
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)
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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() +
'}';
}
}

View File

@@ -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);
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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() +
'}';
}
}

View File

@@ -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
);
}
}

View File

@@ -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
);
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.customcrops.api.core;
public interface ClearableRegistry<K, T> extends WriteableRegistry<K, T> {
void clear();
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,8 @@
package net.momirealms.customcrops.api.core;
public enum ExistenceForm {
BLOCK,
FURNITURE,
ANY
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,8 @@
package net.momirealms.customcrops.api.core;
public enum InteractionResult {
SUCCESS,
FAIL,
PASS
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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"));
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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 + "}";
}
}

View File

@@ -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);
}

View File

@@ -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) {
}
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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() +
'}';
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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();
}

View File

@@ -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));
}
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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