mirror of
https://github.com/Xiao-MoMi/Custom-Crops.git
synced 2025-12-28 03:19:15 +00:00
3.6
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api;
|
||||
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.core.AbstractItemManager;
|
||||
import net.momirealms.customcrops.api.core.ConfigManager;
|
||||
import net.momirealms.customcrops.api.core.RegistryAccess;
|
||||
import net.momirealms.customcrops.api.core.world.WorldManager;
|
||||
import net.momirealms.customcrops.api.integration.IntegrationManager;
|
||||
import net.momirealms.customcrops.api.misc.cooldown.CoolDownManager;
|
||||
import net.momirealms.customcrops.api.misc.placeholder.PlaceholderManager;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.common.dependency.DependencyManager;
|
||||
import net.momirealms.customcrops.common.locale.TranslationManager;
|
||||
import net.momirealms.customcrops.common.plugin.CustomCropsPlugin;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.AbstractJavaScheduler;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter;
|
||||
import net.momirealms.customcrops.common.sender.SenderFactory;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class BukkitCustomCropsPlugin implements CustomCropsPlugin {
|
||||
|
||||
private static BukkitCustomCropsPlugin instance;
|
||||
private final Plugin boostrap;
|
||||
|
||||
protected AbstractJavaScheduler<Location, World> scheduler;
|
||||
protected DependencyManager dependencyManager;
|
||||
protected TranslationManager translationManager;
|
||||
protected AbstractItemManager itemManager;
|
||||
protected PlaceholderManager placeholderManager;
|
||||
protected CoolDownManager coolDownManager;
|
||||
protected ConfigManager configManager;
|
||||
protected IntegrationManager integrationManager;
|
||||
protected WorldManager worldManager;
|
||||
protected RegistryAccess registryAccess;
|
||||
protected SenderFactory<BukkitCustomCropsPlugin, CommandSender> senderFactory;
|
||||
|
||||
protected final Map<Class<?>, ActionManager<?>> actionManagers = new HashMap<>();
|
||||
protected final Map<Class<?>, RequirementManager<?>> requirementManagers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a new BukkitCustomCropsPlugin instance.
|
||||
*
|
||||
* @param boostrap the plugin instance used to initialize this class
|
||||
*/
|
||||
public BukkitCustomCropsPlugin(Plugin boostrap) {
|
||||
if (!boostrap.getName().equals("CustomCrops")) {
|
||||
throw new IllegalArgumentException("CustomCrops plugin requires custom crops plugin");
|
||||
}
|
||||
this.boostrap = boostrap;
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of BukkitCustomFishingPlugin.
|
||||
*
|
||||
* @return the singleton instance
|
||||
* @throws IllegalArgumentException if the plugin is not initialized
|
||||
*/
|
||||
public static BukkitCustomCropsPlugin getInstance() {
|
||||
if (instance == null) {
|
||||
throw new IllegalArgumentException("Plugin not initialized");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plugin instance used to initialize this class.
|
||||
*
|
||||
* @return the {@link Plugin} instance
|
||||
*/
|
||||
public Plugin getBoostrap() {
|
||||
return boostrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the DependencyManager.
|
||||
*
|
||||
* @return the {@link DependencyManager}
|
||||
*/
|
||||
@Override
|
||||
public DependencyManager getDependencyManager() {
|
||||
return dependencyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the TranslationManager.
|
||||
*
|
||||
* @return the {@link TranslationManager}
|
||||
*/
|
||||
@Override
|
||||
public TranslationManager getTranslationManager() {
|
||||
return translationManager;
|
||||
}
|
||||
|
||||
public AbstractItemManager getItemManager() {
|
||||
return itemManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerAdapter<Location, World> getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public SenderFactory<BukkitCustomCropsPlugin, CommandSender> getSenderFactory() {
|
||||
return senderFactory;
|
||||
}
|
||||
|
||||
public WorldManager getWorldManager() {
|
||||
return worldManager;
|
||||
}
|
||||
|
||||
public IntegrationManager getIntegrationManager() {
|
||||
return integrationManager;
|
||||
}
|
||||
|
||||
public PlaceholderManager getPlaceholderManager() {
|
||||
return placeholderManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a debug message.
|
||||
*
|
||||
* @param message the message to log
|
||||
*/
|
||||
public abstract void debug(Object message);
|
||||
|
||||
public File getDataFolder() {
|
||||
return boostrap.getDataFolder();
|
||||
}
|
||||
|
||||
public RegistryAccess getRegistryAccess() {
|
||||
return registryAccess;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ActionManager<T> getActionManager(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type cannot be null");
|
||||
}
|
||||
return (ActionManager<T>) actionManagers.get(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> RequirementManager<T> getRequirementManager(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Type cannot be null");
|
||||
}
|
||||
return (RequirementManager<T>) instance.requirementManagers.get(type);
|
||||
}
|
||||
|
||||
public CoolDownManager getCoolDownManager() {
|
||||
return coolDownManager;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api;
|
||||
|
||||
import net.momirealms.customcrops.api.manager.*;
|
||||
import net.momirealms.customcrops.api.scheduler.Scheduler;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public abstract class CustomCropsPlugin extends JavaPlugin {
|
||||
|
||||
protected static CustomCropsPlugin instance;
|
||||
protected VersionManager versionManager;
|
||||
protected ConfigManager configManager;
|
||||
protected Scheduler scheduler;
|
||||
protected RequirementManager requirementManager;
|
||||
protected ActionManager actionManager;
|
||||
protected IntegrationManager integrationManager;
|
||||
protected CoolDownManager coolDownManager;
|
||||
protected WorldManager worldManager;
|
||||
protected ItemManager itemManager;
|
||||
protected AdventureManager adventure;
|
||||
protected MessageManager messageManager;
|
||||
protected ConditionManager conditionManager;
|
||||
protected PlaceholderManager placeholderManager;
|
||||
|
||||
public CustomCropsPlugin() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static CustomCropsPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static CustomCropsPlugin get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/* Get version manager */
|
||||
public VersionManager getVersionManager() {
|
||||
return versionManager;
|
||||
}
|
||||
|
||||
/* Get config manager */
|
||||
public ConfigManager getConfigManager() {
|
||||
return configManager;
|
||||
}
|
||||
|
||||
/* Get scheduler */
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
/* Get requirement manager */
|
||||
public RequirementManager getRequirementManager() {
|
||||
return requirementManager;
|
||||
}
|
||||
|
||||
/* Get integration manager */
|
||||
public IntegrationManager getIntegrationManager() {
|
||||
return integrationManager;
|
||||
}
|
||||
|
||||
/* Get action manager */
|
||||
public ActionManager getActionManager() {
|
||||
return actionManager;
|
||||
}
|
||||
|
||||
/* Get cool down manager */
|
||||
public CoolDownManager getCoolDownManager() {
|
||||
return coolDownManager;
|
||||
}
|
||||
|
||||
/* Get world data manager */
|
||||
public WorldManager getWorldManager() {
|
||||
return worldManager;
|
||||
}
|
||||
|
||||
/* Get item manager */
|
||||
public ItemManager getItemManager() {
|
||||
return itemManager;
|
||||
}
|
||||
|
||||
/* Get message manager */
|
||||
public MessageManager getMessageManager() {
|
||||
return messageManager;
|
||||
}
|
||||
|
||||
/* Get adventure manager */
|
||||
public AdventureManager getAdventure() {
|
||||
return adventure;
|
||||
}
|
||||
|
||||
/* Get condition manager */
|
||||
public ConditionManager getConditionManager() {
|
||||
return conditionManager;
|
||||
}
|
||||
|
||||
/* Get placeholder manager */
|
||||
public PlaceholderManager getPlaceholderManager() {
|
||||
return placeholderManager;
|
||||
}
|
||||
|
||||
public abstract boolean isHookedPluginEnabled(String plugin);
|
||||
|
||||
public abstract void debug(String debug);
|
||||
|
||||
public abstract void reload();
|
||||
|
||||
public abstract boolean isHookedPluginEnabled(String hooked, String... versionPrefix);
|
||||
|
||||
public abstract boolean doesHookedPluginExist(String plugin);
|
||||
|
||||
public abstract String getServerVersion();
|
||||
}
|
||||
@@ -0,0 +1,891 @@
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.context.ContextKeys;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.block.*;
|
||||
import net.momirealms.customcrops.api.core.item.Fertilizer;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerConfig;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsChunk;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.event.CropPlantEvent;
|
||||
import net.momirealms.customcrops.api.misc.placeholder.BukkitPlaceholderManager;
|
||||
import net.momirealms.customcrops.api.misc.value.MathValue;
|
||||
import net.momirealms.customcrops.api.misc.value.TextValue;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.util.*;
|
||||
import net.momirealms.customcrops.common.helper.AdventureHelper;
|
||||
import net.momirealms.customcrops.common.helper.VersionHelper;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customcrops.common.util.*;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import net.momirealms.sparrow.heart.feature.entity.FakeEntity;
|
||||
import net.momirealms.sparrow.heart.feature.entity.armorstand.FakeArmorStand;
|
||||
import net.momirealms.sparrow.heart.feature.entity.display.FakeItemDisplay;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public abstract class AbstractActionManager<T> implements ActionManager<T> {
|
||||
|
||||
protected final BukkitCustomCropsPlugin plugin;
|
||||
private final HashMap<String, ActionFactory<T>> actionFactoryMap = new HashMap<>();
|
||||
private static final String EXPANSION_FOLDER = "expansions/action";
|
||||
|
||||
public AbstractActionManager(BukkitCustomCropsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registerBuiltInActions();
|
||||
}
|
||||
|
||||
protected void registerBuiltInActions() {
|
||||
this.registerCommandAction();
|
||||
this.registerBroadcastAction();
|
||||
this.registerNearbyMessage();
|
||||
this.registerNearbyActionBar();
|
||||
this.registerNearbyTitle();
|
||||
this.registerParticleAction();
|
||||
this.registerQualityCropsAction();
|
||||
this.registerDropItemsAction();
|
||||
this.registerLegacyDropItemsAction();
|
||||
this.registerFakeItemAction();
|
||||
this.registerPlantAction();
|
||||
this.registerBreakAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerAction(ActionFactory<T> actionFactory, String... types) {
|
||||
for (String type : types) {
|
||||
if (this.actionFactoryMap.containsKey(type)) return false;
|
||||
}
|
||||
for (String type : types) {
|
||||
this.actionFactoryMap.put(type, actionFactory);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterAction(String type) {
|
||||
return this.actionFactoryMap.remove(type) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAction(@NotNull String type) {
|
||||
return actionFactoryMap.containsKey(type);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ActionFactory<T> getActionFactory(@NotNull String type) {
|
||||
return actionFactoryMap.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<T> parseAction(Section section) {
|
||||
if (section == null) return Action.empty();
|
||||
ActionFactory<T> factory = getActionFactory(section.getString("type"));
|
||||
if (factory == null) {
|
||||
plugin.getPluginLogger().warn("Action type: " + section.getString("type") + " doesn't exist.");
|
||||
return Action.empty();
|
||||
}
|
||||
return factory.process(section.get("value"), section.getDouble("chance", 1d));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Action<T>[] parseActions(Section section) {
|
||||
ArrayList<Action<T>> actionList = new ArrayList<>();
|
||||
if (section != null)
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
Action<T> action = parseAction(innerSection);
|
||||
if (action != null)
|
||||
actionList.add(action);
|
||||
}
|
||||
}
|
||||
return actionList.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<T> parseAction(@NotNull String type, @NotNull Object args) {
|
||||
ActionFactory<T> factory = getActionFactory(type);
|
||||
if (factory == null) {
|
||||
plugin.getPluginLogger().warn("Action type: " + type + " doesn't exist.");
|
||||
return Action.empty();
|
||||
}
|
||||
return factory.process(args, 1);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ResultOfMethodCallIgnored", "unchecked"})
|
||||
protected void loadExpansions(Class<T> tClass) {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
if (!expansionFolder.exists())
|
||||
expansionFolder.mkdirs();
|
||||
|
||||
List<Class<? extends ActionExpansion<T>>> classes = new ArrayList<>();
|
||||
File[] expansionJars = expansionFolder.listFiles();
|
||||
if (expansionJars == null) return;
|
||||
for (File expansionJar : expansionJars) {
|
||||
if (expansionJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
Class<? extends ActionExpansion<T>> expansionClass = (Class<? extends ActionExpansion<T>>) ClassUtils.findClass(expansionJar, ActionExpansion.class, tClass);
|
||||
classes.add(expansionClass);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (Class<? extends ActionExpansion<T>> expansionClass : classes) {
|
||||
ActionExpansion<T> expansion = expansionClass.getDeclaredConstructor().newInstance();
|
||||
unregisterAction(expansion.getActionType());
|
||||
registerAction(expansion.getActionFactory(), expansion.getActionType());
|
||||
plugin.getPluginLogger().info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() );
|
||||
}
|
||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerBroadcastAction() {
|
||||
registerAction((args, chance) -> {
|
||||
List<String> messages = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer offlinePlayer = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
offlinePlayer = player;
|
||||
}
|
||||
List<String> replaced = plugin.getPlaceholderManager().parse(offlinePlayer, messages, context.placeholderMap());
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
for (String text : replaced) {
|
||||
audience.sendMessage(AdventureHelper.miniMessage(text));
|
||||
}
|
||||
}
|
||||
};
|
||||
}, "broadcast");
|
||||
}
|
||||
|
||||
protected void registerNearbyMessage() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<String> messages = ListUtils.toList(section.get("message"));
|
||||
MathValue<T> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
double realRange = range.evaluate(context);
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(
|
||||
owner,
|
||||
messages,
|
||||
context.placeholderMap()
|
||||
);
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
for (String text : replaced) {
|
||||
audience.sendMessage(AdventureHelper.miniMessage(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at message-nearby action which should be Section");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "message-nearby");
|
||||
}
|
||||
|
||||
protected void registerNearbyActionBar() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String actionbar = section.getString("actionbar");
|
||||
MathValue<T> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
double realRange = range.evaluate(context);
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
String replaced = plugin.getPlaceholderManager().parse(owner, actionbar, context.placeholderMap());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
audience.sendActionBar(AdventureHelper.miniMessage(replaced));
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at actionbar-nearby action which should be Section");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "actionbar-nearby");
|
||||
}
|
||||
|
||||
protected void registerCommandAction() {
|
||||
registerAction((args, chance) -> {
|
||||
List<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(owner, commands, context.placeholderMap());
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (String text : replaced) {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
|
||||
}
|
||||
}, null);
|
||||
};
|
||||
}, "command");
|
||||
registerAction((args, chance) -> {
|
||||
List<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
String random = commands.get(ThreadLocalRandom.current().nextInt(commands.size()));
|
||||
random = BukkitPlaceholderManager.getInstance().parse(owner, random, context.placeholderMap());
|
||||
String finalRandom = random;
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom);
|
||||
}, null);
|
||||
};
|
||||
}, "random-command");
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<String> cmd = ListUtils.toList(section.get("command"));
|
||||
MathValue<T> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
OfflinePlayer owner = null;
|
||||
if (context.holder() instanceof Player player) {
|
||||
owner = player;
|
||||
}
|
||||
double realRange = range.evaluate(context);
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(owner, cmd, context.placeholderMap());
|
||||
for (String text : replaced) {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at command-nearby action which should be Section");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "command-nearby");
|
||||
}
|
||||
|
||||
protected void registerBundleAction(Class<T> tClass) {
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> actions = new ArrayList<>();
|
||||
if (args instanceof Section section) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
actions.add(parseAction(innerSection));
|
||||
}
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
};
|
||||
}, "chain");
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> actions = new ArrayList<>();
|
||||
int delay;
|
||||
boolean async;
|
||||
if (args instanceof Section section) {
|
||||
delay = section.getInt("delay", 1);
|
||||
async = section.getBoolean("async", false);
|
||||
Section actionSection = section.getSection("actions");
|
||||
if (actionSection != null)
|
||||
for (Map.Entry<String, Object> entry : actionSection.getStringRouteMappedValues(false).entrySet())
|
||||
if (entry.getValue() instanceof Section innerSection)
|
||||
actions.add(parseAction(innerSection));
|
||||
} else {
|
||||
delay = 1;
|
||||
async = false;
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = context.arg(ContextKeys.LOCATION);
|
||||
if (async) {
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
plugin.getScheduler().sync().runLater(() -> {
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay, location);
|
||||
}
|
||||
};
|
||||
}, "delay");
|
||||
registerAction((args, chance) -> {
|
||||
List<Action<T>> actions = new ArrayList<>();
|
||||
int delay, duration, period;
|
||||
boolean async;
|
||||
if (args instanceof Section section) {
|
||||
delay = section.getInt("delay", 2);
|
||||
duration = section.getInt("duration", 20);
|
||||
period = section.getInt("period", 2);
|
||||
async = section.getBoolean("async", false);
|
||||
Section actionSection = section.getSection("actions");
|
||||
if (actionSection != null)
|
||||
for (Map.Entry<String, Object> entry : actionSection.getStringRouteMappedValues(false).entrySet())
|
||||
if (entry.getValue() instanceof Section innerSection)
|
||||
actions.add(parseAction(innerSection));
|
||||
} else {
|
||||
delay = 1;
|
||||
period = 1;
|
||||
async = false;
|
||||
duration = 20;
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = context.arg(ContextKeys.LOCATION);
|
||||
SchedulerTask task;
|
||||
if (async) {
|
||||
task = plugin.getScheduler().asyncRepeating(() -> {
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
}, delay * 50L, period * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
task = plugin.getScheduler().sync().runRepeating(() -> {
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
}, delay, period, location);
|
||||
}
|
||||
plugin.getScheduler().asyncLater(task::cancel, duration * 50L, TimeUnit.MILLISECONDS);
|
||||
};
|
||||
}, "timer");
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
Action<T>[] actions = parseActions(section.getSection("actions"));
|
||||
Requirement<T>[] requirements = plugin.getRequirementManager(tClass).parseRequirements(section.getSection("conditions"), true);
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Requirement<T> requirement : requirements) {
|
||||
if (!requirement.isSatisfied(condition)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(condition);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at conditional action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "conditional");
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<Pair<Requirement<T>[], Action<T>[]>> conditionActionPairList = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
Action<T>[] actions = parseActions(inner.getSection("actions"));
|
||||
Requirement<T>[] requirements = plugin.getRequirementManager(tClass).parseRequirements(inner.getSection("conditions"), false);
|
||||
conditionActionPairList.add(Pair.of(requirements, actions));
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
outer:
|
||||
for (Pair<Requirement<T>[], Action<T>[]> pair : conditionActionPairList) {
|
||||
if (pair.left() != null)
|
||||
for (Requirement<T> requirement : pair.left()) {
|
||||
if (!requirement.isSatisfied(context)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (pair.right() != null)
|
||||
for (Action<T> action : pair.right()) {
|
||||
action.trigger(context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at priority action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "priority");
|
||||
}
|
||||
|
||||
protected void registerNearbyTitle() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
TextValue<T> title = TextValue.auto(section.getString("title"));
|
||||
TextValue<T> subtitle = TextValue.auto(section.getString("subtitle"));
|
||||
int fadeIn = section.getInt("fade-in", 20);
|
||||
int stay = section.getInt("stay", 30);
|
||||
int fadeOut = section.getInt("fade-out", 10);
|
||||
int range = section.getInt("range", 0);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= range) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
AdventureHelper.sendTitle(audience,
|
||||
AdventureHelper.miniMessage(title.render(context)),
|
||||
AdventureHelper.miniMessage(subtitle.render(context)),
|
||||
fadeIn, stay, fadeOut
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title-nearby action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "title-nearby");
|
||||
}
|
||||
|
||||
protected void registerParticleAction() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
Particle particleType = ParticleUtils.getParticle(section.getString("particle", "ASH").toUpperCase(Locale.ENGLISH));
|
||||
double x = section.getDouble("x",0.0);
|
||||
double y = section.getDouble("y",0.0);
|
||||
double z = section.getDouble("z",0.0);
|
||||
double offSetX = section.getDouble("offset-x",0.0);
|
||||
double offSetY = section.getDouble("offset-y",0.0);
|
||||
double offSetZ = section.getDouble("offset-z",0.0);
|
||||
int count = section.getInt("count", 1);
|
||||
double extra = section.getDouble("extra", 0.0);
|
||||
float scale = section.getDouble("scale", 1d).floatValue();
|
||||
|
||||
ItemStack itemStack;
|
||||
if (section.contains("itemStack"))
|
||||
itemStack = BukkitCustomCropsPlugin.getInstance()
|
||||
.getItemManager()
|
||||
.build(null, section.getString("itemStack"));
|
||||
else
|
||||
itemStack = null;
|
||||
|
||||
Color color;
|
||||
if (section.contains("color")) {
|
||||
String[] rgb = section.getString("color","255,255,255").split(",");
|
||||
color = Color.fromRGB(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
|
||||
} else {
|
||||
color = null;
|
||||
}
|
||||
|
||||
Color toColor;
|
||||
if (section.contains("color")) {
|
||||
String[] rgb = section.getString("to-color","255,255,255").split(",");
|
||||
toColor = Color.fromRGB(Integer.parseInt(rgb[0]), Integer.parseInt(rgb[1]), Integer.parseInt(rgb[2]));
|
||||
} else {
|
||||
toColor = null;
|
||||
}
|
||||
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
location.getWorld().spawnParticle(
|
||||
particleType,
|
||||
location.getX() + x, location.getY() + y, location.getZ() + z,
|
||||
count,
|
||||
offSetX, offSetY, offSetZ,
|
||||
extra,
|
||||
itemStack != null ? itemStack : (color != null && toColor != null ? new Particle.DustTransition(color, toColor, scale) : (color != null ? new Particle.DustOptions(color, scale) : null))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at particle action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "particle");
|
||||
}
|
||||
|
||||
protected void registerQualityCropsAction() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
MathValue<T> min = MathValue.auto(section.get("min"));
|
||||
MathValue<T> max = MathValue.auto(section.get("max"));
|
||||
boolean toInv = section.getBoolean("to-inventory", false);
|
||||
String[] qualityLoots = new String[ConfigManager.defaultQualityRatio().length];
|
||||
for (int i = 1; i <= ConfigManager.defaultQualityRatio().length; i++) {
|
||||
qualityLoots[i-1] = section.getString("items." + i);
|
||||
if (qualityLoots[i-1] == null) {
|
||||
plugin.getPluginLogger().warn("items." + i + " should not be null");
|
||||
qualityLoots[i-1] = "";
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
double[] ratio = ConfigManager.defaultQualityRatio();
|
||||
int random = RandomUtils.generateRandomInt((int) min.evaluate(context), (int) max.evaluate(context));
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
Optional<CustomCropsWorld<?>> world = plugin.getWorldManager().getWorld(location.getWorld());
|
||||
if (world.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
Fertilizer[] fertilizers = null;
|
||||
Player player = null;
|
||||
if (context.holder() instanceof Player p) {
|
||||
player = p;
|
||||
}
|
||||
Pos3 potLocation = pos3.add(0, -1, 0);
|
||||
Optional<CustomCropsChunk> chunk = world.get().getChunk(potLocation.toChunkPos());
|
||||
if (chunk.isPresent()) {
|
||||
Optional<CustomCropsBlockState> state = chunk.get().getBlockState(potLocation);
|
||||
if (state.isPresent()) {
|
||||
if (state.get().type() instanceof PotBlock potBlock) {
|
||||
fertilizers = potBlock.fertilizers(state.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayList<FertilizerConfig> configs = new ArrayList<>();
|
||||
if (fertilizers != null) {
|
||||
for (Fertilizer fertilizer : fertilizers) {
|
||||
Optional.ofNullable(fertilizer.config()).ifPresent(configs::add);
|
||||
}
|
||||
}
|
||||
for (FertilizerConfig config : configs) {
|
||||
random = config.processDroppedItemAmount(random);
|
||||
double[] newRatio = config.overrideQualityRatio();
|
||||
if (newRatio != null) {
|
||||
ratio = newRatio;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < random; i++) {
|
||||
double r1 = Math.random();
|
||||
for (int j = 0; j < ratio.length; j++) {
|
||||
if (r1 < ratio[j]) {
|
||||
ItemStack drop = plugin.getItemManager().build(player, qualityLoots[j]);
|
||||
if (drop == null || drop.getType() == Material.AIR) return;
|
||||
if (toInv && player != null) {
|
||||
PlayerUtils.giveItem(player, drop, 1);
|
||||
} else {
|
||||
location.getWorld().dropItemNaturally(location, drop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at quality-crops action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "quality-crops");
|
||||
}
|
||||
|
||||
protected void registerDropItemsAction() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
boolean ignoreFertilizer = section.getBoolean("ignore-fertilizer", true);
|
||||
String item = section.getString("item");
|
||||
MathValue<T> min = MathValue.auto(section.get("min"));
|
||||
MathValue<T> max = MathValue.auto(section.get("max"));
|
||||
boolean toInv = section.getBoolean("to-inventory", false);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
Optional<CustomCropsWorld<?>> world = plugin.getWorldManager().getWorld(location.getWorld());
|
||||
if (world.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player player = null;
|
||||
if (context.holder() instanceof Player p) {
|
||||
player = p;
|
||||
}
|
||||
ItemStack itemStack = plugin.getItemManager().build(player, item);
|
||||
if (itemStack != null) {
|
||||
int random = RandomUtils.generateRandomInt((int) min.evaluate(context), (int) max.evaluate(context));
|
||||
if (!ignoreFertilizer) {
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
Fertilizer[] fertilizers = null;
|
||||
Pos3 potLocation = pos3.add(0, -1, 0);
|
||||
Optional<CustomCropsChunk> chunk = world.get().getChunk(potLocation.toChunkPos());
|
||||
if (chunk.isPresent()) {
|
||||
Optional<CustomCropsBlockState> state = chunk.get().getBlockState(potLocation);
|
||||
if (state.isPresent()) {
|
||||
if (state.get().type() instanceof PotBlock potBlock) {
|
||||
fertilizers = potBlock.fertilizers(state.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayList<FertilizerConfig> configs = new ArrayList<>();
|
||||
if (fertilizers != null) {
|
||||
for (Fertilizer fertilizer : fertilizers) {
|
||||
Optional.ofNullable(fertilizer.config()).ifPresent(configs::add);
|
||||
}
|
||||
}
|
||||
for (FertilizerConfig config : configs) {
|
||||
random = config.processDroppedItemAmount(random);
|
||||
}
|
||||
}
|
||||
itemStack.setAmount(random);
|
||||
if (toInv && player != null) {
|
||||
PlayerUtils.giveItem(player, itemStack, random);
|
||||
} else {
|
||||
location.getWorld().dropItemNaturally(location, itemStack);
|
||||
}
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Item: " + item + " doesn't exist");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at drop-item action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "drop-item");
|
||||
}
|
||||
|
||||
protected void registerLegacyDropItemsAction() {
|
||||
registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<Action<T>> actions = new ArrayList<>();
|
||||
Section otherItemSection = section.getSection("other-items");
|
||||
if (otherItemSection != null) {
|
||||
for (Map.Entry<String, Object> entry : otherItemSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
actions.add(requireNonNull(getActionFactory("drop-item")).process(inner, inner.getDouble("chance", 1D)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Section qualitySection = section.getSection("quality-crops");
|
||||
if (qualitySection != null) {
|
||||
actions.add(requireNonNull(getActionFactory("quality-crops")).process(qualitySection, 1));
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Action<T> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at drop-items action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "drop-items");
|
||||
}
|
||||
|
||||
private void registerFakeItemAction() {
|
||||
registerAction(((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String itemID = section.getString("item", "");
|
||||
String[] split = itemID.split(":");
|
||||
if (split.length >= 2) itemID = split[split.length - 1];
|
||||
MathValue<T> duration = MathValue.auto(section.get("duration", 20));
|
||||
boolean position = !section.getString("position", "player").equals("player");
|
||||
MathValue<T> x = MathValue.auto(section.get("x", 0));
|
||||
MathValue<T> y = MathValue.auto(section.get("y", 0));
|
||||
MathValue<T> z = MathValue.auto(section.get("z", 0));
|
||||
MathValue<T> yaw = MathValue.auto(section.get("yaw", 0));
|
||||
int range = section.getInt("range", 0);
|
||||
boolean useItemDisplay = section.getBoolean("use-item-display", false);
|
||||
boolean onlyShowToOne = !section.getBoolean("visible-to-all", true);
|
||||
String finalItemID = itemID;
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
if (context.argOrDefault(ContextKeys.OFFLINE, false)) return;
|
||||
Player owner = null;
|
||||
if (context.holder() instanceof Player p) {
|
||||
owner = p;
|
||||
}
|
||||
Location location = position ? requireNonNull(context.arg(ContextKeys.LOCATION)).clone() : requireNonNull(owner).getLocation().clone();
|
||||
location.add(x.evaluate(context), y.evaluate(context) - 1, z.evaluate(context));
|
||||
location.setPitch(0);
|
||||
location.setYaw((float) yaw.evaluate(context));
|
||||
FakeEntity fakeEntity;
|
||||
if (useItemDisplay && VersionHelper.isVersionNewerThan1_19_4()) {
|
||||
location.add(0,1.5,0);
|
||||
FakeItemDisplay itemDisplay = SparrowHeart.getInstance().createFakeItemDisplay(location);
|
||||
itemDisplay.item(plugin.getItemManager().build(owner, finalItemID));
|
||||
fakeEntity = itemDisplay;
|
||||
} else {
|
||||
FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location);
|
||||
armorStand.invisible(true);
|
||||
armorStand.equipment(EquipmentSlot.HEAD, plugin.getItemManager().build(owner, finalItemID));
|
||||
fakeEntity = armorStand;
|
||||
}
|
||||
ArrayList<Player> viewers = new ArrayList<>();
|
||||
if (onlyShowToOne) {
|
||||
viewers.add(owner);
|
||||
} else {
|
||||
if (range > 0) {
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= range) {
|
||||
viewers.add(player);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewers.add(owner);
|
||||
}
|
||||
}
|
||||
for (Player player : viewers) {
|
||||
fakeEntity.spawn(player);
|
||||
}
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
for (Player player : viewers) {
|
||||
if (player.isOnline() && player.isValid()) {
|
||||
fakeEntity.destroy(player);
|
||||
}
|
||||
}
|
||||
}, (long) (duration.evaluate(context) * 50), TimeUnit.MILLISECONDS);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at fake-item action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}), "fake-item");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerPlantAction() {
|
||||
this.registerAction((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
int point = section.getInt("point", 0);
|
||||
String key = requireNonNull(section.getString("crop"));
|
||||
int y = section.getInt("y", 0);
|
||||
boolean triggerAction = section.getBoolean("trigger-event", false);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
CropConfig cropConfig = Registries.CROP.get(key);
|
||||
if (cropConfig == null) {
|
||||
plugin.getPluginLogger().warn("`plant` action is not executed due to crop[" + key + "] not exists");
|
||||
return;
|
||||
}
|
||||
Location cropLocation = requireNonNull(context.arg(ContextKeys.LOCATION)).clone().add(0,y,0);
|
||||
Location potLocation = cropLocation.clone().subtract(0,1,0);
|
||||
Optional<CustomCropsWorld<?>> optionalWorld = plugin.getWorldManager().getWorld(cropLocation.getWorld());
|
||||
if (optionalWorld.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
CustomCropsWorld<?> world = optionalWorld.get();
|
||||
PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic();
|
||||
Pos3 potPos3 = Pos3.from(potLocation);
|
||||
String potItemID = plugin.getItemManager().blockID(potLocation);
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(potItemID);
|
||||
CustomCropsBlockState potState = potBlock.fixOrGetState(world, potPos3, potConfig, potItemID);
|
||||
if (potState == null) {
|
||||
plugin.getPluginLogger().warn("Pot doesn't exist below the crop when executing `plant` action at location[" + world.worldName() + "," + potPos3 + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
CropBlock cropBlock = (CropBlock) BuiltInBlockMechanics.CROP.mechanic();
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState();
|
||||
cropBlock.id(state, key);
|
||||
cropBlock.point(state, point);
|
||||
|
||||
if (context.holder() instanceof Player player) {
|
||||
EquipmentSlot slot = requireNonNull(context.arg(ContextKeys.SLOT));
|
||||
CropPlantEvent plantEvent = new CropPlantEvent(player, player.getInventory().getItem(slot), slot, cropLocation, cropConfig, state, point);
|
||||
if (EventUtils.fireAndCheckCancel(plantEvent)) {
|
||||
return;
|
||||
}
|
||||
cropBlock.point(state, plantEvent.getPoint());
|
||||
if (triggerAction) {
|
||||
ActionManager.trigger((Context<Player>) context, cropConfig.plantActions());
|
||||
}
|
||||
}
|
||||
|
||||
CropStageConfig stageConfigWithModel = cropConfig.stageWithModelByPoint(cropBlock.point(state));
|
||||
world.addBlockState(Pos3.from(cropLocation), state);
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
plugin.getItemManager().remove(cropLocation, ExistenceForm.ANY);
|
||||
plugin.getItemManager().place(cropLocation, stageConfigWithModel.existenceForm(), requireNonNull(stageConfigWithModel.stageID()), cropConfig.rotation() ? FurnitureRotation.random() : FurnitureRotation.NONE);
|
||||
}, cropLocation);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plant action which is expected to be `Section`");
|
||||
return Action.empty();
|
||||
}
|
||||
}, "plant", "replant");
|
||||
}
|
||||
|
||||
protected void registerBreakAction() {
|
||||
this.registerAction((args, chance) -> {
|
||||
boolean triggerEvent = (boolean) args;
|
||||
return context -> {
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
Optional<CustomCropsWorld<?>> optionalWorld = plugin.getWorldManager().getWorld(location.getWorld());
|
||||
if (optionalWorld.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
CustomCropsWorld<?> world = optionalWorld.get();
|
||||
Optional<CustomCropsBlockState> optionalState = world.getBlockState(pos3);
|
||||
if (optionalState.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
CustomCropsBlockState state = optionalState.get();
|
||||
if (!(state.type() instanceof CropBlock cropBlock)) {
|
||||
return;
|
||||
}
|
||||
CropConfig config = cropBlock.config(state);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
if (triggerEvent) {
|
||||
CropStageConfig stageConfig = config.stageWithModelByPoint(cropBlock.point(state));
|
||||
Player player = null;
|
||||
if (context.holder() instanceof Player p) {
|
||||
player = p;
|
||||
}
|
||||
FakeCancellable fakeCancellable = new FakeCancellable();
|
||||
if (player != null) {
|
||||
EquipmentSlot slot = requireNonNull(context.arg(ContextKeys.SLOT));
|
||||
ItemStack itemStack = player.getInventory().getItem(slot);
|
||||
state.type().onBreak(new WrappedBreakEvent(player, null, world, location, stageConfig.stageID(), itemStack, plugin.getItemManager().id(itemStack), BreakReason.ACTION, fakeCancellable));
|
||||
} else {
|
||||
state.type().onBreak(new WrappedBreakEvent(null, null, world, location, stageConfig.stageID(), null, null, BreakReason.ACTION, fakeCancellable));
|
||||
}
|
||||
if (fakeCancellable.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
world.removeBlockState(pos3);
|
||||
plugin.getItemManager().remove(location, ExistenceForm.ANY);
|
||||
};
|
||||
}, "break");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,16 +15,25 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.item.fertilizer;
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.item.Fertilizer;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
|
||||
public interface Variation extends Fertilizer {
|
||||
/**
|
||||
* The Action interface defines a generic action that can be triggered based on a provided context.
|
||||
*
|
||||
* @param <T> the type of the object that is used in the context for triggering the action.
|
||||
*/
|
||||
public interface Action<T> {
|
||||
|
||||
/**
|
||||
* Get the bonus of variation chance
|
||||
* Triggers the action based on the provided condition.
|
||||
*
|
||||
* @return chance bonus
|
||||
* @param context the context
|
||||
*/
|
||||
double getChanceBonus();
|
||||
void trigger(Context<T> context);
|
||||
|
||||
static <T> Action<T> empty() {
|
||||
return EmptyAction.instance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
/**
|
||||
* Abstract class representing an expansion of an action.
|
||||
* This class should be extended to provide specific implementations of actions.
|
||||
*
|
||||
* @param <T> the type parameter for the action factory
|
||||
*/
|
||||
public abstract class ActionExpansion<T> {
|
||||
|
||||
/**
|
||||
* Retrieves the version of this action expansion.
|
||||
*
|
||||
* @return a String representing the version of the action expansion
|
||||
*/
|
||||
public abstract String getVersion();
|
||||
|
||||
/**
|
||||
* Retrieves the author of this action expansion.
|
||||
*
|
||||
* @return a String representing the author of the action expansion
|
||||
*/
|
||||
public abstract String getAuthor();
|
||||
|
||||
/**
|
||||
* Retrieves the type of this action.
|
||||
*
|
||||
* @return a String representing the type of action
|
||||
*/
|
||||
public abstract String getActionType();
|
||||
|
||||
/**
|
||||
* Retrieves the action factory associated with this action expansion.
|
||||
*
|
||||
* @return an ActionFactory of type T that creates instances of the action
|
||||
*/
|
||||
public abstract ActionFactory<T> getActionFactory();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,18 +15,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.common.item;
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customcrops.api.mechanic.requirement.State;
|
||||
|
||||
public interface EventItem {
|
||||
/**
|
||||
* Interface representing a factory for creating actions.
|
||||
*
|
||||
* @param <T> the type of object that the action will operate on
|
||||
*/
|
||||
public interface ActionFactory<T> {
|
||||
|
||||
/**
|
||||
* Trigger events
|
||||
* Constructs an action based on the provided arguments.
|
||||
*
|
||||
* @param actionTrigger trigger
|
||||
* @param state state
|
||||
* @param args the args containing the arguments needed to build the action
|
||||
* @return the constructed action
|
||||
*/
|
||||
void trigger(ActionTrigger actionTrigger, State state);
|
||||
Action<T> process(Object args, double chance);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.common.plugin.feature.Reloadable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The ActionManager interface manages custom action types and provides methods for handling actions.
|
||||
*
|
||||
* @param <T> the type of the context in which the actions are triggered.
|
||||
*/
|
||||
public interface ActionManager<T> extends Reloadable {
|
||||
|
||||
/**
|
||||
* Registers a custom action type with its corresponding factory.
|
||||
*
|
||||
* @param actionFactory The factory responsible for creating instances of the action.
|
||||
* @param alias The type identifier of the action.
|
||||
* @return True if registration was successful, false if the type is already registered.
|
||||
*/
|
||||
boolean registerAction(ActionFactory<T> actionFactory, String... alias);
|
||||
|
||||
/**
|
||||
* Unregisters a custom action type.
|
||||
*
|
||||
* @param type The type identifier of the action to unregister.
|
||||
* @return True if unregistration was successful, false if the type is not registered.
|
||||
*/
|
||||
boolean unregisterAction(String type);
|
||||
|
||||
/**
|
||||
* Checks if an action type is registered.
|
||||
*
|
||||
* @param type The type identifier of the action.
|
||||
* @return True if the action type is registered, otherwise false.
|
||||
*/
|
||||
boolean hasAction(@NotNull String type);
|
||||
|
||||
/**
|
||||
* Retrieves the action factory for the specified action type.
|
||||
*
|
||||
* @param type The type identifier of the action.
|
||||
* @return The action factory for the specified type, or null if no factory is found.
|
||||
*/
|
||||
@Nullable
|
||||
ActionFactory<T> getActionFactory(@NotNull String type);
|
||||
|
||||
/**
|
||||
* Parses an action from a configuration section.
|
||||
*
|
||||
* @param section The configuration section containing the action definition.
|
||||
* @return The parsed action.
|
||||
*/
|
||||
Action<T> parseAction(Section section);
|
||||
|
||||
/**
|
||||
* Parses an array of actions from a configuration section.
|
||||
*
|
||||
* @param section The configuration section containing the action definitions.
|
||||
* @return An array of parsed actions.
|
||||
*/
|
||||
@NotNull
|
||||
Action<T>[] parseActions(Section section);
|
||||
|
||||
/**
|
||||
* Parses an action from the given type and arguments.
|
||||
*
|
||||
* @param type The type identifier of the action.
|
||||
* @param args The arguments for the action.
|
||||
* @return The parsed action.
|
||||
*/
|
||||
Action<T> parseAction(@NotNull String type, @NotNull Object args);
|
||||
|
||||
/**
|
||||
* Generates a map of actions triggered by specific events from a configuration section.
|
||||
*
|
||||
* @param section The configuration section containing event-action mappings.
|
||||
* @return A map where the keys are action triggers and the values are arrays of actions associated with those triggers.
|
||||
*/
|
||||
default Map<ActionTrigger, Action<T>[]> parseEventActions(Section section) {
|
||||
HashMap<ActionTrigger, Action<T>[]> actionMap = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
try {
|
||||
actionMap.put(
|
||||
ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)),
|
||||
parseActions(innerSection)
|
||||
);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a configuration section to generate a map of timed actions.
|
||||
*
|
||||
* @param section The configuration section containing time-action mappings.
|
||||
* @return A TreeMap where the keys are time values (in integer form) and the values are arrays of actions associated with those times.
|
||||
*/
|
||||
default TreeMap<Integer, Action<T>[]> parseTimesActions(Section section) {
|
||||
TreeMap<Integer, Action<T>[]> actionMap = new TreeMap<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
actionMap.put(Integer.parseInt(entry.getKey()), parseActions(innerSection));
|
||||
}
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a list of actions with the given context.
|
||||
* If the list of actions is not null, each action in the list is triggered.
|
||||
*
|
||||
* @param context The context associated with the actions.
|
||||
* @param actions The list of actions to trigger.
|
||||
*/
|
||||
static <T> void trigger(@NotNull Context<T> context, @Nullable List<Action<T>> actions) {
|
||||
if (actions != null)
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an array of actions with the given context.
|
||||
* If the array of actions is not null, each action in the array is triggered.
|
||||
*
|
||||
* @param context The context associated with the actions.
|
||||
* @param actions The array of actions to trigger.
|
||||
*/
|
||||
static <T> void trigger(@NotNull Context<T> context, @Nullable Action<T>[] actions) {
|
||||
if (actions != null)
|
||||
for (Action<T> action : actions)
|
||||
action.trigger(context);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,12 +15,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.item;
|
||||
package net.momirealms.customcrops.api.action;
|
||||
|
||||
public enum ItemType {
|
||||
CROP,
|
||||
POT,
|
||||
SPRINKLER,
|
||||
GREENHOUSE,
|
||||
SCARECROW
|
||||
public enum ActionTrigger {
|
||||
PLACE,
|
||||
BREAK,
|
||||
WORK, INTERACT
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 net.momirealms.customcrops.api.context.Context;
|
||||
|
||||
/**
|
||||
* 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 <T> EmptyAction<T> instance() {
|
||||
return new EmptyAction<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trigger(Context<T> context) {
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.common;
|
||||
|
||||
public class Tuple<L, M, R> {
|
||||
|
||||
private L left;
|
||||
private M mid;
|
||||
private R right;
|
||||
|
||||
public Tuple(L left, M mid, R right) {
|
||||
this.left = left;
|
||||
this.mid = mid;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public static <L, M, R> Tuple<L, M, R> of(final L left, final M mid, final R right) {
|
||||
return new Tuple<>(left, mid, right);
|
||||
}
|
||||
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public void setLeft(L left) {
|
||||
this.left = left;
|
||||
}
|
||||
|
||||
public M getMid() {
|
||||
return mid;
|
||||
}
|
||||
|
||||
public void setMid(M mid) {
|
||||
this.mid = mid;
|
||||
}
|
||||
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public void setRight(R right) {
|
||||
this.right = right;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.common.item;
|
||||
|
||||
public interface KeyItem {
|
||||
|
||||
/**
|
||||
* Get item's key
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
String getKey();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.momirealms.customcrops.api.context;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public abstract class AbstractContext<T> implements Context<T> {
|
||||
|
||||
private final T holder;
|
||||
private final Map<ContextKeys<?>, Object> args;
|
||||
private final Map<String, String> placeholderMap;
|
||||
|
||||
public AbstractContext(@Nullable T holder, boolean sync) {
|
||||
this.holder = holder;
|
||||
this.args = sync ? new ConcurrentHashMap<>() : new HashMap<>();
|
||||
this.placeholderMap = sync ? new ConcurrentHashMap<>() : new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContextKeys<?>, Object> args() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> placeholderMap() {
|
||||
return placeholderMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> AbstractContext<T> arg(ContextKeys<C> key, C value) {
|
||||
if (key == null || value == null) return this;
|
||||
this.args.put(key, value);
|
||||
this.placeholderMap.put("{" + key.key() + "}", value.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContext<T> combine(Context<T> other) {
|
||||
this.args.putAll(other.args());
|
||||
this.placeholderMap.putAll(other.placeholderMap());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C> C arg(ContextKeys<C> key) {
|
||||
return (C) args.get(key);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <C> C remove(ContextKeys<C> key) {
|
||||
placeholderMap.remove("{" + key.key() + "}");
|
||||
return (C) args.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T holder() {
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.momirealms.customcrops.api.context;
|
||||
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BlockContextImpl extends AbstractContext<CustomCropsBlockState> {
|
||||
|
||||
public BlockContextImpl(@NotNull CustomCropsBlockState block, boolean sync) {
|
||||
super(block, sync);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlockContext{" +
|
||||
"args=" + args() +
|
||||
", block=" + holder() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.context;
|
||||
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The Context interface represents a generic context for custom crops mechanics.
|
||||
* It allows for storing and retrieving arguments, as well as getting the holder
|
||||
* of the context. This can be used to maintain state or pass parameters within
|
||||
* the custom crops mechanics.
|
||||
*
|
||||
* @param <T> the type of the holder object for this context
|
||||
*/
|
||||
public interface Context<T> {
|
||||
|
||||
/**
|
||||
* Retrieves the map of arguments associated with this context.
|
||||
*
|
||||
* @return a map where the keys are argument names and the values are argument values.
|
||||
*/
|
||||
Map<ContextKeys<?>, Object> args();
|
||||
|
||||
/**
|
||||
* Converts the context to a map of placeholders
|
||||
*
|
||||
* @return a map of placeholders
|
||||
*/
|
||||
Map<String, String> placeholderMap();
|
||||
|
||||
/**
|
||||
* Adds or updates an argument in the context.
|
||||
* This method allows adding a new argument or updating the value of an existing argument.
|
||||
*
|
||||
* @param <C> the type of the value being added to the context.
|
||||
* @param key the ContextKeys key representing the argument to be added or updated.
|
||||
* @param value the value to be associated with the specified key.
|
||||
* @return the current context instance, allowing for method chaining.
|
||||
*/
|
||||
<C> Context<T> arg(ContextKeys<C> key, C value);
|
||||
|
||||
/**
|
||||
* Combines one context with another
|
||||
*
|
||||
* @param other other
|
||||
* @return this context
|
||||
*/
|
||||
Context<T> combine(Context<T> other);
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specific argument from the context.
|
||||
* This method fetches the value associated with the specified ContextKeys key.
|
||||
*
|
||||
* @param <C> the type of the value being retrieved.
|
||||
* @param key the ContextKeys key representing the argument to be retrieved.
|
||||
* @return the value associated with the specified key, or null if the key does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
<C> C arg(ContextKeys<C> key);
|
||||
|
||||
/**
|
||||
* Retrieves the value of a specific argument from the context.
|
||||
* This method fetches the value associated with the specified ContextKeys key.
|
||||
*
|
||||
* @param <C> the type of the value being retrieved.
|
||||
* @param key the ContextKeys key representing the argument to be retrieved.
|
||||
* @return the value associated with the specified key, or null if the key does not exist.
|
||||
*/
|
||||
default <C> C argOrDefault(ContextKeys<C> key, C value) {
|
||||
C result = arg(key);
|
||||
return result == null ? value : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the key from the context
|
||||
*
|
||||
* @param key the ContextKeys key
|
||||
* @return the removed value
|
||||
* @param <C> the type of the value being removed.
|
||||
*/
|
||||
@Nullable
|
||||
<C> C remove(ContextKeys<C> key);
|
||||
|
||||
/**
|
||||
* Gets the holder of this context.
|
||||
*
|
||||
* @return the holder object of type T.
|
||||
*/
|
||||
T holder();
|
||||
|
||||
/**
|
||||
* Creates a player-specific context.
|
||||
*
|
||||
* @param player the player to be used as the holder of the context.
|
||||
* @return a new Context instance with the specified player as the holder.
|
||||
*/
|
||||
static Context<Player> player(@Nullable Player player) {
|
||||
return new PlayerContextImpl(player, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block-specific context.
|
||||
*
|
||||
* @param block the block to be used as the holder of the context.
|
||||
* @return a new Context instance with the specified block as the holder.
|
||||
*/
|
||||
static Context<CustomCropsBlockState> block(@NotNull CustomCropsBlockState block) {
|
||||
return new BlockContextImpl(block, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a player-specific context.
|
||||
*
|
||||
* @param player the player to be used as the holder of the context.
|
||||
* @param threadSafe is the created map thread safe
|
||||
* @return a new Context instance with the specified player as the holder.
|
||||
*/
|
||||
static Context<Player> player(@Nullable Player player, boolean threadSafe) {
|
||||
return new PlayerContextImpl(player, threadSafe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block-specific context.
|
||||
*
|
||||
* @param block the block to be used as the holder of the context.
|
||||
* @param threadSafe is the created map thread safe
|
||||
* @return a new Context instance with the specified block as the holder.
|
||||
*/
|
||||
static Context<CustomCropsBlockState> block(@NotNull CustomCropsBlockState block, boolean threadSafe) {
|
||||
return new BlockContextImpl(block, threadSafe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.context;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents keys for accessing context values with specific types.
|
||||
*
|
||||
* @param <T> the type of the value associated with the context key.
|
||||
*/
|
||||
public class ContextKeys<T> {
|
||||
|
||||
public static final ContextKeys<Location> LOCATION = of("location", Location.class);
|
||||
public static final ContextKeys<Integer> X = of("x", Integer.class);
|
||||
public static final ContextKeys<Integer> Y = of("y", Integer.class);
|
||||
public static final ContextKeys<Integer> Z = of("z", Integer.class);
|
||||
public static final ContextKeys<String> WORLD = of("world", String.class);
|
||||
public static final ContextKeys<String> PLAYER = of("player", String.class);
|
||||
public static final ContextKeys<EquipmentSlot> SLOT = of("slot", EquipmentSlot.class);
|
||||
public static final ContextKeys<String> TEMP_NEAR_PLAYER = of("near", String.class);
|
||||
public static final ContextKeys<Boolean> OFFLINE = of("offline", Boolean.class);
|
||||
|
||||
private final String key;
|
||||
private final Class<T> type;
|
||||
|
||||
protected ContextKeys(String key, Class<T> type) {
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key.
|
||||
*
|
||||
* @return the key.
|
||||
*/
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type associated with the key.
|
||||
*
|
||||
* @return the type.
|
||||
*/
|
||||
public Class<T> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context key.
|
||||
*
|
||||
* @param key the key.
|
||||
* @param type the type.
|
||||
* @param <T> the type of the value.
|
||||
* @return a new ContextKeys instance.
|
||||
*/
|
||||
public static <T> ContextKeys<T> of(String key, Class<T> type) {
|
||||
return new ContextKeys<T>(key, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(final Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
} else if (other != null && this.getClass() == other.getClass()) {
|
||||
ContextKeys<?> that = (ContextKeys) other;
|
||||
return Objects.equals(this.key, that.key);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return Objects.hashCode(this.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContextKeys{" +
|
||||
"key='" + key + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) <2024> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.context;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class PlayerContextImpl extends AbstractContext<Player> {
|
||||
|
||||
public PlayerContextImpl(@Nullable Player player, boolean sync) {
|
||||
super(player, sync);
|
||||
if (player == null) return;
|
||||
final Location location = player.getLocation();
|
||||
arg(ContextKeys.PLAYER, player.getName())
|
||||
.arg(ContextKeys.LOCATION, location)
|
||||
.arg(ContextKeys.X, location.getBlockX())
|
||||
.arg(ContextKeys.Y, location.getBlockY())
|
||||
.arg(ContextKeys.Z, location.getBlockZ())
|
||||
.arg(ContextKeys.WORLD, location.getWorld().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerContext{" +
|
||||
"args=" + args() +
|
||||
", player=" + holder() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.helper.VersionHelper;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractCustomEventListener implements Listener {
|
||||
|
||||
private final HashSet<EntityType> entities = new HashSet<>();
|
||||
private final HashSet<Material> blocks = new HashSet<>();
|
||||
|
||||
protected final AbstractItemManager itemManager;
|
||||
|
||||
public AbstractCustomEventListener(AbstractItemManager itemManager) {
|
||||
this.itemManager = itemManager;
|
||||
this.entities.addAll(List.of(EntityType.ITEM_FRAME, EntityType.ARMOR_STAND));
|
||||
if (VersionHelper.isVersionNewerThan1_19_4()) {
|
||||
this.entities.addAll(List.of(EntityType.ITEM_DISPLAY, EntityType.INTERACTION));
|
||||
}
|
||||
this.blocks.addAll(List.of(
|
||||
Material.NOTE_BLOCK,
|
||||
Material.MUSHROOM_STEM, Material.BROWN_MUSHROOM_BLOCK, Material.RED_MUSHROOM_BLOCK,
|
||||
Material.TRIPWIRE,
|
||||
Material.CHORUS_PLANT, Material.CHORUS_FLOWER,
|
||||
Material.ACACIA_LEAVES, Material.BIRCH_LEAVES, Material.JUNGLE_LEAVES, Material.DARK_OAK_LEAVES, Material.AZALEA_LEAVES, Material.FLOWERING_AZALEA_LEAVES, Material.OAK_LEAVES, Material.SPRUCE_LEAVES,
|
||||
Material.CAVE_VINES, Material.TWISTING_VINES, Material.WEEPING_VINES,
|
||||
Material.KELP,
|
||||
Material.CACTUS
|
||||
));
|
||||
if (VersionHelper.isVersionNewerThan1_19()) {
|
||||
this.blocks.add(Material.MANGROVE_LEAVES);
|
||||
}
|
||||
if (VersionHelper.isVersionNewerThan1_20()) {
|
||||
this.blocks.add(Material.CHERRY_LEAVES);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInteractAir(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR)
|
||||
return;
|
||||
this.itemManager.handlePlayerInteractAir(
|
||||
event.getPlayer(),
|
||||
event.getHand(), event.getItem()
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInteractBlock(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
Block block = event.getClickedBlock();
|
||||
assert block != null;
|
||||
if (blocks.contains(block.getType())) {
|
||||
return;
|
||||
}
|
||||
this.itemManager.handlePlayerInteractBlock(
|
||||
event.getPlayer(),
|
||||
block,
|
||||
block.getType().name(), event.getBlockFace(),
|
||||
event.getHand(),
|
||||
event.getItem(),
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInteractEntity(PlayerInteractAtEntityEvent event) {
|
||||
EntityType type = event.getRightClicked().getType();
|
||||
if (entities.contains(type)) {
|
||||
return;
|
||||
}
|
||||
this.itemManager.handlePlayerInteractFurniture(
|
||||
event.getPlayer(),
|
||||
event.getRightClicked().getLocation(), type.name(),
|
||||
event.getHand(), event.getPlayer().getInventory().getItem(event.getHand()),
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlaceBlock(BlockPlaceEvent event) {
|
||||
Block block = event.getBlock();
|
||||
if (blocks.contains(block.getType())) {
|
||||
return;
|
||||
}
|
||||
this.itemManager.handlePlayerPlace(
|
||||
event.getPlayer(),
|
||||
block.getLocation(),
|
||||
block.getType().name(),
|
||||
event.getHand(),
|
||||
event.getItemInHand(),
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBreakBlock(BlockBreakEvent event) {
|
||||
Block block = event.getBlock();
|
||||
if (blocks.contains(block.getType())) {
|
||||
return;
|
||||
}
|
||||
this.itemManager.handlePlayerBreak(
|
||||
event.getPlayer(),
|
||||
block.getLocation(), event.getPlayer().getInventory().getItemInMainHand(), block.getType().name(),
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public abstract class AbstractItemManager implements ItemManager {
|
||||
|
||||
public abstract void handlePlayerInteractAir(
|
||||
Player player,
|
||||
EquipmentSlot hand,
|
||||
ItemStack itemInHand
|
||||
);
|
||||
|
||||
public abstract void handlePlayerInteractBlock(
|
||||
Player player,
|
||||
Block block,
|
||||
String blockID,
|
||||
BlockFace blockFace,
|
||||
EquipmentSlot hand,
|
||||
ItemStack itemInHand,
|
||||
Cancellable event
|
||||
);
|
||||
|
||||
// it's not a good choice to use Entity as parameter because the entity might be fake
|
||||
public abstract void handlePlayerInteractFurniture(
|
||||
Player player,
|
||||
Location location,
|
||||
String furnitureID,
|
||||
EquipmentSlot hand,
|
||||
ItemStack itemInHand,
|
||||
Cancellable event
|
||||
);
|
||||
|
||||
public abstract void handlePlayerBreak(
|
||||
Player player,
|
||||
Location location,
|
||||
ItemStack itemInHand,
|
||||
String brokenID,
|
||||
Cancellable event
|
||||
);
|
||||
|
||||
public abstract void handlePlayerPlace(
|
||||
Player player,
|
||||
Location location,
|
||||
String placedID,
|
||||
EquipmentSlot hand,
|
||||
ItemStack itemInHand,
|
||||
Cancellable event
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class BuiltInBlockMechanics {
|
||||
|
||||
public static final BuiltInBlockMechanics CROP = create("crop");
|
||||
public static final BuiltInBlockMechanics SPRINKLER = create("sprinkler");
|
||||
public static final BuiltInBlockMechanics GREENHOUSE = create("greenhouse");
|
||||
public static final BuiltInBlockMechanics POT = create("pot");
|
||||
public static final BuiltInBlockMechanics SCARECROW = create("scarecrow");
|
||||
|
||||
private final Key key;
|
||||
|
||||
public BuiltInBlockMechanics(Key key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
static BuiltInBlockMechanics create(String id) {
|
||||
return new BuiltInBlockMechanics(Key.key("customcrops", id));
|
||||
}
|
||||
|
||||
public Key key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public CustomCropsBlockState createBlockState() {
|
||||
return mechanic().createBlockState();
|
||||
}
|
||||
|
||||
public CustomCropsBlockState createBlockState(CompoundMap data) {
|
||||
return mechanic().createBlockState(data);
|
||||
}
|
||||
|
||||
public CustomCropsBlock mechanic() {
|
||||
return Objects.requireNonNull(Registries.BLOCK.get(key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.item.CustomCropsItem;
|
||||
|
||||
public class BuiltInItemMechanics {
|
||||
|
||||
public static final BuiltInItemMechanics WATERING_CAN = create("watering_can");
|
||||
public static final BuiltInItemMechanics FERTILIZER = create("fertilizer");
|
||||
public static final BuiltInItemMechanics SEED = create("seed");
|
||||
public static final BuiltInItemMechanics SPRINKLER_ITEM = create("sprinkler_item");
|
||||
|
||||
private final Key key;
|
||||
|
||||
public BuiltInItemMechanics(Key key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
static BuiltInItemMechanics create(String id) {
|
||||
return new BuiltInItemMechanics(Key.key("customcrops", id));
|
||||
}
|
||||
|
||||
public Key key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public CustomCropsItem mechanic() {
|
||||
return Registries.ITEM.get(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
|
||||
public class ClearableMappedRegistry<K, T> extends MappedRegistry<K, T> implements ClearableRegistry<K, T> {
|
||||
|
||||
public ClearableMappedRegistry(Key key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.byID.clear();
|
||||
super.byKey.clear();
|
||||
super.byValue.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public interface ClearableRegistry<K, T> extends WriteableRegistry<K, T> {
|
||||
|
||||
void clear();
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
|
||||
import dev.dejvokep.boostedyaml.libs.org.snakeyaml.engine.v2.common.ScalarStyle;
|
||||
import dev.dejvokep.boostedyaml.libs.org.snakeyaml.engine.v2.nodes.Tag;
|
||||
import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
|
||||
import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
|
||||
import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
|
||||
import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
|
||||
import dev.dejvokep.boostedyaml.utils.format.NodeRole;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.core.block.*;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerConfig;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
import net.momirealms.customcrops.api.core.item.WateringCanConfig;
|
||||
import net.momirealms.customcrops.api.core.water.FillMethod;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.util.PluginUtils;
|
||||
import net.momirealms.customcrops.common.config.ConfigLoader;
|
||||
import net.momirealms.customcrops.common.plugin.feature.Reloadable;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class ConfigManager implements ConfigLoader, Reloadable {
|
||||
|
||||
private static ConfigManager instance;
|
||||
protected final BukkitCustomCropsPlugin plugin;
|
||||
|
||||
protected boolean doubleCheck;
|
||||
protected boolean metrics;
|
||||
protected boolean checkUpdate;
|
||||
protected boolean debug;
|
||||
protected String absoluteWorldPath;
|
||||
|
||||
protected Set<String> scarecrow;
|
||||
protected ExistenceForm scarecrowExistenceForm;
|
||||
protected boolean enableScarecrow;
|
||||
protected boolean protectOriginalLore;
|
||||
protected int scarecrowRange;
|
||||
protected boolean scarecrowProtectChunk;
|
||||
|
||||
protected Set<String> greenhouse;
|
||||
protected boolean enableGreenhouse;
|
||||
protected ExistenceForm greenhouseExistenceForm;
|
||||
protected int greenhouseRange;
|
||||
|
||||
protected boolean syncSeasons;
|
||||
protected String referenceWorld;
|
||||
|
||||
protected String[] itemDetectOrder;
|
||||
|
||||
protected double[] defaultQualityRatio;
|
||||
|
||||
protected boolean hasNamespace;
|
||||
|
||||
public ConfigManager(BukkitCustomCropsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static boolean syncSeasons() {
|
||||
return instance.syncSeasons;
|
||||
}
|
||||
|
||||
public static String referenceWorld() {
|
||||
return instance.referenceWorld;
|
||||
}
|
||||
|
||||
public static boolean enableGreenhouse() {
|
||||
return instance.enableGreenhouse;
|
||||
}
|
||||
|
||||
public static ExistenceForm greenhouseExistenceForm() {
|
||||
return instance.greenhouseExistenceForm;
|
||||
}
|
||||
|
||||
public static int greenhouseRange() {
|
||||
return instance.greenhouseRange;
|
||||
}
|
||||
|
||||
public static int scarecrowRange() {
|
||||
return instance.scarecrowRange;
|
||||
}
|
||||
|
||||
public static boolean enableScarecrow() {
|
||||
return instance.enableScarecrow;
|
||||
}
|
||||
|
||||
public static boolean scarecrowProtectChunk() {
|
||||
return instance.scarecrowProtectChunk;
|
||||
}
|
||||
|
||||
public static boolean debug() {
|
||||
return instance.debug;
|
||||
}
|
||||
|
||||
public static boolean metrics() {
|
||||
return instance.metrics;
|
||||
}
|
||||
|
||||
public static boolean checkUpdate() {
|
||||
return instance.checkUpdate;
|
||||
}
|
||||
|
||||
public static String absoluteWorldPath() {
|
||||
return instance.absoluteWorldPath;
|
||||
}
|
||||
|
||||
public static Set<String> greenhouse() {
|
||||
return instance.greenhouse;
|
||||
}
|
||||
|
||||
public static ExistenceForm scarecrowExistenceForm() {
|
||||
return instance.scarecrowExistenceForm;
|
||||
}
|
||||
|
||||
public static boolean doubleCheck() {
|
||||
return instance.doubleCheck;
|
||||
}
|
||||
|
||||
public static Set<String> scarecrow() {
|
||||
return instance.scarecrow;
|
||||
}
|
||||
|
||||
public static boolean protectOriginalLore() {
|
||||
return instance.protectOriginalLore;
|
||||
}
|
||||
|
||||
public static String[] itemDetectOrder() {
|
||||
return instance.itemDetectOrder;
|
||||
}
|
||||
|
||||
public static double[] defaultQualityRatio() {
|
||||
return instance.defaultQualityRatio;
|
||||
}
|
||||
|
||||
public static boolean hasNamespace() {
|
||||
return instance.hasNamespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlDocument loadConfig(String filePath) {
|
||||
return loadConfig(filePath, '.');
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlDocument loadConfig(String filePath, char routeSeparator) {
|
||||
try (InputStream inputStream = new FileInputStream(resolveConfig(filePath).toFile())) {
|
||||
return YamlDocument.create(
|
||||
inputStream,
|
||||
plugin.getResourceStream(filePath),
|
||||
GeneralSettings.builder().setRouteSeparator(routeSeparator).build(),
|
||||
LoaderSettings
|
||||
.builder()
|
||||
.setAutoUpdate(true)
|
||||
.build(),
|
||||
DumperSettings.builder()
|
||||
.setScalarFormatter((tag, value, role, def) -> {
|
||||
if (role == NodeRole.KEY) {
|
||||
return ScalarStyle.PLAIN;
|
||||
} else {
|
||||
return tag == Tag.STR ? ScalarStyle.DOUBLE_QUOTED : ScalarStyle.PLAIN;
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
UpdaterSettings
|
||||
.builder()
|
||||
.setVersioning(new BasicVersioning("config-version"))
|
||||
.build()
|
||||
);
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().severe("Failed to load config " + filePath, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlDocument loadData(File file) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return YamlDocument.create(inputStream);
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().severe("Failed to load config " + file, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlDocument loadData(File file, char routeSeparator) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return YamlDocument.create(inputStream, GeneralSettings.builder()
|
||||
.setRouteSeparator(routeSeparator)
|
||||
.build());
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().severe("Failed to load config " + file, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Path resolveConfig(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
|
||||
}
|
||||
filePath = filePath.replace('\\', '/');
|
||||
Path configFile = plugin.getConfigDirectory().resolve(filePath);
|
||||
// if the config doesn't exist, create it based on the template in the resources dir
|
||||
if (!Files.exists(configFile)) {
|
||||
try {
|
||||
Files.createDirectories(configFile.getParent());
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
try (InputStream is = plugin.getResourceStream(filePath)) {
|
||||
if (is == null) {
|
||||
throw new IllegalArgumentException("The embedded resource '" + filePath + "' cannot be found");
|
||||
}
|
||||
Files.copy(is, configFile);
|
||||
addDefaultNamespace(configFile.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return configFile;
|
||||
}
|
||||
|
||||
public abstract void registerWateringCanConfig(WateringCanConfig config);
|
||||
|
||||
public abstract void registerFertilizerConfig(FertilizerConfig config);
|
||||
|
||||
public abstract void registerCropConfig(CropConfig config);
|
||||
|
||||
public abstract void registerPotConfig(PotConfig config);
|
||||
|
||||
public abstract void registerSprinklerConfig(SprinklerConfig config);
|
||||
|
||||
public WateringMethod[] getWateringMethods(Section section) {
|
||||
ArrayList<WateringMethod> methods = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
WateringMethod fillMethod = new WateringMethod(
|
||||
Preconditions.checkNotNull(innerSection.getString("item"), "fill-method item should not be null"),
|
||||
innerSection.getInt("item-amount", 1),
|
||||
innerSection.getString("return"),
|
||||
innerSection.getInt("return-amount", 1),
|
||||
innerSection.getInt("amount", 1),
|
||||
plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions")),
|
||||
plugin.getRequirementManager(Player.class).parseRequirements(innerSection.getSection("requirements"), true)
|
||||
);
|
||||
methods.add(fillMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods.toArray(new WateringMethod[0]);
|
||||
}
|
||||
|
||||
public FillMethod[] getFillMethods(Section section) {
|
||||
ArrayList<FillMethod> methods = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
FillMethod fillMethod = new FillMethod(
|
||||
Preconditions.checkNotNull(innerSection.getString("target"), "fill-method target should not be null"),
|
||||
innerSection.getInt("amount", 1),
|
||||
plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions")),
|
||||
plugin.getRequirementManager(Player.class).parseRequirements(innerSection.getSection("requirements"), true)
|
||||
);
|
||||
methods.add(fillMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods.toArray(new FillMethod[0]);
|
||||
}
|
||||
|
||||
public HashMap<Integer, Integer> getInt2IntMap(Section section) {
|
||||
HashMap<Integer, Integer> map = new HashMap<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
try {
|
||||
int i1 = Integer.parseInt(entry.getKey());
|
||||
if (entry.getValue() instanceof Number i2) {
|
||||
map.put(i1, i2.intValue());
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public double[] getQualityRatio(String ratios) {
|
||||
String[] split = ratios.split("/");
|
||||
double[] ratio = new double[split.length];
|
||||
double weightTotal = Arrays.stream(split).mapToInt(Integer::parseInt).sum();
|
||||
double temp = 0;
|
||||
for (int i = 0; i < ratio.length; i++) {
|
||||
temp += Integer.parseInt(split[i]);
|
||||
ratio[i] = temp / weightTotal;
|
||||
}
|
||||
return ratio;
|
||||
}
|
||||
|
||||
public List<Pair<Double, Integer>> getIntChancePair(Section section) {
|
||||
ArrayList<Pair<Double, Integer>> pairs = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
int point = Integer.parseInt(entry.getKey());
|
||||
if (entry.getValue() instanceof Number n) {
|
||||
Pair<Double, Integer> pair = new Pair<>(n.doubleValue(), point);
|
||||
pairs.add(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
public HashMap<FertilizerType, Pair<String, String>> getFertilizedPotMap(Section section) {
|
||||
HashMap<FertilizerType, Pair<String, String>> map = new HashMap<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
FertilizerType type = Registries.FERTILIZER_TYPE.get(entry.getKey().replace("-", "_"));
|
||||
if (type != null) {
|
||||
map.put(type, Pair.of(
|
||||
Preconditions.checkNotNull(innerSection.getString("dry"), entry.getKey() + ".dry should not be null"),
|
||||
Preconditions.checkNotNull(innerSection.getString("wet"), entry.getKey() + ".wet should not be null")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public BoneMeal[] getBoneMeals(Section section) {
|
||||
ArrayList<BoneMeal> boneMeals = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
BoneMeal boneMeal = new BoneMeal(
|
||||
Preconditions.checkNotNull(innerSection.getString("item"), "Bone meal item can't be null"),
|
||||
innerSection.getInt("item-amount",1),
|
||||
innerSection.getString("return"),
|
||||
innerSection.getInt("return-amount",1),
|
||||
innerSection.getBoolean("dispenser",true),
|
||||
getIntChancePair(innerSection.getSection("chance")),
|
||||
plugin.getActionManager(Player.class).parseActions(innerSection.getSection("actions"))
|
||||
);
|
||||
boneMeals.add(boneMeal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return boneMeals.toArray(new BoneMeal[0]);
|
||||
}
|
||||
|
||||
public DeathCondition[] getDeathConditions(Section section, ExistenceForm original) {
|
||||
ArrayList<DeathCondition> conditions = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
DeathCondition deathCondition = new DeathCondition(
|
||||
plugin.getRequirementManager(CustomCropsBlockState.class).parseRequirements(inner.getSection("conditions"), false),
|
||||
inner.getString("model"),
|
||||
Optional.ofNullable(inner.getString("type")).map(ExistenceForm::valueOf).orElse(original),
|
||||
inner.getInt("delay", 0)
|
||||
);
|
||||
conditions.add(deathCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
return conditions.toArray(new DeathCondition[0]);
|
||||
}
|
||||
|
||||
public GrowCondition[] getGrowConditions(Section section) {
|
||||
ArrayList<GrowCondition> conditions = new ArrayList<>();
|
||||
if (section != null) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
GrowCondition growCondition = new GrowCondition(
|
||||
plugin.getRequirementManager(CustomCropsBlockState.class).parseRequirements(inner.getSection("conditions"), false),
|
||||
inner.getInt("point", 1)
|
||||
);
|
||||
conditions.add(growCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
return conditions.toArray(new GrowCondition[0]);
|
||||
}
|
||||
|
||||
protected void addDefaultNamespace(File file) {
|
||||
boolean hasNamespace = PluginUtils.isEnabled("ItemsAdder");
|
||||
String line;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append(System.lineSeparator());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
try (BufferedWriter writer = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
|
||||
String finalStr = sb.toString();
|
||||
if (!hasNamespace) {
|
||||
finalStr = finalStr.replace("CHORUS", "TRIPWIRE").replace("<font:customcrops:default>", "<font:minecraft:customcrops>");
|
||||
}
|
||||
writer.write(finalStr.replace("{0}", hasNamespace ? "customcrops:" : ""));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public enum CustomForm {
|
||||
|
||||
TRIPWIRE(ExistenceForm.BLOCK),
|
||||
NOTE_BLOCK(ExistenceForm.BLOCK),
|
||||
MUSHROOM(ExistenceForm.BLOCK),
|
||||
CHORUS(ExistenceForm.BLOCK),
|
||||
ITEM_FRAME(ExistenceForm.FURNITURE),
|
||||
ITEM_DISPLAY(ExistenceForm.FURNITURE),
|
||||
ARMOR_STAND(ExistenceForm.FURNITURE);
|
||||
|
||||
private final ExistenceForm form;
|
||||
|
||||
CustomForm(ExistenceForm form) {
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public ExistenceForm existenceForm() {
|
||||
return form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public interface CustomItemProvider {
|
||||
|
||||
boolean removeCustomBlock(Location location);
|
||||
|
||||
boolean placeCustomBlock(Location location, String id);
|
||||
|
||||
@Nullable
|
||||
Entity placeFurniture(Location location, String id);
|
||||
|
||||
boolean removeFurniture(Entity entity);
|
||||
|
||||
@Nullable
|
||||
String blockID(Block block);
|
||||
|
||||
@Nullable
|
||||
String itemID(ItemStack itemStack);
|
||||
|
||||
@Nullable
|
||||
ItemStack itemStack(Player player, String id);
|
||||
|
||||
@Nullable
|
||||
String furnitureID(Entity entity);
|
||||
|
||||
boolean isFurniture(Entity entity);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public enum ExistenceForm {
|
||||
|
||||
BLOCK,
|
||||
FURNITURE,
|
||||
ANY
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.RandomUtils;
|
||||
import org.bukkit.Rotation;
|
||||
|
||||
public enum FurnitureRotation {
|
||||
|
||||
NONE(0f),
|
||||
EAST(-90f),
|
||||
SOUTH(0f),
|
||||
WEST(90f),
|
||||
NORTH(180f);
|
||||
|
||||
private final float yaw;
|
||||
|
||||
FurnitureRotation(float yaw) {
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
public float getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
public static FurnitureRotation random() {
|
||||
return FurnitureRotation.values()[RandomUtils.generateRandomInt(1, 4)];
|
||||
}
|
||||
|
||||
public static FurnitureRotation getByRotation(Rotation rotation) {
|
||||
switch (rotation) {
|
||||
default -> {
|
||||
return FurnitureRotation.SOUTH;
|
||||
}
|
||||
case CLOCKWISE -> {
|
||||
return FurnitureRotation.WEST;
|
||||
}
|
||||
case COUNTER_CLOCKWISE -> {
|
||||
return FurnitureRotation.EAST;
|
||||
}
|
||||
case FLIPPED -> {
|
||||
return FurnitureRotation.NORTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FurnitureRotation getByYaw(float yaw) {
|
||||
yaw = (Math.abs(yaw + 180) % 360);
|
||||
switch ((int) (yaw/90)) {
|
||||
case 1 -> {
|
||||
return FurnitureRotation.WEST;
|
||||
}
|
||||
case 2 -> {
|
||||
return FurnitureRotation.NORTH;
|
||||
}
|
||||
case 3 -> {
|
||||
return FurnitureRotation.EAST;
|
||||
}
|
||||
default -> {
|
||||
return FurnitureRotation.SOUTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rotation getBukkitRotation() {
|
||||
switch (this) {
|
||||
case EAST -> {
|
||||
return Rotation.COUNTER_CLOCKWISE;
|
||||
}
|
||||
case WEST -> {
|
||||
return Rotation.CLOCKWISE;
|
||||
}
|
||||
case NORTH -> {
|
||||
return Rotation.FLIPPED;
|
||||
}
|
||||
default -> {
|
||||
return Rotation.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IdMap<T> extends Iterable<T> {
|
||||
int DEFAULT = -1;
|
||||
|
||||
int getId(T value);
|
||||
|
||||
@Nullable
|
||||
T byId(int index);
|
||||
|
||||
default T byIdOrThrow(int index) {
|
||||
T object = this.byId(index);
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException("No value with id " + index);
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
default int getIdOrThrow(T value) {
|
||||
int i = this.getId(value);
|
||||
if (i == -1) {
|
||||
throw new IllegalArgumentException("Can't find id for '" + value + "' in map " + this);
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
int size();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public enum InteractionResult {
|
||||
|
||||
SUCCESS,
|
||||
FAIL,
|
||||
PASS
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.item.Item;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ItemManager {
|
||||
|
||||
void place(@NotNull Location location, @NotNull ExistenceForm form, @NotNull String id, FurnitureRotation rotation);
|
||||
|
||||
FurnitureRotation remove(@NotNull Location location, @NotNull ExistenceForm form);
|
||||
|
||||
void placeBlock(@NotNull Location location, @NotNull String id);
|
||||
|
||||
void placeFurniture(@NotNull Location location, @NotNull String id, FurnitureRotation rotation);
|
||||
|
||||
void removeBlock(@NotNull Location location);
|
||||
|
||||
@Nullable
|
||||
FurnitureRotation removeFurniture(@NotNull Location location);
|
||||
|
||||
@NotNull
|
||||
default String blockID(@NotNull Location location) {
|
||||
return blockID(location.getBlock());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String blockID(@NotNull Block block);
|
||||
|
||||
@Nullable
|
||||
String furnitureID(@NotNull Entity entity);
|
||||
|
||||
@NotNull String entityID(@NotNull Entity entity);
|
||||
|
||||
@Nullable
|
||||
String furnitureID(Location location);
|
||||
|
||||
@NotNull
|
||||
String anyID(Location location);
|
||||
|
||||
@Nullable
|
||||
String id(Location location, ExistenceForm form);
|
||||
|
||||
void setCustomEventListener(@NotNull AbstractCustomEventListener listener);
|
||||
|
||||
void setCustomItemProvider(@NotNull CustomItemProvider provider);
|
||||
|
||||
String id(ItemStack itemStack);
|
||||
|
||||
@Nullable
|
||||
ItemStack build(Player player, String id);
|
||||
|
||||
Item<ItemStack> wrap(ItemStack itemStack);
|
||||
|
||||
void decreaseDamage(Player player, ItemStack itemStack, int amount);
|
||||
|
||||
void increaseDamage(Player holder, ItemStack itemStack, int amount);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class MappedRegistry<K, T> implements WriteableRegistry<K, T> {
|
||||
|
||||
protected final Map<K, T> byKey = new HashMap<>(1024);
|
||||
protected final Map<T, K> byValue = new IdentityHashMap<>(1024);
|
||||
protected final ArrayList<T> byID = new ArrayList<>(1024);
|
||||
private final Key key;
|
||||
|
||||
public MappedRegistry(Key key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(K key, T value) {
|
||||
byKey.put(key, value);
|
||||
byValue.put(value, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId(@Nullable T value) {
|
||||
return byID.indexOf(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public T byId(int index) {
|
||||
return byID.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return byKey.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public T get(@Nullable K key) {
|
||||
return byKey.get(key);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return this.byKey.values().iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.api.core.block.CropConfig;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.block.PotConfig;
|
||||
import net.momirealms.customcrops.api.core.block.SprinklerConfig;
|
||||
import net.momirealms.customcrops.api.core.item.CustomCropsItem;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerConfig;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
import net.momirealms.customcrops.api.core.item.WateringCanConfig;
|
||||
import net.momirealms.customcrops.common.annotation.DoNotUse;
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class Registries {
|
||||
|
||||
@DoNotUse
|
||||
public static final WriteableRegistry<Key, CustomCropsBlock> BLOCK = new MappedRegistry<>(Key.key("mechanic", "block"));
|
||||
@DoNotUse
|
||||
public static final WriteableRegistry<Key, CustomCropsItem> ITEM = new MappedRegistry<>(Key.key("mechanic", "item"));
|
||||
@DoNotUse
|
||||
public static final WriteableRegistry<String, FertilizerType> FERTILIZER_TYPE = new ClearableMappedRegistry<>(Key.key("mechanic", "fertilizer_type"));
|
||||
@DoNotUse
|
||||
public static final ClearableRegistry<String, CustomCropsBlock> BLOCKS = new ClearableMappedRegistry<>(Key.key("internal", "blocks"));
|
||||
@DoNotUse
|
||||
public static final ClearableRegistry<String, CustomCropsItem> ITEMS = new ClearableMappedRegistry<>(Key.key("internal", "items"));
|
||||
|
||||
public static final ClearableRegistry<String, SprinklerConfig> SPRINKLER = new ClearableMappedRegistry<>(Key.key("config", "sprinkler"));
|
||||
public static final ClearableRegistry<String, PotConfig> POT = new ClearableMappedRegistry<>(Key.key("config", "pot"));
|
||||
public static final ClearableRegistry<String, CropConfig> CROP = new ClearableMappedRegistry<>(Key.key("config", "crop"));
|
||||
public static final ClearableRegistry<String, FertilizerConfig> FERTILIZER = new ClearableMappedRegistry<>(Key.key("config", "fertilizer"));
|
||||
public static final ClearableRegistry<String, WateringCanConfig> WATERING_CAN = new ClearableMappedRegistry<>(Key.key("config", "watering_can"));
|
||||
|
||||
public static final ClearableRegistry<String, CropConfig> SEED_TO_CROP = new ClearableMappedRegistry<>(Key.key("fast_lookup", "seed_to_crop"));
|
||||
public static final ClearableRegistry<String, List<CropConfig>> STAGE_TO_CROP_UNSAFE = new ClearableMappedRegistry<>(Key.key("fast_lookup", "stage_to_crop"));
|
||||
public static final ClearableRegistry<String, FertilizerConfig> ITEM_TO_FERTILIZER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_fertilizer"));
|
||||
public static final ClearableRegistry<String, PotConfig> ITEM_TO_POT = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_pot"));
|
||||
public static final ClearableRegistry<String, SprinklerConfig> ITEM_TO_SPRINKLER = new ClearableMappedRegistry<>(Key.key("fast_lookup", "item_to_sprinkler"));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface Registry<K, T> extends IdMap<T> {
|
||||
|
||||
Key key();
|
||||
|
||||
@Override
|
||||
int getId(@Nullable T value);
|
||||
|
||||
@Nullable
|
||||
T get(@Nullable K key);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.item.CustomCropsItem;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
|
||||
public interface RegistryAccess {
|
||||
|
||||
void registerBlockMechanic(CustomCropsBlock block);
|
||||
|
||||
void registerItemMechanic(CustomCropsItem item);
|
||||
|
||||
void registerFertilizerType(FertilizerType type);
|
||||
|
||||
Registry<Key, CustomCropsBlock> getBlockRegistry();
|
||||
|
||||
Registry<Key, CustomCropsItem> getItemRegistry();
|
||||
|
||||
Registry<String, FertilizerType> getFertilizerTypeRegistry();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.item.CustomCropsItem;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
|
||||
public class SimpleRegistryAccess implements RegistryAccess {
|
||||
|
||||
private BukkitCustomCropsPlugin plugin;
|
||||
private boolean frozen;
|
||||
|
||||
public SimpleRegistryAccess(BukkitCustomCropsPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
this.frozen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBlockMechanic(CustomCropsBlock block) {
|
||||
if (frozen) throw new RuntimeException("Registries are frozen");
|
||||
Registries.BLOCK.register(block.type(), block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerItemMechanic(CustomCropsItem item) {
|
||||
if (frozen) throw new RuntimeException("Registries are frozen");
|
||||
Registries.ITEM.register(item.type(), item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFertilizerType(FertilizerType type) {
|
||||
if (frozen) throw new RuntimeException("Registries are frozen");
|
||||
Registries.FERTILIZER_TYPE.register(type.id(), type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<Key, CustomCropsBlock> getBlockRegistry() {
|
||||
return Registries.BLOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<Key, CustomCropsItem> getItemRegistry() {
|
||||
return Registries.ITEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<String, FertilizerType> getFertilizerTypeRegistry() {
|
||||
return Registries.FERTILIZER_TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface StatedItem<T> {
|
||||
|
||||
String currentState(Context<T> context);
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.world;
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import com.flowpowered.nbt.Tag;
|
||||
@@ -37,7 +37,7 @@ public class SynchronizedCompoundMap {
|
||||
this.compoundMap = compoundMap;
|
||||
}
|
||||
|
||||
public CompoundMap getOriginalMap() {
|
||||
public CompoundMap originalMap() {
|
||||
return compoundMap;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,15 @@ public class SynchronizedCompoundMap {
|
||||
}
|
||||
}
|
||||
|
||||
public Tag<?> remove(String key) {
|
||||
writeLock.lock();
|
||||
try {
|
||||
return compoundMap.remove(key);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -78,16 +87,16 @@ public class SynchronizedCompoundMap {
|
||||
Tag<?> tag = entry.getValue();
|
||||
String tagValue;
|
||||
switch (tag.getType()) {
|
||||
case TAG_STRING, TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY, TAG_LONG, TAG_SHORT, TAG_SHORT_ARRAY, TAG_LONG_ARRAY, TAG_BYTE_ARRAY ->
|
||||
case TAG_STRING, TAG_BYTE, TAG_DOUBLE, TAG_FLOAT, TAG_INT, TAG_INT_ARRAY, TAG_LONG, TAG_SHORT, TAG_SHORT_ARRAY, TAG_LONG_ARRAY, TAG_BYTE_ARRAY,
|
||||
TAG_LIST ->
|
||||
tagValue = tag.getValue().toString();
|
||||
case TAG_COMPOUND -> tagValue = compoundMapToString(tag.getAsCompoundTag().get().getValue());
|
||||
case TAG_LIST -> tagValue = tag.getAsListTag().get().getValue().toString();
|
||||
case TAG_COMPOUND -> tagValue = compoundMapToString((CompoundMap) tag.getValue());
|
||||
default -> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
joiner.add("\"" + entry.getKey() + "\":\"" + tagValue + "\"");
|
||||
}
|
||||
return "{" + joiner + "}";
|
||||
return "BlockData{" + joiner + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.customcrops.api.core;
|
||||
|
||||
public interface WriteableRegistry<K, T> extends Registry<K, T> {
|
||||
|
||||
void register(K key, T value);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import com.flowpowered.nbt.IntTag;
|
||||
import com.flowpowered.nbt.StringTag;
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
|
||||
public abstract class AbstractCustomCropsBlock implements CustomCropsBlock {
|
||||
|
||||
private final Key type;
|
||||
|
||||
public AbstractCustomCropsBlock(Key type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsBlockState createBlockState() {
|
||||
return CustomCropsBlockState.create(this, new CompoundMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsBlockState createBlockState(CompoundMap compoundMap) {
|
||||
return CustomCropsBlockState.create(this, compoundMap);
|
||||
}
|
||||
|
||||
public String id(CustomCropsBlockState state) {
|
||||
return state.get("key").getAsStringTag()
|
||||
.map(StringTag::getValue)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void id(CustomCropsBlockState state, String id) {
|
||||
state.set("key", new StringTag("key", id));
|
||||
}
|
||||
|
||||
protected boolean canTick(CustomCropsBlockState state, int interval) {
|
||||
if (interval <= 0) return false;
|
||||
if (interval == 1) return true;
|
||||
Tag<?> tag = state.get("tick");
|
||||
int tick = 0;
|
||||
if (tag != null) tick = tag.getAsIntTag().map(IntTag::getValue).orElse(0);
|
||||
if (++tick >= interval) {
|
||||
state.set("tick", new IntTag("tick", 0));
|
||||
return true;
|
||||
} else {
|
||||
state.set("tick", new IntTag("tick", tick));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -15,67 +15,53 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.item;
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.common.Pair;
|
||||
import net.momirealms.customcrops.api.manager.ActionManager;
|
||||
import net.momirealms.customcrops.api.mechanic.action.Action;
|
||||
import net.momirealms.customcrops.api.mechanic.requirement.State;
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BoneMeal {
|
||||
|
||||
private final String item;
|
||||
private final int usedAmount;
|
||||
private final int requiredAmount;
|
||||
private final String returned;
|
||||
private final int returnedAmount;
|
||||
private final List<Pair<Double, Integer>> pointGainList;
|
||||
private final Action[] actions;
|
||||
private final Action<Player>[] actions;
|
||||
private final boolean dispenserAllowed;
|
||||
|
||||
public BoneMeal(
|
||||
String item,
|
||||
int usedAmount,
|
||||
int requiredAmount,
|
||||
String returned,
|
||||
int returnedAmount,
|
||||
boolean dispenserAllowed,
|
||||
List<Pair<Double, Integer>> pointGainList,
|
||||
Action[] actions
|
||||
Action<Player>[] actions
|
||||
) {
|
||||
this.item = item;
|
||||
this.returned = returned;
|
||||
this.pointGainList = pointGainList;
|
||||
this.actions = actions;
|
||||
this.usedAmount = usedAmount;
|
||||
this.requiredAmount = requiredAmount;
|
||||
this.returnedAmount = returnedAmount;
|
||||
this.dispenserAllowed = dispenserAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the bone meal item
|
||||
*
|
||||
* @return bonemeal item id
|
||||
*/
|
||||
public String getItem() {
|
||||
public String requiredItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the returned item's ID
|
||||
*
|
||||
* @return returned item ID
|
||||
*/
|
||||
public String getReturned() {
|
||||
public String returnedItem() {
|
||||
return returned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the points to gain in one try
|
||||
*
|
||||
* @return points
|
||||
*/
|
||||
public int getPoint() {
|
||||
public int rollPoint() {
|
||||
for (Pair<Double, Integer> pair : pointGainList) {
|
||||
if (Math.random() < pair.left()) {
|
||||
return pair.right();
|
||||
@@ -84,38 +70,18 @@ public class BoneMeal {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the actions of using the bone meal
|
||||
*
|
||||
* @param state player state
|
||||
*/
|
||||
public void trigger(State state) {
|
||||
ActionManager.triggerActions(state, actions);
|
||||
public void triggerActions(Context<Player> context) {
|
||||
ActionManager.trigger(context, actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount to consume
|
||||
*
|
||||
* @return amount to consume
|
||||
*/
|
||||
public int getUsedAmount() {
|
||||
return usedAmount;
|
||||
public int amountOfRequiredItem() {
|
||||
return requiredAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of the returned items
|
||||
*
|
||||
* @return amount of the returned items
|
||||
*/
|
||||
public int getReturnedAmount() {
|
||||
public int amountOfReturnItem() {
|
||||
return returnedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bone meal can be used with a dispenser
|
||||
*
|
||||
* @return can be used or not
|
||||
*/
|
||||
public boolean isDispenserAllowed() {
|
||||
return dispenserAllowed;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
public enum BreakReason {
|
||||
// Crop was broken by a player
|
||||
BREAK,
|
||||
// Crop was trampled
|
||||
TRAMPLE,
|
||||
// Crop was broken due to an explosion (block or entity)
|
||||
EXPLODE,
|
||||
// Crop was broken due to a specific action
|
||||
ACTION
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.flowpowered.nbt.IntTag;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.item.Fertilizer;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerConfig;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
import net.momirealms.customcrops.api.event.BoneMealUseEvent;
|
||||
import net.momirealms.customcrops.api.event.CropBreakEvent;
|
||||
import net.momirealms.customcrops.api.event.CropInteractEvent;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.api.util.PlayerUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CropBlock extends AbstractCustomCropsBlock {
|
||||
|
||||
public CropBlock() {
|
||||
super(BuiltInBlockMechanics.CROP.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (!world.setting().randomTickCrop() && canTick(state, world.setting().tickCropInterval())) {
|
||||
tickCrop(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (world.setting().randomTickCrop() && canTick(state, world.setting().tickCropInterval())) {
|
||||
tickCrop(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
List<CropConfig> configs = Registries.STAGE_TO_CROP_UNSAFE.get(event.brokenID());
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
if (configs == null || configs.isEmpty()) {
|
||||
world.removeBlockState(pos3);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomCropsBlockState state = fixOrGetState(world, pos3, event.brokenID());
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check data for precise data
|
||||
CropConfig cropConfig = config(state);
|
||||
// the config not exists or it's a wrong one
|
||||
if (cropConfig == null || !configs.contains(cropConfig)) {
|
||||
if (configs.size() != 1) {
|
||||
return;
|
||||
}
|
||||
cropConfig = configs.get(0);
|
||||
}
|
||||
|
||||
CropStageConfig stageConfig = cropConfig.stageByID(event.brokenID());
|
||||
assert stageConfig != null;
|
||||
|
||||
final Player player = event.playerBreaker();
|
||||
Context<Player> context = Context.player(player);
|
||||
|
||||
// check requirements
|
||||
if (!RequirementManager.isSatisfied(context, cropConfig.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (!RequirementManager.isSatisfied(context, stageConfig.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
CropBreakEvent breakEvent = new CropBreakEvent(event.entityBreaker(), event.blockBreaker(), cropConfig, event.brokenID(), event.location(),
|
||||
state, BreakReason.BREAK);
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, stageConfig.breakActions());
|
||||
ActionManager.trigger(context, cropConfig.breakActions());
|
||||
world.removeBlockState(pos3);
|
||||
}
|
||||
|
||||
/**
|
||||
* This can only be triggered admins because players shouldn't get the stages
|
||||
*/
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
List<CropConfig> configs = Registries.STAGE_TO_CROP_UNSAFE.get(event.placedID());
|
||||
if (configs == null || configs.size() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
CropConfig cropConfig = configs.get(0);
|
||||
Context<Player> context = Context.player(event.player());
|
||||
// if (!RequirementManager.isSatisfied(context, cropConfig.plantRequirements())) {
|
||||
// event.setCancelled(true);
|
||||
// return;
|
||||
// }
|
||||
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
if (world.setting().cropPerChunk() >= 0) {
|
||||
if (world.testChunkLimitation(pos3, this.getClass(), world.setting().cropPerChunk())) {
|
||||
ActionManager.trigger(context, cropConfig.reachLimitActions());
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fixOrGetState(world, pos3, event.placedID());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
final Player player = event.player();
|
||||
Context<Player> context = Context.player(player);
|
||||
|
||||
// data first
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Location location = event.location();
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
// fix if possible
|
||||
CustomCropsBlockState state = fixOrGetState(world, pos3, event.relatedID());
|
||||
if (state == null) return;
|
||||
|
||||
CropConfig cropConfig = config(state);
|
||||
if (!RequirementManager.isSatisfied(context, cropConfig.interactRequirements())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int point = point(state);
|
||||
CropStageConfig stageConfig = cropConfig.getFloorStageEntry(point).getValue();
|
||||
if (!RequirementManager.isSatisfied(context, stageConfig.interactRequirements())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
// trigger event
|
||||
CropInteractEvent interactEvent = new CropInteractEvent(player, itemInHand, location, state, event.hand(), cropConfig, event.relatedID());
|
||||
if (EventUtils.fireAndCheckCancel(interactEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location potLocation = location.clone().subtract(0,1,0);
|
||||
String blockBelowID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(potLocation.getBlock());
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(blockBelowID);
|
||||
if (potConfig != null) {
|
||||
PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic();
|
||||
assert potBlock != null;
|
||||
// fix or get data
|
||||
CustomCropsBlockState potState = potBlock.fixOrGetState(world, Pos3.from(potLocation), potConfig, event.relatedID());
|
||||
if (potBlock.tryWateringPot(player, context, potState, event.hand(), event.itemID(), potConfig, potLocation, itemInHand))
|
||||
return;
|
||||
}
|
||||
|
||||
if (point < cropConfig.maxPoints()) {
|
||||
for (BoneMeal boneMeal : cropConfig.boneMeals()) {
|
||||
if (boneMeal.requiredItem().equals(event.itemID()) && boneMeal.amountOfRequiredItem() <= itemInHand.getAmount()) {
|
||||
BoneMealUseEvent useEvent = new BoneMealUseEvent(player, itemInHand, location, boneMeal, state, event.hand(), cropConfig);
|
||||
if (EventUtils.fireAndCheckCancel(useEvent))
|
||||
return;
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
itemInHand.setAmount(itemInHand.getAmount() - boneMeal.amountOfRequiredItem());
|
||||
if (boneMeal.returnedItem() != null) {
|
||||
ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, boneMeal.returnedItem());
|
||||
if (returned != null) {
|
||||
PlayerUtils.giveItem(player, returned, boneMeal.amountOfReturnItem());
|
||||
}
|
||||
}
|
||||
}
|
||||
boneMeal.triggerActions(context);
|
||||
|
||||
int afterPoints = Math.min(point + boneMeal.rollPoint(), cropConfig.maxPoints());
|
||||
point(state, afterPoints);
|
||||
|
||||
String afterStage = null;
|
||||
ExistenceForm afterForm = null;
|
||||
int tempPoints = afterPoints;
|
||||
while (tempPoints >= 0) {
|
||||
Map.Entry<Integer, CropStageConfig> afterEntry = cropConfig.getFloorStageEntry(tempPoints);
|
||||
CropStageConfig after = afterEntry.getValue();
|
||||
if (after.stageID() != null) {
|
||||
afterStage = after.stageID();
|
||||
afterForm = after.existenceForm();
|
||||
break;
|
||||
}
|
||||
tempPoints = after.point() - 1;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(afterForm);
|
||||
Objects.requireNonNull(afterStage);
|
||||
|
||||
Context<CustomCropsBlockState> blockContext = Context.block(state);
|
||||
for (int i = point + 1; i <= afterPoints; i++) {
|
||||
CropStageConfig stage = cropConfig.stageByPoint(i);
|
||||
if (stage != null) {
|
||||
ActionManager.trigger(blockContext, stage.growActions());
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.equals(afterStage, event.relatedID())) return;
|
||||
Location bukkitLocation = location.toLocation(world.bukkitWorld());
|
||||
FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY);
|
||||
if (rotation == FurnitureRotation.NONE && cropConfig.rotation()) {
|
||||
rotation = FurnitureRotation.random();
|
||||
}
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, afterForm, afterStage, rotation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, cropConfig.interactActions());
|
||||
ActionManager.trigger(context, stageConfig.interactActions());
|
||||
}
|
||||
|
||||
public CustomCropsBlockState fixOrGetState(CustomCropsWorld<?> world, Pos3 pos3, String stageID) {
|
||||
List<CropConfig> configList = Registries.STAGE_TO_CROP_UNSAFE.get(stageID);
|
||||
if (configList == null) return null;
|
||||
|
||||
Optional<CustomCropsBlockState> optionalPotState = world.getBlockState(pos3);
|
||||
if (optionalPotState.isPresent()) {
|
||||
CustomCropsBlockState potState = optionalPotState.get();
|
||||
if (potState.type() instanceof CropBlock cropBlock) {
|
||||
if (configList.stream().map(CropConfig::id).toList().contains(cropBlock.id(potState))) {
|
||||
return potState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configList.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
CropConfig cropConfig = configList.get(0);
|
||||
CropStageConfig stageConfig = cropConfig.stageByID(stageID);
|
||||
int point = stageConfig.point();
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState();
|
||||
point(state, point);
|
||||
id(state, cropConfig.id());
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private void tickCrop(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
CropConfig config = config(state);
|
||||
if (config == null) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Crop data is removed at location[" + world.worldName() + "," + location + "] because the crop config[" + id(state) + "] has been removed.");
|
||||
world.removeBlockState(location);
|
||||
return;
|
||||
}
|
||||
|
||||
int previousPoint = point(state);
|
||||
World bukkitWorld = world.bukkitWorld();
|
||||
if (ConfigManager.doubleCheck()) {
|
||||
Map.Entry<Integer, CropStageConfig> nearest = config.getFloorStageEntry(previousPoint);
|
||||
String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(bukkitWorld), nearest.getValue().existenceForm());
|
||||
if (!config.stageIDs().contains(blockID)) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Crop[" + config.id() + "] is removed at location[" + world.worldName() + "," + location + "] because the id of the block is [" + blockID + "]");
|
||||
world.removeBlockState(location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Context<CustomCropsBlockState> context = Context.block(state);
|
||||
for (DeathCondition deathCondition : config.deathConditions()) {
|
||||
if (deathCondition.isMet(context)) {
|
||||
Location bukkitLocation = location.toLocation(bukkitWorld);
|
||||
BukkitCustomCropsPlugin.getInstance().getScheduler().sync().runLater(() -> {
|
||||
FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY);
|
||||
world.removeBlockState(location);
|
||||
Optional.ofNullable(deathCondition.deathStage()).ifPresent(it -> {
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, deathCondition.existenceForm(), it, rotation);
|
||||
});
|
||||
}, deathCondition.deathDelay(), bukkitLocation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (previousPoint >= config.maxPoints()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int pointToAdd = 1;
|
||||
for (GrowCondition growCondition : config.growConditions()) {
|
||||
if (growCondition.isMet(context)) {
|
||||
pointToAdd = growCondition.pointToAdd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<CustomCropsBlockState> optionalState = world.getBlockState(location.add(0,-1,0));
|
||||
if (optionalState.isPresent()) {
|
||||
CustomCropsBlockState belowState = optionalState.get();
|
||||
if (belowState.type() instanceof PotBlock potBlock) {
|
||||
for (Fertilizer fertilizer : potBlock.fertilizers(belowState)) {
|
||||
FertilizerConfig fertilizerConfig = fertilizer.config();
|
||||
if (fertilizerConfig != null) {
|
||||
pointToAdd = fertilizerConfig.processGainPoints(pointToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int afterPoints = Math.min(previousPoint + pointToAdd, config.maxPoints());
|
||||
point(state, afterPoints);
|
||||
|
||||
int tempPoints = previousPoint;
|
||||
String preStage = null;
|
||||
while (tempPoints >= 0) {
|
||||
Map.Entry<Integer, CropStageConfig> preEntry = config.getFloorStageEntry(tempPoints);
|
||||
CropStageConfig pre = preEntry.getValue();
|
||||
if (pre.stageID() != null) {
|
||||
preStage = pre.stageID();
|
||||
break;
|
||||
}
|
||||
tempPoints = pre.point() - 1;
|
||||
}
|
||||
|
||||
String afterStage = null;
|
||||
ExistenceForm afterForm = null;
|
||||
tempPoints = afterPoints;
|
||||
while (tempPoints >= 0) {
|
||||
Map.Entry<Integer, CropStageConfig> afterEntry = config.getFloorStageEntry(tempPoints);
|
||||
CropStageConfig after = afterEntry.getValue();
|
||||
if (after.stageID() != null) {
|
||||
afterStage = after.stageID();
|
||||
afterForm = after.existenceForm();
|
||||
break;
|
||||
}
|
||||
tempPoints = after.point() - 1;
|
||||
}
|
||||
|
||||
Location bukkitLocation = location.toLocation(bukkitWorld);
|
||||
|
||||
final String finalPreStage = preStage;
|
||||
final String finalAfterStage = afterStage;
|
||||
final ExistenceForm finalAfterForm = afterForm;
|
||||
|
||||
Objects.requireNonNull(finalAfterStage);
|
||||
Objects.requireNonNull(finalPreStage);
|
||||
Objects.requireNonNull(finalAfterForm);
|
||||
|
||||
BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(() -> {
|
||||
for (int i = previousPoint + 1; i <= afterPoints; i++) {
|
||||
CropStageConfig stage = config.stageByPoint(i);
|
||||
if (stage != null) {
|
||||
ActionManager.trigger(context, stage.growActions());
|
||||
}
|
||||
}
|
||||
if (Objects.equals(finalAfterStage, finalPreStage)) return;
|
||||
FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY);
|
||||
if (rotation == FurnitureRotation.NONE && config.rotation()) {
|
||||
rotation = FurnitureRotation.random();
|
||||
}
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, finalAfterForm, finalAfterStage, rotation);
|
||||
}, bukkitLocation);
|
||||
}
|
||||
|
||||
public int point(CustomCropsBlockState state) {
|
||||
return state.get("point").getAsIntTag().map(IntTag::getValue).orElse(0);
|
||||
}
|
||||
|
||||
public void point(CustomCropsBlockState state, int point) {
|
||||
state.set("point", new IntTag("point", point));
|
||||
}
|
||||
|
||||
public CropConfig config(CustomCropsBlockState state) {
|
||||
return Registries.CROP.get(id(state));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface CropConfig {
|
||||
|
||||
String id();
|
||||
|
||||
String seed();
|
||||
|
||||
int maxPoints();
|
||||
|
||||
Requirement<Player>[] plantRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Requirement<Player>[] interactRequirements();
|
||||
|
||||
GrowCondition[] growConditions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<Player>[] plantActions();
|
||||
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
Action<CustomCropsBlockState>[] deathActions();
|
||||
|
||||
DeathCondition[] deathConditions();
|
||||
|
||||
BoneMeal[] boneMeals();
|
||||
|
||||
boolean rotation();
|
||||
|
||||
Set<String> potWhitelist();
|
||||
|
||||
CropStageConfig stageByPoint(int point);
|
||||
|
||||
@Nullable
|
||||
CropStageConfig stageByID(String stageModel);
|
||||
|
||||
CropStageConfig stageWithModelByPoint(int point);
|
||||
|
||||
Collection<CropStageConfig> stages();
|
||||
|
||||
Collection<String> stageIDs();
|
||||
|
||||
Map.Entry<Integer, CropStageConfig> getFloorStageEntry(int previousPoint);
|
||||
|
||||
static Builder builder() {
|
||||
return new CropConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
CropConfig build();
|
||||
|
||||
Builder id(String id);
|
||||
|
||||
Builder seed(String seed);
|
||||
|
||||
Builder maxPoints(int maxPoints);
|
||||
|
||||
Builder wrongPotActions(Action<Player>[] wrongPotActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder plantActions(Action<Player>[] plantActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder plantRequirements(Requirement<Player>[] plantRequirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] breakRequirements);
|
||||
|
||||
Builder interactRequirements(Requirement<Player>[] interactRequirements);
|
||||
|
||||
Builder growConditions(GrowCondition[] growConditions);
|
||||
|
||||
Builder deathConditions(DeathCondition[] deathConditions);
|
||||
|
||||
Builder boneMeals(BoneMeal[] boneMeals);
|
||||
|
||||
Builder rotation(boolean rotation);
|
||||
|
||||
Builder potWhitelist(Set<String> whitelist);
|
||||
|
||||
Builder stages(Collection<CropStageConfig.Builder> stages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CropConfigImpl implements CropConfig {
|
||||
|
||||
private final String id;
|
||||
private final String seed;
|
||||
private final int maxPoints;
|
||||
private final Action<Player>[] wrongPotActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] plantActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<CustomCropsBlockState>[] deathActions;
|
||||
private final Requirement<Player>[] plantRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] interactRequirements;
|
||||
private final GrowCondition[] growConditions;
|
||||
private final DeathCondition[] deathConditions;
|
||||
private final BoneMeal[] boneMeals;
|
||||
private final boolean rotation;
|
||||
private final Set<String> potWhitelist;
|
||||
private final HashMap<Integer, CropStageConfig> point2Stages = new HashMap<>();
|
||||
private final NavigableMap<Integer, CropStageConfig> navigablePoint2Stages = new TreeMap<>();
|
||||
private final HashMap<String, CropStageConfig> id2Stages = new HashMap<>();
|
||||
private final Set<String> stageIDs = new HashSet<>();
|
||||
private final HashMap<Integer, CropStageConfig> cropStageWithModelMap = new HashMap<>();
|
||||
|
||||
public CropConfigImpl(
|
||||
String id,
|
||||
String seed,
|
||||
int maxPoints,
|
||||
Action<Player>[] wrongPotActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] plantActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<CustomCropsBlockState>[] deathActions,
|
||||
Requirement<Player>[] plantRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] interactRequirements,
|
||||
GrowCondition[] growConditions,
|
||||
DeathCondition[] deathConditions,
|
||||
BoneMeal[] boneMeals,
|
||||
boolean rotation,
|
||||
Set<String> potWhitelist,
|
||||
Collection<CropStageConfig.Builder> stageBuilders
|
||||
) {
|
||||
this.id = id;
|
||||
this.seed = seed;
|
||||
this.maxPoints = maxPoints;
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
this.interactActions = interactActions;
|
||||
this.breakActions = breakActions;
|
||||
this.plantActions = plantActions;
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
this.deathActions = deathActions;
|
||||
this.plantRequirements = plantRequirements;
|
||||
this.breakRequirements = breakRequirements;
|
||||
this.interactRequirements = interactRequirements;
|
||||
this.growConditions = growConditions;
|
||||
this.deathConditions = deathConditions;
|
||||
this.boneMeals = boneMeals;
|
||||
this.rotation = rotation;
|
||||
this.potWhitelist = potWhitelist;
|
||||
for (CropStageConfig.Builder builder : stageBuilders) {
|
||||
CropStageConfig config = builder.crop(this).build();
|
||||
point2Stages.put(config.point(), config);
|
||||
navigablePoint2Stages.put(config.point(), config);
|
||||
String stageID = config.stageID();
|
||||
id2Stages.put(stageID, config);
|
||||
stageIDs.add(stageID);
|
||||
}
|
||||
CropStageConfig tempConfig = null;
|
||||
for (int i = 0; i <= maxPoints; i++) {
|
||||
CropStageConfig config = point2Stages.get(i);
|
||||
if (config != null) {
|
||||
String stageModel = config.stageID();
|
||||
if (stageModel != null) {
|
||||
tempConfig = config;
|
||||
}
|
||||
}
|
||||
cropStageWithModelMap.put(i, tempConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String seed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxPoints() {
|
||||
return maxPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] plantRequirements() {
|
||||
return plantRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] interactRequirements() {
|
||||
return interactRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GrowCondition[] growConditions() {
|
||||
return growConditions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] plantActions() {
|
||||
return plantActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] deathActions() {
|
||||
return deathActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeathCondition[] deathConditions() {
|
||||
return deathConditions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoneMeal[] boneMeals() {
|
||||
return boneMeals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> potWhitelist() {
|
||||
return potWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CropStageConfig stageByPoint(int id) {
|
||||
return point2Stages.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CropStageConfig stageByID(String stageModel) {
|
||||
return id2Stages.get(stageModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CropStageConfig stageWithModelByPoint(int point) {
|
||||
return cropStageWithModelMap.get(Math.min(maxPoints, point));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CropStageConfig> stages() {
|
||||
return point2Stages.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> stageIDs() {
|
||||
return stageIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Integer, CropStageConfig> getFloorStageEntry(int point) {
|
||||
Preconditions.checkArgument(point >= 0, "Point should be no lower than " + point);
|
||||
return navigablePoint2Stages.floorEntry(point);
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
|
||||
private String id;
|
||||
private String seed;
|
||||
private ExistenceForm existenceForm;
|
||||
private int maxPoints;
|
||||
private Action<Player>[] wrongPotActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] plantActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<CustomCropsBlockState>[] deathActions;
|
||||
private Requirement<Player>[] plantRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] interactRequirements;
|
||||
private GrowCondition[] growConditions;
|
||||
private DeathCondition[] deathConditions;
|
||||
private BoneMeal[] boneMeals;
|
||||
private boolean rotation;
|
||||
private Set<String> potWhitelist;
|
||||
private Collection<CropStageConfig.Builder> stages;
|
||||
|
||||
@Override
|
||||
public CropConfig build() {
|
||||
return new CropConfigImpl(id, seed, maxPoints, wrongPotActions, interactActions, breakActions, plantActions, reachLimitActions, deathActions, plantRequirements, breakRequirements, interactRequirements, growConditions, deathConditions, boneMeals, rotation, potWhitelist, stages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder seed(String seed) {
|
||||
this.seed = seed;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder maxPoints(int maxPoints) {
|
||||
this.maxPoints = maxPoints;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wrongPotActions(Action<Player>[] wrongPotActions) {
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder plantActions(Action<Player>[] plantActions) {
|
||||
this.plantActions = plantActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder deathActions(Action<CustomCropsBlockState>[] deathActions) {
|
||||
this.deathActions = deathActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder plantRequirements(Requirement<Player>[] plantRequirements) {
|
||||
this.plantRequirements = plantRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] breakRequirements) {
|
||||
this.breakRequirements = breakRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactRequirements(Requirement<Player>[] interactRequirements) {
|
||||
this.interactRequirements = interactRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder growConditions(GrowCondition[] growConditions) {
|
||||
this.growConditions = growConditions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder deathConditions(DeathCondition[] deathConditions) {
|
||||
this.deathConditions = deathConditions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder boneMeals(BoneMeal[] boneMeals) {
|
||||
this.boneMeals = boneMeals;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder rotation(boolean rotation) {
|
||||
this.rotation = rotation;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder potWhitelist(Set<String> potWhitelist) {
|
||||
this.potWhitelist = new HashSet<>(potWhitelist);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stages(Collection<CropStageConfig.Builder> stages) {
|
||||
this.stages = new HashSet<>(stages);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface CropStageConfig {
|
||||
|
||||
CropConfig crop();
|
||||
|
||||
double displayInfoOffset();
|
||||
|
||||
@Nullable
|
||||
String stageID();
|
||||
|
||||
int point();
|
||||
|
||||
Requirement<Player>[] interactRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<CustomCropsBlockState>[] growActions();
|
||||
|
||||
ExistenceForm existenceForm();
|
||||
|
||||
static Builder builder() {
|
||||
return new CropStageConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
CropStageConfig build();
|
||||
|
||||
Builder crop(CropConfig crop);
|
||||
|
||||
Builder displayInfoOffset(double offset);
|
||||
|
||||
Builder stageID(String id);
|
||||
|
||||
Builder point(int i);
|
||||
|
||||
Builder interactRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder interactActions(Action<Player>[] actions);
|
||||
|
||||
Builder breakActions(Action<Player>[] actions);
|
||||
|
||||
Builder growActions(Action<CustomCropsBlockState>[] actions);
|
||||
|
||||
Builder existenceForm(ExistenceForm existenceForm);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CropStageConfigImpl implements CropStageConfig {
|
||||
|
||||
private final CropConfig crop;
|
||||
private final ExistenceForm existenceForm;
|
||||
private final double offset;
|
||||
private final String stageID;
|
||||
private final int point;
|
||||
private final Requirement<Player>[] interactRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<CustomCropsBlockState>[] growActions;
|
||||
|
||||
public CropStageConfigImpl(
|
||||
CropConfig crop,
|
||||
ExistenceForm existenceForm,
|
||||
double offset,
|
||||
String stageID,
|
||||
int point,
|
||||
Requirement<Player>[] interactRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<CustomCropsBlockState>[] growActions
|
||||
) {
|
||||
this.crop = crop;
|
||||
this.existenceForm = existenceForm;
|
||||
this.offset = offset;
|
||||
this.stageID = stageID;
|
||||
this.point = point;
|
||||
this.interactRequirements = interactRequirements;
|
||||
this.breakRequirements = breakRequirements;
|
||||
this.interactActions = interactActions;
|
||||
this.breakActions = breakActions;
|
||||
this.growActions = growActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CropConfig crop() {
|
||||
return crop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double displayInfoOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String stageID() {
|
||||
return stageID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int point() {
|
||||
return point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] interactRequirements() {
|
||||
return interactRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] growActions() {
|
||||
return growActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExistenceForm existenceForm() {
|
||||
return existenceForm;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
|
||||
private CropConfig crop;
|
||||
private ExistenceForm existenceForm;
|
||||
private double offset;
|
||||
private String stageID;
|
||||
private int point;
|
||||
private Requirement<Player>[] interactRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<CustomCropsBlockState>[] growActions;
|
||||
|
||||
@Override
|
||||
public CropStageConfig build() {
|
||||
return new CropStageConfigImpl(crop, existenceForm, offset, stageID, point, interactRequirements, breakRequirements, interactActions, breakActions, growActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder crop(CropConfig crop) {
|
||||
this.crop = crop;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder displayInfoOffset(double offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder stageID(String id) {
|
||||
this.stageID = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder point(int i) {
|
||||
this.point = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactRequirements(Requirement<Player>[] requirements) {
|
||||
this.interactRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] requirements) {
|
||||
this.breakRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] actions) {
|
||||
this.interactActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] actions) {
|
||||
this.breakActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder growActions(Action<CustomCropsBlockState>[] actions) {
|
||||
this.growActions = actions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder existenceForm(ExistenceForm existenceForm) {
|
||||
this.existenceForm = existenceForm;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.util.LocationUtils;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customcrops.common.util.RandomUtils;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import net.momirealms.sparrow.heart.feature.entity.armorstand.FakeArmorStand;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CrowAttack {
|
||||
|
||||
private SchedulerTask task;
|
||||
private final Location dynamicLocation;
|
||||
private final Location cropLocation;
|
||||
private final Vector vectorDown;
|
||||
private final Vector vectorUp;
|
||||
private final Player[] viewers;
|
||||
private int timer;
|
||||
private final ItemStack flyModel;
|
||||
private final ItemStack standModel;
|
||||
|
||||
public CrowAttack(Location location, ItemStack flyModel, ItemStack standModel) {
|
||||
ArrayList<Player> viewers = new ArrayList<>();
|
||||
for (Player player : location.getWorld().getPlayers()) {
|
||||
if (LocationUtils.getDistance(player.getLocation(), location) <= 48) {
|
||||
viewers.add(player);
|
||||
}
|
||||
}
|
||||
this.viewers = viewers.toArray(new Player[0]);
|
||||
this.cropLocation = location.clone().add(RandomUtils.generateRandomDouble(-0.25, 0.25), 0, RandomUtils.generateRandomDouble(-0.25, 0.25));
|
||||
float yaw = RandomUtils.generateRandomInt(-180, 180);
|
||||
this.cropLocation.setYaw(yaw);
|
||||
this.flyModel = flyModel;
|
||||
this.standModel = standModel;
|
||||
this.dynamicLocation = cropLocation.clone().add((10 * Math.sin((Math.PI * yaw) / 180)), 10, (- 10 * Math.cos((Math.PI * yaw) / 180)));
|
||||
this.dynamicLocation.setYaw(yaw);
|
||||
Location relative = cropLocation.clone().subtract(dynamicLocation);
|
||||
this.vectorDown = new Vector(relative.getX() / 100, -0.1, relative.getZ() / 100);
|
||||
this.vectorUp = new Vector(relative.getX() / 100, 0.1, relative.getZ() / 100);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (this.viewers.length == 0) return;
|
||||
FakeArmorStand fake1 = SparrowHeart.getInstance().createFakeArmorStand(dynamicLocation);
|
||||
fake1.invisible(true);
|
||||
fake1.small(true);
|
||||
fake1.equipment(EquipmentSlot.HEAD, flyModel);
|
||||
FakeArmorStand fake2 = SparrowHeart.getInstance().createFakeArmorStand(cropLocation);
|
||||
fake1.invisible(true);
|
||||
fake1.small(true);
|
||||
fake1.equipment(EquipmentSlot.HEAD, standModel);
|
||||
for (Player player : this.viewers) {
|
||||
fake1.spawn(player);
|
||||
}
|
||||
this.task = BukkitCustomCropsPlugin.getInstance().getScheduler().asyncRepeating(() -> {
|
||||
timer++;
|
||||
if (timer < 100) {
|
||||
dynamicLocation.add(vectorDown);
|
||||
for (Player player : this.viewers) {
|
||||
SparrowHeart.getInstance().sendClientSideTeleportEntity(player, dynamicLocation, false, fake1.entityID());
|
||||
}
|
||||
} else if (timer == 100){
|
||||
for (Player player : this.viewers) {
|
||||
fake1.destroy(player);
|
||||
}
|
||||
for (Player player : this.viewers) {
|
||||
fake2.spawn(player);
|
||||
}
|
||||
} else if (timer == 150) {
|
||||
for (Player player : this.viewers) {
|
||||
fake2.destroy(player);
|
||||
}
|
||||
for (Player player : this.viewers) {
|
||||
fake1.spawn(player);
|
||||
}
|
||||
} else if (timer > 150) {
|
||||
dynamicLocation.add(vectorUp);
|
||||
for (Player player : this.viewers) {
|
||||
SparrowHeart.getInstance().sendClientSideTeleportEntity(player, dynamicLocation, false, fake1.entityID());
|
||||
}
|
||||
}
|
||||
if (timer > 300) {
|
||||
for (Player player : this.viewers) {
|
||||
fake1.destroy(player);
|
||||
}
|
||||
task.cancel();
|
||||
}
|
||||
}, 50, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
|
||||
public interface CustomCropsBlock {
|
||||
|
||||
Key type();
|
||||
|
||||
CustomCropsBlockState createBlockState();
|
||||
|
||||
CustomCropsBlockState createBlockState(CompoundMap data);
|
||||
|
||||
void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location);
|
||||
|
||||
void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location);
|
||||
|
||||
void onInteract(WrappedInteractEvent event);
|
||||
|
||||
void onBreak(WrappedBreakEvent event);
|
||||
|
||||
void onPlace(WrappedPlaceEvent event);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class DeathCondition {
|
||||
|
||||
private final Requirement<CustomCropsBlockState>[] requirements;
|
||||
private final String deathStage;
|
||||
private final ExistenceForm existenceForm;
|
||||
private final int deathDelay;
|
||||
|
||||
public DeathCondition(Requirement<CustomCropsBlockState>[] requirements, String deathStage, ExistenceForm existenceForm, int deathDelay) {
|
||||
this.requirements = requirements;
|
||||
this.deathStage = deathStage;
|
||||
this.existenceForm = existenceForm;
|
||||
this.deathDelay = deathDelay;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String deathStage() {
|
||||
return deathStage;
|
||||
}
|
||||
|
||||
public int deathDelay() {
|
||||
return deathDelay;
|
||||
}
|
||||
|
||||
public boolean isMet(Context<CustomCropsBlockState> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
|
||||
public ExistenceForm existenceForm() {
|
||||
return existenceForm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.core.BuiltInBlockMechanics;
|
||||
import net.momirealms.customcrops.api.core.ConfigManager;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
import net.momirealms.customcrops.api.event.GreenhouseGlassBreakEvent;
|
||||
import net.momirealms.customcrops.api.event.GreenhouseGlassInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.GreenhouseGlassPlaceEvent;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class GreenhouseBlock extends AbstractCustomCropsBlock {
|
||||
|
||||
public GreenhouseBlock() {
|
||||
super(BuiltInBlockMechanics.GREENHOUSE.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
//tickGreenhouse(world, location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
tickGreenhouse(world, location);
|
||||
}
|
||||
|
||||
private void tickGreenhouse(CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (!ConfigManager.doubleCheck()) return;
|
||||
String id = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(world.bukkitWorld()), ConfigManager.greenhouseExistenceForm());
|
||||
if (ConfigManager.greenhouse().contains(id)) return;
|
||||
// remove outdated data
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Greenhouse is removed at location[" + world.worldName() + "," + location + "] because the id of the block/furniture is [" + id + "]");
|
||||
world.removeBlockState(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsBlockState state = getOrFixState(world, pos3);
|
||||
GreenhouseGlassInteractEvent interactEvent = new GreenhouseGlassInteractEvent(event.player(), event.itemInHand(), event.location(), event.relatedID(), state, event.hand());
|
||||
EventUtils.fireAndForget(interactEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsBlockState state = getOrFixState(world, pos3);
|
||||
GreenhouseGlassBreakEvent breakEvent = new GreenhouseGlassBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), event.brokenID(), state, event.reason());
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
return;
|
||||
}
|
||||
world.removeBlockState(pos3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
CustomCropsBlockState state = createBlockState();
|
||||
GreenhouseGlassPlaceEvent placeEvent = new GreenhouseGlassPlaceEvent(event.player(), event.location(), event.itemID(), state);
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent)) {
|
||||
return;
|
||||
}
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public CustomCropsBlockState getOrFixState(CustomCropsWorld<?> world, Pos3 pos3) {
|
||||
Optional<CustomCropsBlockState> optional = world.getBlockState(pos3);
|
||||
if (optional.isPresent() && optional.get().type() instanceof GreenhouseBlock) {
|
||||
return optional.get();
|
||||
}
|
||||
CustomCropsBlockState state = createBlockState();
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at pos3[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
|
||||
public class GrowCondition {
|
||||
|
||||
private final Requirement<CustomCropsBlockState>[] requirements;
|
||||
private final int pointToAdd;
|
||||
|
||||
public GrowCondition(Requirement<CustomCropsBlockState>[] requirements, int pointToAdd) {
|
||||
this.requirements = requirements;
|
||||
this.pointToAdd = pointToAdd;
|
||||
}
|
||||
|
||||
public int pointToAdd() {
|
||||
return pointToAdd;
|
||||
}
|
||||
|
||||
public boolean isMet(Context<CustomCropsBlockState> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.flowpowered.nbt.*;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.item.Fertilizer;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerConfig;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
import net.momirealms.customcrops.api.event.*;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.api.util.PlayerUtils;
|
||||
import net.momirealms.customcrops.api.util.StringUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
import org.bukkit.block.data.type.Farmland;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PotBlock extends AbstractCustomCropsBlock {
|
||||
|
||||
public PotBlock() {
|
||||
super(BuiltInBlockMechanics.POT.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (!world.setting().randomTickPot() && canTick(state, world.setting().tickPotInterval())) {
|
||||
tickPot(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (world.setting().randomTickPot() && canTick(state, world.setting().tickPotInterval())) {
|
||||
tickPot(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
PotConfig config = Registries.ITEM_TO_POT.get(event.brokenID());
|
||||
if (config == null) {
|
||||
world.removeBlockState(pos3);
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.playerBreaker();
|
||||
Context<Player> context = Context.player(player);
|
||||
if (!RequirementManager.isSatisfied(context, config.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Location upperLocation = event.location().clone().add(0,1,0);
|
||||
String upperID = BukkitCustomCropsPlugin.getInstance().getItemManager().anyID(upperLocation);
|
||||
List<CropConfig> cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(upperID);
|
||||
|
||||
CropConfig cropConfig = null;
|
||||
CropStageConfig stageConfig = null;
|
||||
|
||||
outer: {
|
||||
if (cropConfigs != null && !cropConfigs.isEmpty()) {
|
||||
CropBlock cropBlock = (CropBlock) BuiltInBlockMechanics.CROP.mechanic();
|
||||
CustomCropsBlockState state = cropBlock.fixOrGetState(world, pos3.add(0,1,0), upperID);
|
||||
if (state == null) {
|
||||
// remove ambiguous stage
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY);
|
||||
break outer;
|
||||
}
|
||||
|
||||
cropConfig = cropBlock.config(state);
|
||||
if (cropConfig == null || !cropConfigs.contains(cropConfig)) {
|
||||
if (cropConfigs.size() != 1) {
|
||||
// remove ambiguous stage
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY);
|
||||
world.removeBlockState(pos3.add(0,1,0));
|
||||
break outer;
|
||||
}
|
||||
cropConfig = cropConfigs.get(0);
|
||||
}
|
||||
|
||||
if (!RequirementManager.isSatisfied(context, cropConfig.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
stageConfig = cropConfig.stageByID(upperID);
|
||||
// should not be null
|
||||
assert stageConfig != null;
|
||||
if (!RequirementManager.isSatisfied(context, stageConfig.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
CropBreakEvent breakEvent = new CropBreakEvent(event.entityBreaker(), event.blockBreaker(), cropConfig, upperID, upperLocation, state, event.reason());
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomCropsBlockState potState = fixOrGetState(world, pos3, config, event.brokenID());
|
||||
|
||||
PotBreakEvent breakEvent = new PotBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), config, potState, event.reason());
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, config.breakActions());
|
||||
if (stageConfig != null) {
|
||||
ActionManager.trigger(context, stageConfig.breakActions());
|
||||
}
|
||||
if (cropConfig != null) {
|
||||
ActionManager.trigger(context, cropConfig.breakActions());
|
||||
world.removeBlockState(pos3.add(0,1,0));
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().remove(upperLocation, ExistenceForm.ANY);
|
||||
}
|
||||
|
||||
world.removeBlockState(pos3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
PotConfig config = Registries.ITEM_TO_POT.get(event.placedID());
|
||||
if (config == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Context<Player> context = Context.player(event.player());
|
||||
if (!RequirementManager.isSatisfied(context, config.placeRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
if (world.setting().potPerChunk() >= 0) {
|
||||
if (world.testChunkLimitation(pos3, this.getClass(), world.setting().potPerChunk())) {
|
||||
event.setCancelled(true);
|
||||
ActionManager.trigger(context, config.reachLimitActions());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.POT.createBlockState();
|
||||
id(state, config.id());
|
||||
water(state, config.isWet(event.placedID()) ? 1 : 0);
|
||||
|
||||
PotPlaceEvent placeEvent = new PotPlaceEvent(event.player(), event.location(), config, state, event.item(), event.hand());
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
ActionManager.trigger(context, config.placeActions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(event.relatedID());
|
||||
if (potConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = event.location();
|
||||
Pos3 pos3 = Pos3.from(location);
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
|
||||
// fix or get data
|
||||
CustomCropsBlockState state = fixOrGetState(world, pos3, potConfig, event.relatedID());
|
||||
|
||||
final Player player = event.player();
|
||||
Context<Player> context = Context.player(player);
|
||||
// check use requirements
|
||||
if (!RequirementManager.isSatisfied(context, potConfig.useRequirements())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
// trigger event
|
||||
PotInteractEvent interactEvent = new PotInteractEvent(player, event.hand(), itemInHand, potConfig, location, state);
|
||||
if (EventUtils.fireAndCheckCancel(interactEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tryWateringPot(player, context, state, event.hand(), event.itemID(), potConfig, location, itemInHand))
|
||||
return;
|
||||
|
||||
ActionManager.trigger(context, potConfig.interactActions());
|
||||
}
|
||||
|
||||
protected boolean tryWateringPot(Player player, Context<Player> context, CustomCropsBlockState state, EquipmentSlot hand, String itemID, PotConfig potConfig, Location potLocation, ItemStack itemInHand) {
|
||||
int waterInPot = water(state);
|
||||
for (WateringMethod method : potConfig.wateringMethods()) {
|
||||
if (method.getUsed().equals(itemID) && method.getUsedAmount() <= itemInHand.getAmount()) {
|
||||
if (method.checkRequirements(context)) {
|
||||
if (waterInPot >= potConfig.storage()) {
|
||||
ActionManager.trigger(context, potConfig.fullWaterActions());
|
||||
} else {
|
||||
PotFillEvent waterEvent = new PotFillEvent(player, itemInHand, hand, potLocation, method, state, potConfig);
|
||||
if (EventUtils.fireAndCheckCancel(waterEvent))
|
||||
return true;
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
itemInHand.setAmount(Math.max(0, itemInHand.getAmount() - method.getUsedAmount()));
|
||||
if (method.getReturned() != null) {
|
||||
ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, method.getReturned());
|
||||
if (returned != null) {
|
||||
PlayerUtils.giveItem(player, returned, method.getReturnedAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
method.triggerActions(context);
|
||||
ActionManager.trigger(context, potConfig.addWaterActions());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public CustomCropsBlockState fixOrGetState(CustomCropsWorld<?> world, Pos3 pos3, PotConfig potConfig, String blockID) {
|
||||
Optional<CustomCropsBlockState> optionalPotState = world.getBlockState(pos3);
|
||||
if (optionalPotState.isPresent()) {
|
||||
CustomCropsBlockState potState = optionalPotState.get();
|
||||
if (potState.type() instanceof PotBlock potBlock) {
|
||||
if (potBlock.id(potState).equals(potConfig.id())) {
|
||||
return potState;
|
||||
}
|
||||
}
|
||||
}
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.POT.createBlockState();
|
||||
id(state, potConfig.id());
|
||||
water(state, potConfig.isWet(blockID) ? 1 : 0);
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private void tickPot(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
PotConfig config = config(state);
|
||||
if (config == null) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Pot data is removed at location[" + world.worldName() + "," + location + "] because the pot config[" + id(state) + "] has been removed.");
|
||||
world.removeBlockState(location);
|
||||
return;
|
||||
}
|
||||
|
||||
World bukkitWorld = world.bukkitWorld();
|
||||
if (ConfigManager.doubleCheck()) {
|
||||
String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(location.toLocation(bukkitWorld));
|
||||
if (!config.blocks().contains(blockID)) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Pot[" + config.id() + "] is removed at location[" + world.worldName() + "," + location + "] because the id of the block is [" + blockID + "]");
|
||||
world.removeBlockState(location);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasNaturalWater = false;
|
||||
boolean waterChanged = false;
|
||||
|
||||
if (config.isRainDropAccepted()) {
|
||||
if (bukkitWorld.hasStorm() || (!bukkitWorld.isClearWeather() && !bukkitWorld.isThundering())) {
|
||||
double temperature = bukkitWorld.getTemperature(location.x(), location.y(), location.z());
|
||||
if (temperature > 0.15 && temperature < 0.85) {
|
||||
int y = bukkitWorld.getHighestBlockYAt(location.x(), location.z());
|
||||
if (y == location.y()) {
|
||||
if (addWater(state, 1)) {
|
||||
waterChanged = true;
|
||||
}
|
||||
hasNaturalWater = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNaturalWater && config.isNearbyWaterAccepted()) {
|
||||
for (int i = -4; i <= 4; i++) {
|
||||
for (int j = -4; j <= 4; j++) {
|
||||
for (int k : new int[]{0, 1}) {
|
||||
BlockData block = bukkitWorld.getBlockData(location.x() + i, location.y() + j, location.z() + k);
|
||||
if (block.getMaterial() == Material.WATER || (block instanceof Waterlogged waterlogged && waterlogged.isWaterlogged())) {
|
||||
if (addWater(state, 1)) {
|
||||
waterChanged = true;
|
||||
}
|
||||
hasNaturalWater = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNaturalWater) {
|
||||
int waterToLose = 1;
|
||||
Fertilizer[] fertilizers = fertilizers(state);
|
||||
for (Fertilizer fertilizer : fertilizers) {
|
||||
FertilizerConfig fertilizerConfig = fertilizer.config();
|
||||
if (fertilizerConfig != null) {
|
||||
waterToLose = fertilizerConfig.processWaterToLose(waterToLose);
|
||||
}
|
||||
}
|
||||
if (waterToLose > 0) {
|
||||
if (addWater(state, -waterToLose)) {
|
||||
waterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean fertilizerChanged = tickFertilizer(state);
|
||||
|
||||
if (fertilizerChanged || waterChanged) {
|
||||
updateBlockAppearance(location.toLocation(bukkitWorld), config, hasNaturalWater, fertilizers(state));
|
||||
}
|
||||
}
|
||||
|
||||
public int water(CustomCropsBlockState state) {
|
||||
Tag<?> tag = state.get("water");
|
||||
if (tag == null) {
|
||||
return 0;
|
||||
}
|
||||
return tag.getAsIntTag().map(IntTag::getValue).orElse(0);
|
||||
}
|
||||
|
||||
public boolean addWater(CustomCropsBlockState state, int water) {
|
||||
return water(state, water + water(state));
|
||||
}
|
||||
|
||||
public boolean addWater(CustomCropsBlockState state, PotConfig config, int water) {
|
||||
return water(state, config, water + water(state));
|
||||
}
|
||||
|
||||
public boolean consumeWater(CustomCropsBlockState state, int water) {
|
||||
return water(state, water(state) - water);
|
||||
}
|
||||
|
||||
public boolean consumeWater(CustomCropsBlockState state, PotConfig config, int water) {
|
||||
return water(state, config, water(state) - water);
|
||||
}
|
||||
|
||||
public boolean water(CustomCropsBlockState state, int water) {
|
||||
return water(state, config(state), water);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the water for a pot
|
||||
*
|
||||
* @param state the block state
|
||||
* @param config the pot config
|
||||
* @param water the amount of water
|
||||
* @return whether the moisture state has been changed
|
||||
*/
|
||||
public boolean water(CustomCropsBlockState state, PotConfig config, int water) {
|
||||
if (water < 0) water = 0;
|
||||
int current = Math.min(water, config.storage());
|
||||
int previous = water(state);
|
||||
if (water == previous) return false;
|
||||
state.set("water", new IntTag("water", current));
|
||||
return previous == 0 ^ current == 0;
|
||||
}
|
||||
|
||||
public PotConfig config(CustomCropsBlockState state) {
|
||||
return Registries.POT.get(id(state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fertilizers in the pot
|
||||
*
|
||||
* @param state the block state
|
||||
* @return applied fertilizers
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
public Fertilizer[] fertilizers(CustomCropsBlockState state) {
|
||||
Tag<?> fertilizerTag = state.get("fertilizers");
|
||||
if (fertilizerTag == null) return new Fertilizer[0];
|
||||
List<CompoundTag> tags = ((ListTag<CompoundTag>) fertilizerTag.getValue()).getValue();
|
||||
Fertilizer[] fertilizers = new Fertilizer[tags.size()];
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
CompoundTag tag = tags.get(i);
|
||||
fertilizers[i] = tagToFertilizer(tag.getValue());
|
||||
}
|
||||
return fertilizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the fertilizer can be applied to this pot
|
||||
*
|
||||
* @param state the block state
|
||||
* @param fertilizer the fertilizer to apply
|
||||
* @return can be applied or not
|
||||
*/
|
||||
public boolean canApplyFertilizer(CustomCropsBlockState state, Fertilizer fertilizer) {
|
||||
Fertilizer[] fertilizers = fertilizers(state);
|
||||
boolean hasSameTypeFertilizer = false;
|
||||
for (Fertilizer applied : fertilizers) {
|
||||
if (fertilizer.id().equals(applied.id())) {
|
||||
return true;
|
||||
}
|
||||
if (fertilizer.type() == applied.type()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PotConfig config = config(state);
|
||||
return config.maxFertilizers() > fertilizers.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fertilizer to the pot
|
||||
* If the pot contains the fertilizer, the times would be reset.
|
||||
*
|
||||
* @param state the block state
|
||||
* @param fertilizer the fertilizer to apply
|
||||
* @return whether to update pot appearance
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean addFertilizer(CustomCropsBlockState state, Fertilizer fertilizer) {
|
||||
Tag<?> fertilizerTag = state.get("fertilizers");
|
||||
if (fertilizerTag == null) {
|
||||
fertilizerTag = new ListTag<CompoundTag>("", TagType.TAG_COMPOUND, new ArrayList<>());
|
||||
state.set("fertilizers", fertilizerTag);
|
||||
}
|
||||
List<CompoundTag> tags = ((ListTag<CompoundTag>) fertilizerTag.getValue()).getValue();
|
||||
for (CompoundTag tag : tags) {
|
||||
CompoundMap map = tag.getValue();
|
||||
Fertilizer applied = tagToFertilizer(map);
|
||||
if (fertilizer.id().equals(applied.id())) {
|
||||
map.put(new IntTag("times", fertilizer.times()));
|
||||
return false;
|
||||
}
|
||||
if (fertilizer.type() == applied.type()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PotConfig config = config(state);
|
||||
if (config.maxFertilizers() <= tags.size()) {
|
||||
return false;
|
||||
}
|
||||
tags.add(new CompoundTag("", fertilizerToTag(fertilizer)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean tickFertilizer(CustomCropsBlockState state) {
|
||||
// no fertilizers applied
|
||||
Tag<?> fertilizerTag = state.get("fertilizers");
|
||||
if (fertilizerTag == null) {
|
||||
return false;
|
||||
}
|
||||
List<CompoundTag> tags = ((ListTag<CompoundTag>) fertilizerTag.getValue()).getValue();
|
||||
if (tags.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
List<Integer> fertilizerToRemove = new ArrayList<>();
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
CompoundMap map = tags.get(i).getValue();
|
||||
Fertilizer applied = tagToFertilizer(map);
|
||||
if (applied.reduceTimes()) {
|
||||
fertilizerToRemove.add(i);
|
||||
} else {
|
||||
tags.get(i).setValue(fertilizerToTag(applied));
|
||||
}
|
||||
}
|
||||
// no fertilizer is used up
|
||||
if (fertilizerToRemove.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
CompoundTag lastEntry = tags.get(tags.size() - 1);
|
||||
Collections.reverse(fertilizerToRemove);
|
||||
for (int i : fertilizerToRemove) {
|
||||
tags.remove(i);
|
||||
}
|
||||
// all the fertilizers are used up
|
||||
if (tags.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
// if the most recent applied fertilizer is the same
|
||||
CompoundTag newLastEntry = tags.get(tags.size() - 1);
|
||||
return lastEntry != newLastEntry;
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, CustomCropsBlockState state) {
|
||||
updateBlockAppearance(location, state, fertilizers(state));
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, CustomCropsBlockState state, Fertilizer[] fertilizers) {
|
||||
updateBlockAppearance(location, state, water(state) != 0, fertilizers);
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, CustomCropsBlockState state, boolean hasWater, Fertilizer[] fertilizers) {
|
||||
updateBlockAppearance(
|
||||
location,
|
||||
config(state),
|
||||
hasWater,
|
||||
// always using the latest fertilizer as appearance
|
||||
fertilizers.length == 0 ? null : fertilizers[fertilizers.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, PotConfig config, boolean hasWater, Fertilizer[] fertilizers) {
|
||||
updateBlockAppearance(
|
||||
location,
|
||||
config,
|
||||
hasWater,
|
||||
// always using the latest fertilizer as appearance
|
||||
fertilizers.length == 0 ? null : fertilizers[fertilizers.length - 1]
|
||||
);
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, PotConfig config, boolean hasWater, @Nullable Fertilizer fertilizer) {
|
||||
String appearance = config.getPotAppearance(hasWater, fertilizer == null ? null : fertilizer.type());
|
||||
if (StringUtils.isCapitalLetter(appearance)) {
|
||||
Block block = location.getBlock();
|
||||
Material type = Material.valueOf(appearance);
|
||||
if (type == Material.FARMLAND) {
|
||||
Farmland data = ((Farmland) Material.FARMLAND.createBlockData());
|
||||
data.setMoisture(hasWater ? 7 : 0);
|
||||
block.setBlockData(data, false);
|
||||
} else {
|
||||
block.setType(type, false);
|
||||
}
|
||||
} else {
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, ExistenceForm.BLOCK, appearance, FurnitureRotation.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
private Fertilizer tagToFertilizer(CompoundMap tag) {
|
||||
return Fertilizer.builder()
|
||||
.id(((StringTag) tag.get("id")).getValue())
|
||||
.times(((IntTag) tag.get("times")).getValue())
|
||||
.build();
|
||||
}
|
||||
|
||||
private CompoundMap fertilizerToTag(Fertilizer fertilizer) {
|
||||
CompoundMap tag = new CompoundMap();
|
||||
tag.put(new IntTag("times", fertilizer.times()));
|
||||
tag.put(new StringTag("id", fertilizer.id()));
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
public interface PotConfig {
|
||||
|
||||
String id();
|
||||
|
||||
int storage();
|
||||
|
||||
boolean isRainDropAccepted();
|
||||
|
||||
boolean isNearbyWaterAccepted();
|
||||
|
||||
WateringMethod[] wateringMethods();
|
||||
|
||||
Set<String> blocks();
|
||||
|
||||
boolean isWet(String blockID);
|
||||
|
||||
WaterBar waterBar();
|
||||
|
||||
int maxFertilizers();
|
||||
|
||||
String getPotAppearance(boolean watered, FertilizerType type);
|
||||
|
||||
Requirement<Player>[] placeRequirements();
|
||||
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
Requirement<Player>[] useRequirements();
|
||||
|
||||
Action<CustomCropsBlockState>[] tickActions();
|
||||
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
Action<Player>[] placeActions();
|
||||
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
Action<Player>[] fullWaterActions();
|
||||
|
||||
static Builder builder() {
|
||||
return new PotConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
PotConfig build();
|
||||
|
||||
Builder id(String id);
|
||||
|
||||
Builder storage(int storage);
|
||||
|
||||
Builder isRainDropAccepted(boolean isRainDropAccepted);
|
||||
|
||||
Builder isNearbyWaterAccepted(boolean isNearbyWaterAccepted);
|
||||
|
||||
Builder wateringMethods(WateringMethod[] wateringMethods);
|
||||
|
||||
Builder waterBar(WaterBar waterBar);
|
||||
|
||||
Builder maxFertilizers(int maxFertilizers);
|
||||
|
||||
Builder placeRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder useRequirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder tickActions(Action<CustomCropsBlockState>[] tickActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder placeActions(Action<Player>[] placeActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder fullWaterActions(Action<Player>[] fullWaterActions);
|
||||
|
||||
Builder basicAppearance(Pair<String, String> basicAppearance);
|
||||
|
||||
Builder potAppearanceMap(HashMap<FertilizerType, Pair<String, String>> potAppearanceMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.item.FertilizerType;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class PotConfigImpl implements PotConfig {
|
||||
|
||||
private final String id;
|
||||
private final Pair<String, String> basicAppearance;
|
||||
private final HashMap<FertilizerType, Pair<String, String>> potAppearanceMap;
|
||||
private final Set<String> blocks = new HashSet<>();
|
||||
private final Set<String> wetBlocks = new HashSet<>();
|
||||
private final int storage;
|
||||
private final boolean isRainDropAccepted;
|
||||
private final boolean isNearbyWaterAccepted;
|
||||
private final WateringMethod[] wateringMethods;
|
||||
private final WaterBar waterBar;
|
||||
private final int maxFertilizers;
|
||||
private final Requirement<Player>[] placeRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] useRequirements;
|
||||
private final Action<CustomCropsBlockState>[] tickActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] placeActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] fullWaterActions;
|
||||
|
||||
public PotConfigImpl(
|
||||
String id,
|
||||
Pair<String, String> basicAppearance,
|
||||
HashMap<FertilizerType, Pair<String, String>> potAppearanceMap,
|
||||
int storage,
|
||||
boolean isRainDropAccepted,
|
||||
boolean isNearbyWaterAccepted,
|
||||
WateringMethod[] wateringMethods,
|
||||
WaterBar waterBar,
|
||||
int maxFertilizers,
|
||||
Requirement<Player>[] placeRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] useRequirements,
|
||||
Action<CustomCropsBlockState>[] tickActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] placeActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] fullWaterActions
|
||||
) {
|
||||
this.id = id;
|
||||
this.basicAppearance = basicAppearance;
|
||||
this.potAppearanceMap = potAppearanceMap;
|
||||
this.storage = storage;
|
||||
this.isRainDropAccepted = isRainDropAccepted;
|
||||
this.isNearbyWaterAccepted = isNearbyWaterAccepted;
|
||||
this.wateringMethods = wateringMethods;
|
||||
this.waterBar = waterBar;
|
||||
this.maxFertilizers = maxFertilizers;
|
||||
this.placeRequirements = placeRequirements;
|
||||
this.breakRequirements = breakRequirements;
|
||||
this.useRequirements = useRequirements;
|
||||
this.tickActions = tickActions;
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
this.interactActions = interactActions;
|
||||
this.placeActions = placeActions;
|
||||
this.breakActions = breakActions;
|
||||
this.addWaterActions = addWaterActions;
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
this.blocks.add(basicAppearance.left());
|
||||
this.blocks.add(basicAppearance.right());
|
||||
this.wetBlocks.add(basicAppearance.right());
|
||||
for (Pair<String, String> pair : potAppearanceMap.values()) {
|
||||
this.blocks.add(pair.left());
|
||||
this.blocks.add(pair.right());
|
||||
this.wetBlocks.add(pair.right());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRainDropAccepted() {
|
||||
return isRainDropAccepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNearbyWaterAccepted() {
|
||||
return isNearbyWaterAccepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WateringMethod[] wateringMethods() {
|
||||
return wateringMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> blocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWet(String blockID) {
|
||||
return wetBlocks.contains(blockID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterBar waterBar() {
|
||||
return waterBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxFertilizers() {
|
||||
return maxFertilizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPotAppearance(boolean watered, FertilizerType type) {
|
||||
if (type != null) {
|
||||
Pair<String, String> appearance = potAppearanceMap.get(type);
|
||||
if (appearance != null) {
|
||||
return watered ? appearance.right() : appearance.left();
|
||||
}
|
||||
}
|
||||
return watered ? basicAppearance.right() : basicAppearance.left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] placeRequirements() {
|
||||
return placeRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] useRequirements() {
|
||||
return useRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] tickActions() {
|
||||
return tickActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] placeActions() {
|
||||
return placeActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] fullWaterActions() {
|
||||
return fullWaterActions;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
|
||||
private String id;
|
||||
private ExistenceForm existenceForm;
|
||||
private Pair<String, String> basicAppearance;
|
||||
private HashMap<FertilizerType, Pair<String, String>> potAppearanceMap;
|
||||
private int storage;
|
||||
private boolean isRainDropAccepted;
|
||||
private boolean isNearbyWaterAccepted;
|
||||
private WateringMethod[] wateringMethods;
|
||||
private WaterBar waterBar;
|
||||
private int maxFertilizers;
|
||||
private Requirement<Player>[] placeRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] useRequirements;
|
||||
private Action<CustomCropsBlockState>[] tickActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] placeActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] fullWaterActions;
|
||||
|
||||
@Override
|
||||
public PotConfig build() {
|
||||
return new PotConfigImpl(id, basicAppearance, potAppearanceMap, storage, isRainDropAccepted, isNearbyWaterAccepted, wateringMethods, waterBar, maxFertilizers, placeRequirements, breakRequirements, useRequirements, tickActions, reachLimitActions, interactActions, placeActions, breakActions, addWaterActions, fullWaterActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder storage(int storage) {
|
||||
this.storage = storage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder isRainDropAccepted(boolean isRainDropAccepted) {
|
||||
this.isRainDropAccepted = isRainDropAccepted;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder isNearbyWaterAccepted(boolean isNearbyWaterAccepted) {
|
||||
this.isNearbyWaterAccepted = isNearbyWaterAccepted;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wateringMethods(WateringMethod[] wateringMethods) {
|
||||
this.wateringMethods = wateringMethods;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder waterBar(WaterBar waterBar) {
|
||||
this.waterBar = waterBar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder maxFertilizers(int maxFertilizers) {
|
||||
this.maxFertilizers = maxFertilizers;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeRequirements(Requirement<Player>[] requirements) {
|
||||
this.placeRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] requirements) {
|
||||
this.breakRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder useRequirements(Requirement<Player>[] requirements) {
|
||||
this.useRequirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder tickActions(Action<CustomCropsBlockState>[] tickActions) {
|
||||
this.tickActions = tickActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeActions(Action<Player>[] placeActions) {
|
||||
this.placeActions = placeActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullWaterActions(Action<Player>[] fullWaterActions) {
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder basicAppearance(Pair<String, String> basicAppearance) {
|
||||
this.basicAppearance = basicAppearance;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder potAppearanceMap(HashMap<FertilizerType, Pair<String, String>> potAppearanceMap) {
|
||||
this.potAppearanceMap = potAppearanceMap;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.core.BuiltInBlockMechanics;
|
||||
import net.momirealms.customcrops.api.core.ConfigManager;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
import net.momirealms.customcrops.api.event.*;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ScarecrowBlock extends AbstractCustomCropsBlock {
|
||||
|
||||
public ScarecrowBlock() {
|
||||
super(BuiltInBlockMechanics.SCARECROW.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
//tickScarecrow(world, location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
tickScarecrow(world, location);
|
||||
}
|
||||
|
||||
private void tickScarecrow(CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (!ConfigManager.doubleCheck()) return;
|
||||
String id = BukkitCustomCropsPlugin.getInstance().getItemManager().id(location.toLocation(world.bukkitWorld()), ConfigManager.scarecrowExistenceForm());
|
||||
if (ConfigManager.scarecrow().contains(id)) return;
|
||||
// remove outdated data
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Scarecrow is removed at location[" + world.worldName() + "," + location + "] because the id of the block/furniture is [" + id + "]");
|
||||
world.removeBlockState(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsBlockState state = getOrFixState(world, pos3);
|
||||
ScarecrowInteractEvent interactEvent = new ScarecrowInteractEvent(event.player(), event.itemInHand(), event.location(), event.relatedID(), state, event.hand());
|
||||
EventUtils.fireAndForget(interactEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsBlockState state = getOrFixState(world, pos3);
|
||||
ScarecrowBreakEvent breakEvent = new ScarecrowBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), event.brokenID(), state, event.reason());
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
return;
|
||||
}
|
||||
world.removeBlockState(pos3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
CustomCropsBlockState state = createBlockState();
|
||||
ScarecrowPlaceEvent placeEvent = new ScarecrowPlaceEvent(event.player(), event.location(), event.itemID(), state);
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent)) {
|
||||
return;
|
||||
}
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public CustomCropsBlockState getOrFixState(CustomCropsWorld<?> world, Pos3 pos3) {
|
||||
Optional<CustomCropsBlockState> optional = world.getBlockState(pos3);
|
||||
if (optional.isPresent() && optional.get().type() instanceof ScarecrowBlock) {
|
||||
return optional.get();
|
||||
}
|
||||
CustomCropsBlockState state = createBlockState();
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at pos3[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import com.flowpowered.nbt.IntTag;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedPlaceEvent;
|
||||
import net.momirealms.customcrops.api.event.SprinklerBreakEvent;
|
||||
import net.momirealms.customcrops.api.event.SprinklerFillEvent;
|
||||
import net.momirealms.customcrops.api.event.SprinklerInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.SprinklerPlaceEvent;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.api.util.PlayerUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class SprinklerBlock extends AbstractCustomCropsBlock {
|
||||
|
||||
public SprinklerBlock() {
|
||||
super(BuiltInBlockMechanics.SPRINKLER.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (!world.setting().randomTickSprinkler() && canTick(state, world.setting().tickSprinklerInterval())) {
|
||||
tickSprinkler(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduledTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
if (world.setting().randomTickSprinkler() && canTick(state, world.setting().tickSprinklerInterval())) {
|
||||
tickSprinkler(state, world, location);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBreak(WrappedBreakEvent event) {
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.brokenID());
|
||||
if (config == null) {
|
||||
world.removeBlockState(pos3);
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.playerBreaker();
|
||||
Context<Player> context = Context.player(player);
|
||||
CustomCropsBlockState state = fixOrGetState(world, pos3, config, event.brokenID());
|
||||
if (!RequirementManager.isSatisfied(context, config.breakRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
SprinklerBreakEvent breakEvent = new SprinklerBreakEvent(event.entityBreaker(), event.blockBreaker(), event.location(), state, config, event.reason());
|
||||
if (EventUtils.fireAndCheckCancel(breakEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
world.removeBlockState(pos3);
|
||||
ActionManager.trigger(context, config.breakActions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlace(WrappedPlaceEvent event) {
|
||||
SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.placedID());
|
||||
if (config == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.player();
|
||||
Context<Player> context = Context.player(player);
|
||||
if (!RequirementManager.isSatisfied(context, config.placeRequirements())) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Pos3 pos3 = Pos3.from(event.location());
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
if (world.setting().sprinklerPerChunk() >= 0) {
|
||||
if (world.testChunkLimitation(pos3, this.getClass(), world.setting().sprinklerPerChunk())) {
|
||||
event.setCancelled(true);
|
||||
ActionManager.trigger(context, config.reachLimitActions());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CustomCropsBlockState state = createBlockState();
|
||||
id(state, config.id());
|
||||
water(state, config.threeDItemWithWater().equals(event.placedID()) ? 1 : 0);
|
||||
|
||||
SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, event.item(), event.hand(), event.location(), config, state);
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
world.addBlockState(pos3, state);
|
||||
ActionManager.trigger(context, config.placeActions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(WrappedInteractEvent event) {
|
||||
SprinklerConfig config = Registries.ITEM_TO_SPRINKLER.get(event.relatedID());
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = event.player();
|
||||
Context<Player> context = Context.player(player);
|
||||
CustomCropsBlockState state = fixOrGetState(event.world(), Pos3.from(event.location()), config, event.relatedID());
|
||||
if (!RequirementManager.isSatisfied(context, config.useRequirements())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int waterInSprinkler = water(state);
|
||||
String itemID = event.itemID();
|
||||
ItemStack itemInHand = event.itemInHand();
|
||||
if (!config.infinite()) {
|
||||
for (WateringMethod method : config.wateringMethods()) {
|
||||
if (method.getUsed().equals(itemID) && method.getUsedAmount() <= itemInHand.getAmount()) {
|
||||
if (method.checkRequirements(context)) {
|
||||
if (waterInSprinkler >= config.storage()) {
|
||||
ActionManager.trigger(context, config.fullWaterActions());
|
||||
} else {
|
||||
SprinklerFillEvent waterEvent = new SprinklerFillEvent(player, itemInHand, event.hand(), event.location(), method, state, config);
|
||||
if (EventUtils.fireAndCheckCancel(waterEvent))
|
||||
return;
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
itemInHand.setAmount(Math.max(0, itemInHand.getAmount() - method.getUsedAmount()));
|
||||
if (method.getReturned() != null) {
|
||||
ItemStack returned = BukkitCustomCropsPlugin.getInstance().getItemManager().build(player, method.getReturned());
|
||||
if (returned != null) {
|
||||
PlayerUtils.giveItem(player, returned, method.getReturnedAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
method.triggerActions(context);
|
||||
ActionManager.trigger(context, config.addWaterActions());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SprinklerInteractEvent interactEvent = new SprinklerInteractEvent(player, event.itemInHand(), event.location(), config, state, event.hand());
|
||||
if (EventUtils.fireAndCheckCancel(interactEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, config.interactActions());
|
||||
}
|
||||
|
||||
public CustomCropsBlockState fixOrGetState(CustomCropsWorld<?> world, Pos3 pos3, SprinklerConfig sprinklerConfig, String blockID) {
|
||||
Optional<CustomCropsBlockState> optionalPotState = world.getBlockState(pos3);
|
||||
if (optionalPotState.isPresent()) {
|
||||
CustomCropsBlockState potState = optionalPotState.get();
|
||||
if (potState.type() instanceof SprinklerBlock sprinklerBlock) {
|
||||
if (sprinklerBlock.id(potState).equals(sprinklerConfig.id())) {
|
||||
return potState;
|
||||
}
|
||||
}
|
||||
}
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.SPRINKLER.createBlockState();
|
||||
id(state, sprinklerConfig.id());
|
||||
water(state, blockID.equals(sprinklerConfig.threeDItemWithWater()) ? 1 : 0);
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private void tickSprinkler(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location) {
|
||||
SprinklerConfig config = config(state);
|
||||
if (config == null) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Sprinkler data is removed at location[" + world.worldName() + "," + location + "] because the sprinkler config[" + id(state) + "] has been removed.");
|
||||
world.removeBlockState(location);
|
||||
return;
|
||||
}
|
||||
boolean updateState;
|
||||
if (!config.infinite()) {
|
||||
int water = water(state);
|
||||
if (water <= 0) {
|
||||
return;
|
||||
}
|
||||
water(state, --water);
|
||||
updateState = water == 0;
|
||||
} else {
|
||||
updateState = false;
|
||||
}
|
||||
|
||||
Context<CustomCropsBlockState> context = Context.block(state);
|
||||
World bukkitWorld = world.bukkitWorld();
|
||||
Location bukkitLocation = location.toLocation(bukkitWorld);
|
||||
|
||||
CompletableFuture<Boolean> syncCheck = new CompletableFuture<>();
|
||||
|
||||
// place/remove entities on main thread
|
||||
BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(() -> {
|
||||
|
||||
if (ConfigManager.doubleCheck()) {
|
||||
String modelID = BukkitCustomCropsPlugin.getInstance().getItemManager().id(bukkitLocation, config.existenceForm());
|
||||
if (modelID == null || !config.modelIDs().contains(modelID)) {
|
||||
world.removeBlockState(location);
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().warn("Sprinkler[" + config.id() + "] is removed at Location[" + world.worldName() + "," + location + "] because the id of the block/furniture is " + modelID);
|
||||
syncCheck.complete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, config.workActions());
|
||||
if (updateState && !config.threeDItem().equals(config.threeDItemWithWater())) {
|
||||
updateBlockAppearance(bukkitLocation, config, false);
|
||||
}
|
||||
|
||||
syncCheck.complete(true);
|
||||
}, bukkitLocation);
|
||||
|
||||
syncCheck.thenAccept(result -> {
|
||||
if (result) {
|
||||
int[][] range = config.range();
|
||||
Pos3[] pos3s = new Pos3[range.length * 2];
|
||||
for (int i = 0; i < range.length; i++) {
|
||||
int x = range[i][0];
|
||||
int z = range[i][1];
|
||||
pos3s[i] = location.add(x, 0, z);
|
||||
pos3s[i] = location.add(x, -1, z);
|
||||
}
|
||||
|
||||
for (Pos3 pos3 : pos3s) {
|
||||
Optional<CustomCropsBlockState> optionalState = world.getBlockState(pos3);
|
||||
if (optionalState.isPresent()) {
|
||||
CustomCropsBlockState anotherState = optionalState.get();
|
||||
if (anotherState.type() instanceof PotBlock potBlock) {
|
||||
PotConfig potConfig = potBlock.config(anotherState);
|
||||
if (config.potWhitelist().contains(potConfig.id())) {
|
||||
if (potBlock.addWater(anotherState, potConfig, config.sprinklingAmount())) {
|
||||
BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(
|
||||
() -> potBlock.updateBlockAppearance(
|
||||
pos3.toLocation(world.bukkitWorld()),
|
||||
potConfig,
|
||||
true,
|
||||
potBlock.fertilizers(anotherState)
|
||||
),
|
||||
bukkitWorld,
|
||||
pos3.chunkX(), pos3.chunkZ()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean addWater(CustomCropsBlockState state, int water) {
|
||||
return water(state, water + water(state));
|
||||
}
|
||||
|
||||
public boolean addWater(CustomCropsBlockState state, SprinklerConfig config, int water) {
|
||||
return water(state, config, water + water(state));
|
||||
}
|
||||
|
||||
public int water(CustomCropsBlockState state) {
|
||||
return state.get("water").getAsIntTag().map(IntTag::getValue).orElse(0);
|
||||
}
|
||||
|
||||
public boolean water(CustomCropsBlockState state, int water) {
|
||||
return water(state, config(state), water);
|
||||
}
|
||||
|
||||
public boolean water(CustomCropsBlockState state, SprinklerConfig config, int water) {
|
||||
if (water < 0) water = 0;
|
||||
int current = Math.min(water, config.storage());
|
||||
int previous = water(state);
|
||||
if (water == previous) return false;
|
||||
state.set("water", new IntTag("water", current));
|
||||
return previous == 0 ^ current == 0;
|
||||
}
|
||||
|
||||
public SprinklerConfig config(CustomCropsBlockState state) {
|
||||
return Registries.SPRINKLER.get(id(state));
|
||||
}
|
||||
|
||||
public void updateBlockAppearance(Location location, SprinklerConfig config, boolean hasWater) {
|
||||
FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(location, ExistenceForm.ANY);
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(location, config.existenceForm(), hasWater ? config.threeDItemWithWater() : config.threeDItem(), rotation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface SprinklerConfig {
|
||||
|
||||
String id();
|
||||
|
||||
int storage();
|
||||
|
||||
int[][] range();
|
||||
|
||||
boolean infinite();
|
||||
|
||||
int sprinklingAmount();
|
||||
|
||||
@Nullable
|
||||
String twoDItem();
|
||||
|
||||
@NotNull
|
||||
String threeDItem();
|
||||
|
||||
@NotNull
|
||||
String threeDItemWithWater();
|
||||
|
||||
@NotNull
|
||||
Set<String> potWhitelist();
|
||||
|
||||
@NotNull
|
||||
Set<String> modelIDs();
|
||||
|
||||
@Nullable
|
||||
WaterBar waterBar();
|
||||
|
||||
@NotNull
|
||||
ExistenceForm existenceForm();
|
||||
|
||||
/**
|
||||
* Get the requirements for placement
|
||||
*
|
||||
* @return requirements for placement
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] placeRequirements();
|
||||
|
||||
/**
|
||||
* Get the requirements for breaking
|
||||
*
|
||||
* @return requirements for breaking
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] breakRequirements();
|
||||
|
||||
/**
|
||||
* Get the requirements for using
|
||||
*
|
||||
* @return requirements for using
|
||||
*/
|
||||
@Nullable
|
||||
Requirement<Player>[] useRequirements();
|
||||
|
||||
@Nullable
|
||||
Action<CustomCropsBlockState>[] workActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] interactActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] placeActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] breakActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] reachLimitActions();
|
||||
|
||||
@Nullable
|
||||
Action<Player>[] fullWaterActions();
|
||||
|
||||
@NotNull
|
||||
WateringMethod[] wateringMethods();
|
||||
|
||||
static Builder builder() {
|
||||
return new SprinklerConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
SprinklerConfig build();
|
||||
|
||||
Builder id(String id);
|
||||
|
||||
Builder existenceForm(ExistenceForm existenceForm);
|
||||
|
||||
Builder storage(int storage);
|
||||
|
||||
Builder range(int[][] range);
|
||||
|
||||
Builder infinite(boolean infinite);
|
||||
|
||||
Builder sprinklingAmount(int sprinklingAmount);
|
||||
|
||||
Builder potWhitelist(Set<String> potWhitelist);
|
||||
|
||||
Builder waterBar(WaterBar waterBar);
|
||||
|
||||
Builder twoDItem(@Nullable String twoDItem);
|
||||
|
||||
Builder threeDItem(String threeDItem);
|
||||
|
||||
Builder threeDItemWithWater(String threeDItemWithWater);
|
||||
|
||||
Builder placeRequirements(Requirement<Player>[] placeRequirements);
|
||||
|
||||
Builder breakRequirements(Requirement<Player>[] breakRequirements);
|
||||
|
||||
Builder useRequirements(Requirement<Player>[] useRequirements);
|
||||
|
||||
Builder workActions(Action<CustomCropsBlockState>[] workActions);
|
||||
|
||||
Builder interactActions(Action<Player>[] interactActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder reachLimitActions(Action<Player>[] reachLimitActions);
|
||||
|
||||
Builder placeActions(Action<Player>[] placeActions);
|
||||
|
||||
Builder breakActions(Action<Player>[] breakActions);
|
||||
|
||||
Builder fullWaterActions(Action<Player>[] fullWaterActions);
|
||||
|
||||
Builder wateringMethods(WateringMethod[] wateringMethods);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
package net.momirealms.customcrops.api.core.block;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.ExistenceForm;
|
||||
import net.momirealms.customcrops.api.core.water.WateringMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class SprinklerConfigImpl implements SprinklerConfig {
|
||||
private final String id;
|
||||
private final ExistenceForm existenceForm;
|
||||
private final int storage;
|
||||
private final int[][] range;
|
||||
private final boolean infinite;
|
||||
private final int sprinklingAmount;
|
||||
private final Set<String> potWhitelist;
|
||||
private final WaterBar waterBar;
|
||||
private final String twoDItem;
|
||||
private final String threeDItem;
|
||||
private final String threeDItemWithWater;
|
||||
private final Requirement<Player>[] placeRequirements;
|
||||
private final Requirement<Player>[] breakRequirements;
|
||||
private final Requirement<Player>[] useRequirements;
|
||||
private final Action<CustomCropsBlockState>[] workActions;
|
||||
private final Action<Player>[] interactActions;
|
||||
private final Action<Player>[] reachLimitActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] placeActions;
|
||||
private final Action<Player>[] breakActions;
|
||||
private final Action<Player>[] fullWaterActions;
|
||||
private final WateringMethod[] wateringMethods;
|
||||
private final Set<String> modelIDs = new HashSet<>();
|
||||
|
||||
public SprinklerConfigImpl(
|
||||
String id,
|
||||
ExistenceForm existenceForm,
|
||||
int storage,
|
||||
int[][] range,
|
||||
boolean infinite,
|
||||
int sprinklingAmount,
|
||||
Set<String> potWhitelist,
|
||||
WaterBar waterBar,
|
||||
String twoDItem,
|
||||
String threeDItem,
|
||||
String threeDItemWithWater,
|
||||
Requirement<Player>[] placeRequirements,
|
||||
Requirement<Player>[] breakRequirements,
|
||||
Requirement<Player>[] useRequirements,
|
||||
Action<CustomCropsBlockState>[] workActions,
|
||||
Action<Player>[] interactActions,
|
||||
Action<Player>[] reachLimitActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] placeActions,
|
||||
Action<Player>[] breakActions,
|
||||
Action<Player>[] fullWaterActions,
|
||||
WateringMethod[] wateringMethods
|
||||
) {
|
||||
this.id = id;
|
||||
this.existenceForm = existenceForm;
|
||||
this.storage = storage;
|
||||
this.range = range;
|
||||
this.infinite = infinite;
|
||||
this.sprinklingAmount = sprinklingAmount;
|
||||
this.potWhitelist = potWhitelist;
|
||||
this.waterBar = waterBar;
|
||||
this.twoDItem = twoDItem;
|
||||
this.threeDItem = Objects.requireNonNull(threeDItem);
|
||||
this.threeDItemWithWater = Objects.requireNonNullElse(threeDItemWithWater, threeDItem);
|
||||
this.placeRequirements = placeRequirements;
|
||||
this.breakRequirements = breakRequirements;
|
||||
this.useRequirements = useRequirements;
|
||||
this.workActions = workActions;
|
||||
this.interactActions = interactActions;
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
this.addWaterActions = addWaterActions;
|
||||
this.placeActions = placeActions;
|
||||
this.breakActions = breakActions;
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
this.modelIDs.add(twoDItem);
|
||||
this.modelIDs.add(threeDItem);
|
||||
this.modelIDs.add(threeDItemWithWater);
|
||||
this.wateringMethods = wateringMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[][] range() {
|
||||
return range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean infinite() {
|
||||
return infinite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sprinklingAmount() {
|
||||
return sprinklingAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String twoDItem() {
|
||||
return twoDItem;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String threeDItem() {
|
||||
return threeDItem;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String threeDItemWithWater() {
|
||||
return threeDItemWithWater;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> potWhitelist() {
|
||||
return potWhitelist;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> modelIDs() {
|
||||
return modelIDs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterBar waterBar() {
|
||||
return waterBar;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ExistenceForm existenceForm() {
|
||||
return existenceForm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] placeRequirements() {
|
||||
return placeRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] breakRequirements() {
|
||||
return breakRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] useRequirements() {
|
||||
return useRequirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<CustomCropsBlockState>[] workActions() {
|
||||
return workActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] interactActions() {
|
||||
return interactActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] placeActions() {
|
||||
return placeActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] breakActions() {
|
||||
return breakActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] reachLimitActions() {
|
||||
return reachLimitActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] fullWaterActions() {
|
||||
return fullWaterActions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WateringMethod[] wateringMethods() {
|
||||
return wateringMethods == null ? new WateringMethod[0] : wateringMethods;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
private String id;
|
||||
private ExistenceForm existenceForm;
|
||||
private int storage;
|
||||
private int[][] range;
|
||||
private boolean infinite;
|
||||
private int sprinklingAmount;
|
||||
private Set<String> potWhitelist;
|
||||
private WaterBar waterBar;
|
||||
private Requirement<Player>[] placeRequirements;
|
||||
private Requirement<Player>[] breakRequirements;
|
||||
private Requirement<Player>[] useRequirements;
|
||||
private Action<CustomCropsBlockState>[] workActions;
|
||||
private Action<Player>[] interactActions;
|
||||
private Action<Player>[] reachLimitActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] placeActions;
|
||||
private Action<Player>[] breakActions;
|
||||
private Action<Player>[] fullWaterActions;
|
||||
private String twoDItem;
|
||||
private String threeDItem;
|
||||
private String threeDItemWithWater;
|
||||
private WateringMethod[] wateringMethods;
|
||||
|
||||
@Override
|
||||
public SprinklerConfig build() {
|
||||
return new SprinklerConfigImpl(id, existenceForm, storage, range, infinite, sprinklingAmount, potWhitelist, waterBar, twoDItem, threeDItem, threeDItemWithWater,
|
||||
placeRequirements, breakRequirements, useRequirements, workActions, interactActions, reachLimitActions, addWaterActions, placeActions, breakActions, fullWaterActions, wateringMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder existenceForm(ExistenceForm existenceForm) {
|
||||
this.existenceForm = existenceForm;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder storage(int storage) {
|
||||
this.storage = storage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder range(int[][] range) {
|
||||
this.range = range;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder infinite(boolean infinite) {
|
||||
this.infinite = infinite;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder sprinklingAmount(int sprinklingAmount) {
|
||||
this.sprinklingAmount = sprinklingAmount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder potWhitelist(Set<String> potWhitelist) {
|
||||
this.potWhitelist = new HashSet<>(potWhitelist);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder waterBar(WaterBar waterBar) {
|
||||
this.waterBar = waterBar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder twoDItem(String twoDItem) {
|
||||
this.twoDItem = twoDItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder threeDItem(String threeDItem) {
|
||||
this.threeDItem = threeDItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder threeDItemWithWater(String threeDItemWithWater) {
|
||||
this.threeDItemWithWater = threeDItemWithWater;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeRequirements(Requirement<Player>[] placeRequirements) {
|
||||
this.placeRequirements = placeRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakRequirements(Requirement<Player>[] breakRequirements) {
|
||||
this.breakRequirements = breakRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder useRequirements(Requirement<Player>[] useRequirements) {
|
||||
this.useRequirements = useRequirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder workActions(Action<CustomCropsBlockState>[] workActions) {
|
||||
this.workActions = workActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder interactActions(Action<Player>[] interactActions) {
|
||||
this.interactActions = interactActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reachLimitActions(Action<Player>[] reachLimitActions) {
|
||||
this.reachLimitActions = reachLimitActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder placeActions(Action<Player>[] placeActions) {
|
||||
this.placeActions = placeActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder breakActions(Action<Player>[] breakActions) {
|
||||
this.breakActions = breakActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullWaterActions(Action<Player>[] fullWaterActions) {
|
||||
this.fullWaterActions = fullWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wateringMethods(WateringMethod[] wateringMethods) {
|
||||
this.wateringMethods = wateringMethods;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.core.ExistenceForm;
|
||||
|
||||
public class VariationData {
|
||||
|
||||
private final String id;
|
||||
private final ExistenceForm form;
|
||||
private final double chance;
|
||||
|
||||
public VariationData(String id, ExistenceForm form, double chance) {
|
||||
this.id = id;
|
||||
this.form = form;
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ExistenceForm existenceForm() {
|
||||
return form;
|
||||
}
|
||||
|
||||
public double chance() {
|
||||
return chance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.InteractionResult;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
|
||||
public abstract class AbstractCustomCropsItem implements CustomCropsItem {
|
||||
|
||||
private final Key type;
|
||||
|
||||
public AbstractCustomCropsItem(Key type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(WrappedInteractEvent wrapped) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interactAir(WrappedInteractAirEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractFertilizerConfig implements FertilizerConfig {
|
||||
|
||||
protected String id;
|
||||
protected String itemID;
|
||||
protected String icon;
|
||||
protected int times;
|
||||
protected boolean beforePlant;
|
||||
protected Set<String> whitelistPots;
|
||||
protected Requirement<Player>[] requirements;
|
||||
protected Action<Player>[] beforePlantActions;
|
||||
protected Action<Player>[] useActions;
|
||||
protected Action<Player>[] wrongPotActions;
|
||||
|
||||
public AbstractFertilizerConfig(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions
|
||||
) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
this.itemID = Objects.requireNonNull(itemID);
|
||||
this.beforePlant = beforePlant;
|
||||
this.times = times;
|
||||
this.icon = icon;
|
||||
this.requirements = requirements;
|
||||
this.whitelistPots = whitelistPots;
|
||||
this.beforePlantActions = beforePlantActions;
|
||||
this.useActions = useActions;
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] beforePlantActions() {
|
||||
return beforePlantActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] useActions() {
|
||||
return useActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforePlant() {
|
||||
return beforePlant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> whitelistPots() {
|
||||
return whitelistPots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String icon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int times() {
|
||||
return times;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] requirements() {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String itemID() {
|
||||
return itemID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processGainPoints(int previousPoints) {
|
||||
return previousPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processWaterToLose(int waterToLose) {
|
||||
return waterToLose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double processVariationChance(double previousChance) {
|
||||
return previousChance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processDroppedItemAmount(int amount) {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] overrideQualityRatio() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.common.util.Key;
|
||||
import net.momirealms.customcrops.api.core.InteractionResult;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
|
||||
public interface CustomCropsItem {
|
||||
|
||||
Key type();
|
||||
|
||||
InteractionResult interactAt(WrappedInteractEvent event);
|
||||
|
||||
void interactAir(WrappedInteractAirEvent event);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.core.Registries;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface Fertilizer {
|
||||
|
||||
String id();
|
||||
|
||||
int times();
|
||||
|
||||
boolean reduceTimes();
|
||||
|
||||
// Flexibility matters more than performance
|
||||
default FertilizerType type() {
|
||||
FertilizerConfig config = Registries.FERTILIZER.get(id());
|
||||
if (config == null) {
|
||||
return FertilizerType.INVALID;
|
||||
}
|
||||
return config.type();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default FertilizerConfig config() {
|
||||
return Registries.FERTILIZER.get(id());
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new FertilizerImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
Fertilizer build();
|
||||
|
||||
Builder id(String id);
|
||||
|
||||
Builder times(int times);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface FertilizerConfig {
|
||||
|
||||
String id();
|
||||
|
||||
FertilizerType type();
|
||||
|
||||
boolean beforePlant();
|
||||
|
||||
String icon();
|
||||
|
||||
Requirement<Player>[] requirements();
|
||||
|
||||
String itemID();
|
||||
|
||||
int times();
|
||||
|
||||
Set<String> whitelistPots();
|
||||
|
||||
Action<Player>[] beforePlantActions();
|
||||
|
||||
Action<Player>[] useActions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
int processGainPoints(int previousPoints);
|
||||
|
||||
int processWaterToLose(int waterToLose);
|
||||
|
||||
double processVariationChance(double previousChance);
|
||||
|
||||
int processDroppedItemAmount(int amount);
|
||||
|
||||
@Nullable
|
||||
double[] overrideQualityRatio();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
public class FertilizerImpl implements Fertilizer {
|
||||
|
||||
private final String id;
|
||||
private int times;
|
||||
|
||||
public FertilizerImpl(String id, int times) {
|
||||
this.id = id;
|
||||
this.times = times;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int times() {
|
||||
return times;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reduceTimes() {
|
||||
times--;
|
||||
return times <= 0;
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Fertilizer.Builder {
|
||||
|
||||
private String id;
|
||||
private int times;
|
||||
|
||||
@Override
|
||||
public Fertilizer build() {
|
||||
return new FertilizerImpl(id, times);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder times(int times) {
|
||||
this.times = times;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.BuiltInBlockMechanics;
|
||||
import net.momirealms.customcrops.api.core.BuiltInItemMechanics;
|
||||
import net.momirealms.customcrops.api.core.InteractionResult;
|
||||
import net.momirealms.customcrops.api.core.Registries;
|
||||
import net.momirealms.customcrops.api.core.block.CropBlock;
|
||||
import net.momirealms.customcrops.api.core.block.CropConfig;
|
||||
import net.momirealms.customcrops.api.core.block.PotBlock;
|
||||
import net.momirealms.customcrops.api.core.block.PotConfig;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.FertilizerUseEvent;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FertilizerItem extends AbstractCustomCropsItem {
|
||||
|
||||
public FertilizerItem() {
|
||||
super(BuiltInItemMechanics.FERTILIZER.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(WrappedInteractEvent event) {
|
||||
FertilizerConfig fertilizerConfig = Registries.FERTILIZER.get(event.itemID());
|
||||
if (fertilizerConfig == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
final Player player = event.player();
|
||||
final Context<Player> context = Context.player(player);
|
||||
final CustomCropsWorld<?> world = event.world();
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
String targetBlockID = event.relatedID();
|
||||
Location targetLocation = event.location();
|
||||
|
||||
// if the clicked block is a crop, correct the target block
|
||||
List<CropConfig> cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(event.relatedID());
|
||||
if (cropConfigs != null) {
|
||||
// is a crop
|
||||
targetLocation = targetLocation.subtract(0,1,0);
|
||||
targetBlockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetLocation);
|
||||
}
|
||||
|
||||
// if the clicked block is a pot
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(targetBlockID);
|
||||
if (potConfig != null) {
|
||||
// check pot whitelist
|
||||
if (!fertilizerConfig.whitelistPots().contains(potConfig.id())) {
|
||||
ActionManager.trigger(context, fertilizerConfig.wrongPotActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check requirements
|
||||
if (!RequirementManager.isSatisfied(context, fertilizerConfig.requirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
if (!RequirementManager.isSatisfied(context, potConfig.useRequirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check "before-plant"
|
||||
if (fertilizerConfig.beforePlant()) {
|
||||
Location cropLocation = targetLocation.clone().add(0,1,0);
|
||||
Optional<CustomCropsBlockState> state = world.getBlockState(Pos3.from(cropLocation));
|
||||
if (state.isPresent()) {
|
||||
CustomCropsBlockState blockState = state.get();
|
||||
if (blockState.type() instanceof CropBlock) {
|
||||
ActionManager.trigger(context, fertilizerConfig.beforePlantActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic();
|
||||
assert potBlock != null;
|
||||
// fix or get data
|
||||
Fertilizer fertilizer = Fertilizer.builder()
|
||||
.times(fertilizerConfig.times())
|
||||
.id(fertilizerConfig.id())
|
||||
.build();
|
||||
CustomCropsBlockState potState = potBlock.fixOrGetState(world, Pos3.from(targetLocation), potConfig, event.relatedID());
|
||||
if (!potBlock.canApplyFertilizer(potState,fertilizer)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// trigger event
|
||||
FertilizerUseEvent useEvent = new FertilizerUseEvent(player, itemInHand, fertilizer, targetLocation, potState, event.hand(), potConfig);
|
||||
if (EventUtils.fireAndCheckCancel(useEvent))
|
||||
return InteractionResult.FAIL;
|
||||
// add the fertilizer
|
||||
if (potBlock.addFertilizer(potState, fertilizer)) {
|
||||
potBlock.updateBlockAppearance(targetLocation, potState, potBlock.fertilizers(potState));
|
||||
}
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
itemInHand.setAmount(itemInHand.getAmount() - 1);
|
||||
}
|
||||
ActionManager.trigger(context, fertilizerConfig.useActions());
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.core.ConfigManager;
|
||||
import net.momirealms.customcrops.common.util.TriFunction;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FertilizerType {
|
||||
|
||||
public static final FertilizerType SPEED_GROW = of("speed_grow",
|
||||
(manager, id, section) -> {
|
||||
ActionManager<Player> pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class);
|
||||
return SpeedGrow.create(
|
||||
id, section.getString("item"),
|
||||
section.getInt("times", 14), section.getString("icon", ""),
|
||||
section.getBoolean("before-plant", false),
|
||||
new HashSet<>(section.getStringList("pot-whitelist")),
|
||||
BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true),
|
||||
pam.parseActions(section.getSection("events.before_plant")),
|
||||
pam.parseActions(section.getSection("events.use")),
|
||||
pam.parseActions(section.getSection("events.wrong_pot")),
|
||||
manager.getIntChancePair(section.getSection("chance"))
|
||||
);
|
||||
}
|
||||
);
|
||||
public static final FertilizerType QUALITY = of("quality",
|
||||
(manager, id, section) -> {
|
||||
ActionManager<Player> pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class);
|
||||
return Quality.create(
|
||||
id, section.getString("item"),
|
||||
section.getInt("times", 14), section.getString("icon", ""),
|
||||
section.getBoolean("before-plant", false),
|
||||
new HashSet<>(section.getStringList("pot-whitelist")),
|
||||
BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true),
|
||||
pam.parseActions(section.getSection("events.before_plant")),
|
||||
pam.parseActions(section.getSection("events.use")),
|
||||
pam.parseActions(section.getSection("events.wrong_pot")),
|
||||
section.getDouble("chance", 1d),
|
||||
manager.getQualityRatio(section.getString("ratio"))
|
||||
);
|
||||
}
|
||||
);
|
||||
public static final FertilizerType SOIL_RETAIN = of("soil_retain",
|
||||
(manager, id, section) -> {
|
||||
ActionManager<Player> pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class);
|
||||
return SoilRetain.create(
|
||||
id, section.getString("item"),
|
||||
section.getInt("times", 14), section.getString("icon", ""),
|
||||
section.getBoolean("before-plant", false),
|
||||
new HashSet<>(section.getStringList("pot-whitelist")),
|
||||
BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true),
|
||||
pam.parseActions(section.getSection("events.before_plant")),
|
||||
pam.parseActions(section.getSection("events.use")),
|
||||
pam.parseActions(section.getSection("events.wrong_pot")),
|
||||
section.getDouble("chance", 1d)
|
||||
);
|
||||
}
|
||||
);
|
||||
public static final FertilizerType VARIATION = of("variation",
|
||||
(manager, id, section) -> {
|
||||
ActionManager<Player> pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class);
|
||||
return Variation.create(
|
||||
id, section.getString("item"),
|
||||
section.getInt("times", 14), section.getString("icon", ""),
|
||||
section.getBoolean("before-plant", false),
|
||||
new HashSet<>(section.getStringList("pot-whitelist")),
|
||||
BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true),
|
||||
pam.parseActions(section.getSection("events.before_plant")),
|
||||
pam.parseActions(section.getSection("events.use")),
|
||||
pam.parseActions(section.getSection("events.wrong_pot")),
|
||||
section.getBoolean("addOrMultiply", true),
|
||||
section.getDouble("chance", 0.01d)
|
||||
);
|
||||
}
|
||||
);
|
||||
public static final FertilizerType YIELD_INCREASE = of("yield_increase",
|
||||
(manager, id, section) -> {
|
||||
ActionManager<Player> pam = BukkitCustomCropsPlugin.getInstance().getActionManager(Player.class);
|
||||
return YieldIncrease.create(
|
||||
id, section.getString("item"),
|
||||
section.getInt("times", 14), section.getString("icon", ""),
|
||||
section.getBoolean("before-plant", false),
|
||||
new HashSet<>(section.getStringList("pot-whitelist")),
|
||||
BukkitCustomCropsPlugin.getInstance().getRequirementManager(Player.class).parseRequirements(section.getSection("requirements"), true),
|
||||
pam.parseActions(section.getSection("events.before_plant")),
|
||||
pam.parseActions(section.getSection("events.use")),
|
||||
pam.parseActions(section.getSection("events.wrong_pot")),
|
||||
manager.getIntChancePair(section.getSection("chance"))
|
||||
);
|
||||
}
|
||||
);
|
||||
public static final FertilizerType INVALID = of("invalid",
|
||||
(manager, id, section) -> null
|
||||
);
|
||||
|
||||
private final String id;
|
||||
private final TriFunction<ConfigManager, String, Section, FertilizerConfig> argumentConsumer;
|
||||
|
||||
public FertilizerType(String id, TriFunction<ConfigManager, String, Section, FertilizerConfig> argumentConsumer) {
|
||||
this.id = id;
|
||||
this.argumentConsumer = argumentConsumer;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static FertilizerType of(String id, TriFunction<ConfigManager, String, Section, FertilizerConfig> argumentConsumer) {
|
||||
return new FertilizerType(id, argumentConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FertilizerType that = (FertilizerType) o;
|
||||
return Objects.equals(id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
public FertilizerConfig parse(ConfigManager manager, String id, Section section) {
|
||||
return argumentConsumer.apply(manager, id, section);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface Quality extends FertilizerConfig {
|
||||
|
||||
double chance();
|
||||
|
||||
double[] ratio();
|
||||
|
||||
static Quality create(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance,
|
||||
double[] ratio
|
||||
) {
|
||||
return new QualityImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance, ratio);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class QualityImpl extends AbstractFertilizerConfig implements Quality {
|
||||
|
||||
private final double chance;
|
||||
private final double[] ratio;
|
||||
|
||||
protected QualityImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance,
|
||||
double[] ratio
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chance = chance;
|
||||
this.ratio = ratio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double chance() {
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] ratio() {
|
||||
return ratio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FertilizerType type() {
|
||||
return FertilizerType.QUALITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double[] overrideQualityRatio() {
|
||||
return ratio();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.context.ContextKeys;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.block.CropBlock;
|
||||
import net.momirealms.customcrops.api.core.block.CropConfig;
|
||||
import net.momirealms.customcrops.api.core.block.CropStageConfig;
|
||||
import net.momirealms.customcrops.api.core.block.PotConfig;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.CropPlantEvent;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.api.util.LocationUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class SeedItem extends AbstractCustomCropsItem {
|
||||
|
||||
public SeedItem() {
|
||||
super(BuiltInBlockMechanics.CROP.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(WrappedInteractEvent event) {
|
||||
// check if it's a pot
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(event.relatedID());
|
||||
if (potConfig == null) return InteractionResult.PASS;
|
||||
// check if the crop exists
|
||||
CropConfig cropConfig = Registries.SEED_TO_CROP.get(event.itemID());
|
||||
if (cropConfig == null) return InteractionResult.FAIL;
|
||||
// check the block face
|
||||
if (event.clickedBlockFace() != BlockFace.UP)
|
||||
return InteractionResult.PASS;
|
||||
|
||||
final Player player = event.player();
|
||||
Context<Player> context = Context.player(player);
|
||||
// check pot whitelist
|
||||
if (!cropConfig.potWhitelist().contains(potConfig.id())) {
|
||||
ActionManager.trigger(context, cropConfig.wrongPotActions());
|
||||
}
|
||||
// check plant requirements
|
||||
if (!RequirementManager.isSatisfied(context, cropConfig.plantRequirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check if the block is empty
|
||||
if (!suitableForSeed(event.location())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
CustomCropsWorld<?> world = event.world();
|
||||
Location seedLocation = event.location().add(0, 1, 0);
|
||||
Pos3 pos3 = Pos3.from(seedLocation);
|
||||
// check limitation
|
||||
if (world.setting().cropPerChunk() >= 0) {
|
||||
if (world.testChunkLimitation(pos3, CropBlock.class, world.setting().cropPerChunk())) {
|
||||
ActionManager.trigger(context, cropConfig.reachLimitActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.CROP.createBlockState();
|
||||
CropBlock cropBlock = (CropBlock) state.type();
|
||||
cropBlock.id(state, cropConfig.id());
|
||||
// trigger event
|
||||
CropPlantEvent plantEvent = new CropPlantEvent(player, itemInHand, event.hand(), seedLocation, cropConfig, state, 0);
|
||||
if (EventUtils.fireAndCheckCancel(plantEvent)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
int point = plantEvent.getPoint();
|
||||
int temp = point;
|
||||
ExistenceForm form = null;
|
||||
String stageID = null;
|
||||
while (temp >= 0) {
|
||||
Map.Entry<Integer, CropStageConfig> entry = cropConfig.getFloorStageEntry(temp);
|
||||
CropStageConfig stageConfig = entry.getValue();
|
||||
if (stageConfig.stageID() != null) {
|
||||
form = stageConfig.existenceForm();
|
||||
stageID = stageConfig.stageID();
|
||||
break;
|
||||
}
|
||||
temp = stageConfig.point() - 1;
|
||||
}
|
||||
if (stageID == null || form == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// reduce item
|
||||
if (player.getGameMode() != GameMode.CREATIVE)
|
||||
itemInHand.setAmount(itemInHand.getAmount() - 1);
|
||||
// place model
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(seedLocation, form, stageID, cropConfig.rotation() ? FurnitureRotation.random() : FurnitureRotation.NONE);
|
||||
cropBlock.point(state, point);
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
|
||||
// set slot arg
|
||||
context.arg(ContextKeys.SLOT, event.hand());
|
||||
// trigger plant actions
|
||||
ActionManager.trigger(context, cropConfig.plantActions());
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
private boolean suitableForSeed(Location location) {
|
||||
Block block = location.getBlock();
|
||||
if (block.getType() != Material.AIR) return false;
|
||||
Location center = LocationUtils.toBlockCenterLocation(location);
|
||||
Collection<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5);
|
||||
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
|
||||
return entities.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface SoilRetain extends FertilizerConfig {
|
||||
|
||||
double chance();
|
||||
|
||||
static SoilRetain create(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance
|
||||
) {
|
||||
return new SoilRetainImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class SoilRetainImpl extends AbstractFertilizerConfig implements SoilRetain {
|
||||
|
||||
private final double chance;
|
||||
|
||||
public SoilRetainImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
double chance
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double chance() {
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FertilizerType type() {
|
||||
return FertilizerType.SOIL_RETAIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processWaterToLose(int waterToLose) {
|
||||
return Math.min(waterToLose, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface SpeedGrow extends FertilizerConfig {
|
||||
|
||||
int pointBonus();
|
||||
|
||||
static SpeedGrow create(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
return new SpeedGrowImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class SpeedGrowImpl extends AbstractFertilizerConfig implements SpeedGrow {
|
||||
|
||||
private final List<Pair<Double, Integer>> chances;
|
||||
|
||||
public SpeedGrowImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chances = chances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int pointBonus() {
|
||||
for (Pair<Double, Integer> pair : chances) {
|
||||
if (Math.random() < pair.left()) {
|
||||
return pair.right();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FertilizerType type() {
|
||||
return FertilizerType.SPEED_GROW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processGainPoints(int previousPoints) {
|
||||
return pointBonus() + previousPoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.block.SprinklerBlock;
|
||||
import net.momirealms.customcrops.api.core.block.SprinklerConfig;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.SprinklerPlaceEvent;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.api.util.LocationUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class SprinklerItem extends AbstractCustomCropsItem {
|
||||
|
||||
public SprinklerItem() {
|
||||
super(BuiltInItemMechanics.SPRINKLER_ITEM.key());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(WrappedInteractEvent event) {
|
||||
// should be place on block
|
||||
if (event.existenceForm() != ExistenceForm.BLOCK)
|
||||
return InteractionResult.PASS;
|
||||
SprinklerConfig config = Registries.SPRINKLER.get(event.itemID());
|
||||
if (config == null) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
Block clicked = event.location().getBlock();
|
||||
Location targetLocation;
|
||||
if (clicked.isReplaceable()) {
|
||||
targetLocation = event.location();
|
||||
} else {
|
||||
if (event.clickedBlockFace() != BlockFace.UP)
|
||||
return InteractionResult.PASS;
|
||||
if (!clicked.isSolid())
|
||||
return InteractionResult.PASS;
|
||||
targetLocation = event.location().clone().add(0,1,0);
|
||||
if (!suitableForSprinkler(targetLocation)) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
}
|
||||
|
||||
final Player player = event.player();
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
Context<Player> context = Context.player(player);
|
||||
// check requirements
|
||||
if (!RequirementManager.isSatisfied(context, config.placeRequirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
final CustomCropsWorld<?> world = event.world();
|
||||
Pos3 pos3 = Pos3.from(targetLocation);
|
||||
// check limitation
|
||||
if (world.setting().sprinklerPerChunk() >= 0) {
|
||||
if (world.testChunkLimitation(pos3, SprinklerBlock.class, world.setting().sprinklerPerChunk())) {
|
||||
ActionManager.trigger(context, config.reachLimitActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
// generate state
|
||||
CustomCropsBlockState state = BuiltInBlockMechanics.SPRINKLER.createBlockState();
|
||||
SprinklerBlock sprinklerBlock = (SprinklerBlock) BuiltInBlockMechanics.SPRINKLER.mechanic();
|
||||
sprinklerBlock.id(state, config.id());
|
||||
sprinklerBlock.water(state, 0);
|
||||
// trigger event
|
||||
SprinklerPlaceEvent placeEvent = new SprinklerPlaceEvent(player, itemInHand, event.hand(), targetLocation.clone(), config, state);
|
||||
if (EventUtils.fireAndCheckCancel(placeEvent))
|
||||
return InteractionResult.FAIL;
|
||||
// clear replaceable block
|
||||
targetLocation.getBlock().setType(Material.AIR, false);
|
||||
if (player.getGameMode() != GameMode.CREATIVE)
|
||||
itemInHand.setAmount(itemInHand.getAmount() - 1);
|
||||
|
||||
// place the sprinkler
|
||||
BukkitCustomCropsPlugin.getInstance().getItemManager().place(LocationUtils.toSurfaceCenterLocation(targetLocation), config.existenceForm(), config.threeDItem(), FurnitureRotation.NONE);
|
||||
world.addBlockState(pos3, state).ifPresent(previous -> {
|
||||
BukkitCustomCropsPlugin.getInstance().debug(
|
||||
"Overwrite old data with " + state.compoundMap().toString() +
|
||||
" at location[" + world.worldName() + "," + pos3 + "] which used to be " + previous.compoundMap().toString()
|
||||
);
|
||||
});
|
||||
ActionManager.trigger(context, config.placeActions());
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
private boolean suitableForSprinkler(Location location) {
|
||||
Block block = location.getBlock();
|
||||
if (block.getType() != Material.AIR) return false;
|
||||
Location center = LocationUtils.toBlockCenterLocation(location);
|
||||
Collection<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.51,0.5);
|
||||
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
|
||||
return entities.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface Variation extends FertilizerConfig {
|
||||
|
||||
double chanceBonus();
|
||||
|
||||
boolean addOrMultiply();
|
||||
|
||||
static Variation create(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
boolean addOrMultiply,
|
||||
double chance
|
||||
) {
|
||||
return new VariationImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, addOrMultiply, chance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class VariationImpl extends AbstractFertilizerConfig implements Variation {
|
||||
|
||||
private final double chance;
|
||||
private final boolean addOrMultiply;
|
||||
|
||||
public VariationImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
boolean addOrMultiply,
|
||||
double chance
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chance = chance;
|
||||
this.addOrMultiply = addOrMultiply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double chanceBonus() {
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addOrMultiply() {
|
||||
return addOrMultiply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FertilizerType type() {
|
||||
return FertilizerType.VARIATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double processVariationChance(double previousChance) {
|
||||
if (addOrMultiply()) {
|
||||
return previousChance + chanceBonus();
|
||||
} else {
|
||||
return previousChance * chanceBonus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.water.FillMethod;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.misc.value.TextValue;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface WateringCanConfig {
|
||||
|
||||
String id();
|
||||
|
||||
String itemID();
|
||||
|
||||
int width();
|
||||
|
||||
int length();
|
||||
|
||||
int storage();
|
||||
|
||||
int wateringAmount();
|
||||
|
||||
boolean dynamicLore();
|
||||
|
||||
Set<String> whitelistPots();
|
||||
|
||||
Set<String> whitelistSprinklers();
|
||||
|
||||
List<TextValue<Player>> lore();
|
||||
|
||||
WaterBar waterBar();
|
||||
|
||||
Requirement<Player>[] requirements();
|
||||
|
||||
boolean infinite();
|
||||
|
||||
Integer appearance(int water);
|
||||
|
||||
Action<Player>[] fullActions();
|
||||
|
||||
Action<Player>[] addWaterActions();
|
||||
|
||||
Action<Player>[] consumeWaterActions();
|
||||
|
||||
Action<Player>[] runOutOfWaterActions();
|
||||
|
||||
Action<Player>[] wrongPotActions();
|
||||
|
||||
Action<Player>[] wrongSprinklerActions();
|
||||
|
||||
FillMethod[] fillMethods();
|
||||
|
||||
static Builder builder() {
|
||||
return new WateringCanConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
WateringCanConfig build();
|
||||
|
||||
Builder id(String id);
|
||||
|
||||
Builder itemID(String itemID);
|
||||
|
||||
Builder width(int width);
|
||||
|
||||
Builder length(int length);
|
||||
|
||||
Builder storage(int storage);
|
||||
|
||||
Builder wateringAmount(int wateringAmount);
|
||||
|
||||
Builder dynamicLore(boolean dynamicLore);
|
||||
|
||||
Builder potWhitelist(Set<String> whitelistPots);
|
||||
|
||||
Builder sprinklerWhitelist(Set<String> whitelistSprinklers);
|
||||
|
||||
Builder lore(List<TextValue<Player>> lore);
|
||||
|
||||
Builder waterBar(WaterBar waterBar);
|
||||
|
||||
Builder requirements(Requirement<Player>[] requirements);
|
||||
|
||||
Builder infinite(boolean infinite);
|
||||
|
||||
Builder appearances(Map<Integer, Integer> appearances);
|
||||
|
||||
Builder fullActions(Action<Player>[] fullActions);
|
||||
|
||||
Builder addWaterActions(Action<Player>[] addWaterActions);
|
||||
|
||||
Builder consumeWaterActions(Action<Player>[] consumeWaterActions);
|
||||
|
||||
Builder runOutOfWaterActions(Action<Player>[] runOutOfWaterActions);
|
||||
|
||||
Builder wrongPotActions(Action<Player>[] wrongPotActions);
|
||||
|
||||
Builder wrongSprinklerActions(Action<Player>[] wrongSprinklerActions);
|
||||
|
||||
Builder fillMethods(FillMethod[] fillMethods);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.core.water.FillMethod;
|
||||
import net.momirealms.customcrops.api.misc.WaterBar;
|
||||
import net.momirealms.customcrops.api.misc.value.TextValue;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class WateringCanConfigImpl implements WateringCanConfig {
|
||||
|
||||
private final String id;
|
||||
private final String itemID;
|
||||
private final int width;
|
||||
private final int length;
|
||||
private final int storage;
|
||||
private final int wateringAmount;
|
||||
private final boolean dynamicLore;
|
||||
private final Set<String> whitelistPots;
|
||||
private final Set<String> whitelistSprinklers;
|
||||
private final List<TextValue<Player>> lore;
|
||||
private final WaterBar waterBar;
|
||||
private final Requirement<Player>[] requirements;
|
||||
private final boolean infinite;
|
||||
private final HashMap<Integer, Integer> appearances;
|
||||
private final Action<Player>[] fullActions;
|
||||
private final Action<Player>[] addWaterActions;
|
||||
private final Action<Player>[] consumeWaterActions;
|
||||
private final Action<Player>[] runOutOfWaterActions;
|
||||
private final Action<Player>[] wrongPotActions;
|
||||
private final Action<Player>[] wrongSprinklerActions;
|
||||
private final FillMethod[] fillMethods;
|
||||
|
||||
public WateringCanConfigImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int width,
|
||||
int length,
|
||||
int storage,
|
||||
int wateringAmount,
|
||||
boolean dynamicLore,
|
||||
Set<String> whitelistPots,
|
||||
Set<String> whitelistSprinklers,
|
||||
List<TextValue<Player>> lore,
|
||||
WaterBar waterBar,
|
||||
Requirement<Player>[] requirements,
|
||||
boolean infinite,
|
||||
HashMap<Integer, Integer> appearances,
|
||||
Action<Player>[] fullActions,
|
||||
Action<Player>[] addWaterActions,
|
||||
Action<Player>[] consumeWaterActions,
|
||||
Action<Player>[] runOutOfWaterActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
Action<Player>[] wrongSprinklerActions,
|
||||
FillMethod[] fillMethods
|
||||
) {
|
||||
this.id = id;
|
||||
this.itemID = itemID;
|
||||
this.width = width;
|
||||
this.length = length;
|
||||
this.storage = storage;
|
||||
this.wateringAmount = wateringAmount;
|
||||
this.dynamicLore = dynamicLore;
|
||||
this.whitelistPots = whitelistPots;
|
||||
this.whitelistSprinklers = whitelistSprinklers;
|
||||
this.lore = lore;
|
||||
this.waterBar = waterBar;
|
||||
this.requirements = requirements;
|
||||
this.infinite = infinite;
|
||||
this.appearances = appearances;
|
||||
this.fullActions = fullActions;
|
||||
this.addWaterActions = addWaterActions;
|
||||
this.consumeWaterActions = consumeWaterActions;
|
||||
this.runOutOfWaterActions = runOutOfWaterActions;
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
this.wrongSprinklerActions = wrongSprinklerActions;
|
||||
this.fillMethods = fillMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String itemID() {
|
||||
return itemID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wateringAmount() {
|
||||
return wateringAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dynamicLore() {
|
||||
return dynamicLore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> whitelistPots() {
|
||||
return whitelistPots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> whitelistSprinklers() {
|
||||
return whitelistSprinklers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TextValue<Player>> lore() {
|
||||
return lore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WaterBar waterBar() {
|
||||
return waterBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Requirement<Player>[] requirements() {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean infinite() {
|
||||
return infinite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer appearance(int water) {
|
||||
return appearances.get(water);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] fullActions() {
|
||||
return fullActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] addWaterActions() {
|
||||
return addWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] consumeWaterActions() {
|
||||
return consumeWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] runOutOfWaterActions() {
|
||||
return runOutOfWaterActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongPotActions() {
|
||||
return wrongPotActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player>[] wrongSprinklerActions() {
|
||||
return wrongSprinklerActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FillMethod[] fillMethods() {
|
||||
return fillMethods;
|
||||
}
|
||||
|
||||
static class BuilderImpl implements Builder {
|
||||
|
||||
private String id;
|
||||
private String itemID;
|
||||
private int width;
|
||||
private int length;
|
||||
private int storage;
|
||||
private int wateringAmount;
|
||||
private boolean dynamicLore;
|
||||
private Set<String> whitelistPots;
|
||||
private Set<String> whitelistSprinklers;
|
||||
private List<TextValue<Player>> lore;
|
||||
private WaterBar waterBar;
|
||||
private Requirement<Player>[] requirements;
|
||||
private boolean infinite;
|
||||
private HashMap<Integer, Integer> appearances;
|
||||
private Action<Player>[] fullActions;
|
||||
private Action<Player>[] addWaterActions;
|
||||
private Action<Player>[] consumeWaterActions;
|
||||
private Action<Player>[] runOutOfWaterActions;
|
||||
private Action<Player>[] wrongPotActions;
|
||||
private Action<Player>[] wrongSprinklerActions;
|
||||
private FillMethod[] fillMethods;
|
||||
|
||||
@Override
|
||||
public WateringCanConfig build() {
|
||||
return new WateringCanConfigImpl(id, itemID, width, length, storage, wateringAmount, dynamicLore, whitelistPots, whitelistSprinklers, lore, waterBar, requirements, infinite, appearances, fullActions, addWaterActions, consumeWaterActions, runOutOfWaterActions, wrongPotActions, wrongSprinklerActions, fillMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder itemID(String itemID) {
|
||||
this.itemID = itemID;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder width(int width) {
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder length(int length) {
|
||||
this.length = length;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder storage(int storage) {
|
||||
this.storage = storage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wateringAmount(int wateringAmount) {
|
||||
this.wateringAmount = wateringAmount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder dynamicLore(boolean dynamicLore) {
|
||||
this.dynamicLore = dynamicLore;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder potWhitelist(Set<String> whitelistPots) {
|
||||
this.whitelistPots = new HashSet<>(whitelistPots);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder sprinklerWhitelist(Set<String> whitelistSprinklers) {
|
||||
this.whitelistSprinklers = new HashSet<>(whitelistSprinklers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder lore(List<TextValue<Player>> lore) {
|
||||
this.lore = new ArrayList<>(lore);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder waterBar(WaterBar waterBar) {
|
||||
this.waterBar = waterBar;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder requirements(Requirement<Player>[] requirements) {
|
||||
this.requirements = requirements;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder infinite(boolean infinite) {
|
||||
this.infinite = infinite;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder appearances(Map<Integer, Integer> appearances) {
|
||||
this.appearances = new HashMap<>(appearances);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fullActions(Action<Player>[] fullActions) {
|
||||
this.fullActions = fullActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder addWaterActions(Action<Player>[] addWaterActions) {
|
||||
this.addWaterActions = addWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder consumeWaterActions(Action<Player>[] consumeWaterActions) {
|
||||
this.consumeWaterActions = consumeWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder runOutOfWaterActions(Action<Player>[] runOutOfWaterActions) {
|
||||
this.runOutOfWaterActions = runOutOfWaterActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wrongPotActions(Action<Player>[] wrongPotActions) {
|
||||
this.wrongPotActions = wrongPotActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder wrongSprinklerActions(Action<Player>[] wrongSprinklerActions) {
|
||||
this.wrongSprinklerActions = wrongSprinklerActions;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder fillMethods(FillMethod[] fillMethods) {
|
||||
this.fillMethods = fillMethods;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.context.ContextKeys;
|
||||
import net.momirealms.customcrops.api.core.*;
|
||||
import net.momirealms.customcrops.api.core.block.*;
|
||||
import net.momirealms.customcrops.api.core.water.FillMethod;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
|
||||
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
|
||||
import net.momirealms.customcrops.api.core.world.Pos3;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractAirEvent;
|
||||
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
|
||||
import net.momirealms.customcrops.api.event.WateringCanFillEvent;
|
||||
import net.momirealms.customcrops.api.event.WateringCanWaterPotEvent;
|
||||
import net.momirealms.customcrops.api.event.WateringCanWaterSprinklerEvent;
|
||||
import net.momirealms.customcrops.api.misc.value.TextValue;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import net.momirealms.customcrops.api.util.EventUtils;
|
||||
import net.momirealms.customcrops.common.helper.AdventureHelper;
|
||||
import net.momirealms.customcrops.common.item.Item;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.data.Waterlogged;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class WateringCanItem extends AbstractCustomCropsItem {
|
||||
|
||||
public WateringCanItem() {
|
||||
super(BuiltInItemMechanics.WATERING_CAN.key());
|
||||
}
|
||||
|
||||
public int getCurrentWater(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) return 0;
|
||||
Item<ItemStack> wrapped = BukkitCustomCropsPlugin.getInstance().getItemManager().wrap(itemStack);
|
||||
return (int) wrapped.getTag("CustomCrops", "water").orElse(0);
|
||||
}
|
||||
|
||||
public void setCurrentWater(ItemStack itemStack, WateringCanConfig config, int water, Context<Player> context) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) return;
|
||||
Item<ItemStack> wrapped = BukkitCustomCropsPlugin.getInstance().getItemManager().wrap(itemStack);
|
||||
int realWater = Math.min(config.storage(), water);
|
||||
if (!config.infinite()) {
|
||||
wrapped.setTag("CustomCrops", "water", realWater);
|
||||
wrapped.maxDamage().ifPresent(max -> {
|
||||
if (max <= 0) return;
|
||||
int damage = (int) (max * (((double) config.storage() - realWater) / config.storage()));
|
||||
wrapped.damage(damage);
|
||||
});
|
||||
// set appearance
|
||||
Optional.ofNullable(config.appearance(realWater)).ifPresent(wrapped::customModelData);
|
||||
}
|
||||
if (config.dynamicLore()) {
|
||||
List<String> lore = new ArrayList<>(wrapped.lore().orElse(List.of()));
|
||||
if (ConfigManager.protectOriginalLore()) {
|
||||
lore.removeIf(line -> {
|
||||
Component component = AdventureHelper.jsonToComponent(line);
|
||||
return component instanceof ScoreComponent scoreComponent
|
||||
&& scoreComponent.objective().equals("water")
|
||||
&& scoreComponent.name().equals("cc");
|
||||
});
|
||||
} else {
|
||||
lore.clear();
|
||||
}
|
||||
for (TextValue<Player> newLore : config.lore()) {
|
||||
ScoreComponent.Builder builder = Component.score().name("cc").objective("water");
|
||||
builder.append(AdventureHelper.miniMessage(newLore.render(context)));
|
||||
lore.add(AdventureHelper.componentToJson(builder.build()));
|
||||
}
|
||||
wrapped.lore(lore);
|
||||
}
|
||||
wrapped.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interactAir(WrappedInteractAirEvent event) {
|
||||
WateringCanConfig config = Registries.WATERING_CAN.get(event.itemID());
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
final Player player = event.player();;
|
||||
Context<Player> context = Context.player(player);
|
||||
// check requirements
|
||||
if (!RequirementManager.isSatisfied(context, config.requirements())) {
|
||||
return;
|
||||
}
|
||||
// ignore infinite
|
||||
if (config.infinite())
|
||||
return;
|
||||
// get target block
|
||||
Block targetBlock = player.getTargetBlockExact(5, FluidCollisionMode.ALWAYS);
|
||||
if (targetBlock == null)
|
||||
return;
|
||||
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
int water = getCurrentWater(itemInHand);
|
||||
|
||||
String blockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetBlock);
|
||||
if (targetBlock.getBlockData() instanceof Waterlogged waterlogged && waterlogged.isWaterlogged()) {
|
||||
blockID = "WATER";
|
||||
}
|
||||
|
||||
for (FillMethod method : config.fillMethods()) {
|
||||
if (method.getID().equals(blockID)) {
|
||||
if (method.checkRequirements(context)) {
|
||||
if (water >= config.storage()) {
|
||||
ActionManager.trigger(context, config.fullActions());
|
||||
return;
|
||||
}
|
||||
WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, event.hand(), itemInHand, targetBlock.getLocation(), config, method);
|
||||
if (EventUtils.fireAndCheckCancel(fillEvent))
|
||||
return;
|
||||
setCurrentWater(itemInHand, config, water + method.amountOfWater(), context);
|
||||
method.triggerActions(context);
|
||||
ActionManager.trigger(context, config.addWaterActions());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interactAt(WrappedInteractEvent event) {
|
||||
WateringCanConfig wateringCanConfig = Registries.WATERING_CAN.get(event.itemID());
|
||||
if (wateringCanConfig == null)
|
||||
return InteractionResult.FAIL;
|
||||
|
||||
final Player player = event.player();
|
||||
final Context<Player> context = Context.player(player);
|
||||
|
||||
// check watering can requirements
|
||||
if (!RequirementManager.isSatisfied(context, wateringCanConfig.requirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
final CustomCropsWorld<?> world = event.world();
|
||||
final ItemStack itemInHand = event.itemInHand();
|
||||
String targetBlockID = event.relatedID();
|
||||
Location targetLocation = event.location();
|
||||
BlockFace blockFace = event.clickedBlockFace();
|
||||
|
||||
int waterInCan = getCurrentWater(itemInHand);
|
||||
|
||||
SprinklerConfig sprinklerConfig = Registries.SPRINKLER.get(targetBlockID);
|
||||
if (sprinklerConfig != null) {
|
||||
// ignore infinite sprinkler
|
||||
if (sprinklerConfig.infinite()) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check requirements
|
||||
if (!RequirementManager.isSatisfied(context, sprinklerConfig.useRequirements())) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check water
|
||||
if (waterInCan <= 0 && !wateringCanConfig.infinite()) {
|
||||
ActionManager.trigger(context, wateringCanConfig.runOutOfWaterActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check whitelist
|
||||
if (!wateringCanConfig.whitelistSprinklers().contains(sprinklerConfig.id())) {
|
||||
ActionManager.trigger(context, wateringCanConfig.wrongSprinklerActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
SprinklerBlock sprinklerBlock = (SprinklerBlock) BuiltInBlockMechanics.SPRINKLER.mechanic();
|
||||
CustomCropsBlockState sprinklerState = sprinklerBlock.fixOrGetState(world, Pos3.from(targetLocation), sprinklerConfig, targetBlockID);
|
||||
|
||||
// check full
|
||||
if (sprinklerBlock.water(sprinklerState) >= sprinklerConfig.storage()) {
|
||||
ActionManager.trigger(context, sprinklerConfig.fullWaterActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
// trigger event
|
||||
WateringCanWaterSprinklerEvent waterSprinklerEvent = new WateringCanWaterSprinklerEvent(player, itemInHand, event.hand(), wateringCanConfig, sprinklerConfig, sprinklerState, targetLocation);
|
||||
if (EventUtils.fireAndCheckCancel(waterSprinklerEvent)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// add water
|
||||
if (sprinklerBlock.addWater(sprinklerState, sprinklerConfig, wateringCanConfig.wateringAmount())) {
|
||||
if (!sprinklerConfig.threeDItem().equals(sprinklerConfig.threeDItemWithWater())) {
|
||||
sprinklerBlock.updateBlockAppearance(targetLocation, sprinklerConfig, true);
|
||||
}
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, wateringCanConfig.consumeWaterActions());
|
||||
setCurrentWater(itemInHand, wateringCanConfig, waterInCan - 1, context);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
// try filling the watering can
|
||||
for (FillMethod method : wateringCanConfig.fillMethods()) {
|
||||
if (method.getID().equals(event.relatedID())) {
|
||||
if (method.checkRequirements(context)) {
|
||||
if (waterInCan >= wateringCanConfig.storage()) {
|
||||
ActionManager.trigger(context, wateringCanConfig.fullActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
WateringCanFillEvent fillEvent = new WateringCanFillEvent(player, event.hand(), itemInHand, targetLocation, wateringCanConfig, method);
|
||||
if (EventUtils.fireAndCheckCancel(fillEvent))
|
||||
return InteractionResult.FAIL;
|
||||
setCurrentWater(itemInHand, wateringCanConfig, waterInCan + method.amountOfWater(), context);
|
||||
method.triggerActions(context);
|
||||
ActionManager.trigger(context, wateringCanConfig.addWaterActions());
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// if the clicked block is a crop, correct the target block
|
||||
List<CropConfig> cropConfigs = Registries.STAGE_TO_CROP_UNSAFE.get(event.relatedID());
|
||||
if (cropConfigs != null) {
|
||||
// is a crop
|
||||
targetLocation = targetLocation.subtract(0,1,0);
|
||||
targetBlockID = BukkitCustomCropsPlugin.getInstance().getItemManager().blockID(targetLocation);
|
||||
blockFace = BlockFace.UP;
|
||||
}
|
||||
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(targetBlockID);
|
||||
if (potConfig != null) {
|
||||
// need to click the upper face
|
||||
if (blockFace != BlockFace.UP)
|
||||
return InteractionResult.PASS;
|
||||
// check whitelist
|
||||
if (!wateringCanConfig.whitelistPots().contains(potConfig.id())) {
|
||||
ActionManager.trigger(context, wateringCanConfig.wrongPotActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
// check water
|
||||
if (waterInCan <= 0 && !wateringCanConfig.infinite()) {
|
||||
ActionManager.trigger(context, wateringCanConfig.runOutOfWaterActions());
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
World bukkitWorld = targetLocation.getWorld();
|
||||
ArrayList<Pair<Pos3, String>> pots = potInRange(bukkitWorld, Pos3.from(targetLocation), wateringCanConfig.width(), wateringCanConfig.length(), player.getLocation().getYaw(), potConfig);
|
||||
|
||||
WateringCanWaterPotEvent waterPotEvent = new WateringCanWaterPotEvent(player, itemInHand, event.hand(), wateringCanConfig, potConfig, pots);
|
||||
if (EventUtils.fireAndCheckCancel(waterPotEvent)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
|
||||
PotBlock potBlock = (PotBlock) BuiltInBlockMechanics.POT.mechanic();
|
||||
for (Pair<Pos3, String> pair : waterPotEvent.getPotWithIDs()) {
|
||||
CustomCropsBlockState potState = potBlock.fixOrGetState(world,pair.left(), potConfig, pair.right());
|
||||
if (potBlock.addWater(potState, potConfig, wateringCanConfig.wateringAmount())) {
|
||||
Location temp = pair.left().toLocation(bukkitWorld);
|
||||
potBlock.updateBlockAppearance(temp, potConfig, true, potBlock.fertilizers(potState));
|
||||
context.arg(ContextKeys.LOCATION, temp);
|
||||
ActionManager.trigger(context, potConfig.addWaterActions());
|
||||
}
|
||||
}
|
||||
|
||||
ActionManager.trigger(context, wateringCanConfig.consumeWaterActions());
|
||||
setCurrentWater(itemInHand, wateringCanConfig, waterInCan - 1, context);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
public ArrayList<Pair<Pos3, String>> potInRange(World world, Pos3 pos3, int width, int length, float yaw, PotConfig config) {
|
||||
ArrayList<Pos3> potPos = new ArrayList<>();
|
||||
int extend = (width-1) / 2;
|
||||
int extra = (width-1) % 2;
|
||||
switch ((int) ((yaw + 180) / 45)) {
|
||||
case 0 -> {
|
||||
// -180 ~ -135
|
||||
for (int i = -extend; i <= extend + extra; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(i, 0, -j));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 1 -> {
|
||||
// -135 ~ -90
|
||||
for (int i = -extend - extra; i <= extend; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(j, 0, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 2 -> {
|
||||
// -90 ~ -45
|
||||
for (int i = -extend; i <= extend + extra; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(j, 0, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 3 -> {
|
||||
// -45 ~ 0
|
||||
for (int i = -extend; i <= extend + extra; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(i, 0, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 4 -> {
|
||||
// 0 ~ 45
|
||||
for (int i = -extend - extra; i <= extend; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(i, 0, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 5 -> {
|
||||
// 45 ~ 90
|
||||
for (int i = -extend; i <= extend + extra; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(-j, 0, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 6 -> {
|
||||
// 90 ~ 135
|
||||
for (int i = -extend - extra; i <= extend; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(-j, 0, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
case 7 -> {
|
||||
// 135 ~ 180
|
||||
for (int i = -extend - extra; i <= extend; i++) {
|
||||
for (int j = 0; j < length; j++) {
|
||||
potPos.add(pos3.add(i, 0, -j));
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> potPos.add(pos3);
|
||||
}
|
||||
ItemManager itemManager = BukkitCustomCropsPlugin.getInstance().getItemManager();
|
||||
ArrayList<Pair<Pos3, String>> pots = new ArrayList<>();
|
||||
for (Pos3 loc : potPos) {
|
||||
Block block = world.getBlockAt(loc.x(), loc.y(), loc.z());
|
||||
String blockID = itemManager.blockID(block);
|
||||
PotConfig potConfig = Registries.ITEM_TO_POT.get(blockID);
|
||||
if (potConfig == config) {
|
||||
pots.add(Pair.of(loc, blockID));
|
||||
}
|
||||
}
|
||||
return pots;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface YieldIncrease extends FertilizerConfig {
|
||||
|
||||
int amountBonus();
|
||||
|
||||
static YieldIncrease create(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
return new YieldIncreaseImpl(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions, chances);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.momirealms.customcrops.api.core.item;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.common.util.Pair;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class YieldIncreaseImpl extends AbstractFertilizerConfig implements YieldIncrease {
|
||||
|
||||
private final List<Pair<Double, Integer>> chances;
|
||||
|
||||
public YieldIncreaseImpl(
|
||||
String id,
|
||||
String itemID,
|
||||
int times,
|
||||
String icon,
|
||||
boolean beforePlant,
|
||||
Set<String> whitelistPots,
|
||||
Requirement<Player>[] requirements,
|
||||
Action<Player>[] beforePlantActions,
|
||||
Action<Player>[] useActions,
|
||||
Action<Player>[] wrongPotActions,
|
||||
List<Pair<Double, Integer>> chances
|
||||
) {
|
||||
super(id, itemID, times, icon, beforePlant, whitelistPots, requirements, beforePlantActions, useActions, wrongPotActions);
|
||||
this.chances = chances;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int amountBonus() {
|
||||
for (Pair<Double, Integer> pair : chances) {
|
||||
if (Math.random() < pair.left()) {
|
||||
return pair.right();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FertilizerType type() {
|
||||
return FertilizerType.YIELD_INCREASE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processDroppedItemAmount(int amount) {
|
||||
return amount + amountBonus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.water;
|
||||
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.action.ActionManager;
|
||||
import net.momirealms.customcrops.api.context.Context;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.requirement.RequirementManager;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public abstract class AbstractMethod {
|
||||
|
||||
protected int amount;
|
||||
private final Action<Player>[] actions;
|
||||
private final Requirement<Player>[] requirements;
|
||||
|
||||
protected AbstractMethod(int amount, Action<Player>[] actions, Requirement<Player>[] requirements) {
|
||||
this.amount = amount;
|
||||
this.actions = actions;
|
||||
this.requirements = requirements;
|
||||
}
|
||||
|
||||
public int amountOfWater() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void triggerActions(Context<Player> context) {
|
||||
ActionManager.trigger(context, actions);
|
||||
}
|
||||
|
||||
public boolean checkRequirements(Context<Player> context) {
|
||||
return RequirementManager.isSatisfied(context, requirements);
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,17 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.item.water;
|
||||
package net.momirealms.customcrops.api.core.water;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.action.Action;
|
||||
import net.momirealms.customcrops.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PositiveFillMethod extends AbstractFillMethod {
|
||||
public class FillMethod extends AbstractMethod {
|
||||
|
||||
private final String id;
|
||||
|
||||
public PositiveFillMethod(String id, int amount, Action[] actions, Requirement[] requirements) {
|
||||
public FillMethod(String id, int amount, Action<Player>[] actions, Requirement<Player>[] requirements) {
|
||||
super(amount, actions, requirements);
|
||||
this.id = id;
|
||||
}
|
||||
@@ -15,20 +15,29 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.item.water;
|
||||
package net.momirealms.customcrops.api.core.water;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.action.Action;
|
||||
import net.momirealms.customcrops.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customcrops.api.action.Action;
|
||||
import net.momirealms.customcrops.api.requirement.Requirement;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PassiveFillMethod extends AbstractFillMethod {
|
||||
public class WateringMethod extends AbstractMethod {
|
||||
|
||||
private final String used;
|
||||
private final int usedAmount;
|
||||
private final String returned;
|
||||
private final int returnedAmount;
|
||||
|
||||
public PassiveFillMethod(String used, int usedAmount, @Nullable String returned, int returnedAmount, int amount, Action[] actions, Requirement[] requirements) {
|
||||
public WateringMethod(
|
||||
String used,
|
||||
int usedAmount,
|
||||
@Nullable String returned,
|
||||
int returnedAmount,
|
||||
int amount,
|
||||
Action<Player>[] actions,
|
||||
Requirement<Player>[] requirements
|
||||
) {
|
||||
super(amount, actions, requirements);
|
||||
this.used = used;
|
||||
this.returned = returned;
|
||||
@@ -15,9 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.world;
|
||||
|
||||
import java.util.Objects;
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
public class BlockPos {
|
||||
|
||||
@@ -31,31 +29,27 @@ public class BlockPos {
|
||||
this.position = ((x & 0xF) << 28) | ((z & 0xF) << 24) | (y & 0xFFFFFF);
|
||||
}
|
||||
|
||||
public static BlockPos getByLocation(SimpleLocation location) {
|
||||
return new BlockPos(location.getX() % 16, location.getY(), location.getZ() % 16);
|
||||
public static BlockPos fromPos3(Pos3 location) {
|
||||
return new BlockPos(location.x() % 16, location.y(), location.z() % 16);
|
||||
}
|
||||
|
||||
public SimpleLocation getLocation(String world, ChunkPos coordinate) {
|
||||
return new SimpleLocation(world, coordinate.x() * 16 + getX(), getY(), coordinate.z() * 16 + getZ());
|
||||
public Pos3 toPos3(ChunkPos coordinate) {
|
||||
return new Pos3(coordinate.x() * 16 + x(), y(), coordinate.z() * 16 + z());
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
public int position() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
public int x() {
|
||||
return (position >> 28) & 0xF;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
public int z() {
|
||||
return (position >> 24) & 0xF;
|
||||
}
|
||||
|
||||
public int getSectionID() {
|
||||
return (int) Math.floor((double) getY() / 16);
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
public int y() {
|
||||
int y = position & 0xFFFFFF;
|
||||
if ((y & 0x800000) != 0) {
|
||||
y |= 0xFF000000;
|
||||
@@ -63,6 +57,10 @@ public class BlockPos {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int sectionID() {
|
||||
return (int) Math.floor((double) y() / 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -73,15 +71,15 @@ public class BlockPos {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(position);
|
||||
return Math.abs(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlockPos{" +
|
||||
"x=" + getX() +
|
||||
"y=" + getY() +
|
||||
"z=" + getZ() +
|
||||
"x=" + x() +
|
||||
"y=" + y() +
|
||||
"z=" + z() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.mechanic.world;
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import org.bukkit.Chunk;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -26,17 +26,21 @@ public record ChunkPos(int x, int z) {
|
||||
return new ChunkPos(x, z);
|
||||
}
|
||||
|
||||
public static ChunkPos getByString(String coordinate) {
|
||||
public static ChunkPos fromString(String coordinate) {
|
||||
String[] split = coordinate.split(",", 2);
|
||||
try {
|
||||
int x = Integer.parseInt(split[0]);
|
||||
int z = Integer.parseInt(split[1]);
|
||||
return new ChunkPos(x, z);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ChunkPos fromPos3(Pos3 pos3) {
|
||||
int chunkX = (int) Math.floor((double) pos3.x() / 16.0);
|
||||
int chunkZ = (int) Math.floor((double) pos3.z() / 16.0);
|
||||
return ChunkPos.of(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,16 +68,16 @@ public record ChunkPos(int x, int z) {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RegionPos getRegionPos() {
|
||||
public RegionPos toRegionPos() {
|
||||
return RegionPos.getByChunkPos(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ChunkPos getByBukkitChunk(@NotNull Chunk chunk) {
|
||||
public static ChunkPos fromBukkitChunk(@NotNull Chunk chunk) {
|
||||
return new ChunkPos(chunk.getX(), chunk.getZ());
|
||||
}
|
||||
|
||||
public String getAsString() {
|
||||
public String asString() {
|
||||
return x + "," + z;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface CustomCropsBlockState extends DataBlock {
|
||||
|
||||
@NotNull
|
||||
CustomCropsBlock type();
|
||||
|
||||
static CustomCropsBlockState create(CustomCropsBlock owner, CompoundMap compoundMap) {
|
||||
return new CustomCropsBlockStateImpl(owner, compoundMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import com.flowpowered.nbt.CompoundMap;
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import net.momirealms.customcrops.api.core.SynchronizedCompoundMap;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class CustomCropsBlockStateImpl implements CustomCropsBlockState {
|
||||
|
||||
private final SynchronizedCompoundMap compoundMap;
|
||||
private final CustomCropsBlock owner;
|
||||
|
||||
protected CustomCropsBlockStateImpl(CustomCropsBlock owner, CompoundMap compoundMap) {
|
||||
this.compoundMap = new SynchronizedCompoundMap(compoundMap);
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomCropsBlock type() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag<?> set(String key, Tag<?> tag) {
|
||||
return compoundMap.put(key, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag<?> get(String key) {
|
||||
return compoundMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag<?> remove(String key) {
|
||||
return compoundMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SynchronizedCompoundMap compoundMap() {
|
||||
return compoundMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomCropsBlock{" +
|
||||
"Type{" + owner.type().asString() +
|
||||
"}, " + compoundMap +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface CustomCropsChunk {
|
||||
|
||||
/**
|
||||
* Set if the chunk can be force loaded.
|
||||
* If a chunk is force loaded, no one can unload it unless you set force load to false.
|
||||
* This can prevent CustomCrops from unloading the chunks on {@link org.bukkit.event.world.ChunkUnloadEvent}
|
||||
*
|
||||
* This value will not be persistently stored. Please use {@link org.bukkit.World#setChunkForceLoaded(int, int, boolean)}
|
||||
* if you want to force a chunk loaded.
|
||||
*
|
||||
* @param forceLoad force loaded
|
||||
*/
|
||||
void setForceLoaded(boolean forceLoad);
|
||||
|
||||
/**
|
||||
* Indicates whether the chunk is force loaded
|
||||
*
|
||||
* @return force loaded or not
|
||||
*/
|
||||
boolean isForceLoaded();
|
||||
|
||||
/**
|
||||
* Loads the chunk to cache and participate in the mechanism of the plugin.
|
||||
*
|
||||
* @param loadBukkitChunk whether to load Bukkit chunks temporarily if it's not loaded
|
||||
*/
|
||||
void load(boolean loadBukkitChunk);
|
||||
|
||||
/**
|
||||
* Unloads the chunk. Lazy refer to those chunks that will be delayed for unloading.
|
||||
* Recently unloaded chunks are likely to be loaded again soon.
|
||||
*
|
||||
* @param lazy delay unload or not
|
||||
*/
|
||||
void unload(boolean lazy);
|
||||
|
||||
/**
|
||||
* Unloads the chunk if it is a lazy chunk
|
||||
*/
|
||||
void unloadLazy();
|
||||
|
||||
/**
|
||||
* Indicates whether the chunk is in lazy state
|
||||
*
|
||||
* @return lazy or not
|
||||
*/
|
||||
boolean isLazy();
|
||||
|
||||
/**
|
||||
* Indicates whether the chunk is loaded
|
||||
*
|
||||
* @return loaded or not
|
||||
*/
|
||||
boolean isLoaded();
|
||||
|
||||
/**
|
||||
* Get the world associated with the chunk
|
||||
*
|
||||
* @return CustomCrops world
|
||||
*/
|
||||
CustomCropsWorld<?> getWorld();
|
||||
|
||||
/**
|
||||
* Get the position of the chunk
|
||||
*
|
||||
* @return chunk position
|
||||
*/
|
||||
ChunkPos chunkPos();
|
||||
|
||||
/**
|
||||
* Do second timer
|
||||
*/
|
||||
void timer();
|
||||
|
||||
/**
|
||||
* Get the unloaded time in seconds
|
||||
* This value would increase if the chunk is lazy
|
||||
*
|
||||
* @return the unloaded time
|
||||
*/
|
||||
int unloadedSeconds();
|
||||
|
||||
/**
|
||||
* Set the unloaded seconds
|
||||
*
|
||||
* @param unloadedSeconds unloadedSeconds
|
||||
*/
|
||||
void unloadedSeconds(int unloadedSeconds);
|
||||
|
||||
/**
|
||||
* Get the last loaded time
|
||||
*
|
||||
* @return last loaded time
|
||||
*/
|
||||
long lastLoadedTime();
|
||||
|
||||
/**
|
||||
* Set the last loaded time to current time
|
||||
*/
|
||||
void updateLastLoadedTime();
|
||||
|
||||
/**
|
||||
* Get the loaded time in seconds
|
||||
*
|
||||
* @return loaded time
|
||||
*/
|
||||
int loadedMilliSeconds();
|
||||
|
||||
/**
|
||||
* Get block data at a certain location
|
||||
*
|
||||
* @param location location
|
||||
* @return block data
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> getBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Remove any block data from a certain location
|
||||
*
|
||||
* @param location location
|
||||
* @return block data
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> removeBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Add a custom block data at a certain location
|
||||
*
|
||||
* @param block block to add
|
||||
* @return the previous block data
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> addBlockState(Pos3 location, CustomCropsBlockState block);
|
||||
|
||||
/**
|
||||
* Get CustomCrops sections
|
||||
*
|
||||
* @return sections
|
||||
*/
|
||||
@NotNull
|
||||
Stream<CustomCropsSection> sectionsToSave();
|
||||
|
||||
/**
|
||||
* Get section by ID
|
||||
*
|
||||
* @param sectionID id
|
||||
* @return section
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsSection> getLoadedSection(int sectionID);
|
||||
|
||||
CustomCropsSection getSection(int sectionID);
|
||||
|
||||
Collection<CustomCropsSection> sections();
|
||||
|
||||
Optional<CustomCropsSection> removeSection(int sectionID);
|
||||
|
||||
void resetUnloadedSeconds();
|
||||
|
||||
boolean canPrune();
|
||||
|
||||
boolean isOfflineTaskNotified();
|
||||
|
||||
PriorityQueue<DelayedTickTask> tickTaskQueue();
|
||||
|
||||
Set<BlockPos> tickedBlocks();
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import net.momirealms.customcrops.common.util.RandomUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CustomCropsChunkImpl implements CustomCropsChunk {
|
||||
|
||||
private final CustomCropsWorld<?> world;
|
||||
private final ChunkPos chunkPos;
|
||||
private final ConcurrentHashMap<Integer, CustomCropsSection> loadedSections;
|
||||
private final PriorityQueue<DelayedTickTask> queue;
|
||||
private final Set<BlockPos> tickedBlocks;
|
||||
private long lastLoadedTime;
|
||||
private int loadedSeconds;
|
||||
private int unloadedSeconds;
|
||||
private boolean notified;
|
||||
private boolean isLoaded;
|
||||
private boolean forceLoad;
|
||||
|
||||
protected CustomCropsChunkImpl(CustomCropsWorld<?> world, ChunkPos chunkPos) {
|
||||
this.world = world;
|
||||
this.chunkPos = chunkPos;
|
||||
this.loadedSections = new ConcurrentHashMap<>(16);
|
||||
this.queue = new PriorityQueue<>();
|
||||
this.unloadedSeconds = 0;
|
||||
this.tickedBlocks = Collections.synchronizedSet(new HashSet<>());
|
||||
this.updateLastLoadedTime();
|
||||
this.notified = true;
|
||||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
protected CustomCropsChunkImpl(
|
||||
CustomCropsWorld<?> world,
|
||||
ChunkPos chunkPos,
|
||||
int loadedSeconds,
|
||||
long lastLoadedTime,
|
||||
ConcurrentHashMap<Integer, CustomCropsSection> loadedSections,
|
||||
PriorityQueue<DelayedTickTask> queue,
|
||||
HashSet<BlockPos> tickedBlocks
|
||||
) {
|
||||
this.world = world;
|
||||
this.chunkPos = chunkPos;
|
||||
this.loadedSections = loadedSections;
|
||||
this.lastLoadedTime = lastLoadedTime;
|
||||
this.loadedSeconds = loadedSeconds;
|
||||
this.queue = queue;
|
||||
this.unloadedSeconds = 0;
|
||||
this.tickedBlocks = Collections.synchronizedSet(tickedBlocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceLoaded(boolean forceLoad) {
|
||||
this.forceLoad = forceLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isForceLoaded() {
|
||||
return this.forceLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(boolean loadBukkitChunk) {
|
||||
if (!isLoaded()) {
|
||||
if (((CustomCropsWorldImpl<?>) world).loadChunk(this)) {
|
||||
this.isLoaded = true;
|
||||
}
|
||||
if (loadBukkitChunk && !this.world.bukkitWorld().isChunkLoaded(chunkPos.x(), chunkPos.z())) {
|
||||
this.world.bukkitWorld().getChunkAt(chunkPos.x(), chunkPos.z());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(boolean lazy) {
|
||||
if (isLoaded() && !isForceLoaded()) {
|
||||
if (((CustomCropsWorldImpl<?>) world).unloadChunk(this, lazy)) {
|
||||
this.isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadLazy() {
|
||||
if (!isLoaded() && isLazy()) {
|
||||
((CustomCropsWorldImpl<?>) world).unloadLazyChunk(chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazy() {
|
||||
return ((CustomCropsWorldImpl<?>) world).getLazyChunk(chunkPos) == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return this.isLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsWorld<?> getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkPos chunkPos() {
|
||||
return chunkPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void timer() {
|
||||
WorldSetting setting = world.setting();
|
||||
int interval = setting.minTickUnit();
|
||||
this.loadedSeconds++;
|
||||
// if loadedSeconds reach another recycle, rearrange the tasks
|
||||
if (this.loadedSeconds >= interval) {
|
||||
this.loadedSeconds = 0;
|
||||
this.tickedBlocks.clear();
|
||||
this.queue.clear();
|
||||
this.arrangeTasks(interval);
|
||||
}
|
||||
scheduledTick();
|
||||
randomTick(setting.randomTickSpeed());
|
||||
}
|
||||
|
||||
private void arrangeTasks(int unit) {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
for (CustomCropsSection section : loadedSections.values()) {
|
||||
for (Map.Entry<BlockPos, CustomCropsBlockState> entry : section.blockMap().entrySet()) {
|
||||
this.queue.add(new DelayedTickTask(
|
||||
random.nextInt(0, unit),
|
||||
entry.getKey()
|
||||
));
|
||||
this.tickedBlocks.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduledTick() {
|
||||
while (!queue.isEmpty() && queue.peek().getTime() <= loadedSeconds) {
|
||||
DelayedTickTask task = queue.poll();
|
||||
if (task != null) {
|
||||
BlockPos pos = task.blockPos();
|
||||
CustomCropsSection section = loadedSections.get(pos.sectionID());
|
||||
if (section != null) {
|
||||
Optional<CustomCropsBlockState> block = section.getBlockState(pos);
|
||||
block.ifPresent(state -> state.type().scheduledTick(state, world, pos.toPos3(chunkPos)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void randomTick(int randomTickSpeed) {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
for (CustomCropsSection section : loadedSections.values()) {
|
||||
int sectionID = section.getSectionID();
|
||||
int baseY = sectionID * 16;
|
||||
for (int i = 0; i < randomTickSpeed; i++) {
|
||||
int x = random.nextInt(16);
|
||||
int y = random.nextInt(16) + baseY;
|
||||
int z = random.nextInt(16);
|
||||
BlockPos pos = new BlockPos(x,y,z);
|
||||
Optional<CustomCropsBlockState> block = section.getBlockState(pos);
|
||||
block.ifPresent(state -> state.type().randomTick(state, world, pos.toPos3(chunkPos)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int unloadedSeconds() {
|
||||
return unloadedSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadedSeconds(int unloadedSeconds) {
|
||||
this.unloadedSeconds = unloadedSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastLoadedTime() {
|
||||
return lastLoadedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLastLoadedTime() {
|
||||
this.lastLoadedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadedMilliSeconds() {
|
||||
return (int) (System.currentTimeMillis() - lastLoadedTime);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> getBlockState(Pos3 location) {
|
||||
BlockPos pos = BlockPos.fromPos3(location);
|
||||
return getLoadedSection(pos.sectionID()).flatMap(section -> section.getBlockState(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> removeBlockState(Pos3 location) {
|
||||
BlockPos pos = BlockPos.fromPos3(location);
|
||||
return getLoadedSection(pos.sectionID()).flatMap(section -> section.removeBlockState(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> addBlockState(Pos3 location, CustomCropsBlockState block) {
|
||||
BlockPos pos = BlockPos.fromPos3(location);
|
||||
CustomCropsSection section = getSection(pos.sectionID());
|
||||
this.arrangeScheduledTickTaskForNewBlock(pos);
|
||||
return section.addBlockState(pos, block);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Stream<CustomCropsSection> sectionsToSave() {
|
||||
return loadedSections.values().stream().filter(section -> !section.canPrune());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsSection> getLoadedSection(int sectionID) {
|
||||
return Optional.ofNullable(loadedSections.get(sectionID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsSection getSection(int sectionID) {
|
||||
return getLoadedSection(sectionID).orElseGet(() -> {
|
||||
CustomCropsSection section = new CustomCropsSectionImpl(sectionID);
|
||||
this.loadedSections.put(sectionID, section);
|
||||
return section;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CustomCropsSection> sections() {
|
||||
return loadedSections.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomCropsSection> removeSection(int sectionID) {
|
||||
return Optional.ofNullable(loadedSections.remove(sectionID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetUnloadedSeconds() {
|
||||
this.unloadedSeconds = 0;
|
||||
this.notified = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPrune() {
|
||||
return loadedSections.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOfflineTaskNotified() {
|
||||
return notified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityQueue<DelayedTickTask> tickTaskQueue() {
|
||||
return queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BlockPos> tickedBlocks() {
|
||||
return tickedBlocks;
|
||||
}
|
||||
|
||||
private void arrangeScheduledTickTaskForNewBlock(BlockPos pos) {
|
||||
WorldSetting setting = world.setting();
|
||||
if (!tickedBlocks.contains(pos)) {
|
||||
tickedBlocks.add(pos);
|
||||
int random = RandomUtils.generateRandomInt(0, setting.minTickUnit() - 1);
|
||||
if (random > loadedSeconds) {
|
||||
queue.add(new DelayedTickTask(random, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,23 +15,32 @@
|
||||
* 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.core.world;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.item.Fertilizer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface QualityCrop extends Fertilizer {
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Get the chance of taking effect
|
||||
*
|
||||
* @return chance
|
||||
*/
|
||||
double getChance();
|
||||
public interface CustomCropsRegion {
|
||||
|
||||
/**
|
||||
* Get the modified quality ratio
|
||||
*
|
||||
* @return the modified quality ratio
|
||||
*/
|
||||
double[] getRatio();
|
||||
boolean isLoaded();
|
||||
|
||||
void unload();
|
||||
|
||||
void load();
|
||||
|
||||
@NotNull
|
||||
CustomCropsWorld<?> getWorld();
|
||||
|
||||
byte[] getCachedChunkBytes(ChunkPos pos);
|
||||
|
||||
RegionPos regionPos();
|
||||
|
||||
boolean removeCachedChunk(ChunkPos pos);
|
||||
|
||||
void setCachedChunk(ChunkPos pos, byte[] data);
|
||||
|
||||
Map<ChunkPos, byte[]> dataToSave();
|
||||
|
||||
boolean canPrune();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CustomCropsRegionImpl implements CustomCropsRegion {
|
||||
|
||||
private final CustomCropsWorld<?> world;
|
||||
private final RegionPos regionPos;
|
||||
private final ConcurrentHashMap<ChunkPos, byte[]> cachedChunks;
|
||||
private boolean isLoaded = false;
|
||||
|
||||
protected CustomCropsRegionImpl(CustomCropsWorld<?> world, RegionPos regionPos) {
|
||||
this.world = world;
|
||||
this.cachedChunks = new ConcurrentHashMap<>();
|
||||
this.regionPos = regionPos;
|
||||
}
|
||||
|
||||
protected CustomCropsRegionImpl(CustomCropsWorld<?> world, RegionPos regionPos, ConcurrentHashMap<ChunkPos, byte[]> cachedChunks) {
|
||||
this.world = world;
|
||||
this.regionPos = regionPos;
|
||||
this.cachedChunks = cachedChunks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded() {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (this.isLoaded) {
|
||||
if (((CustomCropsWorldImpl<?>) world).unloadRegion(this)) {
|
||||
this.isLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (!this.isLoaded) {
|
||||
if (((CustomCropsWorldImpl<?>) world).loadRegion(this)) {
|
||||
this.isLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomCropsWorld<?> getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getCachedChunkBytes(ChunkPos pos) {
|
||||
return this.cachedChunks.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionPos regionPos() {
|
||||
return this.regionPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeCachedChunk(ChunkPos pos) {
|
||||
return cachedChunks.remove(pos) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCachedChunk(ChunkPos pos, byte[] data) {
|
||||
this.cachedChunks.put(pos, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ChunkPos, byte[]> dataToSave() {
|
||||
return new HashMap<>(cachedChunks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPrune() {
|
||||
return cachedChunks.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public interface CustomCropsSection {
|
||||
|
||||
static CustomCropsSection create(int sectionID) {
|
||||
return new CustomCropsSectionImpl(sectionID);
|
||||
}
|
||||
|
||||
static CustomCropsSection restore(int sectionID, ConcurrentHashMap<BlockPos, CustomCropsBlockState> blocks) {
|
||||
return new CustomCropsSectionImpl(sectionID, blocks);
|
||||
}
|
||||
|
||||
int getSectionID();
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> getBlockState(BlockPos pos);
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> removeBlockState(BlockPos pos);
|
||||
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> addBlockState(BlockPos pos, CustomCropsBlockState block);
|
||||
|
||||
boolean canPrune();
|
||||
|
||||
CustomCropsBlockState[] blocks();
|
||||
|
||||
Map<BlockPos, CustomCropsBlockState> blockMap();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class CustomCropsSectionImpl implements CustomCropsSection {
|
||||
|
||||
private final int sectionID;
|
||||
private final ConcurrentHashMap<BlockPos, CustomCropsBlockState> blocks;
|
||||
|
||||
protected CustomCropsSectionImpl(int sectionID) {
|
||||
this.sectionID = sectionID;
|
||||
this.blocks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
protected CustomCropsSectionImpl(int sectionID, ConcurrentHashMap<BlockPos, CustomCropsBlockState> blocks) {
|
||||
this.sectionID = sectionID;
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionID() {
|
||||
return sectionID;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> getBlockState(BlockPos pos) {
|
||||
return Optional.ofNullable(blocks.get(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> removeBlockState(BlockPos pos) {
|
||||
return Optional.ofNullable(blocks.remove(pos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> addBlockState(BlockPos pos, CustomCropsBlockState block) {
|
||||
return Optional.ofNullable(blocks.put(pos, block));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPrune() {
|
||||
return blocks.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomCropsBlockState[] blocks() {
|
||||
return blocks.values().toArray(new CustomCropsBlockState[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<BlockPos, CustomCropsBlockState> blockMap() {
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public interface CustomCropsWorld<W> {
|
||||
|
||||
/**
|
||||
* Create a CustomCrops world
|
||||
*
|
||||
* @param world world instance
|
||||
* @param adaptor the world adaptor
|
||||
* @return CustomCrops world
|
||||
* @param <W> world type
|
||||
*/
|
||||
static <W> CustomCropsWorld<W> create(W world, WorldAdaptor<W> adaptor) {
|
||||
return new CustomCropsWorldImpl<>(world, adaptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CustomCropsChunk associated with this world
|
||||
*
|
||||
* @param pos the position of the chunk
|
||||
* @return the created chunk
|
||||
*/
|
||||
default CustomCropsChunk createChunk(ChunkPos pos) {
|
||||
return new CustomCropsChunkImpl(this, pos);
|
||||
}
|
||||
|
||||
default CustomCropsChunk restoreChunk(
|
||||
ChunkPos pos,
|
||||
int loadedSeconds,
|
||||
long lastLoadedTime,
|
||||
ConcurrentHashMap<Integer, CustomCropsSection> loadedSections,
|
||||
PriorityQueue<DelayedTickTask> queue,
|
||||
HashSet<BlockPos> tickedBlocks
|
||||
) {
|
||||
return new CustomCropsChunkImpl(this, pos, loadedSeconds, lastLoadedTime, loadedSections, queue, tickedBlocks);
|
||||
}
|
||||
|
||||
default CustomCropsRegion createRegion(RegionPos pos) {
|
||||
return new CustomCropsRegionImpl(this, pos);
|
||||
}
|
||||
|
||||
default CustomCropsRegion restoreRegion(RegionPos pos, ConcurrentHashMap<ChunkPos, byte[]> cachedChunks) {
|
||||
return new CustomCropsRegionImpl(this, pos, cachedChunks);
|
||||
}
|
||||
|
||||
WorldAdaptor<W> adaptor();
|
||||
|
||||
WorldExtraData extraData();
|
||||
|
||||
boolean testChunkLimitation(Pos3 pos3, Class<? extends CustomCropsBlock> clazz, int amount);
|
||||
|
||||
boolean doesChunkHaveBlock(Pos3 pos3, Class<? extends CustomCropsBlock> clazz);
|
||||
|
||||
int getChunkBlockAmount(Pos3 pos3, Class<? extends CustomCropsBlock> clazz);
|
||||
|
||||
/**
|
||||
* Get the state of the block at a certain location
|
||||
*
|
||||
* @param location location of the block state
|
||||
* @return the optional block state
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> getBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Remove the block state from a certain location
|
||||
*
|
||||
* @param location the location of the block state
|
||||
* @return the optional removed state
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> removeBlockState(Pos3 location);
|
||||
|
||||
/**
|
||||
* Add block state at the certain location
|
||||
*
|
||||
* @param location location of the state
|
||||
* @param block block state to add
|
||||
* @return the optional previous state
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsBlockState> addBlockState(Pos3 location, CustomCropsBlockState block);
|
||||
|
||||
/**
|
||||
* Save the world to file
|
||||
*/
|
||||
void save();
|
||||
|
||||
/**
|
||||
* Set if the ticking task is ongoing
|
||||
*
|
||||
* @param tick ongoing or not
|
||||
*/
|
||||
void setTicking(boolean tick);
|
||||
|
||||
/**
|
||||
* Get the world associated with this world
|
||||
*
|
||||
* @return Bukkit world
|
||||
*/
|
||||
W world();
|
||||
|
||||
World bukkitWorld();
|
||||
|
||||
String worldName();
|
||||
|
||||
/**
|
||||
* Get the settings of the world
|
||||
*
|
||||
* @return the setting
|
||||
*/
|
||||
@NotNull
|
||||
WorldSetting setting();
|
||||
|
||||
/**
|
||||
* Set the settings of the world
|
||||
*
|
||||
* @param setting setting
|
||||
*/
|
||||
void setting(WorldSetting setting);
|
||||
|
||||
/*
|
||||
* Chunks
|
||||
*/
|
||||
boolean isChunkLoaded(ChunkPos pos);
|
||||
|
||||
/**
|
||||
* Get loaded chunk from cache
|
||||
*
|
||||
* @param chunkPos the position of the chunk
|
||||
* @return the optional loaded chunk
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsChunk> getLoadedChunk(ChunkPos chunkPos);
|
||||
|
||||
/**
|
||||
* Get chunk from cache or file
|
||||
*
|
||||
* @param chunkPos the position of the chunk
|
||||
* @return the optional chunk
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsChunk> getChunk(ChunkPos chunkPos);
|
||||
|
||||
/**
|
||||
* Get chunk from cache or file, create if not found
|
||||
*
|
||||
* @param chunkPos the position of the chunk
|
||||
* @return the chunk
|
||||
*/
|
||||
@NotNull
|
||||
CustomCropsChunk getOrCreateChunk(ChunkPos chunkPos);
|
||||
|
||||
/**
|
||||
* Check if a region is loaded
|
||||
*
|
||||
* @param regionPos the position of the region
|
||||
* @return loaded or not
|
||||
*/
|
||||
boolean isRegionLoaded(RegionPos regionPos);
|
||||
|
||||
/**
|
||||
* Get the loaded region, empty if not loaded
|
||||
*
|
||||
* @param regionPos position of the region
|
||||
* @return the optional loaded region
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsRegion> getLoadedRegion(RegionPos regionPos);
|
||||
|
||||
/**
|
||||
* Get the region from cache or file
|
||||
*
|
||||
* @param regionPos position of the region
|
||||
* @return the optional region
|
||||
*/
|
||||
@NotNull
|
||||
Optional<CustomCropsRegion> getRegion(RegionPos regionPos);
|
||||
|
||||
/**
|
||||
* Get the region from cache or file, create if not found
|
||||
*
|
||||
* @param regionPos position of the region
|
||||
* @return the region
|
||||
*/
|
||||
@NotNull
|
||||
CustomCropsRegion getOrCreateRegion(RegionPos regionPos);
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
package net.momirealms.customcrops.api.core.world;
|
||||
|
||||
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
|
||||
import net.momirealms.customcrops.api.core.block.CustomCropsBlock;
|
||||
import net.momirealms.customcrops.api.core.world.adaptor.WorldAdaptor;
|
||||
import net.momirealms.customcrops.common.helper.VersionHelper;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerAdapter;
|
||||
import net.momirealms.customcrops.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CustomCropsWorldImpl<W> implements CustomCropsWorld<W> {
|
||||
|
||||
private final ConcurrentHashMap<ChunkPos, CustomCropsChunk> loadedChunks = new ConcurrentHashMap<>(512);
|
||||
private final ConcurrentHashMap<ChunkPos, CustomCropsChunk> lazyChunks = new ConcurrentHashMap<>(128);
|
||||
private final ConcurrentHashMap<RegionPos, CustomCropsRegion> loadedRegions = new ConcurrentHashMap<>(128);
|
||||
private final WeakReference<W> world;
|
||||
private final WeakReference<World> bukkitWorld;
|
||||
private final String worldName;
|
||||
private long currentMinecraftDay;
|
||||
private int regionTimer;
|
||||
private SchedulerTask tickTask;
|
||||
private WorldSetting setting;
|
||||
private final WorldAdaptor<W> adaptor;
|
||||
private final WorldExtraData extraData;
|
||||
|
||||
public CustomCropsWorldImpl(W world, WorldAdaptor<W> adaptor) {
|
||||
this.world = new WeakReference<>(world);
|
||||
this.worldName = adaptor.getName(world);
|
||||
this.bukkitWorld = new WeakReference<>(Bukkit.getWorld(worldName));
|
||||
this.regionTimer = 0;
|
||||
this.adaptor = adaptor;
|
||||
this.extraData = adaptor.loadExtraData(world);
|
||||
this.currentMinecraftDay = (int) (adaptor.getWorldFullTime(world) / 24_000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldAdaptor<W> adaptor() {
|
||||
return adaptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public WorldExtraData extraData() {
|
||||
return extraData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testChunkLimitation(Pos3 pos3, Class<? extends CustomCropsBlock> clazz, int amount) {
|
||||
Optional<CustomCropsChunk> optional = getChunk(pos3.toChunkPos());
|
||||
if (optional.isPresent()) {
|
||||
int i = 0;
|
||||
CustomCropsChunk chunk = optional.get();
|
||||
for (CustomCropsSection section : chunk.sections()) {
|
||||
for (CustomCropsBlockState state : section.blockMap().values()) {
|
||||
if (clazz.isAssignableFrom(state.type().getClass())) {
|
||||
i++;
|
||||
if (i >= amount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesChunkHaveBlock(Pos3 pos3, Class<? extends CustomCropsBlock> clazz) {
|
||||
Optional<CustomCropsChunk> optional = getChunk(pos3.toChunkPos());
|
||||
if (optional.isPresent()) {
|
||||
CustomCropsChunk chunk = optional.get();
|
||||
for (CustomCropsSection section : chunk.sections()) {
|
||||
for (CustomCropsBlockState state : section.blockMap().values()) {
|
||||
if (clazz.isAssignableFrom(state.type().getClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChunkBlockAmount(Pos3 pos3, Class<? extends CustomCropsBlock> clazz) {
|
||||
Optional<CustomCropsChunk> optional = getChunk(pos3.toChunkPos());
|
||||
if (optional.isPresent()) {
|
||||
int i = 0;
|
||||
CustomCropsChunk chunk = optional.get();
|
||||
for (CustomCropsSection section : chunk.sections()) {
|
||||
for (CustomCropsBlockState state : section.blockMap().values()) {
|
||||
if (clazz.isAssignableFrom(state.type().getClass())) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return i;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> getBlockState(Pos3 location) {
|
||||
ChunkPos pos = location.toChunkPos();
|
||||
Optional<CustomCropsChunk> chunk = getChunk(pos);
|
||||
if (chunk.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
CustomCropsChunk customChunk = chunk.get();
|
||||
// to let the bukkit system trigger the ChunkUnloadEvent later
|
||||
customChunk.load(true);
|
||||
return customChunk.getBlockState(location);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> removeBlockState(Pos3 location) {
|
||||
ChunkPos pos = location.toChunkPos();
|
||||
Optional<CustomCropsChunk> chunk = getChunk(pos);
|
||||
if (chunk.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
CustomCropsChunk customChunk = chunk.get();
|
||||
// to let the bukkit system trigger the ChunkUnloadEvent later
|
||||
customChunk.load(true);
|
||||
return customChunk.removeBlockState(location);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsBlockState> addBlockState(Pos3 location, CustomCropsBlockState block) {
|
||||
ChunkPos pos = location.toChunkPos();
|
||||
CustomCropsChunk chunk = getOrCreateChunk(pos);
|
||||
// to let the bukkit system trigger the ChunkUnloadEvent later
|
||||
chunk.load(true);
|
||||
return chunk.addBlockState(location, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
long time1 = System.currentTimeMillis();
|
||||
this.adaptor.saveExtraData(this);
|
||||
for (CustomCropsChunk chunk : loadedChunks.values()) {
|
||||
this.adaptor.saveChunk(this, chunk);
|
||||
}
|
||||
for (CustomCropsChunk chunk : lazyChunks.values()) {
|
||||
this.adaptor.saveChunk(this, chunk);
|
||||
}
|
||||
for (CustomCropsRegion region : loadedRegions.values()) {
|
||||
this.adaptor.saveRegion(this, region);
|
||||
}
|
||||
long time2 = System.currentTimeMillis();
|
||||
BukkitCustomCropsPlugin.getInstance().debug("Took " + (time2-time1) + "ms to save world " + worldName + ". Saved " + (lazyChunks.size() + loadedChunks.size()) + " chunks.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTicking(boolean tick) {
|
||||
if (tick) {
|
||||
if (this.tickTask == null || this.tickTask.isCancelled())
|
||||
this.tickTask = BukkitCustomCropsPlugin.getInstance().getScheduler().asyncRepeating(this::timer, 1, 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
if (this.tickTask != null && !this.tickTask.isCancelled())
|
||||
this.tickTask.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void timer() {
|
||||
saveLazyChunks();
|
||||
saveLazyRegions();
|
||||
if (isANewDay()) {
|
||||
updateSeasonAndDate();
|
||||
}
|
||||
if (setting().enableScheduler()) {
|
||||
tickChunks();
|
||||
}
|
||||
}
|
||||
|
||||
private void tickChunks() {
|
||||
if (VersionHelper.isFolia()) {
|
||||
SchedulerAdapter<Location, World> scheduler = BukkitCustomCropsPlugin.getInstance().getScheduler();
|
||||
for (CustomCropsChunk chunk : loadedChunks.values()) {
|
||||
scheduler.sync().run(chunk::timer, bukkitWorld(), chunk.chunkPos().x(), chunk.chunkPos().z());
|
||||
}
|
||||
} else {
|
||||
for (CustomCropsChunk chunk : loadedChunks.values()) {
|
||||
chunk.timer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSeasonAndDate() {
|
||||
int date = extraData().getDate();
|
||||
date++;
|
||||
if (date > setting().seasonDuration()) {
|
||||
Season season = extraData().getSeason().getNextSeason();
|
||||
extraData().setSeason(season);
|
||||
extraData().setDate(1);
|
||||
} else {
|
||||
extraData().setDate(date);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isANewDay() {
|
||||
long currentDay = bukkitWorld().getFullTime() / 24_000;
|
||||
if (currentDay != currentMinecraftDay) {
|
||||
currentMinecraftDay = currentDay;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveLazyRegions() {
|
||||
this.regionTimer++;
|
||||
if (this.regionTimer >= 600) {
|
||||
this.regionTimer = 0;
|
||||
ArrayList<CustomCropsRegion> removed = new ArrayList<>();
|
||||
for (Map.Entry<RegionPos, CustomCropsRegion> entry : loadedRegions.entrySet()) {
|
||||
if (shouldUnloadRegion(entry.getKey())) {
|
||||
removed.add(entry.getValue());
|
||||
}
|
||||
}
|
||||
for (CustomCropsRegion region : removed) {
|
||||
region.unload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLazyChunks() {
|
||||
ArrayList<CustomCropsChunk> chunksToSave = new ArrayList<>();
|
||||
for (Map.Entry<ChunkPos, CustomCropsChunk> lazyEntry : this.lazyChunks.entrySet()) {
|
||||
CustomCropsChunk chunk = lazyEntry.getValue();
|
||||
int sec = chunk.unloadedSeconds() + 1;
|
||||
if (sec >= 30) {
|
||||
chunksToSave.add(chunk);
|
||||
} else {
|
||||
chunk.unloadedSeconds(sec);
|
||||
}
|
||||
}
|
||||
for (CustomCropsChunk chunk : chunksToSave) {
|
||||
unloadLazyChunk(chunk.chunkPos());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public W world() {
|
||||
return world.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public World bukkitWorld() {
|
||||
return bukkitWorld.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String worldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull WorldSetting setting() {
|
||||
return setting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setting(WorldSetting setting) {
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
/*
|
||||
* Chunks
|
||||
*/
|
||||
@Nullable
|
||||
public CustomCropsChunk removeLazyChunk(ChunkPos chunkPos) {
|
||||
return this.lazyChunks.remove(chunkPos);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CustomCropsChunk getLazyChunk(ChunkPos chunkPos) {
|
||||
return this.lazyChunks.get(chunkPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChunkLoaded(ChunkPos pos) {
|
||||
return this.loadedChunks.containsKey(pos);
|
||||
}
|
||||
|
||||
public boolean loadChunk(CustomCropsChunk chunk) {
|
||||
Optional<CustomCropsChunk> previousChunk = getLoadedChunk(chunk.chunkPos());
|
||||
if (previousChunk.isPresent()) {
|
||||
BukkitCustomCropsPlugin.getInstance().debug("Chunk " + chunk.chunkPos() + " already loaded.");
|
||||
if (previousChunk.get() != chunk) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to load the chunk. There is already a different chunk instance with the same coordinates in the cache. " + chunk.chunkPos());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.loadedChunks.put(chunk.chunkPos(), chunk);
|
||||
this.lazyChunks.remove(chunk.chunkPos());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public boolean unloadChunk(CustomCropsChunk chunk, boolean lazy) {
|
||||
ChunkPos pos = chunk.chunkPos();
|
||||
Optional<CustomCropsChunk> previousChunk = getLoadedChunk(chunk.chunkPos());
|
||||
if (previousChunk.isPresent()) {
|
||||
if (previousChunk.get() != chunk) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to remove the chunk. The provided chunk instance is inconsistent with the one in the cache. " + chunk.chunkPos());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
this.loadedChunks.remove(chunk.chunkPos());
|
||||
chunk.updateLastLoadedTime();
|
||||
if (lazy) {
|
||||
this.lazyChunks.put(pos, chunk);
|
||||
} else {
|
||||
this.adaptor.saveChunk(this, chunk);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public boolean unloadChunk(ChunkPos pos, boolean lazy) {
|
||||
CustomCropsChunk removed = this.loadedChunks.remove(pos);
|
||||
if (removed != null) {
|
||||
removed.updateLastLoadedTime();
|
||||
if (lazy) {
|
||||
this.lazyChunks.put(pos, removed);
|
||||
} else {
|
||||
this.adaptor.saveChunk(this, removed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public boolean unloadLazyChunk(ChunkPos pos) {
|
||||
CustomCropsChunk removed = this.lazyChunks.remove(pos);
|
||||
if (removed != null) {
|
||||
this.adaptor.saveChunk(this, removed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsChunk> getLoadedChunk(ChunkPos chunkPos) {
|
||||
return Optional.ofNullable(this.loadedChunks.get(chunkPos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsChunk> getChunk(ChunkPos chunkPos) {
|
||||
return Optional.ofNullable(getLoadedChunk(chunkPos).orElseGet(() -> {
|
||||
CustomCropsChunk chunk = getLazyChunk(chunkPos);
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
return this.adaptor.loadChunk(this, chunkPos, false);
|
||||
}));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomCropsChunk getOrCreateChunk(ChunkPos chunkPos) {
|
||||
return Objects.requireNonNull(getLoadedChunk(chunkPos).orElseGet(() -> {
|
||||
CustomCropsChunk chunk = getLazyChunk(chunkPos);
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
return this.adaptor.loadChunk(this, chunkPos, true);
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* Regions
|
||||
*/
|
||||
@Override
|
||||
public boolean isRegionLoaded(RegionPos pos) {
|
||||
return this.loadedRegions.containsKey(pos);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public boolean loadRegion(CustomCropsRegion region) {
|
||||
Optional<CustomCropsRegion> previousRegion = getLoadedRegion(region.regionPos());
|
||||
if (previousRegion.isPresent()) {
|
||||
BukkitCustomCropsPlugin.getInstance().debug("Region " + region.regionPos() + " already loaded.");
|
||||
if (previousRegion.get() != region) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to load the region. There is already a different region instance with the same coordinates in the cache. " + region.regionPos());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.loadedRegions.put(region.regionPos(), region);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsRegion> getLoadedRegion(RegionPos regionPos) {
|
||||
return Optional.ofNullable(loadedRegions.get(regionPos));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<CustomCropsRegion> getRegion(RegionPos regionPos) {
|
||||
return Optional.ofNullable(getLoadedRegion(regionPos).orElse(adaptor.loadRegion(this, regionPos, false)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CustomCropsRegion getOrCreateRegion(RegionPos regionPos) {
|
||||
return Objects.requireNonNull(getLoadedRegion(regionPos).orElse(adaptor.loadRegion(this, regionPos, true)));
|
||||
}
|
||||
|
||||
private boolean shouldUnloadRegion(RegionPos regionPos) {
|
||||
for (int chunkX = regionPos.x() * 32; chunkX < regionPos.x() * 32 + 32; chunkX++) {
|
||||
for (int chunkZ = regionPos.z() * 32; chunkZ < regionPos.z() * 32 + 32; chunkZ++) {
|
||||
// if a chunk is unloaded, then it should not be in the loaded chunks map
|
||||
ChunkPos pos = ChunkPos.of(chunkX, chunkZ);
|
||||
if (isChunkLoaded(pos) || this.lazyChunks.containsKey(pos)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unloadRegion(CustomCropsRegion region) {
|
||||
Optional<CustomCropsRegion> previousRegion = getLoadedRegion(region.regionPos());
|
||||
if (previousRegion.isPresent()) {
|
||||
if (previousRegion.get() != region) {
|
||||
BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to remove the region. The provided region instance is inconsistent with the one in the cache. " + region.regionPos());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
RegionPos regionPos = region.regionPos();
|
||||
for (int chunkX = regionPos.x() * 32; chunkX < regionPos.x() * 32 + 32; chunkX++) {
|
||||
for (int chunkZ = regionPos.z() * 32; chunkZ < regionPos.z() * 32 + 32; chunkZ++) {
|
||||
ChunkPos pos = ChunkPos.of(chunkX, chunkZ);
|
||||
if (!unloadLazyChunk(pos)) {
|
||||
unloadChunk(pos, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.loadedRegions.remove(region.regionPos());
|
||||
this.adaptor.saveRegion(this, region);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,23 @@
|
||||
* 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.core.world;
|
||||
|
||||
import net.momirealms.customcrops.api.mechanic.item.Fertilizer;
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import net.momirealms.customcrops.api.core.SynchronizedCompoundMap;
|
||||
|
||||
public interface SoilRetain extends Fertilizer {
|
||||
public interface DataBlock {
|
||||
|
||||
Tag<?> set(String key, Tag<?> tag);
|
||||
|
||||
Tag<?> get(String key);
|
||||
|
||||
Tag<?> remove(String key);
|
||||
|
||||
/**
|
||||
* Get the chance of taking effect
|
||||
* Get the data map
|
||||
*
|
||||
* @return chance
|
||||
* @return data map
|
||||
*/
|
||||
double getChance();
|
||||
SynchronizedCompoundMap compoundMap();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class DelayedTickTask implements Comparable<DelayedTickTask> {
|
||||
|
||||
private static int taskID;
|
||||
private final int time;
|
||||
private final BlockPos blockPos;
|
||||
private final int id;
|
||||
|
||||
public DelayedTickTask(int time, BlockPos blockPos) {
|
||||
this.time = time;
|
||||
this.blockPos = blockPos;
|
||||
this.id = taskID++;
|
||||
}
|
||||
|
||||
public BlockPos blockPos() {
|
||||
return blockPos;
|
||||
}
|
||||
|
||||
public int getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull DelayedTickTask o) {
|
||||
if (this.time > o.time) {
|
||||
return 1;
|
||||
} else if (this.time < o.time) {
|
||||
return -1;
|
||||
} else {
|
||||
return Integer.compare(this.id, o.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.core.world;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
public record Pos3(int x, int y, int z) {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Pos3 other = (Pos3) obj;
|
||||
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.x));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.y));
|
||||
hash = 19 * hash + Long.hashCode(Double.doubleToLongBits(this.z));
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static Pos3 from(Location location) {
|
||||
return new Pos3(location.getBlockX(), location.getBlockY(), location.getBlockZ());
|
||||
}
|
||||
|
||||
public Location toLocation(World world) {
|
||||
return new Location(world, x, y, z);
|
||||
}
|
||||
|
||||
public Pos3 add(int x, int y, int z) {
|
||||
return new Pos3(this.x + x, this.y + y, this.z + z);
|
||||
}
|
||||
|
||||
public ChunkPos toChunkPos() {
|
||||
return ChunkPos.fromPos3(this);
|
||||
}
|
||||
|
||||
public int chunkX() {
|
||||
return (int) Math.floor((double) this.x() / 16.0);
|
||||
}
|
||||
|
||||
public int chunkZ() {
|
||||
return (int) Math.floor((double) this.z() / 16.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pos3{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
", z=" + z +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user