mirror of
https://github.com/Xiao-MoMi/Custom-Fishing.git
synced 2025-12-29 11:59:11 +00:00
comments
This commit is contained in:
@@ -137,6 +137,9 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the plugin
|
||||
*/
|
||||
@Override
|
||||
public void reload() {
|
||||
CFConfig.load();
|
||||
@@ -181,6 +184,9 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin {
|
||||
this.coolDownManager.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin dependencies
|
||||
*/
|
||||
private void loadDependencies() {
|
||||
String mavenRepo = TimeZone.getDefault().getID().startsWith("Asia") ?
|
||||
"https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/";
|
||||
@@ -203,6 +209,9 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable NBT API logs
|
||||
*/
|
||||
private void disableNBTAPILogs() {
|
||||
MinecraftVersion.disableBStats();
|
||||
MinecraftVersion.disableUpdateCheck();
|
||||
@@ -236,6 +245,12 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a YAML configuration from a file within the plugin's data folder.
|
||||
*
|
||||
* @param file The name of the configuration file.
|
||||
* @return A YamlConfiguration object representing the configuration.
|
||||
*/
|
||||
@Override
|
||||
public YamlConfiguration getConfig(String file) {
|
||||
File config = new File(this.getDataFolder(), file);
|
||||
@@ -243,23 +258,44 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin {
|
||||
return YamlConfiguration.loadConfiguration(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specified plugin is enabled on the Bukkit server.
|
||||
*
|
||||
* @param plugin The name of the plugin to check.
|
||||
* @return True if the plugin is enabled, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isHookedPluginEnabled(String plugin) {
|
||||
return Bukkit.getPluginManager().isPluginEnabled(plugin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a debugging message if the debug mode is enabled.
|
||||
*
|
||||
* @param message The debugging message to be logged.
|
||||
*/
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
if (!CFConfig.debug) return;
|
||||
LogUtils.info(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CoolDownManager instance associated with the plugin.
|
||||
*
|
||||
* @return The CoolDownManager instance.
|
||||
*/
|
||||
public CoolDownManager getCoolDownManager() {
|
||||
return coolDownManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ProtocolManager instance used for managing packets.
|
||||
*
|
||||
* @return The ProtocolManager instance.
|
||||
*/
|
||||
@NotNull
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class CompetitionCommand {
|
||||
}
|
||||
|
||||
private CommandAPICommand getCompetitionStartCommand() {
|
||||
Set<String> allCompetitions = CustomFishingPlugin.get().getCompetitionManager().getAllCompetitions();
|
||||
Set<String> allCompetitions = CustomFishingPlugin.get().getCompetitionManager().getAllCompetitionKeys();
|
||||
var command = new CommandAPICommand("start")
|
||||
.withArguments(
|
||||
new StringArgument("id")
|
||||
|
||||
@@ -120,7 +120,7 @@ public class DebugCommand {
|
||||
for (EffectModifier modifier : totemEffect.getEffectModifiers()) {
|
||||
modifier.modify(initialEffect, fishingPreparation);
|
||||
}
|
||||
var map = CustomFishingPlugin.get().getFishingManager().getPossibleLootKeysWithWeight(initialEffect, fishingPreparation);
|
||||
var map = CustomFishingPlugin.get().getLootManager().getPossibleLootKeysWithWeight(initialEffect, fishingPreparation);
|
||||
List<LootWithWeight> loots = new ArrayList<>();
|
||||
double sum = 0;
|
||||
for (Map.Entry<String, Double> entry : map.entrySet()) {
|
||||
|
||||
@@ -48,18 +48,18 @@ public class IntegrationManagerImpl implements IntegrationManager {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
private final HashMap<String, LevelInterface> levelPluginMap;
|
||||
private final HashMap<String, EnchantmentInterface> enchantments;
|
||||
private final HashMap<String, EnchantmentInterface> enchantmentPluginMap;
|
||||
private SeasonInterface seasonInterface;
|
||||
|
||||
public IntegrationManagerImpl(CustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.levelPluginMap = new HashMap<>();
|
||||
this.enchantments = new HashMap<>();
|
||||
this.enchantmentPluginMap = new HashMap<>();
|
||||
this.load();
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
this.enchantments.clear();
|
||||
this.enchantmentPluginMap.clear();
|
||||
this.levelPluginMap.clear();
|
||||
}
|
||||
|
||||
@@ -121,13 +121,13 @@ public class IntegrationManagerImpl implements IntegrationManager {
|
||||
hookMessage("AureliumSkills");
|
||||
}
|
||||
if (plugin.isHookedPluginEnabled("EcoEnchants")) {
|
||||
this.enchantments.put("EcoEnchants", new VanillaEnchantmentsImpl());
|
||||
this.enchantmentPluginMap.put("EcoEnchants", new VanillaEnchantmentsImpl());
|
||||
hookMessage("EcoEnchants");
|
||||
} else {
|
||||
this.enchantments.put("vanilla", new VanillaEnchantmentsImpl());
|
||||
this.enchantmentPluginMap.put("vanilla", new VanillaEnchantmentsImpl());
|
||||
}
|
||||
if (plugin.isHookedPluginEnabled("AdvancedEnchantments")) {
|
||||
this.enchantments.put("AdvancedEnchantments", new AdvancedEnchantmentsImpl());
|
||||
this.enchantmentPluginMap.put("AdvancedEnchantments", new AdvancedEnchantmentsImpl());
|
||||
hookMessage("AdvancedEnchantments");
|
||||
}
|
||||
if (plugin.isHookedPluginEnabled("RealisticSeasons")) {
|
||||
@@ -161,6 +161,13 @@ public class IntegrationManagerImpl implements IntegrationManager {
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a level plugin with the specified name.
|
||||
*
|
||||
* @param plugin The name of the level plugin.
|
||||
* @param level The implementation of the LevelInterface.
|
||||
* @return true if the registration was successful, false if the plugin name is already registered.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerLevelPlugin(String plugin, LevelInterface level) {
|
||||
if (levelPluginMap.containsKey(plugin)) return false;
|
||||
@@ -168,46 +175,100 @@ public class IntegrationManagerImpl implements IntegrationManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a level plugin with the specified name.
|
||||
*
|
||||
* @param plugin The name of the level plugin to unregister.
|
||||
* @return true if the unregistration was successful, false if the plugin name is not found.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterLevelPlugin(String plugin) {
|
||||
return levelPluginMap.remove(plugin) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an enchantment provided by a plugin.
|
||||
*
|
||||
* @param plugin The name of the plugin providing the enchantment.
|
||||
* @param enchantment The enchantment to register.
|
||||
* @return true if the registration was successful, false if the enchantment name is already in use.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerEnchantment(String plugin, EnchantmentInterface enchantment) {
|
||||
if (enchantments.containsKey(plugin)) return false;
|
||||
enchantments.put(plugin, enchantment);
|
||||
if (enchantmentPluginMap.containsKey(plugin)) return false;
|
||||
enchantmentPluginMap.put(plugin, enchantment);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an enchantment provided by a plugin.
|
||||
*
|
||||
* @param plugin The name of the plugin providing the enchantment.
|
||||
* @return true if the enchantment was successfully unregistered, false if the enchantment was not found.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterEnchantment(String plugin) {
|
||||
return enchantments.remove(plugin) != null;
|
||||
return enchantmentPluginMap.remove(plugin) != null;
|
||||
}
|
||||
|
||||
private void hookMessage(String plugin) {
|
||||
LogUtils.info( plugin + " hooked!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LevelInterface provided by a plugin.
|
||||
*
|
||||
* @param plugin The name of the plugin providing the LevelInterface.
|
||||
* @return The LevelInterface provided by the specified plugin, or null if the plugin is not registered.
|
||||
*/
|
||||
@Override
|
||||
public LevelInterface getLevelHook(String plugin) {
|
||||
@Nullable
|
||||
public LevelInterface getLevelPlugin(String plugin) {
|
||||
return levelPluginMap.get(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an enchantment plugin by its plugin name.
|
||||
*
|
||||
* @param plugin The name of the enchantment plugin.
|
||||
* @return The enchantment plugin interface, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public EnchantmentInterface getEnchantmentPlugin(String plugin) {
|
||||
return enchantmentPluginMap.get(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of enchantment keys with level applied to the given ItemStack.
|
||||
*
|
||||
* @param itemStack The ItemStack to check for enchantments.
|
||||
* @return A list of enchantment names applied to the ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public List<String> getEnchantments(ItemStack itemStack) {
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
for (EnchantmentInterface enchantmentInterface : enchantments.values()) {
|
||||
for (EnchantmentInterface enchantmentInterface : enchantmentPluginMap.values()) {
|
||||
list.addAll(enchantmentInterface.getEnchants(itemStack));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current season interface, if available.
|
||||
*
|
||||
* @return The current season interface, or null if not available.
|
||||
*/
|
||||
@Nullable
|
||||
public SeasonInterface getSeasonInterface() {
|
||||
return seasonInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current season interface.
|
||||
*
|
||||
* @param season The season interface to set.
|
||||
*/
|
||||
@Override
|
||||
public void setSeasonInterface(SeasonInterface season) {
|
||||
this.seasonInterface = season;
|
||||
|
||||
@@ -37,6 +37,6 @@ public class CustomFishingItemImpl implements ItemLibrary {
|
||||
|
||||
@Override
|
||||
public String getItemID(ItemStack itemStack) {
|
||||
return CustomFishingPlugin.get().getItemManager().getItemID(itemStack);
|
||||
return CustomFishingPlugin.get().getItemManager().getCustomFishingItemID(itemStack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +92,36 @@ public class PlaceholderManagerImpl implements PlaceholderManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set placeholders in a text string for a player.
|
||||
*
|
||||
* @param player The player for whom the placeholders should be set.
|
||||
* @param text The text string containing placeholders.
|
||||
* @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text.
|
||||
*/
|
||||
@Override
|
||||
public String setPlaceholders(Player player, String text) {
|
||||
return hasPapi ? ParseUtils.setPlaceholders(player, text) : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set placeholders in a text string for an offline player.
|
||||
*
|
||||
* @param player The offline player for whom the placeholders should be set.
|
||||
* @param text The text string containing placeholders.
|
||||
* @return The text string with placeholders replaced if PlaceholderAPI is available; otherwise, the original text.
|
||||
*/
|
||||
@Override
|
||||
public String setPlaceholders(OfflinePlayer player, String text) {
|
||||
return hasPapi ? ParseUtils.setPlaceholders(player, text) : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and extract placeholders from a text string.
|
||||
*
|
||||
* @param text The text string to search for placeholders.
|
||||
* @return A list of detected placeholders in the text.
|
||||
*/
|
||||
@Override
|
||||
public List<String> detectPlaceholders(String text) {
|
||||
List<String> placeholders = new ArrayList<>();
|
||||
@@ -110,6 +130,14 @@ public class PlaceholderManagerImpl implements PlaceholderManager, Listener {
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value associated with a single placeholder.
|
||||
*
|
||||
* @param player The player for whom the placeholders are being resolved (nullable).
|
||||
* @param placeholder The placeholder to look up.
|
||||
* @param placeholders A map of placeholders to their corresponding values.
|
||||
* @return The value associated with the placeholder, or the original placeholder if not found.
|
||||
*/
|
||||
@Override
|
||||
public String getSingleValue(@Nullable Player player, String placeholder, Map<String, String> placeholders) {
|
||||
String result = null;
|
||||
@@ -123,6 +151,14 @@ public class PlaceholderManagerImpl implements PlaceholderManager, Listener {
|
||||
return setPlaceholders(player, custom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a text string by replacing placeholders with their corresponding values.
|
||||
*
|
||||
* @param player The offline player for whom the placeholders are being resolved (nullable).
|
||||
* @param text The text string containing placeholders.
|
||||
* @param placeholders A map of placeholders to their corresponding values.
|
||||
* @return The text string with placeholders replaced by their values.
|
||||
*/
|
||||
@Override
|
||||
public String parse(@Nullable OfflinePlayer player, String text, Map<String, String> placeholders) {
|
||||
var list = detectPlaceholders(text);
|
||||
@@ -144,8 +180,30 @@ public class PlaceholderManagerImpl implements PlaceholderManager, Listener {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a list of text strings by replacing placeholders with their corresponding values.
|
||||
*
|
||||
* @param player The player for whom the placeholders are being resolved (can be null for offline players).
|
||||
* @param list The list of text strings containing placeholders.
|
||||
* @param replacements A map of custom replacements for placeholders.
|
||||
* @return The list of text strings with placeholders replaced by their values.
|
||||
*/
|
||||
@Override
|
||||
public String parseCacheable(Player player, String text) {
|
||||
public List<String> parse(@Nullable OfflinePlayer player, List<String> list, Map<String, String> replacements) {
|
||||
return list.stream()
|
||||
.map(s -> parse(player, s, replacements))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a text string by replacing placeholders with their corresponding values, using caching for the player's placeholders.
|
||||
*
|
||||
* @param player The player for whom the placeholders are being resolved.
|
||||
* @param text The text string containing placeholders.
|
||||
* @return The text string with placeholders replaced by their values.
|
||||
*/
|
||||
@Override
|
||||
public String parseCacheablePlaceholders(Player player, String text) {
|
||||
var list = detectPlaceholders(text);
|
||||
CachedPlaceholder cachedPlaceholder = cachedPlaceholders.computeIfAbsent(player.getUniqueId(), k -> new CachedPlaceholder());
|
||||
for (String papi : list) {
|
||||
@@ -157,13 +215,6 @@ public class PlaceholderManagerImpl implements PlaceholderManager, Listener {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> parse(@Nullable OfflinePlayer player, List<String> list, Map<String, String> replacements) {
|
||||
return list.stream()
|
||||
.map(s -> parse(player, s, replacements))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static PlaceholderManagerImpl getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionExpansion;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionFactory;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Loot;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
@@ -47,6 +48,7 @@ import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -68,6 +70,7 @@ public class ActionManagerImpl implements ActionManager {
|
||||
this.registerInbuiltActions();
|
||||
}
|
||||
|
||||
// Method to register various built-in actions during initialization.
|
||||
private void registerInbuiltActions() {
|
||||
this.registerMessageAction();
|
||||
this.registerCommandAction();
|
||||
@@ -94,6 +97,7 @@ public class ActionManagerImpl implements ActionManager {
|
||||
this.registerMoneyAction();
|
||||
}
|
||||
|
||||
// Method to load expansions and global event actions.
|
||||
public void load() {
|
||||
this.loadExpansions();
|
||||
this.loadGlobalEventActions();
|
||||
@@ -108,11 +112,20 @@ public class ActionManagerImpl implements ActionManager {
|
||||
this.actionBuilderMap.clear();
|
||||
}
|
||||
|
||||
// Method to load global event actions from the plugin's configuration file.
|
||||
private void loadGlobalEventActions() {
|
||||
YamlConfiguration config = plugin.getConfig("config.yml");
|
||||
GlobalSettings.load(config.getConfigurationSection("mechanics.global-events"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an ActionFactory for a specific action type.
|
||||
* This method allows you to associate an ActionFactory with a custom action type.
|
||||
*
|
||||
* @param type The custom action type to register.
|
||||
* @param actionFactory The ActionFactory responsible for creating actions of the specified type.
|
||||
* @return True if the registration was successful (the action type was not already registered), false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerAction(String type, ActionFactory actionFactory) {
|
||||
if (this.actionBuilderMap.containsKey(type)) return false;
|
||||
@@ -120,36 +133,92 @@ public class ActionManagerImpl implements ActionManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an ActionFactory for a specific action type.
|
||||
* This method allows you to remove the association between an action type and its ActionFactory.
|
||||
*
|
||||
* @param type The custom action type to unregister.
|
||||
* @return True if the action type was successfully unregistered, false if it was not found.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterAction(String type) {
|
||||
return this.actionBuilderMap.remove(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an Action object based on the configuration provided in a ConfigurationSection.
|
||||
* This method reads the type of action from the section, obtains the corresponding ActionFactory,
|
||||
* and builds an Action object using the specified values and chance.
|
||||
*
|
||||
* @param section The ConfigurationSection containing the action configuration.
|
||||
* @return An Action object created based on the configuration, or an EmptyAction instance if the action type is invalid.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public Action getAction(ConfigurationSection section) {
|
||||
return getActionBuilder(section.getString("type")).build(section.get("value"), section.getDouble("chance", 1d));
|
||||
ActionFactory factory = getActionFactory(section.getString("type"));
|
||||
if (factory == null) {
|
||||
LogUtils.warn("Action type: " + section.getString("type") + " doesn't exist.");
|
||||
// to prevent NPE
|
||||
return EmptyAction.instance;
|
||||
}
|
||||
return factory.build(
|
||||
section.get("value"),
|
||||
section.getDouble("chance", 1d)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a mapping of ActionTriggers to arrays of Actions from a ConfigurationSection.
|
||||
* This method iterates through the provided ConfigurationSection to extract action triggers
|
||||
* and their associated arrays of Actions.
|
||||
*
|
||||
* @param section The ConfigurationSection containing action mappings.
|
||||
* @return A HashMap where keys are ActionTriggers and values are arrays of Action objects.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public HashMap<ActionTrigger, Action[]> getActionMap(ConfigurationSection section) {
|
||||
// Create an empty HashMap to store the action mappings
|
||||
HashMap<ActionTrigger, Action[]> actionMap = new HashMap<>();
|
||||
|
||||
// If the provided ConfigurationSection is null, return the empty actionMap
|
||||
if (section == null) return actionMap;
|
||||
|
||||
// Iterate through all key-value pairs in the ConfigurationSection
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
actionMap.put(
|
||||
ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)),
|
||||
getActions(innerSection)
|
||||
);
|
||||
// Convert the key to an ActionTrigger enum (assuming it's in uppercase English)
|
||||
// and map it to an array of Actions obtained from the inner section
|
||||
try {
|
||||
actionMap.put(
|
||||
ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)),
|
||||
getActions(innerSection)
|
||||
);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.warn("Event: " + entry.getKey() + " doesn't exist!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/**
|
||||
* Retrieves an array of Action objects from a ConfigurationSection.
|
||||
* This method iterates through the provided ConfigurationSection to extract Action configurations
|
||||
* and build an array of Action objects.
|
||||
*
|
||||
* @param section The ConfigurationSection containing action configurations.
|
||||
* @return An array of Action objects created based on the configurations in the section.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Action[] getActions(ConfigurationSection section) {
|
||||
if (section == null) return null;
|
||||
// Create an ArrayList to store the Actions
|
||||
ArrayList<Action> actionList = new ArrayList<>();
|
||||
if (section == null) return actionList.toArray(new Action[0]);
|
||||
|
||||
// Iterate through all key-value pairs in the ConfigurationSection
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
actionList.add(getAction(innerSection));
|
||||
@@ -158,11 +227,86 @@ public class ActionManagerImpl implements ActionManager {
|
||||
return actionList.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an ActionFactory associated with a specific action type.
|
||||
*
|
||||
* @param type The action type for which to retrieve the ActionFactory.
|
||||
* @return The ActionFactory associated with the specified action type, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public ActionFactory getActionBuilder(String type) {
|
||||
public ActionFactory getActionFactory(String type) {
|
||||
return actionBuilderMap.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads custom ActionExpansions from JAR files located in the expansion directory.
|
||||
* This method scans the expansion folder for JAR files, loads classes that extend ActionExpansion,
|
||||
* and registers them with the appropriate action type and ActionFactory.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void loadExpansions() {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
if (!expansionFolder.exists())
|
||||
expansionFolder.mkdirs();
|
||||
|
||||
List<Class<? extends ActionExpansion>> classes = new ArrayList<>();
|
||||
File[] expansionJars = expansionFolder.listFiles();
|
||||
if (expansionJars == null) return;
|
||||
for (File expansionJar : expansionJars) {
|
||||
if (expansionJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
Class<? extends ActionExpansion> expansionClass = ClassUtils.findClass(expansionJar, ActionExpansion.class);
|
||||
classes.add(expansionClass);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (Class<? extends ActionExpansion> expansionClass : classes) {
|
||||
ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance();
|
||||
unregisterAction(expansion.getActionType());
|
||||
registerAction(expansion.getActionType(), expansion.getActionFactory());
|
||||
LogUtils.info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() );
|
||||
}
|
||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
LogUtils.warn("Error occurred when creating expansion instance.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a mapping of success times to corresponding arrays of actions from a ConfigurationSection.
|
||||
*
|
||||
* @param section The ConfigurationSection containing success times actions.
|
||||
* @return A HashMap where success times associated with actions.
|
||||
*/
|
||||
@Override
|
||||
public HashMap<Integer, Action[]> getTimesActionMap(ConfigurationSection section) {
|
||||
HashMap<Integer, Action[]> actionMap = new HashMap<>();
|
||||
if (section == null) return actionMap;
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
actionMap.put(Integer.parseInt(entry.getKey()), plugin.getActionManager().getActions(innerSection));
|
||||
}
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a list of actions with the given condition.
|
||||
* If the list of actions is not null, each action in the list is triggered.
|
||||
*
|
||||
* @param actions The list of actions to trigger.
|
||||
* @param condition The condition associated with the actions.
|
||||
*/
|
||||
@Override
|
||||
public void triggerActions(List<Action> actions, Condition condition) {
|
||||
if (actions != null)
|
||||
for (Action action : actions)
|
||||
action.trigger(condition);
|
||||
}
|
||||
|
||||
private void registerMessageAction() {
|
||||
registerAction("message", (args, chance) -> {
|
||||
ArrayList<String> msg = ConfigUtils.stringListArgs(args);
|
||||
@@ -462,9 +606,9 @@ public class ActionManagerImpl implements ActionManager {
|
||||
Player player = condition.getPlayer();
|
||||
ItemStack itemStack = player.getInventory().getItem(slot);
|
||||
if (amount > 0) {
|
||||
ItemUtils.addDurability(itemStack, amount, true);
|
||||
ItemUtils.increaseDurability(itemStack, amount, true);
|
||||
} else {
|
||||
ItemUtils.loseDurability(itemStack, -amount, true);
|
||||
ItemUtils.decreaseDurability(itemStack, -amount, true);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -482,7 +626,7 @@ public class ActionManagerImpl implements ActionManager {
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = condition.getPlayer();
|
||||
ItemUtils.giveCertainAmountOfItem(player, CustomFishingPlugin.get().getItemManager().buildAnyItemByID(player, id), amount);
|
||||
ItemUtils.giveCertainAmountOfItem(player, CustomFishingPlugin.get().getItemManager().buildAnyPluginItemByID(player, id), amount);
|
||||
};
|
||||
} else {
|
||||
LogUtils.warn("Illegal value format found at action: give-item");
|
||||
@@ -780,7 +924,7 @@ public class ActionManagerImpl implements ActionManager {
|
||||
String target = section.getString("target");
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
Optional.ofNullable(plugin.getIntegrationManager().getLevelHook(pluginName)).ifPresentOrElse(it -> {
|
||||
Optional.ofNullable(plugin.getIntegrationManager().getLevelPlugin(pluginName)).ifPresentOrElse(it -> {
|
||||
it.addXp(condition.getPlayer(), target, exp);
|
||||
}, () -> LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."));
|
||||
};
|
||||
@@ -796,7 +940,7 @@ public class ActionManagerImpl implements ActionManager {
|
||||
if (Math.random() > chance) return;
|
||||
condition.insertArg("{lava}", String.valueOf(arg));
|
||||
LootManager lootManager = plugin.getLootManager();
|
||||
List<String> loots = plugin.getFishingManager().getPossibleLootKeys(condition).stream().map(lootManager::getLoot).filter(Objects::nonNull).filter(Loot::showInFinder).map(Loot::getNick).toList();
|
||||
List<String> loots = plugin.getLootManager().getPossibleLootKeys(condition).stream().map(lootManager::getLoot).filter(Objects::nonNull).filter(Loot::showInFinder).map(Loot::getNick).toList();
|
||||
StringJoiner stringJoiner = new StringJoiner(CFLocale.MSG_Split_Char);
|
||||
for (String loot : loots) {
|
||||
stringJoiner.add(loot);
|
||||
@@ -806,35 +950,4 @@ public class ActionManagerImpl implements ActionManager {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void loadExpansions() {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
if (!expansionFolder.exists())
|
||||
expansionFolder.mkdirs();
|
||||
|
||||
List<Class<? extends ActionExpansion>> classes = new ArrayList<>();
|
||||
File[] expansionJars = expansionFolder.listFiles();
|
||||
if (expansionJars == null) return;
|
||||
for (File expansionJar : expansionJars) {
|
||||
if (expansionJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
Class<? extends ActionExpansion> expansionClass = ClassUtils.findClass(expansionJar, ActionExpansion.class);
|
||||
classes.add(expansionClass);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
LogUtils.warn("Failed to load expansion: " + expansionJar.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (Class<? extends ActionExpansion> expansionClass : classes) {
|
||||
ActionExpansion expansion = expansionClass.getDeclaredConstructor().newInstance();
|
||||
unregisterAction(expansion.getActionType());
|
||||
registerAction(expansion.getActionType(), expansion.getActionFactory());
|
||||
LogUtils.info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() );
|
||||
}
|
||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
LogUtils.warn("Error occurred when creating expansion instance.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.customfishing.mechanic.action;
|
||||
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
|
||||
/**
|
||||
* An implementation of the Action interface that represents an empty action with no behavior.
|
||||
* This class serves as a default action to prevent NPE.
|
||||
*/
|
||||
public class EmptyAction implements Action {
|
||||
|
||||
public static EmptyAction instance = new EmptyAction();
|
||||
|
||||
@Override
|
||||
public void trigger(Condition condition) {
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import net.momirealms.customfishing.CustomFishingPluginImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.manager.BagManager;
|
||||
import net.momirealms.customfishing.api.manager.EffectManager;
|
||||
import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder;
|
||||
import net.momirealms.customfishing.setting.CFConfig;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -34,6 +35,7 @@ import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
@@ -66,6 +68,13 @@ public class BagManagerImpl implements BagManager, Listener {
|
||||
plugin.getStorageManager().getDataSource().updateManyPlayersData(tempEditMap.values(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the online bag inventory associated with a player's UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player for whom the bag inventory is retrieved.
|
||||
* @return The online bag inventory if the player is online, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Inventory getOnlineBagInventory(UUID uuid) {
|
||||
var onlinePlayer = plugin.getStorageManager().getOnlineUser(uuid);
|
||||
@@ -75,12 +84,23 @@ public class BagManagerImpl implements BagManager, Listener {
|
||||
return onlinePlayer.getHolder().getInventory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the process of editing the bag inventory of an offline player by an admin.
|
||||
*
|
||||
* @param admin The admin player performing the edit.
|
||||
* @param userData The OfflineUser data of the player whose bag is being edited.
|
||||
*/
|
||||
@Override
|
||||
public void editOfflinePlayerBag(Player admin, OfflineUser userData) {
|
||||
this.tempEditMap.put(admin.getUniqueId(), userData);
|
||||
admin.openInventory(userData.getHolder().getInventory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the InventoryCloseEvent to save changes made to an offline player's bag inventory when it's closed.
|
||||
*
|
||||
* @param event The InventoryCloseEvent triggered when the inventory is closed.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onInvClose(InventoryCloseEvent event) {
|
||||
if (!(event.getInventory().getHolder() instanceof FishingBagHolder))
|
||||
@@ -92,6 +112,12 @@ public class BagManagerImpl implements BagManager, Listener {
|
||||
plugin.getStorageManager().saveUserData(offlineUser, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles InventoryClickEvent to prevent certain actions on the Fishing Bag inventory.
|
||||
* This method cancels the event if specific conditions are met to restrict certain item interactions.
|
||||
*
|
||||
* @param event The InventoryClickEvent triggered when an item is clicked in an inventory.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onInvClick(InventoryClickEvent event) {
|
||||
if (event.isCancelled())
|
||||
@@ -106,18 +132,28 @@ public class BagManagerImpl implements BagManager, Listener {
|
||||
return;
|
||||
if (CFConfig.bagWhiteListItems.contains(clickedItem.getType()))
|
||||
return;
|
||||
String id = plugin.getItemManager().getAnyItemID(clickedItem);
|
||||
if (plugin.getEffectManager().getEffect("rod", id) != null)
|
||||
return;
|
||||
if (plugin.getEffectManager().getEffect("bait", id) != null)
|
||||
return;
|
||||
if (plugin.getEffectManager().getEffect("util", id) != null)
|
||||
String id = plugin.getItemManager().getAnyPluginItemID(clickedItem);
|
||||
EffectManager effectManager = plugin.getEffectManager();
|
||||
if (effectManager.hasEffectCarrier("rod", id)
|
||||
|| effectManager.hasEffectCarrier("bait", id)
|
||||
|| effectManager.hasEffectCarrier("util", id)
|
||||
|| effectManager.hasEffectCarrier("hook", id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (CFConfig.bagStoreLoots && plugin.getLootManager().getLoot(id) != null)
|
||||
return;
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the PlayerQuitEvent.
|
||||
* This method is triggered when a player quits the server.
|
||||
* It checks if the player was in the process of editing an offline player's bag inventory,
|
||||
* and if so, saves the offline player's data if necessary.
|
||||
*
|
||||
* @param event The PlayerQuitEvent triggered when a player quits.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
OfflineUser offlineUser = tempEditMap.remove(event.getPlayer().getUniqueId());
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
@@ -74,17 +75,32 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
this.registerInbuiltProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the EntityChangeBlockEvent.
|
||||
* This method is triggered when an entity changes a block, typically when a block falls or lands.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onBlockLands(EntityChangeBlockEvent event) {
|
||||
if (event.isCancelled()) return;
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
|
||||
// Retrieve a custom string value stored in the entity's persistent data container.
|
||||
String temp = event.getEntity().getPersistentDataContainer().get(
|
||||
Objects.requireNonNull(NamespacedKey.fromString("block", CustomFishingPlugin.get())),
|
||||
PersistentDataType.STRING
|
||||
);
|
||||
|
||||
// If the custom string value is not present, return without further action.
|
||||
if (temp == null) return;
|
||||
|
||||
// "BLOCK;PLAYER"
|
||||
String[] split = temp.split(";");
|
||||
|
||||
// If no BlockConfig is found for the specified key, return without further action.
|
||||
BlockConfig blockConfig = blockConfigMap.get(split[0]);
|
||||
if (blockConfig == null) return;
|
||||
|
||||
// If the player is not online or not found, remove the entity and set the block to air
|
||||
Player player = Bukkit.getPlayer(split[1]);
|
||||
if (player == null) {
|
||||
event.getEntity().remove();
|
||||
@@ -92,6 +108,8 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
return;
|
||||
}
|
||||
Location location = event.getBlock().getLocation();
|
||||
|
||||
// Apply block state modifiers from the BlockConfig to the block 1 tick later.
|
||||
plugin.getScheduler().runTaskSyncLater(() -> {
|
||||
BlockState state = location.getBlock().getState();
|
||||
for (BlockStateModifier modifier : blockConfig.getStateModifierList()) {
|
||||
@@ -100,23 +118,40 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}, location, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a BlockLibrary instance.
|
||||
* This method associates a BlockLibrary with its unique identification and adds it to the registry.
|
||||
*
|
||||
* @param blockLibrary The BlockLibrary instance to register.
|
||||
* @return True if the registration was successful (the identification is not already registered), false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerBlockLibrary(BlockLibrary library) {
|
||||
if (this.blockLibraryMap.containsKey(library.identification())) return false;
|
||||
this.blockLibraryMap.put(library.identification(), library);
|
||||
public boolean registerBlockLibrary(BlockLibrary blockLibrary) {
|
||||
if (this.blockLibraryMap.containsKey(blockLibrary.identification())) return false;
|
||||
this.blockLibraryMap.put(blockLibrary.identification(), blockLibrary);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a BlockLibrary instance by its identification.
|
||||
* This method removes a BlockLibrary from the registry based on its unique identification.
|
||||
*
|
||||
* @param identification The unique identification of the BlockLibrary to unregister.
|
||||
* @return True if the BlockLibrary was successfully unregistered, false if it was not found.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterBlockLibrary(BlockLibrary library) {
|
||||
return unregisterBlockLibrary(library.identification());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterBlockLibrary(String library) {
|
||||
return blockLibraryMap.remove(library) != null;
|
||||
public boolean unregisterBlockLibrary(String identification) {
|
||||
return blockLibraryMap.remove(identification) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a BlockDataModifierBuilder for a specific type.
|
||||
* This method associates a BlockDataModifierBuilder with its type and adds it to the registry.
|
||||
*
|
||||
* @param type The type of the BlockDataModifierBuilder to register.
|
||||
* @param builder The BlockDataModifierBuilder instance to register.
|
||||
* @return True if the registration was successful (the type is not already registered), false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerBlockDataModifierBuilder(String type, BlockDataModifierBuilder builder) {
|
||||
if (dataBuilderMap.containsKey(type)) return false;
|
||||
@@ -124,6 +159,14 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a BlockStateModifierBuilder for a specific type.
|
||||
* This method associates a BlockStateModifierBuilder with its type and adds it to the registry.
|
||||
*
|
||||
* @param type The type of the BlockStateModifierBuilder to register.
|
||||
* @param builder The BlockStateModifierBuilder instance to register.
|
||||
* @return True if the registration was successful (the type is not already registered), false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerBlockStateModifierBuilder(String type, BlockStateModifierBuilder builder) {
|
||||
if (stateBuilderMap.containsKey(type)) return false;
|
||||
@@ -131,6 +174,28 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a BlockDataModifierBuilder with the specified type.
|
||||
*
|
||||
* @param type The type of the BlockDataModifierBuilder to unregister.
|
||||
* @return True if the BlockDataModifierBuilder was successfully unregistered, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterBlockDataModifierBuilder(String type) {
|
||||
return dataBuilderMap.remove(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a BlockStateModifierBuilder with the specified type.
|
||||
*
|
||||
* @param type The type of the BlockStateModifierBuilder to unregister.
|
||||
* @return True if the BlockStateModifierBuilder was successfully unregistered, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterBlockStateModifierBuilder(String type) {
|
||||
return stateBuilderMap.remove(type) != null;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
this.loadConfig();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
@@ -162,6 +227,10 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
this.blockLibraryMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration files from the plugin's data folder and processes them.
|
||||
* Configuration files are organized by type (e.g., "block").
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void loadConfig() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
@@ -187,10 +256,17 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration data from a single YAML file and processes it to create BlockConfig instances.
|
||||
*
|
||||
* @param file The YAML file to load and process.
|
||||
*/
|
||||
private void loadSingleFile(File file) {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection section) {
|
||||
|
||||
// Check if the "block" is null and log a warning if so.
|
||||
String blockID = section.getString("block");
|
||||
if (blockID == null) {
|
||||
LogUtils.warn("Block can't be null. File:" + file.getAbsolutePath() + "; Section:" + section.getCurrentPath());
|
||||
@@ -198,6 +274,8 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
List<BlockDataModifier> dataModifiers = new ArrayList<>();
|
||||
List<BlockStateModifier> stateModifiers = new ArrayList<>();
|
||||
|
||||
// If a "properties" section exists, process its entries.
|
||||
ConfigurationSection property = section.getConfigurationSection("properties");
|
||||
if (property != null) {
|
||||
for (Map.Entry<String, Object> innerEntry : property.getValues(false).entrySet()) {
|
||||
@@ -212,6 +290,8 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a BlockConfig instance with the processed data and add it to the blockConfigMap.
|
||||
BlockConfig blockConfig = new BlockConfig.Builder()
|
||||
.blockID(blockID)
|
||||
.persist(false)
|
||||
@@ -225,6 +305,15 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Summons a falling block at a specified location based on the provided loot.
|
||||
* This method spawns a falling block at the given hookLocation with specific properties determined by the loot.
|
||||
*
|
||||
* @param player The player who triggered the action.
|
||||
* @param hookLocation The location where the hook is positioned.
|
||||
* @param playerLocation The location of the player.
|
||||
* @param loot The loot to be associated with the summoned block.
|
||||
*/
|
||||
@Override
|
||||
public void summonBlock(Player player, Location hookLocation, Location playerLocation, Loot loot) {
|
||||
BlockConfig config = blockConfigMap.get(loot.getID());
|
||||
@@ -253,8 +342,17 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
fallingBlock.setVelocity(vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block ID associated with a given Block instance using block detection order.
|
||||
* This method iterates through the configured block detection order to find the block's ID
|
||||
* by checking different BlockLibrary instances in the specified order.
|
||||
*
|
||||
* @param block The Block instance for which to retrieve the block ID.
|
||||
* @return The block ID
|
||||
*/
|
||||
@Override
|
||||
public String getAnyBlockID(Block block) {
|
||||
@NotNull
|
||||
public String getAnyPluginBlockID(Block block) {
|
||||
for (String plugin : CFConfig.blockDetectOrder) {
|
||||
BlockLibrary blockLibrary = blockLibraryMap.get(plugin);
|
||||
if (blockLibrary != null) {
|
||||
@@ -264,8 +362,8 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
}
|
||||
}
|
||||
// should not reach this because vanilla library would always work
|
||||
return null;
|
||||
// Should not reach this because vanilla library would always work
|
||||
return "AIR";
|
||||
}
|
||||
|
||||
private void registerDirectional() {
|
||||
@@ -389,6 +487,13 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets items in the BLOCK's inventory based on chance and configuration.
|
||||
*
|
||||
* @param tempChanceList A list of tuples containing chance, item ID, and quantity range for each item.
|
||||
* @param player The inventory items are being set.
|
||||
* @param inventory The inventory where the items will be placed.
|
||||
*/
|
||||
private void setInventoryItems(
|
||||
ArrayList<Tuple<Double, String, Pair<Integer, Integer>>> tempChanceList,
|
||||
Player player,
|
||||
@@ -400,11 +505,11 @@ public class BlockManagerImpl implements BlockManager, Listener {
|
||||
}
|
||||
Collections.shuffle(unused);
|
||||
for (Tuple<Double, String, Pair<Integer, Integer>> tuple : tempChanceList) {
|
||||
ItemStack itemStack = plugin.getItemManager().buildAnyItemByID(player, tuple.getMid());
|
||||
ItemStack itemStack = plugin.getItemManager().buildAnyPluginItemByID(player, tuple.getMid());
|
||||
itemStack.setAmount(ThreadLocalRandom.current().nextInt(tuple.getRight().left(), tuple.getRight().right() + 1));
|
||||
if (tuple.getLeft() > Math.random()) {
|
||||
inventory.setItem(unused.pop(), itemStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,12 @@ import net.momirealms.customfishing.setting.CFLocale;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -62,13 +64,19 @@ public class Competition implements FishingCompetition {
|
||||
if (CFConfig.redisRanking) this.ranking = new RedisRankingImpl();
|
||||
else this.ranking = new LocalRankingImpl();
|
||||
this.publicPlaceholders = new ConcurrentHashMap<>();
|
||||
this.publicPlaceholders.put("{goal}", CustomFishingPlugin.get().getCompetitionManager().getCompetitionLocale(goal));
|
||||
this.publicPlaceholders.put("{goal}", CustomFishingPlugin.get().getCompetitionManager().getCompetitionGoalLocale(goal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the fishing competition, initializing its settings and actions.
|
||||
* This method sets the initial progress, remaining time, start time, and updates public placeholders.
|
||||
* It also arranges timer tasks for competition timing and initializes boss bar and action bar managers if configured.
|
||||
* Additionally, it triggers the start actions defined in the competition's configuration.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
this.progress = 1;
|
||||
this.remainingTime = config.getDuration();
|
||||
this.remainingTime = config.getDurationInSeconds();
|
||||
this.startTime = Instant.now().getEpochSecond();
|
||||
this.updatePublicPlaceholders();
|
||||
|
||||
@@ -92,6 +100,11 @@ public class Competition implements FishingCompetition {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arranges the timer task for the fishing competition.
|
||||
* This method schedules a recurring task that updates the competition's remaining time and public placeholders.
|
||||
* If the remaining time reaches zero, the competition is ended.
|
||||
*/
|
||||
private void arrangeTimerTask() {
|
||||
this.competitionTimerTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> {
|
||||
if (decreaseTime()) {
|
||||
@@ -102,6 +115,12 @@ public class Competition implements FishingCompetition {
|
||||
}, 1, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update public placeholders for the fishing competition.
|
||||
* This method updates placeholders representing player rankings, remaining time, and score in public messages.
|
||||
* Placeholders for player rankings include {1_player}, {1_score}, {2_player}, {2_score}, and so on.
|
||||
* The placeholders for time include {hour}, {minute}, {second}, and {seconds}.
|
||||
*/
|
||||
private void updatePublicPlaceholders() {
|
||||
for (int i = 1; i < CFConfig.placeholderLimit + 1; i++) {
|
||||
int finalI = i;
|
||||
@@ -119,6 +138,11 @@ public class Competition implements FishingCompetition {
|
||||
publicPlaceholders.put("{seconds}", String.valueOf(remainingTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the fishing competition.
|
||||
* This method cancels the competition timer task, unloads boss bars and action bars, clears the ranking,
|
||||
* and sets the remaining time to zero.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel();
|
||||
@@ -128,6 +152,11 @@ public class Competition implements FishingCompetition {
|
||||
this.remainingTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the fishing competition.
|
||||
* This method marks the competition as ended, cancels sub-tasks such as timers and bar management,
|
||||
* gives prizes to top participants and participation rewards, performs end actions, and clears the ranking.
|
||||
*/
|
||||
@Override
|
||||
public void end() {
|
||||
// mark it as ended
|
||||
@@ -182,19 +211,36 @@ public class Competition implements FishingCompetition {
|
||||
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(this.ranking::clear, 1500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the fishing competition is ongoing.
|
||||
*
|
||||
* @return {@code true} if the competition is still ongoing, {@code false} if it has ended.
|
||||
*/
|
||||
@Override
|
||||
public boolean isOnGoing() {
|
||||
return remainingTime > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the remaining time for the fishing competition and updates the progress.
|
||||
*
|
||||
* @return {@code true} if the remaining time becomes zero or less, indicating the competition has ended.
|
||||
*/
|
||||
private boolean decreaseTime() {
|
||||
long current = Instant.now().getEpochSecond();
|
||||
int duration = config.getDuration();
|
||||
int duration = config.getDurationInSeconds();
|
||||
remainingTime = duration - (current - startTime);
|
||||
progress = (float) remainingTime / duration;
|
||||
return remainingTime <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the data for a player in the fishing competition, including updating their score and triggering
|
||||
* actions if it's their first time joining the competition.
|
||||
*
|
||||
* @param player The player whose data needs to be refreshed.
|
||||
* @param score The player's current score in the competition.
|
||||
*/
|
||||
@Override
|
||||
public void refreshData(Player player, double score) {
|
||||
// if player join for the first time, trigger join actions
|
||||
@@ -224,46 +270,97 @@ public class Competition implements FishingCompetition {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player has joined the fishing competition based on their name.
|
||||
*
|
||||
* @param player The player to check for participation.
|
||||
* @return {@code true} if the player has joined the competition; {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPlayerJoined(OfflinePlayer player) {
|
||||
return ranking.getPlayerRank(player.getName()) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the progress of the fishing competition as a float value (0~1).
|
||||
*
|
||||
* @return The progress of the fishing competition as a float.
|
||||
*/
|
||||
@Override
|
||||
public float getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the remaining time in seconds for the fishing competition.
|
||||
*
|
||||
* @return The remaining time in seconds.
|
||||
*/
|
||||
@Override
|
||||
public long getRemainingTime() {
|
||||
return remainingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start time of the fishing competition.
|
||||
*
|
||||
* @return The start time of the fishing competition.
|
||||
*/
|
||||
@Override
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration of the fishing competition.
|
||||
*
|
||||
* @return The configuration of the fishing competition.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public CompetitionConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the goal of the fishing competition.
|
||||
*
|
||||
* @return The goal of the fishing competition.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public CompetitionGoal getGoal() {
|
||||
return goal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ranking data for the fishing competition.
|
||||
*
|
||||
* @return The ranking data for the fishing competition.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Ranking getRanking() {
|
||||
return ranking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached placeholders for the fishing competition.
|
||||
*
|
||||
* @return A ConcurrentHashMap containing cached placeholders.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public ConcurrentHashMap<String, String> getCachedPlaceholders() {
|
||||
public Map<String, String> getCachedPlaceholders() {
|
||||
return publicPlaceholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific cached placeholder value by its key.
|
||||
*
|
||||
* @param papi The key of the cached placeholder.
|
||||
* @return The cached placeholder value as a string, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
public String getCachedPlaceholder(String papi) {
|
||||
return publicPlaceholders.get(papi);
|
||||
|
||||
@@ -25,7 +25,6 @@ import net.momirealms.customfishing.api.mechanic.competition.*;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
import net.momirealms.customfishing.api.scheduler.CancellableTask;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.setting.CFConfig;
|
||||
import net.momirealms.customfishing.setting.CFLocale;
|
||||
import net.momirealms.customfishing.storage.method.database.nosql.RedisManager;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -33,12 +32,12 @@ import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CompetitionManagerImpl implements CompetitionManager {
|
||||
@@ -84,8 +83,14 @@ public class CompetitionManagerImpl implements CompetitionManager {
|
||||
currentCompetition.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of all competition names.
|
||||
*
|
||||
* @return A set of competition names.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<String> getAllCompetitions() {
|
||||
public Set<String> getAllCompetitionKeys() {
|
||||
return commandConfigMap.keySet();
|
||||
}
|
||||
|
||||
@@ -202,8 +207,15 @@ public class CompetitionManagerImpl implements CompetitionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the localization key for a given competition goal.
|
||||
*
|
||||
* @param goal The competition goal to retrieve the localization key for.
|
||||
* @return The localization key for the specified competition goal.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCompetitionLocale(CompetitionGoal goal) {
|
||||
public String getCompetitionGoalLocale(CompetitionGoal goal) {
|
||||
switch (goal) {
|
||||
case MAX_SIZE -> {
|
||||
return CFLocale.MSG_Max_Size;
|
||||
@@ -221,16 +233,29 @@ public class CompetitionManagerImpl implements CompetitionManager {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a competition with the specified name, allowing for the option to force start it or apply it to the entire server.
|
||||
*
|
||||
* @param competition The name of the competition to start.
|
||||
* @param force Whether to force start the competition even if amount of the online players is lower than the requirement
|
||||
* @param allServer Whether to apply the competition to the servers that connected to Redis.
|
||||
* @return {@code true} if the competition was started successfully, {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void startCompetition(String competition, boolean force, boolean allServer) {
|
||||
public boolean startCompetition(String competition, boolean force, boolean allServer) {
|
||||
CompetitionConfig config = commandConfigMap.get(competition);
|
||||
if (config == null) {
|
||||
LogUtils.warn("Competition " + competition + " doesn't exist.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
startCompetition(config, force, allServer);
|
||||
return startCompetition(config, force, allServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ongoing fishing competition, if one is currently in progress.
|
||||
*
|
||||
* @return The ongoing fishing competition, or null if there is none.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public FishingCompetition getOnGoingCompetition() {
|
||||
@@ -238,26 +263,36 @@ public class CompetitionManagerImpl implements CompetitionManager {
|
||||
return currentCompetition.isOnGoing() ? currentCompetition : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a competition using the specified configuration.
|
||||
*
|
||||
* @param config The configuration of the competition to start.
|
||||
* @param force Whether to force the start of the competition.
|
||||
* @param allServer Whether the competition should start across all servers in the network.
|
||||
* @return True if the competition was started successfully, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void startCompetition(CompetitionConfig config, boolean force, boolean allServer) {
|
||||
if (!force)
|
||||
this.getPlayerCount().thenAccept(count -> {
|
||||
if (count < config.getMinPlayers()) {
|
||||
var actions = config.getSkipActions();
|
||||
if (actions != null)
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
for (Action action : actions) {
|
||||
action.trigger(new Condition(player));
|
||||
}
|
||||
public boolean startCompetition(CompetitionConfig config, boolean force, boolean allServer) {
|
||||
if (!force) {
|
||||
int players = Bukkit.getOnlinePlayers().size();
|
||||
if (players < config.getMinPlayersToStart()) {
|
||||
var actions = config.getSkipActions();
|
||||
if (actions != null)
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
for (Action action : actions) {
|
||||
action.trigger(new Condition(player));
|
||||
}
|
||||
return;
|
||||
}
|
||||
start(config);
|
||||
});
|
||||
else if (!allServer) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
start(config);
|
||||
return true;
|
||||
} else if (!allServer) {
|
||||
start(config);
|
||||
return true;
|
||||
} else {
|
||||
RedisManager.getInstance().sendRedisMessage("cf_competition", "start;" + config.getKey());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,20 +309,22 @@ public class CompetitionManagerImpl implements CompetitionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of seconds until the next competition.
|
||||
*
|
||||
* @return The number of seconds until the next competition.
|
||||
*/
|
||||
@Override
|
||||
public int getNextCompetitionSeconds() {
|
||||
return nextCompetitionSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getPlayerCount() {
|
||||
if (!CFConfig.redisRanking) {
|
||||
return CompletableFuture.completedFuture(Bukkit.getOnlinePlayers().size());
|
||||
} else {
|
||||
return plugin.getStorageManager().getRedisPlayerCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configuration for a competition based on its key.
|
||||
*
|
||||
* @param key The key of the competition configuration to retrieve.
|
||||
* @return The {@link CompetitionConfig} for the specified key, or {@code null} if no configuration exists with that key.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public CompetitionConfig getConfig(String key) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.common.Key;
|
||||
import net.momirealms.customfishing.api.common.Pair;
|
||||
import net.momirealms.customfishing.api.manager.EffectManager;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.Effect;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectModifier;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.FishingEffect;
|
||||
@@ -31,6 +30,7 @@ import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.util.ConfigUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -51,24 +51,61 @@ public class EffectManagerImpl implements EffectManager {
|
||||
this.effectMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an EffectCarrier with a unique Key.
|
||||
*
|
||||
* @param key The unique Key associated with the EffectCarrier.
|
||||
* @param effect The EffectCarrier to be registered.
|
||||
* @return True if the registration was successful, false if the Key already exists.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerEffectItem(Key key, EffectCarrier effect) {
|
||||
public boolean registerEffectCarrier(Key key, EffectCarrier effect) {
|
||||
if (effectMap.containsKey(key)) return false;
|
||||
this.effectMap.put(key, effect);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an EffectCarrier associated with the specified Key.
|
||||
*
|
||||
* @param key The unique Key of the EffectCarrier to unregister.
|
||||
* @return True if the EffectCarrier was successfully unregistered, false if the Key does not exist.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterEffectItem(Key key) {
|
||||
public boolean unregisterEffectCarrier(Key key) {
|
||||
return this.effectMap.remove(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an EffectCarrier with the specified namespace and id exists.
|
||||
*
|
||||
* @param namespace The namespace of the EffectCarrier.
|
||||
* @param id The unique identifier of the EffectCarrier.
|
||||
* @return True if an EffectCarrier with the given namespace and id exists, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasEffectCarrier(String namespace, String id) {
|
||||
return effectMap.containsKey(Key.of(namespace, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an EffectCarrier with the specified namespace and id.
|
||||
*
|
||||
* @param namespace The namespace of the EffectCarrier.
|
||||
* @param id The unique identifier of the EffectCarrier.
|
||||
* @return The EffectCarrier with the given namespace and id, or null if it doesn't exist.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public EffectCarrier getEffect(String namespace, String id) {
|
||||
public EffectCarrier getEffectCarrier(String namespace, String id) {
|
||||
return effectMap.get(Key.of(namespace, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads EffectCarrier configurations from YAML files in different content folders.
|
||||
* EffectCarrier configurations are organized by type (rod, bait, enchant, util, totem, hook) in separate folders.
|
||||
* Each YAML file within these folders is processed to populate the effectMap.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public void load() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
@@ -94,20 +131,35 @@ public class EffectManagerImpl implements EffectManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads EffectCarrier configurations from a YAML file and populates the effectMap.
|
||||
*
|
||||
* @param file The YAML file to load configurations from.
|
||||
* @param namespace The namespace to use when creating keys for EffectCarriers.
|
||||
*/
|
||||
private void loadSingleFile(File file, String namespace) {
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
|
||||
String value = entry.getKey();
|
||||
if (entry.getValue() instanceof ConfigurationSection section) {
|
||||
Key key = Key.of(namespace, value);
|
||||
EffectCarrier item = getEffectItemFromSection(key, section);
|
||||
EffectCarrier item = getEffectCarrierFromSection(key, section);
|
||||
if (item != null)
|
||||
effectMap.put(key, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EffectCarrier getEffectItemFromSection(Key key, ConfigurationSection section) {
|
||||
/**
|
||||
* Parses a ConfigurationSection to create an EffectCarrier based on the specified key and configuration.
|
||||
*
|
||||
* @param key The key that uniquely identifies the EffectCarrier.
|
||||
* @param section The ConfigurationSection containing the EffectCarrier configuration.
|
||||
* @return An EffectCarrier instance based on the key and configuration, or null if the section is null.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public EffectCarrier getEffectCarrierFromSection(Key key, ConfigurationSection section) {
|
||||
if (section == null) return null;
|
||||
return new EffectCarrier.Builder()
|
||||
.key(key)
|
||||
@@ -117,23 +169,6 @@ public class EffectManagerImpl implements EffectManager {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Effect getEffectFromSection(ConfigurationSection section) {
|
||||
if (section == null) return getInitialEffect();
|
||||
return new FishingEffect.Builder()
|
||||
.addWeightModifier(ConfigUtils.getModifiers(section.getStringList("weight-single")))
|
||||
.addWeightModifier(getGroupModifiers(section.getStringList("weight-group")))
|
||||
.addWeightModifierIgnored(ConfigUtils.getModifiers(section.getStringList("weight-single-ignore-condition")))
|
||||
.addWeightModifierIgnored(getGroupModifiers(section.getStringList("weight-group-ignore-condition")))
|
||||
.timeModifier(section.getDouble("hook-time", 1))
|
||||
.difficultyModifier(section.getDouble("difficulty", 0))
|
||||
.multipleLootChance(section.getDouble("multiple-loot", 0))
|
||||
.lavaFishing(section.getBoolean("lava-fishing", false))
|
||||
.scoreMultiplier(section.getDouble("score-bonus", 1))
|
||||
.sizeMultiplier(section.getDouble("size-bonus", 1))
|
||||
.gameTimeModifier(section.getDouble("game-time", 0))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
HashMap<Key, EffectCarrier> temp = new HashMap<>(effectMap);
|
||||
effectMap.clear();
|
||||
@@ -144,11 +179,23 @@ public class EffectManagerImpl implements EffectManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the initial FishingEffect that represents no special effects.
|
||||
*
|
||||
* @return The initial FishingEffect.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public FishingEffect getInitialEffect() {
|
||||
return new FishingEffect.Builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of modifiers based on specified loot groups.
|
||||
*
|
||||
* @param modList A list of strings containing group modifiers in the format "group:modifier".
|
||||
* @return A list of pairs where each pair represents a loot item and its associated modifier.
|
||||
*/
|
||||
private List<Pair<String, WeightModifier>> getGroupModifiers(List<String> modList) {
|
||||
List<Pair<String, WeightModifier>> result = new ArrayList<>();
|
||||
for (String group : modList) {
|
||||
@@ -166,6 +213,14 @@ public class EffectManagerImpl implements EffectManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a ConfigurationSection to retrieve an array of EffectModifiers.
|
||||
*
|
||||
* @param section The ConfigurationSection to parse.
|
||||
* @return An array of EffectModifiers based on the values found in the section.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public EffectModifier[] getEffectModifiers(ConfigurationSection section) {
|
||||
if (section == null) return new EffectModifier[0];
|
||||
ArrayList<EffectModifier> modifiers = new ArrayList<>();
|
||||
@@ -179,6 +234,14 @@ public class EffectManagerImpl implements EffectManager {
|
||||
return modifiers.toArray(new EffectModifier[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a ConfigurationSection to create an EffectModifier based on the specified type and configuration.
|
||||
*
|
||||
* @param section The ConfigurationSection containing the effect modifier configuration.
|
||||
* @return An EffectModifier instance based on the type and configuration.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public EffectModifier getEffectModifier(ConfigurationSection section) {
|
||||
String type = section.getString("type");
|
||||
if (type == null) return null;
|
||||
|
||||
@@ -60,6 +60,12 @@ public class EntityManagerImpl implements EntityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity library for use in the plugin.
|
||||
*
|
||||
* @param entityLibrary The entity library to register.
|
||||
* @return {@code true} if the entity library was successfully registered, {@code false} if it already exists.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerEntityLibrary(EntityLibrary entityLibrary) {
|
||||
if (entityLibraryMap.containsKey(entityLibrary.identification())) return false;
|
||||
@@ -67,14 +73,15 @@ public class EntityManagerImpl implements EntityManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an entity library by its identification key.
|
||||
*
|
||||
* @param identification The identification key of the entity library to unregister.
|
||||
* @return {@code true} if the entity library was successfully unregistered, {@code false} if it does not exist.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterEntityLibrary(String lib) {
|
||||
return entityLibraryMap.remove(lib) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterEntityLibrary(EntityLibrary entityLibrary) {
|
||||
return unregisterEntityLibrary(entityLibrary.identification());
|
||||
public boolean unregisterEntityLibrary(String identification) {
|
||||
return entityLibraryMap.remove(identification) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@@ -134,6 +141,13 @@ public class EntityManagerImpl implements EntityManager {
|
||||
this.entityLibraryMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Summons an entity based on the given loot configuration to a specified location.
|
||||
*
|
||||
* @param hookLocation The location where the entity will be summoned, typically where the fishing hook is.
|
||||
* @param playerLocation The location of the player who triggered the entity summoning.
|
||||
* @param loot The loot configuration that defines the entity to be summoned.
|
||||
*/
|
||||
@Override
|
||||
public void summonEntity(Location hookLocation, Location playerLocation, Loot loot) {
|
||||
EntityConfig config = entityConfigMap.get(loot.getID());
|
||||
|
||||
@@ -54,7 +54,7 @@ public class BaitAnimationTask implements Runnable {
|
||||
) {
|
||||
cancelAnimation();
|
||||
} else {
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getVelocity(entityID, fishHook.getVelocity()));
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getVelocityPacket(entityID, fishHook.getVelocity()));
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getTpPacket(entityID, fishHook.getLocation()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import net.momirealms.customfishing.api.mechanic.game.GameSettings;
|
||||
import net.momirealms.customfishing.api.mechanic.game.GamingPlayer;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Loot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.LootType;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.WeightModifier;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.api.util.WeightUtils;
|
||||
import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl;
|
||||
@@ -55,7 +54,6 @@ import org.bukkit.event.*;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
@@ -142,7 +140,9 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
this.removeHook(event.getPlayer().getUniqueId());
|
||||
this.removeTempFishingState(player);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -189,6 +189,12 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a fishing hook entity associated with a given UUID.
|
||||
*
|
||||
* @param uuid The UUID of the fishing hook entity to be removed.
|
||||
* @return {@code true} if the fishing hook was successfully removed, {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean removeHook(UUID uuid) {
|
||||
FishHook hook = hookCacheMap.remove(uuid);
|
||||
@@ -200,11 +206,32 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a FishHook object associated with the provided player's UUID
|
||||
*
|
||||
* @param uuid The UUID of the player
|
||||
* @return fishhook entity, null if not exists
|
||||
*/
|
||||
@Override
|
||||
public Optional<FishHook> getHook(UUID uuid) {
|
||||
return Optional.ofNullable(hookCacheMap.get(uuid));
|
||||
@Nullable
|
||||
public FishHook getHook(UUID uuid) {
|
||||
FishHook fishHook = hookCacheMap.get(uuid);
|
||||
if (fishHook != null) {
|
||||
if (!fishHook.isValid()) {
|
||||
hookCacheMap.remove(uuid);
|
||||
return null;
|
||||
} else {
|
||||
return fishHook;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the appropriate fishing state based on the provided PlayerFishEvent and triggers the corresponding action.
|
||||
*
|
||||
* @param event The PlayerFishEvent that represents the fishing action.
|
||||
*/
|
||||
public void selectState(PlayerFishEvent event) {
|
||||
if (event.isCancelled()) return;
|
||||
switch (event.getState()) {
|
||||
@@ -229,7 +256,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
if (compound != null && compound.hasTag("max_dur")) {
|
||||
event.setCancelled(true);
|
||||
hook.remove();
|
||||
ItemUtils.loseDurability(itemStack, 2, true);
|
||||
ItemUtils.decreaseDurability(itemStack, 2, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,9 +270,8 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
return;
|
||||
}
|
||||
// Check mechanic requirements
|
||||
if (!RequirementManager.isRequirementsMet(
|
||||
RequirementManagerImpl.mechanicRequirements,
|
||||
fishingPreparation
|
||||
if (!RequirementManager.isRequirementMet(
|
||||
fishingPreparation, RequirementManagerImpl.mechanicRequirements
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
@@ -330,7 +356,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
if (nbtCompound != null && nbtCompound.hasTag("max_dur")) {
|
||||
event.getHook().remove();
|
||||
event.setCancelled(true);
|
||||
ItemUtils.loseDurability(itemStack, 5, true);
|
||||
ItemUtils.decreaseDurability(itemStack, 5, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,11 +470,21 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the temporary fishing state associated with a player.
|
||||
*
|
||||
* @param player The player whose temporary fishing state should be removed.
|
||||
*/
|
||||
@Override
|
||||
public void removeTempFishingState(Player player) {
|
||||
this.tempFishingStateMap.remove(player.getUniqueId());
|
||||
public TempFishingState removeTempFishingState(Player player) {
|
||||
return this.tempFishingStateMap.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the game result for a gaming player
|
||||
*
|
||||
* @param gamingPlayer The gaming player whose game result should be processed.
|
||||
*/
|
||||
@Override
|
||||
public void processGameResult(GamingPlayer gamingPlayer) {
|
||||
final Player player = gamingPlayer.getPlayer();
|
||||
@@ -458,7 +494,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
LogUtils.warn("Unexpected situation: Can't get player's fish hook when processing game results.");
|
||||
return;
|
||||
}
|
||||
TempFishingState tempFishingState = tempFishingStateMap.remove(uuid);
|
||||
TempFishingState tempFishingState = removeTempFishingState(player);
|
||||
if (tempFishingState == null) {
|
||||
LogUtils.warn("Unexpected situation: Can't get player's fishing state when processing game results.");
|
||||
return;
|
||||
@@ -479,8 +515,8 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
if (damageEvent.isCancelled()) {
|
||||
break outer;
|
||||
}
|
||||
ItemUtils.reduceHookDurability(rod, false);
|
||||
ItemUtils.loseDurability(rod, 1, true);
|
||||
ItemUtils.decreaseHookDurability(rod, 1, false);
|
||||
ItemUtils.decreaseDurability(rod, 1, true);
|
||||
}
|
||||
|
||||
fishHook.remove();
|
||||
@@ -520,7 +556,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
loot.triggerActions(ActionTrigger.FAILURE, fishingPreparation);
|
||||
fishingPreparation.triggerActions(ActionTrigger.FAILURE);
|
||||
|
||||
ItemUtils.reduceHookDurability(fishingPreparation.getRodItemStack(), true);
|
||||
ItemUtils.decreaseHookDurability(fishingPreparation.getRodItemStack(), 1, true);
|
||||
}
|
||||
|
||||
public void success(TempFishingState state, FishHook hook) {
|
||||
@@ -565,7 +601,7 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
plugin.getItemManager().dropItem(player, hook.getLocation(), player.getLocation(), loot, fishingPreparation.getArgs());
|
||||
plugin.getItemManager().dropItem(player, hook.getLocation(), player.getLocation(), loot.getID(), fishingPreparation.getArgs());
|
||||
doSuccessActions(loot, effect, fishingPreparation, player);
|
||||
}
|
||||
}
|
||||
@@ -629,67 +665,56 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
).ifPresent(it -> it.addLootAmount(loot, fishingPreparation, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPossibleLootKeys (Condition condition) {
|
||||
return plugin.getRequirementManager().getLootWithWeight(condition).keySet();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Double> getPossibleLootKeysWithWeight(Effect initialEffect, Condition condition) {
|
||||
Map<String, Double> lootWithWeight = plugin.getRequirementManager().getLootWithWeight(condition);
|
||||
|
||||
Player player = condition.getPlayer();
|
||||
for (Pair<String, WeightModifier> pair : initialEffect.getWeightModifier()) {
|
||||
Double previous = lootWithWeight.get(pair.left());
|
||||
if (previous != null)
|
||||
lootWithWeight.put(pair.left(), pair.right().modify(player, previous));
|
||||
}
|
||||
for (Pair<String, WeightModifier> pair : initialEffect.getWeightModifierIgnored()) {
|
||||
double previous = lootWithWeight.getOrDefault(pair.left(), 0d);
|
||||
lootWithWeight.put(pair.left(), pair.right().modify(player, previous));
|
||||
}
|
||||
return lootWithWeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Loot getNextLoot(Effect initialEffect, Condition condition) {
|
||||
String key = WeightUtils.getRandom(getPossibleLootKeysWithWeight(initialEffect, condition));
|
||||
Loot loot = plugin.getLootManager().getLoot(key);
|
||||
if (loot == null) {
|
||||
LogUtils.warn(String.format("Loot %s doesn't exist!", key));
|
||||
return null;
|
||||
}
|
||||
return loot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a fishing game for the specified player with the given condition and effect.
|
||||
*
|
||||
* @param player The player starting the fishing game.
|
||||
* @param condition The condition used to determine the game.
|
||||
* @param effect The effect applied to the game.
|
||||
*/
|
||||
@Override
|
||||
public void startFishingGame(Player player, Condition condition, Effect effect) {
|
||||
Map<String, Double> gameWithWeight = plugin.getRequirementManager().getGameWithWeight(condition);
|
||||
Map<String, Double> gameWithWeight = plugin.getGameManager().getGameWithWeight(condition);
|
||||
plugin.debug(gameWithWeight.toString());
|
||||
String random = WeightUtils.getRandom(gameWithWeight);
|
||||
Optional<Pair<BasicGameConfig, GameInstance>> gamePair = plugin.getGameManager().getGame(random);
|
||||
if (gamePair.isEmpty()) {
|
||||
LogUtils.warn(String.format("Game %s doesn't exist!", random));
|
||||
Pair<BasicGameConfig, GameInstance> gamePair = plugin.getGameManager().getGameInstance(random);
|
||||
if (random == null) {
|
||||
LogUtils.warn("No game is available for player:" + player.getName() + " location:" + condition.getLocation());
|
||||
return;
|
||||
}
|
||||
if (gamePair == null) {
|
||||
LogUtils.warn(String.format("Game %s doesn't exist.", random));
|
||||
return;
|
||||
}
|
||||
plugin.debug("Game: " + random);
|
||||
startFishingGame(player, Objects.requireNonNull(gamePair.get().left().getGameSetting(effect)), gamePair.get().right());
|
||||
startFishingGame(player, Objects.requireNonNull(gamePair.left().getGameSetting(effect)), gamePair.right());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a fishing game for the specified player with the given settings and game instance.
|
||||
*
|
||||
* @param player The player starting the fishing game.
|
||||
* @param settings The game settings for the fishing game.
|
||||
* @param gameInstance The instance of the fishing game to start.
|
||||
*/
|
||||
@Override
|
||||
public void startFishingGame(Player player, GameSettings settings, GameInstance gameInstance) {
|
||||
plugin.debug("Difficulty:" + settings.getDifficulty());
|
||||
plugin.debug("Time:" + settings.getTime());
|
||||
Optional<FishHook> hook = getHook(player.getUniqueId());
|
||||
if (hook.isPresent()) {
|
||||
this.gamingPlayerMap.put(player.getUniqueId(), gameInstance.start(player, hook.get(), settings));
|
||||
FishHook hook = getHook(player.getUniqueId());
|
||||
if (hook != null) {
|
||||
this.gamingPlayerMap.put(player.getUniqueId(), gameInstance.start(player, hook, settings));
|
||||
} else {
|
||||
LogUtils.warn("It seems that player " + player.getName() + " is not fishing. Fishing game failed to start.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player with the given UUID has cast their fishing hook.
|
||||
*
|
||||
* @param uuid The UUID of the player to check.
|
||||
* @return {@code true} if the player has cast their fishing hook, {@code false} otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasPlayerCastHook(UUID uuid) {
|
||||
FishHook fishHook = hookCacheMap.get(uuid);
|
||||
@@ -701,13 +726,42 @@ public class FishingManagerImpl implements Listener, FishingManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the temporary fishing state for a player.
|
||||
*
|
||||
* @param player The player for whom to set the temporary fishing state.
|
||||
* @param tempFishingState The temporary fishing state to set for the player.
|
||||
*/
|
||||
@Override
|
||||
public void setTempFishingState(Player player, TempFishingState tempFishingState) {
|
||||
tempFishingStateMap.put(player.getUniqueId(), tempFishingState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHookCheckTask(Player player) {
|
||||
hookCheckMap.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link GamingPlayer} object associated with the given UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return The {@link GamingPlayer} object if found, or {@code null} if not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public GamingPlayer getGamingPlayer(UUID uuid) {
|
||||
return gamingPlayerMap.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link TempFishingState} object associated with the given UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return The {@link TempFishingState} object if found, or {@code null} if not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public TempFishingState getTempFishingState(UUID uuid) {
|
||||
return tempFishingStateMap.get(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class HookCheckTimerTask implements Runnable {
|
||||
}
|
||||
|
||||
private void setTempState() {
|
||||
Loot nextLoot = manager.getNextLoot(initialEffect, fishingPreparation);
|
||||
Loot nextLoot = CustomFishingPlugin.get().getLootManager().getNextLoot(initialEffect, fishingPreparation);
|
||||
if (nextLoot == null)
|
||||
return;
|
||||
this.loot = nextLoot;
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.momirealms.customfishing.api.mechanic.game.*;
|
||||
import net.momirealms.customfishing.api.util.FontUtils;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.api.util.OffsetUtils;
|
||||
import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl;
|
||||
import net.momirealms.customfishing.util.ClassUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
@@ -42,13 +43,13 @@ public class GameManagerImpl implements GameManager {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
private final HashMap<String, GameFactory> gameCreatorMap;
|
||||
private final HashMap<String, Pair<BasicGameConfig, GameInstance>> gameMap;
|
||||
private final HashMap<String, Pair<BasicGameConfig, GameInstance>> gameInstanceMap;
|
||||
private final String EXPANSION_FOLDER = "expansions/minigame";
|
||||
|
||||
public GameManagerImpl(CustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.gameCreatorMap = new HashMap<>();
|
||||
this.gameMap = new HashMap<>();
|
||||
this.gameInstanceMap = new HashMap<>();
|
||||
this.registerInbuiltGames();
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ public class GameManagerImpl implements GameManager {
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
this.gameMap.clear();
|
||||
this.gameInstanceMap.clear();
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
@@ -72,6 +73,13 @@ public class GameManagerImpl implements GameManager {
|
||||
this.gameCreatorMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new game type with the specified type identifier.
|
||||
*
|
||||
* @param type The type identifier for the game.
|
||||
* @param gameFactory The {@link GameFactory} that creates instances of the game.
|
||||
* @return {@code true} if the registration was successful, {@code false} if the type identifier is already registered.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerGameType(String type, GameFactory gameFactory) {
|
||||
if (gameCreatorMap.containsKey(type))
|
||||
@@ -81,27 +89,56 @@ public class GameManagerImpl implements GameManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a game type with the specified type identifier.
|
||||
*
|
||||
* @param type The type identifier of the game to unregister.
|
||||
* @return {@code true} if the game type was successfully unregistered, {@code false} if the type identifier was not found.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterGameType(String type) {
|
||||
return gameCreatorMap.remove(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the game factory associated with the specified game type.
|
||||
*
|
||||
* @param type The type identifier of the game.
|
||||
* @return The {@code GameFactory} for the specified game type, or {@code null} if not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public GameFactory getGameFactory(String type) {
|
||||
return gameCreatorMap.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a game instance and its basic configuration associated with the specified key.
|
||||
*
|
||||
* @param key The key identifying the game instance.
|
||||
* @return An {@code Optional} containing a {@code Pair} of the basic game configuration and the game instance
|
||||
* if found, or an empty {@code Optional} if not found.
|
||||
*/
|
||||
@Override
|
||||
public Optional<Pair<BasicGameConfig, GameInstance>> getGame(String key) {
|
||||
return Optional.ofNullable(gameMap.get(key));
|
||||
public Pair<BasicGameConfig, GameInstance> getGameInstance(String key) {
|
||||
return gameInstanceMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a map of game names and their associated weights based on the specified conditions.
|
||||
*
|
||||
* @param condition The condition to evaluate game weights.
|
||||
* @return A {@code HashMap} containing game names as keys and their associated weights as values.
|
||||
*/
|
||||
@Override
|
||||
public HashMap<String, Double> getGameWithWeight(Condition condition) {
|
||||
return plugin.getRequirementManager().getGameWithWeight(condition);
|
||||
return ((RequirementManagerImpl) plugin.getRequirementManager()).getGameWithWeight(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads minigames from the plugin folder.
|
||||
* This method searches for minigame configuration files in the plugin's data folder and loads them.
|
||||
*/
|
||||
public void loadGamesFromPluginFolder() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + "minigame");
|
||||
@@ -124,11 +161,22 @@ public class GameManagerImpl implements GameManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a minigame configuration from a YAML file.
|
||||
* This method parses the YAML file and extracts minigame configurations to be used in the plugin.
|
||||
*
|
||||
* @param file The YAML file to load.
|
||||
*/
|
||||
private void loadSingleFile(File file) {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection section) {
|
||||
GameFactory creator = this.getGameFactory(section.getString("game-type"));
|
||||
if (creator == null) {
|
||||
LogUtils.warn("Game type:" + section.getString("game-type") + " doesn't exist.");
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicGameConfig.Builder basicGameBuilder = new BasicGameConfig.Builder();
|
||||
Object time = section.get("time", 15);
|
||||
if (time instanceof String str) {
|
||||
@@ -144,9 +192,7 @@ public class GameManagerImpl implements GameManager {
|
||||
} else if (difficulty instanceof Integer integer) {
|
||||
basicGameBuilder.difficulty(integer);
|
||||
}
|
||||
if (creator != null) {
|
||||
gameMap.put(entry.getKey(), Pair.of(basicGameBuilder.build(), creator.setArgs(section)));
|
||||
}
|
||||
gameInstanceMap.put(entry.getKey(), Pair.of(basicGameBuilder.build(), creator.setArgs(section)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,6 +497,9 @@ public class GameManagerImpl implements GameManager {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads minigame expansions from the expansion folder.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void loadExpansions() {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
|
||||
@@ -67,6 +67,9 @@ public class HookManagerImpl implements Listener, HookManager {
|
||||
unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration files for the specified types.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void loadConfig() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
@@ -92,6 +95,11 @@ public class HookManagerImpl implements Listener, HookManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from a single configuration file.
|
||||
*
|
||||
* @param file The configuration file to load.
|
||||
*/
|
||||
private void loadSingleFile(File file) {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
|
||||
@@ -105,13 +113,120 @@ public class HookManagerImpl implements Listener, HookManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hook setting by its ID.
|
||||
*
|
||||
* @param id The ID of the hook setting to retrieve.
|
||||
* @return The hook setting with the given ID, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public HookSetting getHookSetting(String id) {
|
||||
return hookSettingMap.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the durability of a fishing hook by a specified amount and optionally updates its lore.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack to modify.
|
||||
* @param amount The amount by which to decrease the durability.
|
||||
* @param updateLore Whether to update the lore of the fishing rod.
|
||||
*/
|
||||
@Override
|
||||
public void decreaseHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
ItemUtils.decreaseHookDurability(rod, amount, updateLore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the durability of a fishing hook by a specified amount and optionally updates its lore.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack to modify.
|
||||
* @param amount The amount by which to increase the durability.
|
||||
* @param updateLore Whether to update the lore of the fishing rod.
|
||||
*/
|
||||
@Override
|
||||
public void increaseHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
ItemUtils.increaseHookDurability(rod, amount, updateLore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the durability of a fishing hook to a specific amount and optionally updates its lore.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack to modify.
|
||||
* @param amount The new durability value to set.
|
||||
* @param updateLore Whether to update the lore of the fishing rod.
|
||||
*/
|
||||
@Override
|
||||
public void setHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
ItemUtils.setHookDurability(rod, amount, updateLore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equips a fishing hook on a fishing rod.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack.
|
||||
* @param hook The fishing hook ItemStack.
|
||||
* @return True if the hook was successfully equipped, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equipHookOnRod(ItemStack rod, ItemStack hook) {
|
||||
if (rod == null || hook == null || hook.getType() == Material.AIR || hook.getAmount() != 1)
|
||||
return false;
|
||||
if (rod.getType() != Material.FISHING_ROD)
|
||||
return false;
|
||||
|
||||
String hookID = plugin.getItemManager().getAnyPluginItemID(hook);
|
||||
HookSetting setting = getHookSetting(hookID);
|
||||
if (setting == null)
|
||||
return false;
|
||||
|
||||
NBTItem rodNBTItem = new NBTItem(rod);
|
||||
NBTCompound cfCompound = rodNBTItem.getOrCreateCompound("CustomFishing");
|
||||
|
||||
cfCompound.setString("hook_id", hookID);
|
||||
cfCompound.setItemStack("hook_item", hook);
|
||||
cfCompound.setInteger("hook_dur", ItemUtils.getDurability(hook));
|
||||
|
||||
ItemUtils.updateNBTItemLore(rodNBTItem);
|
||||
rod.setItemMeta(rodNBTItem.getItem().getItemMeta());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the fishing hook from a fishing rod.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack.
|
||||
* @return The removed fishing hook ItemStack, or null if no hook was found.
|
||||
*/
|
||||
@Override
|
||||
public ItemStack removeHookFromRod(ItemStack rod) {
|
||||
if (rod == null || rod.getType() != Material.FISHING_ROD)
|
||||
return null;
|
||||
|
||||
NBTItem rodNBTItem = new NBTItem(rod);
|
||||
NBTCompound cfCompound = rodNBTItem.getCompound("CustomFishing");
|
||||
if (cfCompound == null)
|
||||
return null;
|
||||
|
||||
ItemStack hook = cfCompound.getItemStack("hook_item");
|
||||
if (hook != null) {
|
||||
cfCompound.removeKey("hook_item");
|
||||
cfCompound.removeKey("hook_id");
|
||||
cfCompound.removeKey("hook_dur");
|
||||
ItemUtils.updateNBTItemLore(rodNBTItem);
|
||||
rod.setItemMeta(rodNBTItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when a player clicks on a fishing rod in their inventory.
|
||||
*
|
||||
* @param event The InventoryClickEvent to handle.
|
||||
*/
|
||||
@EventHandler
|
||||
@SuppressWarnings("deprecation")
|
||||
public void onDragDrop(InventoryClickEvent event) {
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
@@ -123,14 +238,12 @@ public class HookManagerImpl implements Listener, HookManager {
|
||||
return;
|
||||
if (player.getGameMode() != GameMode.SURVIVAL)
|
||||
return;
|
||||
if (plugin.getFishingManager().hasPlayerCastHook(player.getUniqueId()))
|
||||
return;
|
||||
|
||||
ItemStack cursor = event.getCursor();
|
||||
if (cursor == null || cursor.getType() == Material.AIR) {
|
||||
if (event.getClick() == ClickType.RIGHT) {
|
||||
if (plugin.getFishingManager().hasPlayerCastHook(player.getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
NBTItem nbtItem = new NBTItem(clicked);
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
if (cfCompound == null)
|
||||
@@ -150,16 +263,16 @@ public class HookManagerImpl implements Listener, HookManager {
|
||||
return;
|
||||
}
|
||||
|
||||
String hookID = plugin.getItemManager().getAnyItemID(cursor);
|
||||
String hookID = plugin.getItemManager().getAnyPluginItemID(cursor);
|
||||
HookSetting setting = getHookSetting(hookID);
|
||||
if (setting == null)
|
||||
return;
|
||||
|
||||
Condition condition = new Condition(player, new HashMap<>());
|
||||
condition.insertArg("{rod}", plugin.getItemManager().getAnyItemID(clicked));
|
||||
EffectCarrier effectCarrier = plugin.getEffectManager().getEffect("hook", hookID);
|
||||
condition.insertArg("{rod}", plugin.getItemManager().getAnyPluginItemID(clicked));
|
||||
EffectCarrier effectCarrier = plugin.getEffectManager().getEffectCarrier("hook", hookID);
|
||||
if (effectCarrier != null) {
|
||||
if (!RequirementManager.isRequirementsMet(effectCarrier.getRequirements(), condition)) {
|
||||
if (!RequirementManager.isRequirementMet(condition, effectCarrier.getRequirements())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of all item keys in the CustomFishing plugin.
|
||||
*
|
||||
* @return A set of item keys.
|
||||
*/
|
||||
@Override
|
||||
public Set<Key> getAllItemsKey() {
|
||||
return buildableItemMap.keySet();
|
||||
@@ -151,24 +156,29 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerCustomItem(String namespace, String value, BuildableItem buildableItem) {
|
||||
Key key = Key.of(namespace, value);
|
||||
if (buildableItemMap.containsKey(key)) return false;
|
||||
buildableItemMap.put(key, buildableItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterCustomItem(String namespace, String value) {
|
||||
return buildableItemMap.remove(Key.of(namespace, value)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ItemStack with a specified namespace and value for a player.
|
||||
*
|
||||
* @param player The player for whom the ItemStack is being built.
|
||||
* @param namespace The namespace of the item.
|
||||
* @param value The value of the item.
|
||||
* @return The constructed ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public ItemStack build(Player player, String namespace, String value) {
|
||||
return build(player, namespace, value, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ItemStack with a specified namespace and value, replacing placeholders,
|
||||
* for a player.
|
||||
*
|
||||
* @param player The player for whom the ItemStack is being built.
|
||||
* @param namespace The namespace of the item.
|
||||
* @param value The value of the item.
|
||||
* @param placeholders The placeholders to replace in the item's attributes.
|
||||
* @return The constructed ItemStack, or null if the item doesn't exist.
|
||||
*/
|
||||
@Override
|
||||
public ItemStack build(Player player, String namespace, String value, Map<String, String> placeholders) {
|
||||
BuildableItem buildableItem = buildableItemMap.get(Key.of(namespace, value));
|
||||
@@ -176,20 +186,42 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
return buildableItem.build(player, placeholders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ItemStack using an ItemBuilder for a player.
|
||||
*
|
||||
* @param player The player for whom the ItemStack is being built.
|
||||
* @param builder The ItemBuilder used to construct the ItemStack.
|
||||
* @return The constructed ItemStack.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack build(Player player, ItemBuilder builder) {
|
||||
return build(player, builder, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a BuildableItem by its namespace and value.
|
||||
*
|
||||
* @param namespace The namespace of the BuildableItem.
|
||||
* @param value The value of the BuildableItem.
|
||||
* @return The BuildableItem with the specified namespace and value, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public BuildableItem getBuildableItem(String namespace, String value) {
|
||||
return buildableItemMap.get(Key.of(namespace, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item ID associated with the given ItemStack by checking all available item libraries.
|
||||
* The detection order is determined by the configuration.
|
||||
*
|
||||
* @param itemStack The ItemStack to retrieve the item ID from.
|
||||
* @return The item ID or "AIR" if not found or if the ItemStack is null or empty.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String getAnyItemID(ItemStack itemStack) {
|
||||
public String getAnyPluginItemID(ItemStack itemStack) {
|
||||
for (String plugin : CFConfig.itemDetectOrder) {
|
||||
ItemLibrary itemLibrary = itemLibraryMap.get(plugin);
|
||||
if (itemLibrary != null) {
|
||||
@@ -200,11 +232,18 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
}
|
||||
}
|
||||
// should not reach this because vanilla library would always work
|
||||
return null;
|
||||
return "AIR";
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ItemStack for a player based on the provided item ID.
|
||||
*
|
||||
* @param player The player for whom the ItemStack is being built.
|
||||
* @param id The item ID, which may include a namespace (e.g., "namespace:id").
|
||||
* @return The constructed ItemStack or null if the ID is not valid.
|
||||
*/
|
||||
@Override
|
||||
public ItemStack buildAnyItemByID(Player player, String id) {
|
||||
public ItemStack buildAnyPluginItemByID(Player player, String id) {
|
||||
if (id.contains(":")) {
|
||||
String[] split = id.split(":", 2);
|
||||
return itemLibraryMap.get(split[0]).buildItem(player, split[1]);
|
||||
@@ -213,16 +252,28 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided ItemStack is a custom fishing item
|
||||
*
|
||||
* @param itemStack The ItemStack to check.
|
||||
* @return True if the ItemStack is a custom fishing item; otherwise, false.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCustomFishingItem(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) return false;
|
||||
NBTItem nbtItem = new NBTItem(itemStack);
|
||||
return nbtItem.hasTag("CustomFishing");
|
||||
return nbtItem.hasTag("CustomFishing") && !nbtItem.getCompound("CustomFishing").getString("id").equals("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item ID associated with the given ItemStack, if available.
|
||||
*
|
||||
* @param itemStack The ItemStack to retrieve the item ID from.
|
||||
* @return The item ID or null if not found or if the ItemStack is null or empty.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public String getItemID(ItemStack itemStack) {
|
||||
public String getCustomFishingItemID(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) return null;
|
||||
NBTItem nbtItem = new NBTItem(itemStack);
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
@@ -230,6 +281,14 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
return cfCompound.getString("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CFBuilder instance for an item configuration section
|
||||
*
|
||||
* @param section The configuration section containing item settings.
|
||||
* @param type The type of the item (e.g., "rod", "bait").
|
||||
* @param id The unique identifier for the item.
|
||||
* @return A CFBuilder instance representing the configured item, or null if the section is null.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public CFBuilder getItemBuilder(ConfigurationSection section, String type, String id) {
|
||||
@@ -265,7 +324,16 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
return itemCFBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an ItemStack using the provided ItemBuilder, player, and placeholders.
|
||||
*
|
||||
* @param player The player for whom the item is being built.
|
||||
* @param builder The ItemBuilder that defines the item's properties.
|
||||
* @param placeholders A map of placeholders and their corresponding values to be applied to the item.
|
||||
* @return The constructed ItemStack.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public ItemStack build(Player player, ItemBuilder builder, Map<String, String> placeholders) {
|
||||
ItemStack temp = itemLibraryMap.get(builder.getLibrary()).buildItem(player, builder.getId());
|
||||
temp.setAmount(builder.getAmount());
|
||||
@@ -277,6 +345,12 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
return nbtItem.getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an item library.
|
||||
*
|
||||
* @param itemLibrary The item library to register.
|
||||
* @return True if the item library was successfully registered, false if it already exists.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerItemLibrary(ItemLibrary itemLibrary) {
|
||||
if (itemLibraryMap.containsKey(itemLibrary.identification())) return false;
|
||||
@@ -284,21 +358,31 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an item library.
|
||||
*
|
||||
* @param identification The item library to unregister.
|
||||
* @return True if the item library was successfully unregistered, false if it doesn't exist.
|
||||
*/
|
||||
@Override
|
||||
public boolean unRegisterItemLibrary(ItemLibrary itemLibrary) {
|
||||
return itemLibraryMap.remove(itemLibrary.identification(), itemLibrary);
|
||||
public boolean unRegisterItemLibrary(String identification) {
|
||||
return itemLibraryMap.remove(identification) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an item based on the provided loot, applying velocity from a hook location to a player location.
|
||||
*
|
||||
* @param player The player for whom the item is intended.
|
||||
* @param hookLocation The location where the item will initially drop.
|
||||
* @param playerLocation The target location towards which the item's velocity is applied.
|
||||
* @param id The loot object representing the item to be dropped.
|
||||
* @param args A map of placeholders for item customization.
|
||||
*/
|
||||
@Override
|
||||
public boolean unRegisterItemLibrary(String itemLibrary) {
|
||||
return itemLibraryMap.remove(itemLibrary) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropItem(Player player, Location hookLocation, Location playerLocation, Loot loot, Map<String, String> args) {
|
||||
ItemStack item = build(player, "item", loot.getID(), args);
|
||||
public void dropItem(Player player, Location hookLocation, Location playerLocation, String id, Map<String, String> args) {
|
||||
ItemStack item = build(player, "item", id, args);
|
||||
if (item == null) {
|
||||
LogUtils.warn(String.format("Item %s not exists", loot.getID()));
|
||||
LogUtils.warn(String.format("Item %s not exists", id));
|
||||
return;
|
||||
}
|
||||
if (item.getType() == Material.AIR) {
|
||||
@@ -310,6 +394,13 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
itemEntity.setVelocity(vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an item entity at the specified location and applies velocity towards another location.
|
||||
*
|
||||
* @param hookLocation The location where the item will initially drop.
|
||||
* @param playerLocation The target location towards which the item's velocity is applied.
|
||||
* @param itemStack The item stack to be dropped as an entity.
|
||||
*/
|
||||
@Override
|
||||
public void dropItem(Location hookLocation, Location playerLocation, ItemStack itemStack) {
|
||||
Entity itemEntity = hookLocation.getWorld().dropItem(hookLocation, itemStack);
|
||||
@@ -318,6 +409,42 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
itemEntity.setVelocity(vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the durability of an ItemStack by a specified amount and optionally updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to modify.
|
||||
* @param amount The amount by which to decrease the durability.
|
||||
* @param updateLore Whether to update the lore of the ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public void decreaseDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
ItemUtils.decreaseDurability(itemStack, amount, updateLore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the durability of an ItemStack by a specified amount and optionally updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to modify.
|
||||
* @param amount The amount by which to increase the durability.
|
||||
* @param updateLore Whether to update the lore of the ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public void increaseDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
ItemUtils.increaseDurability(itemStack, amount, updateLore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the durability of an ItemStack to a specific amount and optionally updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to modify.
|
||||
* @param amount The new durability value.
|
||||
* @param updateLore Whether to update the lore of the ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public void setDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
ItemUtils.setDurability(itemStack, amount, updateLore);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<Pair<String, Short>> getEnchantmentPair(ConfigurationSection section) {
|
||||
List<Pair<String, Short>> list = new ArrayList<>();
|
||||
@@ -526,7 +653,7 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
placeholders.put("{bonus}", String.format("%.2f", bonus));
|
||||
}
|
||||
float size = Float.parseFloat(placeholders.getOrDefault("{size}", "0"));
|
||||
double price = CustomFishingPlugin.get().getMarketManager().getPrice(
|
||||
double price = CustomFishingPlugin.get().getMarketManager().getFishPrice(
|
||||
base,
|
||||
bonus,
|
||||
size
|
||||
@@ -665,7 +792,7 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
public void onConsumeItem(PlayerItemConsumeEvent event) {
|
||||
if (event.isCancelled()) return;
|
||||
ItemStack itemStack = event.getItem();
|
||||
String id = getAnyItemID(itemStack);
|
||||
String id = getAnyPluginItemID(itemStack);
|
||||
Loot loot = plugin.getLootManager().getLoot(id);
|
||||
if (loot != null) {
|
||||
Condition condition = new Condition(event.getPlayer());
|
||||
@@ -697,7 +824,7 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
NBTCompound compound = nbtItem.getCompound("CustomFishing");
|
||||
if (compound == null) return;
|
||||
event.setCancelled(true);
|
||||
ItemUtils.addDurability(itemStack, event.getRepairAmount(), true);
|
||||
ItemUtils.increaseDurability(itemStack, event.getRepairAmount(), true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@@ -710,12 +837,12 @@ public class ItemManagerImpl implements ItemManager, Listener {
|
||||
if (event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_AIR || event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
|
||||
String id = getAnyItemID(itemStack);
|
||||
EffectCarrier carrier = plugin.getEffectManager().getEffect("util", id);
|
||||
String id = getAnyPluginItemID(itemStack);
|
||||
EffectCarrier carrier = plugin.getEffectManager().getEffectCarrier("util", id);
|
||||
if (carrier == null)
|
||||
return;
|
||||
Condition condition = new Condition(event.getPlayer());
|
||||
if (!RequirementManager.isRequirementsMet(carrier.getRequirements(), condition))
|
||||
if (!RequirementManager.isRequirementMet(condition, carrier.getRequirements()))
|
||||
return;
|
||||
Action[] actions = carrier.getActions(ActionTrigger.INTERACT);
|
||||
if (actions != null)
|
||||
|
||||
@@ -18,16 +18,22 @@
|
||||
package net.momirealms.customfishing.mechanic.loot;
|
||||
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.common.Pair;
|
||||
import net.momirealms.customfishing.api.manager.LootManager;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.Effect;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.CFLoot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Loot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.LootType;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.WeightModifier;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.api.util.WeightUtils;
|
||||
import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl;
|
||||
import net.momirealms.customfishing.util.ConfigUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -36,7 +42,9 @@ import java.util.*;
|
||||
public class LootManagerImpl implements LootManager {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
// A map that associates loot IDs with their respective loot configurations.
|
||||
private final HashMap<String, Loot> lootMap;
|
||||
// A map that associates loot group IDs with lists of loot IDs.
|
||||
private final HashMap<String, List<String>> lootGroupMap;
|
||||
|
||||
public LootManagerImpl(CustomFishingPlugin plugin) {
|
||||
@@ -58,6 +66,12 @@ public class LootManagerImpl implements LootManager {
|
||||
unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads loot configurations from the plugin's content folders.
|
||||
* This method scans the "item," "entity," and "block" subfolders within the plugin's data folder
|
||||
* and loads loot configurations from YAML files.
|
||||
* If the subfolders or default loot files don't exist, it creates them.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public void loadLootsFromPluginFolder() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
@@ -83,33 +97,122 @@ public class LootManagerImpl implements LootManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of loot IDs associated with a loot group key.
|
||||
*
|
||||
* @param key The key of the loot group.
|
||||
* @return A list of loot IDs belonging to the specified loot group, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getLootGroup(String key) {
|
||||
return lootGroupMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a loot configuration based on a provided loot key.
|
||||
*
|
||||
* @param key The key of the loot configuration.
|
||||
* @return The Loot object associated with the specified loot key, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Loot getLoot(String key) {
|
||||
return lootMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of all loot configuration keys.
|
||||
*
|
||||
* @return A collection of all loot configuration keys.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getAllLootKeys() {
|
||||
return lootMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of all loot configurations.
|
||||
*
|
||||
* @return A collection of all loot configurations.
|
||||
*/
|
||||
@Override
|
||||
public Collection<Loot> getAllLoots() {
|
||||
return lootMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves loot configurations with weights based on a given condition.
|
||||
*
|
||||
* @param condition The condition used to filter loot configurations.
|
||||
* @return A mapping of loot configuration keys to their associated weights.
|
||||
*/
|
||||
@Override
|
||||
public HashMap<String, Double> getLootWithWeight(Condition condition) {
|
||||
return plugin.getRequirementManager().getLootWithWeight(condition);
|
||||
return ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of possible loot keys based on a given condition.
|
||||
*
|
||||
* @param condition The condition to determine possible loot.
|
||||
* @return A collection of loot keys.
|
||||
*/
|
||||
@Override
|
||||
public Collection<String> getPossibleLootKeys(Condition condition) {
|
||||
return ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition).keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of possible loot keys with their corresponding weights, considering fishing effect and condition.
|
||||
*
|
||||
* @param initialEffect The effect to apply weight modifiers.
|
||||
* @param condition The condition to determine possible loot.
|
||||
* @return A map of loot keys and their weights.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<String, Double> getPossibleLootKeysWithWeight(Effect initialEffect, Condition condition) {
|
||||
Map<String, Double> lootWithWeight = ((RequirementManagerImpl) plugin.getRequirementManager()).getLootWithWeight(condition);
|
||||
|
||||
Player player = condition.getPlayer();
|
||||
for (Pair<String, WeightModifier> pair : initialEffect.getWeightModifier()) {
|
||||
Double previous = lootWithWeight.get(pair.left());
|
||||
if (previous != null)
|
||||
lootWithWeight.put(pair.left(), pair.right().modify(player, previous));
|
||||
}
|
||||
for (Pair<String, WeightModifier> pair : initialEffect.getWeightModifierIgnored()) {
|
||||
double previous = lootWithWeight.getOrDefault(pair.left(), 0d);
|
||||
lootWithWeight.put(pair.left(), pair.right().modify(player, previous));
|
||||
}
|
||||
return lootWithWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next loot item based on fishing effect and condition.
|
||||
*
|
||||
* @param initialEffect The effect to apply weight modifiers.
|
||||
* @param condition The condition to determine possible loot.
|
||||
* @return The next loot item, or null if it doesn't exist.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Loot getNextLoot(Effect initialEffect, Condition condition) {
|
||||
String key = WeightUtils.getRandom(getPossibleLootKeysWithWeight(initialEffect, condition));
|
||||
Loot loot = getLoot(key);
|
||||
if (loot == null) {
|
||||
LogUtils.warn(String.format("Loot %s doesn't exist.", key));
|
||||
return null;
|
||||
}
|
||||
return loot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads loot configurations from a single YAML file and populates the lootMap and lootGroupMap.
|
||||
*
|
||||
* @param file The YAML file containing loot configurations.
|
||||
* @param namespace The namespace indicating the type of loot (e.g., "item," "entity," "block").
|
||||
*/
|
||||
private void loadSingleFile(File file, String namespace) {
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
|
||||
@@ -119,12 +222,14 @@ public class LootManagerImpl implements LootManager {
|
||||
namespace,
|
||||
entry.getKey()
|
||||
);
|
||||
// Check for duplicate loot configurations and log an error if found.
|
||||
if (lootMap.containsKey(entry.getKey())) {
|
||||
LogUtils.severe("Duplicated loot found: " + entry.getKey() + ".");
|
||||
} else {
|
||||
lootMap.put(entry.getKey(), loot);
|
||||
}
|
||||
String[] group = loot.getLootGroup();
|
||||
// If the loot configuration belongs to one or more groups, update lootGroupMap.
|
||||
if (group != null) {
|
||||
for (String g : group) {
|
||||
List<String> groupMembers = lootGroupMap.computeIfAbsent(g, k -> new ArrayList<>());
|
||||
@@ -135,6 +240,14 @@ public class LootManagerImpl implements LootManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single loot configuration item from a ConfigurationSection.
|
||||
*
|
||||
* @param section The ConfigurationSection containing loot configuration data.
|
||||
* @param namespace The namespace indicating the type of loot (e.g., "item," "entity," "block").
|
||||
* @param key The unique key identifying the loot configuration.
|
||||
* @return A CFLoot object representing the loot configuration.
|
||||
*/
|
||||
private CFLoot getSingleSectionItem(ConfigurationSection section, String namespace, String key) {
|
||||
return new CFLoot.Builder(key, LootType.valueOf(namespace.toUpperCase(Locale.ENGLISH)))
|
||||
.disableStats(section.getBoolean("disable-stat", false))
|
||||
@@ -146,18 +259,7 @@ public class LootManagerImpl implements LootManager {
|
||||
.lootGroup(ConfigUtils.stringListArgs(section.get("group")).toArray(new String[0]))
|
||||
.nick(section.getString("nick", section.getString("display.name", key)))
|
||||
.addActions(plugin.getActionManager().getActionMap(section.getConfigurationSection("events")))
|
||||
.addTimesActions(getTimesActionMap(section.getConfigurationSection("events.success-times")))
|
||||
.addTimesActions(plugin.getActionManager().getTimesActionMap(section.getConfigurationSection("events.success-times")))
|
||||
.build();
|
||||
}
|
||||
|
||||
private HashMap<Integer, Action[]> getTimesActionMap(ConfigurationSection section) {
|
||||
HashMap<Integer, Action[]> actionMap = new HashMap<>();
|
||||
if (section == null) return actionMap;
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
actionMap.put(Integer.parseInt(entry.getKey()), plugin.getActionManager().getActions(innerSection));
|
||||
}
|
||||
}
|
||||
return actionMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,22 @@ import java.util.Map;
|
||||
|
||||
public class MarketGUI {
|
||||
|
||||
// A map that associates characters with MarketGUI elements.
|
||||
private final HashMap<Character, MarketGUIElement> itemsCharMap;
|
||||
// A map that associates slot indices with MarketGUI elements.
|
||||
private final HashMap<Integer, MarketGUIElement> itemsSlotMap;
|
||||
private final Inventory inventory;
|
||||
private final MarketManagerImpl manager;
|
||||
private final Player owner;
|
||||
private final EarningData earningData;
|
||||
|
||||
/**
|
||||
* Constructor for creating a MarketGUI.
|
||||
*
|
||||
* @param manager The Market Manager implementation associated with this MarketGUI.
|
||||
* @param player The player who owns this MarketGUI.
|
||||
* @param earningData Data related to earnings for this MarketGUI.
|
||||
*/
|
||||
public MarketGUI(MarketManagerImpl manager, Player player, EarningData earningData) {
|
||||
this.manager = manager;
|
||||
this.owner = player;
|
||||
@@ -56,6 +65,9 @@ public class MarketGUI {
|
||||
holder.setInventory(this.inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the GUI layout by mapping elements to inventory slots.
|
||||
*/
|
||||
private void init() {
|
||||
int line = 0;
|
||||
for (String content : manager.getLayout()) {
|
||||
@@ -79,6 +91,12 @@ public class MarketGUI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more elements to the GUI.
|
||||
* @param elements Elements to be added.
|
||||
* @return The MarketGUI instance.
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public MarketGUI addElement(MarketGUIElement... elements) {
|
||||
for (MarketGUIElement element : elements) {
|
||||
itemsCharMap.put(element.getSymbol(), element);
|
||||
@@ -86,26 +104,47 @@ public class MarketGUI {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and initialize the GUI.
|
||||
*/
|
||||
public MarketGUI build() {
|
||||
init();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the GUI to a player if the player is the owner.
|
||||
* @param player The player to show the GUI to.
|
||||
*/
|
||||
public void show(Player player) {
|
||||
if (player != owner) return;
|
||||
player.openInventory(inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MarketGUIElement associated with a specific inventory slot.
|
||||
* @param slot The slot index in the inventory.
|
||||
* @return The associated MarketGUIElement or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public MarketGUIElement getElement(int slot) {
|
||||
return itemsSlotMap.get(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MarketGUIElement associated with a specific character symbol.
|
||||
* @param slot The character symbol.
|
||||
* @return The associated MarketGUIElement or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public MarketGUIElement getElement(char slot) {
|
||||
return itemsCharMap.get(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the GUI, updating the display based on current data.
|
||||
* @return The MarketGUI instance.
|
||||
*/
|
||||
public MarketGUI refresh() {
|
||||
double totalWorth = getTotalWorth();
|
||||
MarketDynamicGUIElement functionElement = (MarketDynamicGUIElement) getElement(manager.getFunctionSlot());
|
||||
@@ -145,6 +184,10 @@ public class MarketGUI {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and return the total worth of items in the inventory.
|
||||
* @return The total worth of items.
|
||||
*/
|
||||
public double getTotalWorth() {
|
||||
double money = 0d;
|
||||
MarketGUIElement itemElement = getElement(manager.getItemSlot());
|
||||
@@ -158,10 +201,17 @@ public class MarketGUI {
|
||||
return money;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inventory associated with this MarketGUI.
|
||||
* @return The Inventory object.
|
||||
*/
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear items with non-zero value from the inventory.
|
||||
*/
|
||||
public void clearWorthyItems() {
|
||||
MarketGUIElement itemElement = getElement(manager.getItemSlot());
|
||||
if (itemElement == null) {
|
||||
@@ -175,6 +225,10 @@ public class MarketGUI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an empty slot in the item section of the inventory.
|
||||
* @return The index of an empty slot or -1 if none are found.
|
||||
*/
|
||||
public int getEmptyItemSlot() {
|
||||
MarketGUIElement itemElement = getElement(manager.getItemSlot());
|
||||
if (itemElement == null) {
|
||||
@@ -189,6 +243,9 @@ public class MarketGUI {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return items to the owner's inventory.
|
||||
*/
|
||||
public void returnItems() {
|
||||
MarketGUIElement itemElement = getElement(manager.getItemSlot());
|
||||
if (itemElement == null) {
|
||||
@@ -203,6 +260,10 @@ public class MarketGUI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the earning data associated with this MarketGUI.
|
||||
* @return The EarningData object.
|
||||
*/
|
||||
public EarningData getEarningData() {
|
||||
return earningData;
|
||||
}
|
||||
|
||||
@@ -34,18 +34,22 @@ public class MarketGUIElement {
|
||||
this.slots = new ArrayList<>();
|
||||
}
|
||||
|
||||
// Method to add a slot to the list of slots for this element
|
||||
public void addSlot(int slot) {
|
||||
slots.add(slot);
|
||||
}
|
||||
|
||||
// Getter method to retrieve the symbol associated with this element
|
||||
public char getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
// Getter method to retrieve the cloned ItemStack associated with this element
|
||||
public ItemStack getItemStack() {
|
||||
return itemStack;
|
||||
return itemStack.clone();
|
||||
}
|
||||
|
||||
// Getter method to retrieve the list of slots where this element can appear
|
||||
public List<Integer> getSlots() {
|
||||
return slots;
|
||||
}
|
||||
|
||||
@@ -93,10 +93,13 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
unload();
|
||||
}
|
||||
|
||||
// Load configuration from the plugin's config file
|
||||
private void loadConfig() {
|
||||
YamlConfiguration config = plugin.getConfig("market.yml");
|
||||
this.enable = config.getBoolean("enable", true);
|
||||
if (!this.enable) return;
|
||||
|
||||
// Load various configuration settings
|
||||
this.layout = config.getStringList("layout").toArray(new String[0]);
|
||||
this.title = config.getString("title", "market.title");
|
||||
this.formula = config.getString("price-formula", "{base} + {bonus} * {size}");
|
||||
@@ -111,12 +114,15 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
this.earningLimit = config.getBoolean("limitation.enable", true) ? config.getDouble("limitation.earnings", 100) : -1;
|
||||
this.allowItemWithNoPrice = config.getBoolean("item-slot.allow-items-with-no-price", true);
|
||||
|
||||
// Load item prices from the configuration
|
||||
ConfigurationSection priceSection = config.getConfigurationSection("item-price");
|
||||
if (priceSection != null) {
|
||||
for (Map.Entry<String, Object> entry : priceSection.getValues(false).entrySet()) {
|
||||
this.priceMap.put(entry.getKey(), ConfigUtils.getDoubleValue(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Load decorative icons from the configuration
|
||||
ConfigurationSection decorativeSection = config.getConfigurationSection("decorative-icons");
|
||||
if (decorativeSection != null) {
|
||||
for (Map.Entry<String, Object> entry : decorativeSection.getValues(false).entrySet()) {
|
||||
@@ -129,8 +135,14 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the market GUI for a player
|
||||
*
|
||||
* @param player player
|
||||
*/
|
||||
@Override
|
||||
public void openMarketGUI(Player player) {
|
||||
if (!isEnable()) return;
|
||||
OnlineUser user = plugin.getStorageManager().getOnlineUser(player.getUniqueId());
|
||||
if (user == null) {
|
||||
LogUtils.warn("Player " + player.getName() + "'s market data is not loaded yet.");
|
||||
@@ -147,6 +159,11 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
marketGUIMap.put(player.getUniqueId(), gui);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the closing of an inventory.
|
||||
*
|
||||
* @param event The InventoryCloseEvent that triggered this method.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCloseInv(InventoryCloseEvent event) {
|
||||
if (!(event.getPlayer() instanceof Player player))
|
||||
@@ -158,6 +175,11 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
gui.returnItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles a player quitting the server.
|
||||
*
|
||||
* @param event The PlayerQuitEvent that triggered this method.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
MarketGUI gui = marketGUIMap.remove(event.getPlayer().getUniqueId());
|
||||
@@ -165,6 +187,11 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
gui.returnItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles dragging items in an inventory.
|
||||
*
|
||||
* @param event The InventoryDragEvent that triggered this method.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onDragInv(InventoryDragEvent event) {
|
||||
if (event.isCancelled())
|
||||
@@ -197,14 +224,23 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
plugin.getScheduler().runTaskSyncLater(gui::refresh, player.getLocation(), 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles inventory click events.
|
||||
*
|
||||
* @param event The InventoryClickEvent that triggered this method.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onClickInv(InventoryClickEvent event) {
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
|
||||
Inventory clickedInv = event.getClickedInventory();
|
||||
if (clickedInv == null)
|
||||
return;
|
||||
|
||||
Player player = (Player) event.getWhoClicked();
|
||||
|
||||
// Check if the clicked inventory is a MarketGUI
|
||||
if (!(event.getInventory().getHolder() instanceof MarketGUIHolder))
|
||||
return;
|
||||
|
||||
@@ -241,14 +277,14 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
)));
|
||||
if (worth > 0) {
|
||||
if (earningLimit != -1 && (earningLimit - data.earnings) < worth) {
|
||||
// can't earn more money
|
||||
// Can't earn more money
|
||||
if (limitActions != null) {
|
||||
for (Action action : limitActions) {
|
||||
action.trigger(condition);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// clear items
|
||||
// Clear items and update earnings
|
||||
gui.clearWorthyItems();
|
||||
data.earnings += worth;
|
||||
condition.insertArg("{rest}", String.format("%.2f", (earningLimit - data.earnings)));
|
||||
@@ -259,7 +295,7 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// nothing to sell
|
||||
// Nothing to sell
|
||||
if (denyActions != null) {
|
||||
for (Action action : denyActions) {
|
||||
action.trigger(condition);
|
||||
@@ -268,6 +304,7 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle interactions with the player's inventory
|
||||
ItemStack current = event.getCurrentItem();
|
||||
if (!allowItemWithNoPrice) {
|
||||
double price = getItemPrice(current);
|
||||
@@ -308,38 +345,69 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the GUI
|
||||
plugin.getScheduler().runTaskSyncLater(gui::refresh, player.getLocation(), 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current date as an integer in the format MMDD (e.g., September 21 as 0921).
|
||||
*
|
||||
* @return An integer representing the current date.
|
||||
*/
|
||||
@Override
|
||||
public int getDate() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the price of an ItemStack based on custom data or a predefined price map.
|
||||
*
|
||||
* @param itemStack The ItemStack for which the price is calculated.
|
||||
* @return The calculated price of the ItemStack.
|
||||
*/
|
||||
@Override
|
||||
public double getItemPrice(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return 0;
|
||||
|
||||
NBTItem nbtItem = new NBTItem(itemStack);
|
||||
Double price = nbtItem.getDouble("Price");
|
||||
if (price != null && price != 0) {
|
||||
// If a custom price is defined in the ItemStack's NBT data, use it.
|
||||
return price * itemStack.getAmount();
|
||||
}
|
||||
|
||||
// If no custom price is defined, attempt to fetch the price from a predefined price map.
|
||||
String itemID = itemStack.getType().name();
|
||||
if (nbtItem.hasTag("CustomModelData")) {
|
||||
itemID = itemID + ":" + nbtItem.getInteger("CustomModelData");
|
||||
}
|
||||
|
||||
// Use the price from the price map, or default to 0 if not found.
|
||||
return priceMap.getOrDefault(itemID, 0d) * itemStack.getAmount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formula used for calculating prices.
|
||||
*
|
||||
* @return The pricing formula as a string.
|
||||
*/
|
||||
@Override
|
||||
public String getFormula() {
|
||||
return formula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the price based on a formula with provided variables.
|
||||
*
|
||||
* @param base The base value for the formula.
|
||||
* @param bonus The bonus value for the formula.
|
||||
* @param size The size value for the formula.
|
||||
* @return The calculated price based on the formula and provided variables.
|
||||
*/
|
||||
@Override
|
||||
public double getPrice(float base, float bonus, float size) {
|
||||
public double getFishPrice(float base, float bonus, float size) {
|
||||
Expression expression = new ExpressionBuilder(getFormula())
|
||||
.variables("base", "bonus", "size")
|
||||
.build()
|
||||
@@ -349,38 +417,88 @@ public class MarketManagerImpl implements MarketManager, Listener {
|
||||
return expression.evaluate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the character representing the item slot in the MarketGUI.
|
||||
*
|
||||
* @return The item slot character.
|
||||
*/
|
||||
@Override
|
||||
public char getItemSlot() {
|
||||
return itemSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the character representing the function slot in the MarketGUI.
|
||||
*
|
||||
* @return The function slot character.
|
||||
*/
|
||||
@Override
|
||||
public char getFunctionSlot() {
|
||||
return functionSlot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the layout of the MarketGUI as an array of strings.
|
||||
*
|
||||
* @return The layout of the MarketGUI.
|
||||
*/
|
||||
@Override
|
||||
public String[] getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the MarketGUI.
|
||||
*
|
||||
* @return The title of the MarketGUI.
|
||||
*/
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the earning limit
|
||||
*
|
||||
* @return The earning limit
|
||||
*/
|
||||
@Override
|
||||
public double getEarningLimit() {
|
||||
return earningLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the builder for the function icon representing the limit in the MarketGUI.
|
||||
*
|
||||
* @return The function icon builder for the limit.
|
||||
*/
|
||||
public BuildableItem getFunctionIconLimitBuilder() {
|
||||
return functionIconLimitBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the builder for the function icon representing allow actions in the MarketGUI.
|
||||
*
|
||||
* @return The function icon builder for allow actions.
|
||||
*/
|
||||
public BuildableItem getFunctionIconAllowBuilder() {
|
||||
return functionIconAllowBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the builder for the function icon representing deny actions in the MarketGUI.
|
||||
*
|
||||
* @return The function icon builder for deny actions.
|
||||
*/
|
||||
public BuildableItem getFunctionIconDenyBuilder() {
|
||||
return functionIconDenyBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is market enabled
|
||||
*
|
||||
* @return enable or not
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnable() {
|
||||
return enable;
|
||||
|
||||
@@ -28,6 +28,10 @@ import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages cooldowns for various actions or events.
|
||||
* Keeps track of cooldown times for different keys associated with player UUIDs.
|
||||
*/
|
||||
public class CoolDownManager implements Listener {
|
||||
|
||||
private final ConcurrentHashMap<UUID, Data> dataMap;
|
||||
@@ -38,6 +42,14 @@ public class CoolDownManager implements Listener {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player is currently in cooldown for a specific key.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param key The key associated with the cooldown.
|
||||
* @param time The cooldown time in milliseconds.
|
||||
* @return True if the player is in cooldown, false otherwise.
|
||||
*/
|
||||
public boolean isCoolDown(UUID uuid, String key, long time) {
|
||||
Data data = this.dataMap.computeIfAbsent(uuid, k -> new Data());
|
||||
return data.isCoolDown(key, time);
|
||||
@@ -56,6 +68,11 @@ public class CoolDownManager implements Listener {
|
||||
this.dataMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a player quits the game. Removes their cooldown data.
|
||||
*
|
||||
* @param event The PlayerQuitEvent triggered when a player quits.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
dataMap.remove(event.getPlayer().getUniqueId());
|
||||
@@ -69,14 +86,21 @@ public class CoolDownManager implements Listener {
|
||||
this.coolDownMap = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player is in cooldown for a specific key.
|
||||
*
|
||||
* @param key The key associated with the cooldown.
|
||||
* @param delay The cooldown delay in milliseconds.
|
||||
* @return True if the player is in cooldown, false otherwise.
|
||||
*/
|
||||
public synchronized boolean isCoolDown(String key, long delay) {
|
||||
long time = System.currentTimeMillis();
|
||||
long last = coolDownMap.getOrDefault(key, time - delay);
|
||||
if (last + delay > time) {
|
||||
return true;
|
||||
return true; // Player is in cooldown
|
||||
} else {
|
||||
coolDownMap.put(key, time);
|
||||
return false;
|
||||
return false; // Player is not in cooldown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,19 @@ public class ConditionalElement {
|
||||
public ConditionalElement(
|
||||
Requirement[] requirements,
|
||||
List<Pair<String, WeightModifier>> modifierList,
|
||||
HashMap<String, ConditionalElement> subLoots
|
||||
HashMap<String, ConditionalElement> subElements
|
||||
) {
|
||||
this.modifierList = modifierList;
|
||||
this.requirements = requirements;
|
||||
this.subLoots = subLoots;
|
||||
this.subLoots = subElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the weight modifiers for this element.
|
||||
*
|
||||
* @param player The player for whom the modifiers are applied.
|
||||
* @param weightMap The map of weight modifiers.
|
||||
*/
|
||||
synchronized public void combine(Player player, HashMap<String, Double> weightMap) {
|
||||
for (Pair<String, WeightModifier> modifierPair : this.modifierList) {
|
||||
double previous = weightMap.getOrDefault(modifierPair.left(), 0d);
|
||||
@@ -49,16 +55,11 @@ public class ConditionalElement {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConditionsMet(Condition condition) {
|
||||
for (Requirement requirement : requirements) {
|
||||
if (!requirement.isConditionMet(condition)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
public Requirement[] getRequirements() {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
public HashMap<String, ConditionalElement> getSubLoots() {
|
||||
public HashMap<String, ConditionalElement> getSubElements() {
|
||||
return subLoots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ package net.momirealms.customfishing.mechanic.requirement;
|
||||
import net.momirealms.customfishing.api.mechanic.condition.Condition;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
|
||||
/**
|
||||
* Represents an empty requirement that always returns true when checking conditions.
|
||||
*/
|
||||
public class EmptyRequirement implements Requirement {
|
||||
|
||||
public static EmptyRequirement instance = new EmptyRequirement();
|
||||
|
||||
@@ -82,10 +82,15 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
this.conditionalLootsMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads requirement group configuration data from various configuration files.
|
||||
*/
|
||||
public void loadRequirementGroupFileConfig() {
|
||||
// Load mechanic requirements from the main configuration file
|
||||
YamlConfiguration main = plugin.getConfig("config.yml");
|
||||
mechanicRequirements = getRequirements(main.getConfigurationSection("mechanics.mechanic-requirements"), true);
|
||||
|
||||
// Load conditional loot data from the loot conditions configuration file
|
||||
YamlConfiguration config1 = plugin.getConfig("loot-conditions.yml");
|
||||
for (Map.Entry<String, Object> entry : config1.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection section) {
|
||||
@@ -93,6 +98,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Load conditional game data from the game conditions configuration file
|
||||
YamlConfiguration config2 = plugin.getConfig("game-conditions.yml");
|
||||
for (Map.Entry<String, Object> entry : config2.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection section) {
|
||||
@@ -101,6 +107,13 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a custom requirement type with its corresponding factory.
|
||||
*
|
||||
* @param type The type identifier of the requirement.
|
||||
* @param requirementFactory The factory responsible for creating instances of the requirement.
|
||||
* @return True if registration was successful, false if the type is already registered.
|
||||
*/
|
||||
@Override
|
||||
public boolean registerRequirement(String type, RequirementFactory requirementFactory) {
|
||||
if (this.requirementBuilderMap.containsKey(type)) return false;
|
||||
@@ -108,11 +121,46 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a custom requirement type.
|
||||
*
|
||||
* @param type The type identifier of the requirement to unregister.
|
||||
* @return True if unregistration was successful, false if the type is not registered.
|
||||
*/
|
||||
@Override
|
||||
public boolean unregisterRequirement(String type) {
|
||||
return this.requirementBuilderMap.remove(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a ConditionalElement from a given ConfigurationSection.
|
||||
*
|
||||
* @param section The ConfigurationSection containing the conditional element data.
|
||||
* @return A ConditionalElement instance representing the data in the section.
|
||||
*/
|
||||
private ConditionalElement getConditionalElements(ConfigurationSection section) {
|
||||
var sub = section.getConfigurationSection("sub-groups");
|
||||
if (sub == null) {
|
||||
return new ConditionalElement(
|
||||
getRequirements(section.getConfigurationSection("conditions"), false),
|
||||
ConfigUtils.getModifiers(section.getStringList("list")),
|
||||
null
|
||||
);
|
||||
} else {
|
||||
HashMap<String, ConditionalElement> subElements = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : sub.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
subElements.put(entry.getKey(), getConditionalElements(innerSection));
|
||||
}
|
||||
}
|
||||
return new ConditionalElement(
|
||||
getRequirements(section.getConfigurationSection("conditions"), false),
|
||||
ConfigUtils.getModifiers(section.getStringList("list")),
|
||||
subElements
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerInbuiltRequirements() {
|
||||
this.registerTimeRequirement();
|
||||
this.registerYRequirement();
|
||||
@@ -149,39 +197,21 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
this.registerHookRequirement();
|
||||
}
|
||||
|
||||
public ConditionalElement getConditionalElements(ConfigurationSection section) {
|
||||
var sub = section.getConfigurationSection("sub-groups");
|
||||
if (sub == null) {
|
||||
return new ConditionalElement(
|
||||
getRequirements(section.getConfigurationSection("conditions"), false),
|
||||
ConfigUtils.getModifiers(section.getStringList("list")),
|
||||
null
|
||||
);
|
||||
} else {
|
||||
HashMap<String, ConditionalElement> subElements = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : sub.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection innerSection) {
|
||||
subElements.put(entry.getKey(), getConditionalElements(innerSection));
|
||||
}
|
||||
}
|
||||
return new ConditionalElement(
|
||||
getRequirements(section.getConfigurationSection("conditions"), false),
|
||||
ConfigUtils.getModifiers(section.getStringList("list")),
|
||||
subElements
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, Double> getLootWithWeight(Condition condition) {
|
||||
return getString2DoubleMap(condition, conditionalLootsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, Double> getGameWithWeight(Condition condition) {
|
||||
return getString2DoubleMap(condition, conditionalGamesMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a mapping of strings to doubles based on conditional elements and a player's condition.
|
||||
*
|
||||
* @param condition The player's condition.
|
||||
* @param conditionalGamesMap The map of conditional elements representing loots/games.
|
||||
* @return A HashMap with strings as keys and doubles as values representing loot/game weights.
|
||||
*/
|
||||
@NotNull
|
||||
private HashMap<String, Double> getString2DoubleMap(Condition condition, LinkedHashMap<String, ConditionalElement> conditionalGamesMap) {
|
||||
HashMap<String, Double> lootWeightMap = new HashMap<>();
|
||||
@@ -191,10 +221,10 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
while (!lootQueue.isEmpty()) {
|
||||
HashMap<String, ConditionalElement> currentLootMap = lootQueue.poll();
|
||||
for (ConditionalElement loots : currentLootMap.values()) {
|
||||
if (loots.isConditionsMet(condition)) {
|
||||
if (RequirementManager.isRequirementMet(condition, loots.getRequirements())) {
|
||||
loots.combine(player, lootWeightMap);
|
||||
if (loots.getSubLoots() != null) {
|
||||
lootQueue.add(loots.getSubLoots());
|
||||
if (loots.getSubElements() != null) {
|
||||
lootQueue.add(loots.getSubElements());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,15 +232,24 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
return lootWeightMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/**
|
||||
* Retrieves an array of requirements based on a configuration section.
|
||||
*
|
||||
* @param section The configuration section containing requirement definitions.
|
||||
* @param advanced A flag indicating whether to use advanced requirements.
|
||||
* @return An array of Requirement objects based on the configuration section
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Requirement[] getRequirements(ConfigurationSection section, boolean advanced) {
|
||||
if (section == null) return null;
|
||||
List<Requirement> requirements = new ArrayList<>();
|
||||
if (section == null) {
|
||||
return requirements.toArray(new Requirement[0]);
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
|
||||
String typeOrName = entry.getKey();
|
||||
if (hasRequirement(typeOrName)) {
|
||||
requirements.add(getRequirementBuilder(typeOrName).build(entry.getValue(), null, advanced));
|
||||
requirements.add(getRequirement(typeOrName, entry.getValue()));
|
||||
} else {
|
||||
requirements.add(getRequirement(section.getConfigurationSection(typeOrName), advanced));
|
||||
}
|
||||
@@ -222,6 +261,13 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
return requirementBuilderMap.containsKey(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Requirement object based on a configuration section and advanced flag.
|
||||
*
|
||||
* @param section The configuration section containing requirement definitions.
|
||||
* @param advanced A flag indicating whether to use advanced requirements.
|
||||
* @return A Requirement object based on the configuration section, or an EmptyRequirement if the section is null or invalid.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public Requirement getRequirement(ConfigurationSection section, boolean advanced) {
|
||||
@@ -244,31 +290,48 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
LogUtils.warn("No requirement type found at " + section.getCurrentPath());
|
||||
return EmptyRequirement.instance;
|
||||
}
|
||||
var builder = getRequirementBuilder(type);
|
||||
var builder = getRequirementFactory(type);
|
||||
if (builder == null) {
|
||||
return EmptyRequirement.instance;
|
||||
}
|
||||
return builder.build(section.get("value"), actionList, advanced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a requirement based on the provided key and value.
|
||||
* If a valid RequirementFactory is found for the key, it is used to create the requirement.
|
||||
* If no factory is found, a warning is logged, and an empty requirement instance is returned.
|
||||
*
|
||||
* @param type The key representing the requirement type.
|
||||
* @param value The value associated with the requirement.
|
||||
* @return A Requirement instance based on the key and value, or an empty requirement if not found.
|
||||
*/
|
||||
@Override
|
||||
public Requirement getRequirement(String key, Object value) {
|
||||
return getRequirementBuilder(key).build(value);
|
||||
}
|
||||
|
||||
private Pair<Integer, Integer> getIntegerPair(String range) {
|
||||
String[] split = range.split("~");
|
||||
return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
|
||||
@NotNull
|
||||
public Requirement getRequirement(String type, Object value) {
|
||||
RequirementFactory factory = getRequirementFactory(type);
|
||||
if (factory == null) {
|
||||
LogUtils.warn("Requirement type: " + type + " doesn't exist.");
|
||||
return EmptyRequirement.instance;
|
||||
}
|
||||
return factory.build(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a RequirementFactory based on the specified requirement type.
|
||||
*
|
||||
* @param type The requirement type for which to retrieve a factory.
|
||||
* @return A RequirementFactory for the specified type, or null if no factory is found.
|
||||
*/
|
||||
@Override
|
||||
public RequirementFactory getRequirementBuilder(String type) {
|
||||
@Nullable
|
||||
public RequirementFactory getRequirementFactory(String type) {
|
||||
return requirementBuilderMap.get(type);
|
||||
}
|
||||
|
||||
private void registerTimeRequirement() {
|
||||
registerRequirement("time", (args, actions, advanced) -> {
|
||||
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(this::getIntegerPair).toList();
|
||||
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(ConfigUtils::splitStringIntegerArgs).toList();
|
||||
return condition -> {
|
||||
long time = condition.getLocation().getWorld().getTime();
|
||||
for (Pair<Integer, Integer> pair : timePairs)
|
||||
@@ -322,7 +385,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
@SuppressWarnings("unchecked")
|
||||
private void registerLootRequirement() {
|
||||
registerRequirement("loot", (args, actions, advanced) -> {
|
||||
List<String> arg = (List<String>) args;
|
||||
@@ -346,7 +409,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
|
||||
private void registerYRequirement() {
|
||||
registerRequirement("ypos", (args, actions, advanced) -> {
|
||||
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(this::getIntegerPair).toList();
|
||||
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(ConfigUtils::splitStringIntegerArgs).toList();
|
||||
return condition -> {
|
||||
int y = condition.getLocation().getBlockY();
|
||||
for (Pair<Integer, Integer> pair : timePairs)
|
||||
@@ -363,7 +426,6 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
Requirement[] requirements = getRequirements(section, advanced);
|
||||
return condition -> {
|
||||
if (requirements == null) return true;
|
||||
for (Requirement requirement : requirements) {
|
||||
if (requirement.isConditionMet(condition)) {
|
||||
return true;
|
||||
@@ -384,7 +446,6 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
Requirement[] requirements = getRequirements(section, advanced);
|
||||
return condition -> {
|
||||
if (requirements == null) return true;
|
||||
outer: {
|
||||
for (Requirement requirement : requirements) {
|
||||
if (!requirement.isConditionMet(condition)) {
|
||||
@@ -626,6 +687,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void registerGreaterThanRequirement() {
|
||||
registerRequirement(">=", (args, actions, advanced) -> {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
@@ -713,6 +775,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void registerLessThanRequirement() {
|
||||
registerRequirement("<", (args, actions, advanced) -> {
|
||||
if (args instanceof ConfigurationSection section) {
|
||||
@@ -919,7 +982,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
ItemStack itemStack = mainOrOff ?
|
||||
condition.getPlayer().getInventory().getItemInMainHand()
|
||||
: condition.getPlayer().getInventory().getItemInOffHand();
|
||||
String id = plugin.getItemManager().getAnyItemID(itemStack);
|
||||
String id = plugin.getItemManager().getAnyPluginItemID(itemStack);
|
||||
if (items.contains(id) && itemStack.getAmount() >= amount) return true;
|
||||
if (advanced) triggerActions(actions, condition);
|
||||
return false;
|
||||
@@ -993,7 +1056,7 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
int level = section.getInt("level");
|
||||
String target = section.getString("target");
|
||||
return condition -> {
|
||||
LevelInterface levelInterface = plugin.getIntegrationManager().getLevelHook(pluginName);
|
||||
LevelInterface levelInterface = plugin.getIntegrationManager().getLevelPlugin(pluginName);
|
||||
if (levelInterface == null) {
|
||||
LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.");
|
||||
return true;
|
||||
@@ -1010,12 +1073,25 @@ public class RequirementManagerImpl implements RequirementManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a list of actions with the given condition.
|
||||
* If the list of actions is not null, each action in the list is triggered.
|
||||
*
|
||||
* @param actions The list of actions to trigger.
|
||||
* @param condition The condition associated with the actions.
|
||||
*/
|
||||
private void triggerActions(List<Action> actions, Condition condition) {
|
||||
if (actions != null)
|
||||
for (Action action : actions)
|
||||
action.trigger(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads requirement expansions from external JAR files located in the expansion folder.
|
||||
* Each expansion JAR should contain classes that extends the RequirementExpansion class.
|
||||
* Expansions are registered and used to create custom requirements.
|
||||
* If an error occurs while loading or initializing an expansion, a warning message is logged.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void loadExpansions() {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
|
||||
@@ -49,7 +49,14 @@ public class StatisticsManagerImpl implements StatisticsManager {
|
||||
unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the statistics for a player with the given UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player for whom statistics are retrieved.
|
||||
* @return The player's statistics or null if the player is not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Statistics getStatistics(UUID uuid) {
|
||||
OnlineUser onlineUser = plugin.getStorageManager().getOnlineUser(uuid);
|
||||
if (onlineUser == null) return null;
|
||||
@@ -88,6 +95,12 @@ public class StatisticsManagerImpl implements StatisticsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of strings associated with a specific key in a category map.
|
||||
*
|
||||
* @param key The key to look up in the category map.
|
||||
* @return A list of strings associated with the key or null if the key is not found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public List<String> getCategory(String key) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ActivatedTotem {
|
||||
this.expireTime = System.currentTimeMillis() + config.getDuration() * 1000L;
|
||||
this.coreLocation = coreLocation.clone().add(0.5,0,0.5);
|
||||
this.totemConfig = config;
|
||||
this.effectCarrier = CustomFishingPlugin.get().getEffectManager().getEffect("totem", config.getKey());
|
||||
this.effectCarrier = CustomFishingPlugin.get().getEffectManager().getEffectCarrier("totem", config.getKey());
|
||||
for (ParticleSetting particleSetting : config.getParticleSettings()) {
|
||||
this.subTasks.add(particleSetting.start(coreLocation, config.getRadius()));
|
||||
}
|
||||
|
||||
@@ -105,6 +105,12 @@ public class TotemManagerImpl implements TotemManager, Listener {
|
||||
unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the EffectCarrier associated with an activated totem located near the specified location.
|
||||
*
|
||||
* @param location The location to search for activated totems.
|
||||
* @return The EffectCarrier associated with the nearest activated totem or null if none are found.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public EffectCarrier getTotemEffect(Location location) {
|
||||
@@ -139,7 +145,7 @@ public class TotemManagerImpl implements TotemManager, Listener {
|
||||
return;
|
||||
Block block = event.getClickedBlock();
|
||||
assert block != null;
|
||||
String id = plugin.getBlockManager().getAnyBlockID(block);
|
||||
String id = plugin.getBlockManager().getAnyPluginBlockID(block);
|
||||
List<TotemConfig> configs = totemConfigMap.get(id);
|
||||
if (configs == null)
|
||||
return;
|
||||
@@ -153,7 +159,7 @@ public class TotemManagerImpl implements TotemManager, Listener {
|
||||
if (config == null)
|
||||
return;
|
||||
String totemKey = config.getKey();
|
||||
EffectCarrier carrier = plugin.getEffectManager().getEffect("totem", totemKey);
|
||||
EffectCarrier carrier = plugin.getEffectManager().getEffectCarrier("totem", totemKey);
|
||||
if (carrier == null)
|
||||
return;
|
||||
Condition condition = new Condition(block.getLocation(), event.getPlayer(), new HashMap<>());
|
||||
|
||||
@@ -32,7 +32,7 @@ public class EqualType implements TypeCondition, Serializable {
|
||||
|
||||
@Override
|
||||
public boolean isMet(Block type) {
|
||||
return this.type.equals(CustomFishingPlugin.get().getBlockManager().getAnyBlockID(type));
|
||||
return this.type.equals(CustomFishingPlugin.get().getBlockManager().getAnyPluginBlockID(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,9 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/**
|
||||
* A scheduler implementation for synchronous tasks using Bukkit's Scheduler.
|
||||
*/
|
||||
public class BukkitSchedulerImpl implements SyncScheduler {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
@@ -31,6 +34,13 @@ public class BukkitSchedulerImpl implements SyncScheduler {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a synchronous task on the main server thread using Bukkit's Scheduler.
|
||||
* If already on the main thread, the task is executed immediately.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
*/
|
||||
@Override
|
||||
public void runSyncTask(Runnable runnable, Location location) {
|
||||
if (Bukkit.isPrimaryThread())
|
||||
@@ -39,16 +49,36 @@ public class BukkitSchedulerImpl implements SyncScheduler {
|
||||
Bukkit.getScheduler().runTask(plugin, runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a synchronous task repeatedly with a specified delay and period using Bukkit's Scheduler.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delay The delay in ticks before the first execution.
|
||||
* @param period The period between subsequent executions in ticks.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
|
||||
return new BukkitCancellableTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a synchronous task with a specified delay using Bukkit's Scheduler.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delay The delay in ticks before the task execution.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
|
||||
return new BukkitCancellableTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scheduled task using Bukkit's Scheduler that can be cancelled.
|
||||
*/
|
||||
public static class BukkitCancellableTask implements CancellableTask {
|
||||
|
||||
private final BukkitTask bukkitTask;
|
||||
|
||||
@@ -23,6 +23,9 @@ import net.momirealms.customfishing.api.scheduler.CancellableTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
|
||||
/**
|
||||
* A scheduler implementation for "synchronous" tasks using Folia's RegionScheduler.
|
||||
*/
|
||||
public class FoliaSchedulerImpl implements SyncScheduler {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
@@ -31,21 +34,47 @@ public class FoliaSchedulerImpl implements SyncScheduler {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a "synchronous" task on the region thread using Folia's RegionScheduler.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
*/
|
||||
@Override
|
||||
public void runSyncTask(Runnable runnable, Location location) {
|
||||
Bukkit.getRegionScheduler().execute(plugin, location, runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a "synchronous" task repeatedly with a specified delay and period using Folia's RegionScheduler.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delay The delay in ticks before the first execution.
|
||||
* @param period The period between subsequent executions in ticks.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
|
||||
return new FoliaCancellableTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, (scheduledTask -> runnable.run()), delay, period));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a "synchronous" task with a specified delay using Folia's RegionScheduler.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delay The delay in ticks before the task execution.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
|
||||
return new FoliaCancellableTask(Bukkit.getRegionScheduler().runDelayed(plugin, location, (scheduledTask -> runnable.run()), delay));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a scheduled task using Folia's RegionScheduler that can be cancelled.
|
||||
*/
|
||||
public static class FoliaCancellableTask implements CancellableTask {
|
||||
|
||||
private final ScheduledTask scheduledTask;
|
||||
|
||||
@@ -28,6 +28,9 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A scheduler implementation responsible for scheduling and managing tasks in a multi-threaded environment.
|
||||
*/
|
||||
public class SchedulerImpl implements Scheduler {
|
||||
|
||||
private final SyncScheduler syncScheduler;
|
||||
@@ -44,37 +47,80 @@ public class SchedulerImpl implements Scheduler {
|
||||
this.schedule.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the scheduler configuration based on CustomFishingPlugin settings.
|
||||
*/
|
||||
public void reload() {
|
||||
this.schedule.setCorePoolSize(CFConfig.corePoolSize);
|
||||
this.schedule.setKeepAliveTime(CFConfig.keepAliveTime, TimeUnit.SECONDS);
|
||||
this.schedule.setMaximumPoolSize(CFConfig.maximumPoolSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the scheduler.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (this.schedule != null && !this.schedule.isShutdown())
|
||||
this.schedule.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task synchronously on the main server thread or region thread.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
*/
|
||||
@Override
|
||||
public void runTaskSync(Runnable runnable, Location location) {
|
||||
this.syncScheduler.runSyncTask(runnable, location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
*/
|
||||
@Override
|
||||
public void runTaskAsync(Runnable runnable) {
|
||||
this.schedule.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task synchronously with a specified delay and period.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delayTicks The delay in ticks before the first execution.
|
||||
* @param periodTicks The period between subsequent executions in ticks.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks) {
|
||||
return this.syncScheduler.runTaskSyncTimer(runnable, location, delayTicks, periodTicks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously with a specified delay.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param delay The delay before the task execution.
|
||||
* @param timeUnit The time unit for the delay.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) {
|
||||
return new ScheduledTask(schedule.schedule(runnable, delay, timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task synchronously with a specified delay.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delay The delay before the task execution.
|
||||
* @param timeUnit The time unit for the delay.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit) {
|
||||
return new ScheduledTask(schedule.schedule(() -> {
|
||||
@@ -82,16 +128,36 @@ public class SchedulerImpl implements Scheduler {
|
||||
}, delay, timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task synchronously with a specified delay in ticks.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delayTicks The delay in ticks before the task execution.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks) {
|
||||
return this.syncScheduler.runTaskSyncLater(runnable, location, delayTicks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a task asynchronously with a specified delay and period.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param delay The delay before the first execution.
|
||||
* @param period The period between subsequent executions.
|
||||
* @param timeUnit The time unit for the delay and period.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
@Override
|
||||
public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
|
||||
return new ScheduledTask(schedule.scheduleAtFixedRate(runnable, delay, period, timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a thread-pool task that can be cancelled.
|
||||
*/
|
||||
public static class ScheduledTask implements CancellableTask {
|
||||
|
||||
private final ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
@@ -22,9 +22,32 @@ import org.bukkit.Location;
|
||||
|
||||
public interface SyncScheduler {
|
||||
|
||||
/**
|
||||
* Runs a task synchronously on the main server thread or region thread.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
*/
|
||||
void runSyncTask(Runnable runnable, Location location);
|
||||
|
||||
CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period);
|
||||
/**
|
||||
* Runs a task synchronously with a specified delay and period.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delayTicks The delay in ticks before the first execution.
|
||||
* @param periodTicks The period between subsequent executions in ticks.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delayTicks, long periodTicks);
|
||||
|
||||
CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay);
|
||||
/**
|
||||
* Runs a task synchronously with a specified delay in ticks.
|
||||
*
|
||||
* @param runnable The task to run.
|
||||
* @param location The location associated with the task.
|
||||
* @param delayTicks The delay in ticks before the task execution.
|
||||
* @return A CancellableTask for managing the scheduled task.
|
||||
*/
|
||||
CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delayTicks);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class implements the StorageManager interface and is responsible for managing player data storage.
|
||||
* It includes methods to handle player data retrieval, storage, and serialization.
|
||||
*/
|
||||
public class StorageManagerImpl implements StorageManager, Listener {
|
||||
|
||||
private final CustomFishingPlugin plugin;
|
||||
@@ -81,9 +85,14 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the storage manager configuration.
|
||||
*/
|
||||
public void reload() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
this.uniqueID = config.getString("unique-server-id", "default");
|
||||
|
||||
// Check if storage type has changed and reinitialize if necessary
|
||||
StorageType storageType = StorageType.valueOf(config.getString("data-storage-method", "H2"));
|
||||
if (storageType != previousType) {
|
||||
if (this.dataSource != null) this.dataSource.disable();
|
||||
@@ -100,19 +109,27 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
if (this.dataSource != null) this.dataSource.initialize();
|
||||
else LogUtils.severe("No storage type is set.");
|
||||
}
|
||||
|
||||
// Handle Redis configuration
|
||||
if (!this.hasRedis && config.getBoolean("Redis.enable", false)) {
|
||||
this.hasRedis = true;
|
||||
this.redisManager = new RedisManager(plugin);
|
||||
this.redisManager.initialize();
|
||||
}
|
||||
|
||||
// Disable Redis if it was enabled but is now disabled
|
||||
if (this.hasRedis && !config.getBoolean("Redis.enable", false) && this.redisManager != null) {
|
||||
this.redisManager.disable();
|
||||
this.redisManager = null;
|
||||
}
|
||||
|
||||
// Cancel any existing timerSaveTask
|
||||
if (this.timerSaveTask != null && !this.timerSaveTask.isCancelled()) {
|
||||
this.timerSaveTask.cancel();
|
||||
}
|
||||
if (CFConfig.dataSaveInterval != -1)
|
||||
|
||||
// Schedule periodic data saving if dataSaveInterval is configured
|
||||
if (CFConfig.dataSaveInterval != -1 && CFConfig.dataSaveInterval != 0)
|
||||
this.timerSaveTask = this.plugin.getScheduler().runTaskAsyncTimer(
|
||||
() -> {
|
||||
long time1 = System.currentTimeMillis();
|
||||
@@ -125,6 +142,9 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the storage manager and cleans up resources.
|
||||
*/
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this);
|
||||
this.dataSource.updateManyPlayersData(onlineUserMap.values(), true);
|
||||
@@ -135,16 +155,35 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
this.redisManager.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique server identifier.
|
||||
*
|
||||
* @return The unique server identifier.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String getUniqueID() {
|
||||
return uniqueID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an OnlineUser instance for the specified UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return An OnlineUser instance if the player is online, or null if not.
|
||||
*/
|
||||
@Override
|
||||
public OnlineUser getOnlineUser(UUID uuid) {
|
||||
return onlineUserMap.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieves an OfflineUser instance for the specified UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Whether to lock the data during retrieval.
|
||||
* @return A CompletableFuture that resolves to an Optional containing the OfflineUser instance if found, or empty if not found or locked.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean lock) {
|
||||
var optionalDataFuture = dataSource.getPlayerData(uuid, lock);
|
||||
@@ -163,21 +202,38 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockedData(OfflineUser offlineUser) {
|
||||
return OfflineUserImpl.LOCKED_USER == offlineUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously saves user data for an OfflineUser.
|
||||
*
|
||||
* @param offlineUser The OfflineUser whose data needs to be saved.
|
||||
* @param unlock Whether to unlock the data after saving.
|
||||
* @return A CompletableFuture that resolves to a boolean indicating the success of the data saving operation.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> saveUserData(OfflineUser offlineUser, boolean unlock) {
|
||||
return dataSource.updatePlayerData(offlineUser.getUUID(), offlineUser.getPlayerData(), unlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getRedisPlayerCount() {
|
||||
return redisManager.getPlayerCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data source used for data storage.
|
||||
*
|
||||
* @return The data source.
|
||||
*/
|
||||
@Override
|
||||
public DataStorageInterface getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a player joins the server.
|
||||
* Locks the player's data and initiates data retrieval if Redis is not used,
|
||||
* otherwise, it starts a Redis data retrieval task.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
@@ -186,10 +242,21 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
if (!hasRedis) {
|
||||
waitForDataLockRelease(uuid, 1);
|
||||
} else {
|
||||
redisReadingData(uuid);
|
||||
plugin.getScheduler().runTaskAsyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(changeServer -> {
|
||||
if (!changeServer) {
|
||||
waitForDataLockRelease(uuid, 3);
|
||||
} else {
|
||||
new RedisGetDataTask(uuid);
|
||||
}
|
||||
}), 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for when a player quits the server.
|
||||
* If the player is not locked, it removes their OnlineUser instance,
|
||||
* updates the player's data in Redis and the data source.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
@@ -216,17 +283,10 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void redisReadingData(UUID uuid) {
|
||||
// delay 0.5s for another server to insert the key
|
||||
plugin.getScheduler().runTaskAsyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(changeServer -> {
|
||||
if (!changeServer) {
|
||||
waitForDataLockRelease(uuid, 3);
|
||||
} else {
|
||||
new RedisGetDataTask(uuid);
|
||||
}
|
||||
}), 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable task for asynchronously retrieving data from Redis.
|
||||
* Retries up to 6 times and cancels the task if the player is offline.
|
||||
*/
|
||||
public class RedisGetDataTask implements Runnable {
|
||||
|
||||
private final UUID uuid;
|
||||
@@ -255,21 +315,25 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
if (optionalData.isPresent()) {
|
||||
putDataInCache(player, optionalData.get());
|
||||
task.cancel();
|
||||
if (CFConfig.lockData) dataSource.lockPlayerData(uuid, true);
|
||||
if (CFConfig.lockData) dataSource.lockOrUnlockPlayerData(uuid, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// wait 1 second for the lock to release
|
||||
// try three times at most
|
||||
/**
|
||||
* Waits for data lock release with a delay and a maximum of three retries.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param times The number of times this method has been retried.
|
||||
*/
|
||||
public void waitForDataLockRelease(UUID uuid, int times) {
|
||||
plugin.getScheduler().runTaskAsyncLater(() -> {
|
||||
var player = Bukkit.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline() || times > 3)
|
||||
return;
|
||||
this.dataSource.getPlayerData(uuid, CFConfig.lockData).thenAccept(optionalData -> {
|
||||
// should not be empty
|
||||
// Data should not be empty
|
||||
if (optionalData.isEmpty())
|
||||
return;
|
||||
if (optionalData.get() == PlayerData.LOCKED) {
|
||||
@@ -281,38 +345,80 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts player data in cache and removes the player from the locked set.
|
||||
*
|
||||
* @param player The player whose data is being cached.
|
||||
* @param playerData The data to be cached.
|
||||
*/
|
||||
public void putDataInCache(Player player, PlayerData playerData) {
|
||||
locked.remove(player.getUniqueId());
|
||||
OnlineUserImpl bukkitUser = new OnlineUserImpl(player, playerData);
|
||||
onlineUserMap.put(player.getUniqueId(), bukkitUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Redis is enabled.
|
||||
*
|
||||
* @return True if Redis is enabled; otherwise, false.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRedisEnabled() {
|
||||
return hasRedis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RedisManager instance.
|
||||
*
|
||||
* @return The RedisManager instance.
|
||||
*/
|
||||
@Nullable
|
||||
public RedisManager getRedisManager() {
|
||||
return redisManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PlayerData to bytes.
|
||||
*
|
||||
* @param data The PlayerData to be converted.
|
||||
* @return The byte array representation of PlayerData.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public byte[] toBytes(@NotNull PlayerData data) {
|
||||
return toJson(data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PlayerData to JSON format.
|
||||
*
|
||||
* @param data The PlayerData to be converted.
|
||||
* @return The JSON string representation of PlayerData.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public String toJson(@NotNull PlayerData data) {
|
||||
return gson.toJson(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts JSON string to PlayerData.
|
||||
*
|
||||
* @param json The JSON string to be converted.
|
||||
* @return The PlayerData object.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public PlayerData fromJson(String json) {
|
||||
return gson.fromJson(json, PlayerData.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes to PlayerData.
|
||||
*
|
||||
* @param data The byte array to be converted.
|
||||
* @return The PlayerData object.
|
||||
*/
|
||||
@Override
|
||||
@NotNull
|
||||
public PlayerData fromBytes(byte[] data) {
|
||||
@@ -323,6 +429,9 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom exception class for data serialization errors.
|
||||
*/
|
||||
public static class DataSerializationException extends RuntimeException {
|
||||
protected DataSerializationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
|
||||
@@ -27,6 +27,9 @@ import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* An abstract class that implements the DataStorageInterface and provides common functionality for data storage.
|
||||
*/
|
||||
public abstract class AbstractStorage implements DataStorageInterface {
|
||||
|
||||
protected CustomFishingPlugin plugin;
|
||||
@@ -37,31 +40,44 @@ public abstract class AbstractStorage implements DataStorageInterface {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
// This method can be overridden in subclasses to perform initialization tasks specific to the storage type.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
|
||||
// This method can be overridden in subclasses to perform cleanup or shutdown tasks specific to the storage type.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time in seconds since the Unix epoch.
|
||||
*
|
||||
* @return The current time in seconds.
|
||||
*/
|
||||
public int getCurrentSeconds() {
|
||||
return (int) Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends OfflineUser> users, boolean unlock) {
|
||||
// Update data for multiple players by iterating through the collection of OfflineUser objects.
|
||||
for (OfflineUser user : users) {
|
||||
this.updatePlayerData(user.getUUID(), user.getPlayerData(), unlock);
|
||||
}
|
||||
}
|
||||
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
|
||||
/**
|
||||
* Lock or unlock player data based on the provided UUID and lock flag.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock True to lock the player data, false to unlock it.
|
||||
*/
|
||||
public void lockOrUnlockPlayerData(UUID uuid, boolean lock) {
|
||||
// Note: Only remote database would override this method
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
// By default, delegate to the updatePlayerData method to update or insert player data.
|
||||
return updatePlayerData(uuid, playerData, unlock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* An implementation of AbstractStorage that uses MongoDB for player data storage.
|
||||
*/
|
||||
public class MongoDBImpl extends AbstractStorage {
|
||||
|
||||
private MongoClient mongoClient;
|
||||
@@ -49,6 +52,9 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the MongoDB connection and configuration based on the plugin's YAML configuration.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
@@ -83,6 +89,9 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
this.database = mongoClient.getDatabase(section.getString("database", "minecraft"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the MongoDB connection by closing the MongoClient.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
if (this.mongoClient != null) {
|
||||
@@ -90,10 +99,21 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
public String getCollectionName(String sub) {
|
||||
return getCollectionPrefix() + "_" + sub;
|
||||
/**
|
||||
* Get the collection name for a specific subcategory of data.
|
||||
*
|
||||
* @param value The subcategory identifier.
|
||||
* @return The full collection name including the prefix.
|
||||
*/
|
||||
public String getCollectionName(String value) {
|
||||
return getCollectionPrefix() + "_" + value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection prefix used for MongoDB collections.
|
||||
*
|
||||
* @return The collection prefix.
|
||||
*/
|
||||
public String getCollectionPrefix() {
|
||||
return collectionPrefix;
|
||||
}
|
||||
@@ -103,6 +123,13 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
return StorageType.MongoDB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieve player data from the MongoDB database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Flag indicating whether to lock the data.
|
||||
* @return A CompletableFuture with an optional PlayerData.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
@@ -111,7 +138,7 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
Document doc = collection.find(Filters.eq("uuid", uuid)).first();
|
||||
if (doc == null) {
|
||||
if (Bukkit.getPlayer(uuid) != null) {
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(PlayerData.empty()));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
@@ -122,13 +149,21 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
return;
|
||||
}
|
||||
Binary binary = (Binary) doc.get("data");
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(binary.getData())));
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously update player data in the MongoDB database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player's data to update.
|
||||
* @param unlock Flag indicating whether to unlock the data.
|
||||
* @return A CompletableFuture indicating the update result.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
@@ -149,6 +184,12 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously update data for multiple players in the MongoDB database.
|
||||
*
|
||||
* @param users A collection of OfflineUser instances to update.
|
||||
* @param unlock Flag indicating whether to unlock the data.
|
||||
*/
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends OfflineUser> users, boolean unlock) {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
@@ -170,8 +211,14 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock or unlock player data in the MongoDB database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Flag indicating whether to lock or unlock the data.
|
||||
*/
|
||||
@Override
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
public void lockOrUnlockPlayerData(UUID uuid, boolean lock) {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Document query = new Document("uuid", uuid);
|
||||
@@ -183,6 +230,12 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of unique player UUIDs from the MongoDB database.
|
||||
*
|
||||
* @param legacy Flag indicating whether to retrieve legacy data.
|
||||
* @return A set of unique player UUIDs.
|
||||
*/
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
// no legacy files
|
||||
|
||||
@@ -37,6 +37,9 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A RedisManager class responsible for managing interactions with a Redis server for data storage.
|
||||
*/
|
||||
public class RedisManager extends AbstractStorage {
|
||||
|
||||
private static RedisManager instance;
|
||||
@@ -44,7 +47,6 @@ public class RedisManager extends AbstractStorage {
|
||||
private String password;
|
||||
private int port;
|
||||
private String host;
|
||||
private JedisPoolConfig jedisPoolConfig;
|
||||
private boolean useSSL;
|
||||
|
||||
public RedisManager(CustomFishingPlugin plugin) {
|
||||
@@ -52,14 +54,27 @@ public class RedisManager extends AbstractStorage {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of the RedisManager.
|
||||
*
|
||||
* @return The RedisManager instance.
|
||||
*/
|
||||
public static RedisManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Jedis resource for interacting with the Redis server.
|
||||
*
|
||||
* @return A Jedis resource.
|
||||
*/
|
||||
public Jedis getJedis() {
|
||||
return jedisPool.getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Redis connection and configuration based on the plugin's YAML configuration.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
@@ -69,7 +84,7 @@ public class RedisManager extends AbstractStorage {
|
||||
return;
|
||||
}
|
||||
|
||||
jedisPoolConfig = new JedisPoolConfig();
|
||||
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||
jedisPoolConfig.setTestWhileIdle(true);
|
||||
jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000));
|
||||
jedisPoolConfig.setNumTestsPerEvictionRun(-1);
|
||||
@@ -99,13 +114,21 @@ public class RedisManager extends AbstractStorage {
|
||||
subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the Redis connection by closing the JedisPool.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
this.removeServerPlayers(plugin.getStorageManager().getUniqueID());
|
||||
if (jedisPool != null && !jedisPool.isClosed())
|
||||
jedisPool.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to Redis on a specified channel.
|
||||
*
|
||||
* @param channel The Redis channel to send the message to.
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void sendRedisMessage(@NotNull String channel, @NotNull String message) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.publish(channel, message);
|
||||
@@ -113,6 +136,9 @@ public class RedisManager extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to Redis messages on a separate thread and handle received messages.
|
||||
*/
|
||||
private void subscribe() {
|
||||
Thread thread = new Thread(() -> {
|
||||
try (final Jedis jedis = password.isBlank() ?
|
||||
@@ -160,38 +186,12 @@ public class RedisManager extends AbstractStorage {
|
||||
return StorageType.Redis;
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> getPlayerCount() {
|
||||
var future = new CompletableFuture<Integer>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
int players = 0;
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
var list = jedis.zrangeWithScores("cf_players",0, -1);
|
||||
for (Tuple tuple : list) {
|
||||
players += (int) tuple.getScore();
|
||||
}
|
||||
}
|
||||
future.complete(players);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> setServerPlayers(int amount, String unique) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.zadd("cf_players", amount, unique);
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public void removeServerPlayers(String unique) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.zrem("cf_players", unique);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a "change server" flag for a specified player UUID in Redis.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return A CompletableFuture indicating the operation's completion.
|
||||
*/
|
||||
public CompletableFuture<Void> setChangeServer(UUID uuid) {
|
||||
var future = new CompletableFuture<Void>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
@@ -208,6 +208,12 @@ public class RedisManager extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "change server" flag for a specified player UUID from Redis and remove it.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return A CompletableFuture with a Boolean indicating whether the flag was set.
|
||||
*/
|
||||
public CompletableFuture<Boolean> getChangeServer(UUID uuid) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
@@ -227,6 +233,13 @@ public class RedisManager extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieve player data from Redis.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Flag indicating whether to lock the data.
|
||||
* @return A CompletableFuture with an optional PlayerData.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
@@ -250,6 +263,14 @@ public class RedisManager extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously update player data in Redis.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player's data to update.
|
||||
* @param ignore Flag indicating whether to ignore the update (not used).
|
||||
* @return A CompletableFuture indicating the update result.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
@@ -270,11 +291,25 @@ public class RedisManager extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of unique player UUIDs from Redis (Returns an empty set).
|
||||
* This method is designed for importing and exporting so it would not actually be called.
|
||||
*
|
||||
* @param legacy Flag indicating whether to retrieve legacy data (not used).
|
||||
* @return An empty set of UUIDs.
|
||||
*/
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Redis key for a specified key and UUID.
|
||||
*
|
||||
* @param key The key identifier.
|
||||
* @param uuid The UUID to include in the key.
|
||||
* @return A byte array representing the Redis key.
|
||||
*/
|
||||
private byte[] getRedisKey(String key, @NotNull UUID uuid) {
|
||||
return (key + ":" + uuid).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* An abstract base class for SQL databases using the HikariCP connection pool, which handles player data storage.
|
||||
*/
|
||||
public abstract class AbstractHikariDatabase extends AbstractSQLDatabase implements LegacyDataStorageInterface {
|
||||
|
||||
private HikariDataSource dataSource;
|
||||
@@ -58,6 +61,9 @@ public abstract class AbstractHikariDatabase extends AbstractSQLDatabase impleme
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database connection pool and create tables if they don't exist.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
@@ -109,17 +115,32 @@ public abstract class AbstractHikariDatabase extends AbstractSQLDatabase impleme
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the database by closing the connection pool.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
if (dataSource != null && !dataSource.isClosed())
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection to the SQL database from the connection pool.
|
||||
*
|
||||
* @return A database connection.
|
||||
* @throws SQLException If there is an error establishing a connection.
|
||||
*/
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve legacy player data from the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return A CompletableFuture containing the optional legacy player data.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getLegacyPlayerData(UUID uuid) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
|
||||
@@ -33,6 +33,9 @@ import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* An abstract base class for SQL database implementations that handle player data storage.
|
||||
*/
|
||||
public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
|
||||
protected String tablePrefix;
|
||||
@@ -41,8 +44,17 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection to the SQL database.
|
||||
*
|
||||
* @return A database connection.
|
||||
* @throws SQLException If there is an error establishing a connection.
|
||||
*/
|
||||
public abstract Connection getConnection() throws SQLException;
|
||||
|
||||
/**
|
||||
* Create tables for storing data if they don't exist in the database.
|
||||
*/
|
||||
public void createTableIfNotExist() {
|
||||
try (Connection connection = getConnection()) {
|
||||
final String[] databaseSchema = getSchema(getStorageType().name().toLowerCase(Locale.ENGLISH));
|
||||
@@ -60,23 +72,54 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL schema from a resource file.
|
||||
*
|
||||
* @param fileName The name of the schema file.
|
||||
* @return An array of SQL statements to create tables.
|
||||
* @throws IOException If there is an error reading the schema resource.
|
||||
*/
|
||||
private String[] getSchema(@NotNull String fileName) throws IOException {
|
||||
return replaceSchemaPlaceholder(new String(Objects.requireNonNull(plugin.getResource("schema/" + fileName + ".sql"))
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace placeholder values in SQL schema with the table prefix.
|
||||
*
|
||||
* @param sql The SQL schema string.
|
||||
* @return The SQL schema string with placeholders replaced.
|
||||
*/
|
||||
private String replaceSchemaPlaceholder(@NotNull String sql) {
|
||||
return sql.replace("{prefix}", tablePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of a database table based on a sub-table name and the table prefix.
|
||||
*
|
||||
* @param sub The sub-table name.
|
||||
* @return The full table name.
|
||||
*/
|
||||
public String getTableName(String sub) {
|
||||
return getTablePrefix() + "_" + sub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current table prefix.
|
||||
*
|
||||
* @return The table prefix.
|
||||
*/
|
||||
public String getTablePrefix() {
|
||||
return tablePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player's data from the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Whether to lock the player data during retrieval.
|
||||
* @return A CompletableFuture containing the optional player data.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
@@ -98,7 +141,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
final Blob blob = rs.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
@@ -115,6 +158,14 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a player's data in the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player data to update.
|
||||
* @param unlock Whether to unlock the player data after updating.
|
||||
* @return A CompletableFuture indicating the success of the update.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
@@ -137,6 +188,12 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data for multiple players in the SQL database.
|
||||
*
|
||||
* @param users A collection of OfflineUser objects representing players.
|
||||
* @param unlock Whether to unlock the player data after updating.
|
||||
*/
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends OfflineUser> users, boolean unlock) {
|
||||
String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"));
|
||||
@@ -160,6 +217,13 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new player's data into the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player data to insert.
|
||||
* @param lock Whether to lock the player data upon insertion.
|
||||
*/
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
@@ -174,8 +238,14 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock or unlock a player's data in the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Whether to lock or unlock the player data.
|
||||
*/
|
||||
@Override
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
public void lockOrUnlockPlayerData(UUID uuid, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_LOCK_BY_UUID, getTableName("data")))
|
||||
@@ -188,6 +258,14 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or insert a player's data into the SQL database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player data to update or insert.
|
||||
* @param unlock Whether to unlock the player data after updating or inserting.
|
||||
* @return A CompletableFuture indicating the success of the operation.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
@@ -211,6 +289,12 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of unique user UUIDs from the SQL database.
|
||||
*
|
||||
* @param legacy Whether to include legacy data in the retrieval.
|
||||
* @return A set of unique user UUIDs.
|
||||
*/
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
@@ -228,6 +312,9 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
return uuids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants defining SQL statements used for database operations.
|
||||
*/
|
||||
public static class SqlConstants {
|
||||
public static final String SQL_SELECT_BY_UUID = "SELECT * FROM `%s` WHERE `uuid` = ?";
|
||||
public static final String SQL_SELECT_ALL_UUID = "SELECT uuid FROM `%s`";
|
||||
|
||||
@@ -26,6 +26,9 @@ import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* An implementation of AbstractSQLDatabase that uses the H2 embedded database for player data storage.
|
||||
*/
|
||||
public class H2Impl extends AbstractSQLDatabase {
|
||||
|
||||
private JdbcConnectionPool connectionPool;
|
||||
@@ -34,6 +37,9 @@ public class H2Impl extends AbstractSQLDatabase {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the H2 database and connection pool based on the configuration.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
@@ -45,6 +51,9 @@ public class H2Impl extends AbstractSQLDatabase {
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the H2 database by disposing of the connection pool.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
if (connectionPool != null) {
|
||||
|
||||
@@ -35,6 +35,9 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* An implementation of AbstractSQLDatabase that uses the SQLite database for player data storage.
|
||||
*/
|
||||
public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
|
||||
private Connection connection;
|
||||
@@ -44,6 +47,9 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the SQLite database and connection based on the configuration.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
YamlConfiguration config = plugin.getConfig("database.yml");
|
||||
@@ -52,6 +58,9 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the SQLite database by closing the connection.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
try {
|
||||
@@ -67,6 +76,12 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
return StorageType.SQLite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a connection to the SQLite database.
|
||||
*
|
||||
* @return A database connection.
|
||||
* @throws SQLException If there is an error establishing a connection.
|
||||
*/
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
@@ -75,6 +90,13 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously retrieve player data from the SQLite database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param lock Flag indicating whether to lock the data.
|
||||
* @return A CompletableFuture with an optional PlayerData.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
@@ -94,7 +116,7 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
return;
|
||||
}
|
||||
final byte[] dataByteArray = rs.getBytes("data");
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
@@ -111,6 +133,14 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously update player data in the SQLite database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player's data to update.
|
||||
* @param unlock Flag indicating whether to unlock the data.
|
||||
* @return A CompletableFuture indicating the update result.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
@@ -132,6 +162,12 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously update data for multiple players in the SQLite database.
|
||||
*
|
||||
* @param users A collection of OfflineUser instances to update.
|
||||
* @param unlock Flag indicating whether to unlock the data.
|
||||
*/
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends OfflineUser> users, boolean unlock) {
|
||||
String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"));
|
||||
@@ -155,6 +191,13 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert player data into the SQLite database.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param playerData The player's data to insert.
|
||||
* @param lock Flag indicating whether to lock the data.
|
||||
*/
|
||||
@Override
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
@@ -170,6 +213,10 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the connection to the SQLite database.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void setConnection() {
|
||||
try {
|
||||
if (!databaseFile.exists()) databaseFile.createNewFile();
|
||||
|
||||
@@ -35,6 +35,9 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A data storage implementation that uses JSON files to store player data.
|
||||
*/
|
||||
public class JsonImpl extends AbstractStorage {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@@ -69,10 +72,22 @@ public class JsonImpl extends AbstractStorage {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file associated with a player's UUID for storing JSON data.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return The file for the player's data.
|
||||
*/
|
||||
public File getPlayerDataFile(UUID uuid) {
|
||||
return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an object to a JSON file.
|
||||
*
|
||||
* @param obj The object to be saved as JSON.
|
||||
* @param filepath The file path where the JSON file should be saved.
|
||||
*/
|
||||
public void saveToJsonFile(Object obj, File filepath) {
|
||||
Gson gson = new Gson();
|
||||
try (FileWriter file = new FileWriter(filepath)) {
|
||||
@@ -82,12 +97,27 @@ public class JsonImpl extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read JSON content from a file and parse it into an object of the specified class.
|
||||
*
|
||||
* @param file The JSON file to read.
|
||||
* @param classOfT The class of the object to parse the JSON into.
|
||||
* @param <T> The type of the object.
|
||||
* @return The parsed object.
|
||||
*/
|
||||
public <T> T readFromJsonFile(File file, Class<T> classOfT) {
|
||||
Gson gson = new Gson();
|
||||
String jsonContent = new String(readFileToByteArray(file), StandardCharsets.UTF_8);
|
||||
return gson.fromJson(jsonContent, classOfT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the contents of a file and return them as a byte array.
|
||||
*
|
||||
* @param file The file to read.
|
||||
* @return The byte array representing the file's content.
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public byte[] readFileToByteArray(File file) {
|
||||
byte[] fileBytes = new byte[(int) file.length()];
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
@@ -98,6 +128,7 @@ public class JsonImpl extends AbstractStorage {
|
||||
return fileBytes;
|
||||
}
|
||||
|
||||
// Retrieve a set of unique user UUIDs based on JSON data files in the 'data' folder.
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
// No legacy files
|
||||
|
||||
@@ -31,6 +31,9 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* A data storage implementation that uses YAML files to store player data, with support for legacy data.
|
||||
*/
|
||||
public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterface {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@@ -45,6 +48,12 @@ public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterf
|
||||
return StorageType.YAML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file associated with a player's UUID for storing YAML data.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @return The file for the player's data.
|
||||
*/
|
||||
public File getPlayerDataFile(UUID uuid) {
|
||||
return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".yml");
|
||||
}
|
||||
@@ -109,7 +118,13 @@ public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterf
|
||||
return uuids;
|
||||
}
|
||||
|
||||
public StatisticData getStatistics(ConfigurationSection section) {
|
||||
/**
|
||||
* Parse statistics data from a YAML ConfigurationSection.
|
||||
*
|
||||
* @param section The ConfigurationSection containing statistics data.
|
||||
* @return The parsed StatisticData object.
|
||||
*/
|
||||
private StatisticData getStatistics(ConfigurationSection section) {
|
||||
if (section == null)
|
||||
return StatisticData.empty();
|
||||
else {
|
||||
@@ -123,6 +138,7 @@ public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterf
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getLegacyPlayerData(UUID uuid) {
|
||||
// Retrieve legacy player data (YAML format) for a given UUID.
|
||||
var builder = new PlayerData.Builder().setName("");
|
||||
File bagFile = new File(plugin.getDataFolder(), "data/fishingbag/" + uuid + ".yml");
|
||||
if (bagFile.exists()) {
|
||||
|
||||
@@ -36,6 +36,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of the OfflineUser interface for representing offline player data.
|
||||
*/
|
||||
public class OfflineUserImpl implements OfflineUser {
|
||||
|
||||
private final UUID uuid;
|
||||
@@ -43,13 +46,22 @@ public class OfflineUserImpl implements OfflineUser {
|
||||
private final FishingBagHolder holder;
|
||||
private final EarningData earningData;
|
||||
private final Statistics statistics;
|
||||
public static OfflineUserImpl LOCKED_USER = new OfflineUserImpl(UUID.randomUUID(), "", PlayerData.empty());
|
||||
public static OfflineUserImpl LOCKED_USER = new OfflineUserImpl(UUID.randomUUID(), "-locked-", PlayerData.empty());
|
||||
|
||||
/**
|
||||
* Constructor to create an OfflineUserImpl instance.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param name The name of the player.
|
||||
* @param playerData The player's data, including bag contents, earnings, and statistics.
|
||||
*/
|
||||
public OfflineUserImpl(UUID uuid, String name, PlayerData playerData) {
|
||||
this.name = name;
|
||||
this.uuid = uuid;
|
||||
this.holder = new FishingBagHolder(uuid);
|
||||
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
|
||||
|
||||
// Set up the inventory for the FishingBagHolder
|
||||
this.holder.setInventory(InventoryUtils.createInventory(this.holder, playerData.getBagData().size,
|
||||
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
|
||||
PlaceholderManagerImpl.getInstance().parse(
|
||||
@@ -57,6 +69,7 @@ public class OfflineUserImpl implements OfflineUser {
|
||||
)
|
||||
)));
|
||||
this.holder.setItems(InventoryUtils.getInventoryItems(playerData.getBagData().serialized));
|
||||
|
||||
this.earningData = playerData.getEarningData();
|
||||
this.statistics = new Statistics(playerData.getStatistics());
|
||||
}
|
||||
@@ -94,6 +107,7 @@ public class OfflineUserImpl implements OfflineUser {
|
||||
|
||||
@Override
|
||||
public PlayerData getPlayerData() {
|
||||
// Create a new PlayerData instance based on the stored information
|
||||
return new PlayerData.Builder()
|
||||
.setBagData(new InventoryData(InventoryUtils.stacksToBase64(holder.getInventory().getStorageContents()), holder.getInventory().getSize()))
|
||||
.setEarningData(earningData)
|
||||
|
||||
@@ -21,10 +21,19 @@ import net.momirealms.customfishing.api.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.data.user.OnlineUser;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Implementation of the OnlineUser interface, extending OfflineUserImpl to represent online player data.
|
||||
*/
|
||||
public class OnlineUserImpl extends OfflineUserImpl implements OnlineUser {
|
||||
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* Constructor to create an OnlineUserImpl instance.
|
||||
*
|
||||
* @param player The online player associated with this user.
|
||||
* @param playerData The player's data, including bag contents, earnings, and statistics.
|
||||
*/
|
||||
public OnlineUserImpl(Player player, PlayerData playerData) {
|
||||
super(player.getUniqueId(), player.getName(), playerData);
|
||||
this.player = player;
|
||||
|
||||
@@ -33,14 +33,30 @@ import org.bukkit.inventory.ItemStack;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Utility class for managing armor stands and sending related packets.
|
||||
*/
|
||||
public class ArmorStandUtils {
|
||||
|
||||
/**
|
||||
* Creates a destroy packet for removing an armor stand entity.
|
||||
*
|
||||
* @param id The ID of the armor stand entity to destroy
|
||||
* @return The PacketContainer representing the destroy packet
|
||||
*/
|
||||
public static PacketContainer getDestroyPacket(int id) {
|
||||
PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
|
||||
destroyPacket.getIntLists().write(0, List.of(id));
|
||||
return destroyPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spawn packet for an armor stand entity at the specified location.
|
||||
*
|
||||
* @param id The ID of the armor stand entity to spawn
|
||||
* @param location The location where the armor stand entity should be spawned
|
||||
* @return The PacketContainer representing the spawn packet
|
||||
*/
|
||||
public static PacketContainer getSpawnPacket(int id, Location location) {
|
||||
PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
|
||||
entityPacket.getModifier().write(0, id);
|
||||
@@ -52,6 +68,12 @@ public class ArmorStandUtils {
|
||||
return entityPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metadata packet for updating the metadata of an armor stand entity.
|
||||
*
|
||||
* @param id The ID of the armor stand entity
|
||||
* @return The PacketContainer representing the metadata packet
|
||||
*/
|
||||
public static PacketContainer getMetaPacket(int id) {
|
||||
PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||
metaPacket.getIntegers().write(0, id);
|
||||
@@ -64,6 +86,13 @@ public class ArmorStandUtils {
|
||||
return metaPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value list in a PacketContainer's DataWatcher from a WrappedDataWatcher.
|
||||
*
|
||||
* @param metaPacket The PacketContainer representing the metadata packet
|
||||
* @param wrappedDataWatcher The WrappedDataWatcher containing the value list
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void setValueList(PacketContainer metaPacket, WrappedDataWatcher wrappedDataWatcher) {
|
||||
List<WrappedDataValue> wrappedDataValueList = Lists.newArrayList();
|
||||
wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> {
|
||||
@@ -73,6 +102,13 @@ public class ArmorStandUtils {
|
||||
metaPacket.getDataValueCollectionModifier().write(0, wrappedDataValueList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metadata packet for updating the metadata of an armor stand entity with a custom Component.
|
||||
*
|
||||
* @param id The ID of the armor stand entity
|
||||
* @param component The Component to set as metadata
|
||||
* @return The PacketContainer representing the metadata packet
|
||||
*/
|
||||
public static PacketContainer getMetaPacket(int id, Component component) {
|
||||
PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||
metaPacket.getIntegers().write(0, id);
|
||||
@@ -85,6 +121,11 @@ public class ArmorStandUtils {
|
||||
return metaPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DataWatcher for an invisible armor stand entity.
|
||||
*
|
||||
* @return The created DataWatcher
|
||||
*/
|
||||
public static WrappedDataWatcher createDataWatcher() {
|
||||
WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher();
|
||||
WrappedDataWatcher.Serializer serializer1 = WrappedDataWatcher.Registry.get(Boolean.class);
|
||||
@@ -95,6 +136,12 @@ public class ArmorStandUtils {
|
||||
return wrappedDataWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DataWatcher for an invisible armor stand entity with a custom Component.
|
||||
*
|
||||
* @param component The Component to set in the DataWatcher
|
||||
* @return The created DataWatcher
|
||||
*/
|
||||
public static WrappedDataWatcher createDataWatcher(Component component) {
|
||||
WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher();
|
||||
WrappedDataWatcher.Serializer serializer1 = WrappedDataWatcher.Registry.get(Boolean.class);
|
||||
@@ -107,6 +154,13 @@ public class ArmorStandUtils {
|
||||
return wrappedDataWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an equipment packet for equipping an armor stand with an ItemStack.
|
||||
*
|
||||
* @param id The ID of the armor stand entity
|
||||
* @param itemStack The ItemStack to equip
|
||||
* @return The PacketContainer representing the equipment packet
|
||||
*/
|
||||
public static PacketContainer getEquipPacket(int id, ItemStack itemStack) {
|
||||
PacketContainer equipPacket = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT);
|
||||
equipPacket.getIntegers().write(0, id);
|
||||
@@ -116,18 +170,34 @@ public class ArmorStandUtils {
|
||||
return equipPacket;
|
||||
}
|
||||
|
||||
public static void sendFakeItem(Player player, Location location, ItemStack itemStack, int time) {
|
||||
/**
|
||||
* Sends a fake armor stand entity with item on head to a player at the specified location.
|
||||
*
|
||||
* @param player The player to send the entity to
|
||||
* @param location The location where the entity should appear
|
||||
* @param itemStack The ItemStack to represent the entity
|
||||
* @param seconds The duration (in seconds) the entity should be displayed
|
||||
*/
|
||||
public static void sendFakeItem(Player player, Location location, ItemStack itemStack, int seconds) {
|
||||
int id = new Random().nextInt(Integer.MAX_VALUE);
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getSpawnPacket(id, location.clone().subtract(0,1,0)));
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getMetaPacket(id));
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getEquipPacket(id, itemStack));
|
||||
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), time * 50L, TimeUnit.MILLISECONDS);
|
||||
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), seconds * 50L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void sendHologram(Player player, Location location, Component component, int time) {
|
||||
/**
|
||||
* Sends a hologram (armor stand with custom text) to a player at the specified location.
|
||||
*
|
||||
* @param player The player to send the hologram to
|
||||
* @param location The location where the hologram should appear
|
||||
* @param component The Component representing the hologram's text
|
||||
* @param seconds The duration (in seconds) the hologram should be displayed
|
||||
*/
|
||||
public static void sendHologram(Player player, Location location, Component component, int seconds) {
|
||||
int id = new Random().nextInt(Integer.MAX_VALUE);
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getSpawnPacket(id, location.clone().subtract(0,1,0)));
|
||||
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getMetaPacket(id, component));
|
||||
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), time * 50L, TimeUnit.MILLISECONDS);
|
||||
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getDestroyPacket(id)), seconds * 50L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,16 @@ import java.util.jar.JarInputStream;
|
||||
|
||||
public class ClassUtils {
|
||||
|
||||
/**
|
||||
* Attempts to find a class within a JAR file that extends or implements a given class or interface.
|
||||
*
|
||||
* @param file The JAR file in which to search for the class.
|
||||
* @param clazz The base class or interface to match against.
|
||||
* @param <T> The type of the base class or interface.
|
||||
* @return A Class object representing the found class, or null if not found.
|
||||
* @throws IOException If there is an issue reading the JAR file.
|
||||
* @throws ClassNotFoundException If the specified class cannot be found.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> Class<? extends T> findClass(
|
||||
@NotNull File file,
|
||||
|
||||
@@ -24,9 +24,17 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class CompletableFutures {
|
||||
public class CompletableFutures {
|
||||
|
||||
private CompletableFutures() {}
|
||||
|
||||
/**
|
||||
* A collector for collecting a stream of CompletableFuture instances into a single CompletableFuture that completes
|
||||
* when all of the input CompletableFutures complete.
|
||||
*
|
||||
* @param <T> The type of CompletableFuture.
|
||||
* @return A collector for CompletableFuture instances.
|
||||
*/
|
||||
public static <T extends CompletableFuture<?>> Collector<T, ImmutableList.Builder<T>, CompletableFuture<Void>> collector() {
|
||||
return Collector.of(
|
||||
ImmutableList.Builder::new,
|
||||
@@ -36,11 +44,25 @@ public final class CompletableFutures {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
|
||||
* CompletableFutures complete.
|
||||
*
|
||||
* @param futures A stream of CompletableFuture instances.
|
||||
* @return A CompletableFuture that completes when all input CompletableFutures complete.
|
||||
*/
|
||||
public static CompletableFuture<Void> allOf(Stream<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
|
||||
* CompletableFutures complete.
|
||||
*
|
||||
* @param futures A collection of CompletableFuture instances.
|
||||
* @return A CompletableFuture that completes when all input CompletableFutures complete.
|
||||
*/
|
||||
public static CompletableFuture<Void> allOf(Collection<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(new CompletableFuture[0]);
|
||||
return CompletableFuture.allOf(arr);
|
||||
|
||||
@@ -31,8 +31,17 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class for configuration-related operations.
|
||||
*/
|
||||
public class ConfigUtils {
|
||||
|
||||
/**
|
||||
* Converts an object into an ArrayList of strings.
|
||||
*
|
||||
* @param object The input object
|
||||
* @return An ArrayList of strings
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static ArrayList<String> stringListArgs(Object object) {
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
@@ -46,11 +55,23 @@ public class ConfigUtils {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into a pair of integers using the "~" delimiter.
|
||||
*
|
||||
* @param value The input string
|
||||
* @return A Pair of integers
|
||||
*/
|
||||
public static Pair<Integer, Integer> splitStringIntegerArgs(String value) {
|
||||
String[] split = value.split("~");
|
||||
return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of strings in the format "key:value" into a list of Pairs with keys and doubles.
|
||||
*
|
||||
* @param list The input list of strings
|
||||
* @return A list of Pairs containing keys and doubles
|
||||
*/
|
||||
public static List<Pair<String, Double>> getWeights(List<String> list) {
|
||||
List<Pair<String, Double>> result = new ArrayList<>(list.size());
|
||||
for (String member : list) {
|
||||
@@ -61,6 +82,12 @@ public class ConfigUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object into a double value.
|
||||
*
|
||||
* @param arg The input object
|
||||
* @return A double value
|
||||
*/
|
||||
public static double getDoubleValue(Object arg) {
|
||||
if (arg instanceof Double d) {
|
||||
return d;
|
||||
@@ -70,6 +97,12 @@ public class ConfigUtils {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of strings in the format "key:value" into a list of Pairs with keys and WeightModifiers.
|
||||
*
|
||||
* @param modList The input list of strings
|
||||
* @return A list of Pairs containing keys and WeightModifiers
|
||||
*/
|
||||
public static List<Pair<String, WeightModifier>> getModifiers(List<String> modList) {
|
||||
List<Pair<String, WeightModifier>> result = new ArrayList<>(modList.size());
|
||||
for (String member : modList) {
|
||||
@@ -81,9 +114,10 @@ public class ConfigUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a data file if not exists
|
||||
* @param file file path
|
||||
* @return yaml data
|
||||
* Reads data from a YAML configuration file and creates it if it doesn't exist.
|
||||
*
|
||||
* @param file The file path
|
||||
* @return The YamlConfiguration
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static YamlConfiguration readData(File file) {
|
||||
@@ -99,6 +133,13 @@ public class ConfigUtils {
|
||||
return YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a WeightModifier from a string representation.
|
||||
*
|
||||
* @param text The input string
|
||||
* @return A WeightModifier based on the provided text
|
||||
* @throws IllegalArgumentException if the weight format is invalid
|
||||
*/
|
||||
public static WeightModifier getModifier(String text) {
|
||||
if (text.length() == 0) {
|
||||
throw new IllegalArgumentException("Weight format is invalid.");
|
||||
@@ -130,7 +171,7 @@ public class ConfigUtils {
|
||||
return (player, weight) -> {
|
||||
String temp = formula;
|
||||
if (hasPapi)
|
||||
temp = PlaceholderManagerImpl.getInstance().parseCacheable(player, formula);
|
||||
temp = PlaceholderManagerImpl.getInstance().parseCacheablePlaceholders(player, formula);
|
||||
Expression expression = new ExpressionBuilder(temp)
|
||||
.variables("0")
|
||||
.build()
|
||||
|
||||
@@ -30,7 +30,6 @@ public class DynamicText {
|
||||
private String originalValue;
|
||||
private String latestValue;
|
||||
private String[] placeholders;
|
||||
private String[] previousParsedValues;
|
||||
|
||||
public DynamicText(Player owner, String rawValue) {
|
||||
this.owner = owner;
|
||||
@@ -38,6 +37,8 @@ public class DynamicText {
|
||||
}
|
||||
|
||||
private void analyze(String value) {
|
||||
// Analyze the provided text to find and replace placeholders with '%s'.
|
||||
// Store the original value, placeholders, and the initial latest value.
|
||||
List<String> placeholdersOwner = new ArrayList<>(PlaceholderManagerImpl.getInstance().detectPlaceholders(value));
|
||||
String origin = value;
|
||||
for (String placeholder : placeholdersOwner) {
|
||||
@@ -53,6 +54,7 @@ public class DynamicText {
|
||||
}
|
||||
|
||||
public boolean update(Map<String, String> placeholders) {
|
||||
// Update the dynamic text by replacing placeholders with actual values.
|
||||
String string = originalValue;
|
||||
if (this.placeholders.length != 0) {
|
||||
PlaceholderManagerImpl placeholderManagerImpl = PlaceholderManagerImpl.getInstance();
|
||||
@@ -67,6 +69,7 @@ public class DynamicText {
|
||||
}
|
||||
}
|
||||
if (!latestValue.equals(string)) {
|
||||
// If the updated value is different from the latest value, update it.
|
||||
latestValue = string;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,14 +32,30 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for managing fake item entities using PacketContainers.
|
||||
*/
|
||||
public class FakeItemUtils {
|
||||
|
||||
/**
|
||||
* Creates a destroy packet for removing a fake item entity.
|
||||
*
|
||||
* @param id The ID of the fake item entity to destroy
|
||||
* @return The PacketContainer representing the destroy packet
|
||||
*/
|
||||
public static PacketContainer getDestroyPacket(int id) {
|
||||
PacketContainer destroyPacket = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
|
||||
destroyPacket.getIntLists().write(0, List.of(id));
|
||||
return destroyPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a spawn packet for a fake item entity at the specified location.
|
||||
*
|
||||
* @param id The ID of the fake item entity to spawn
|
||||
* @param location The location where the fake item entity should be spawned
|
||||
* @return The PacketContainer representing the spawn packet
|
||||
*/
|
||||
public static PacketContainer getSpawnPacket(int id, Location location) {
|
||||
PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
|
||||
entityPacket.getModifier().write(0, id);
|
||||
@@ -51,6 +67,13 @@ public class FakeItemUtils {
|
||||
return entityPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metadata packet for updating the metadata of a fake item entity.
|
||||
*
|
||||
* @param id The ID of the fake item entity
|
||||
* @param itemStack The ItemStack to update the metadata with
|
||||
* @return The PacketContainer representing the metadata packet
|
||||
*/
|
||||
public static PacketContainer getMetaPacket(int id, ItemStack itemStack) {
|
||||
PacketContainer metaPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||
metaPacket.getIntegers().write(0, id);
|
||||
@@ -63,6 +86,13 @@ public class FakeItemUtils {
|
||||
return metaPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a teleport packet for moving a fake item entity to the specified location.
|
||||
*
|
||||
* @param id The ID of the fake item entity to teleport
|
||||
* @param location The location to teleport the fake item entity to
|
||||
* @return The PacketContainer representing the teleport packet
|
||||
*/
|
||||
public static PacketContainer getTpPacket(int id, Location location) {
|
||||
PacketContainer tpPacket = new PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT);
|
||||
tpPacket.getModifier().write(0, id);
|
||||
@@ -72,7 +102,14 @@ public class FakeItemUtils {
|
||||
return tpPacket;
|
||||
}
|
||||
|
||||
public static PacketContainer getVelocity(int id, Vector vector) {
|
||||
/**
|
||||
* Creates a velocity packet for applying velocity to a fake item entity.
|
||||
*
|
||||
* @param id The ID of the fake item entity
|
||||
* @param vector The velocity vector to apply
|
||||
* @return The PacketContainer representing the velocity packet
|
||||
*/
|
||||
public static PacketContainer getVelocityPacket(int id, Vector vector) {
|
||||
PacketContainer entityPacket = new PacketContainer(PacketType.Play.Server.ENTITY_VELOCITY);
|
||||
entityPacket.getModifier().write(0, id);
|
||||
entityPacket.getIntegers().write(1, (int) (vector.getX() * 8000));
|
||||
@@ -81,6 +118,12 @@ public class FakeItemUtils {
|
||||
return entityPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DataWatcher for a given ItemStack.
|
||||
*
|
||||
* @param itemStack The ItemStack to create the DataWatcher for
|
||||
* @return The created DataWatcher
|
||||
*/
|
||||
public static WrappedDataWatcher createDataWatcher(ItemStack itemStack) {
|
||||
WrappedDataWatcher wrappedDataWatcher = new WrappedDataWatcher();
|
||||
wrappedDataWatcher.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(8, WrappedDataWatcher.Registry.getItemStackSerializer(false)), itemStack);
|
||||
@@ -88,6 +131,13 @@ public class FakeItemUtils {
|
||||
return wrappedDataWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value list in a PacketContainer's DataWatcher from a WrappedDataWatcher.
|
||||
*
|
||||
* @param metaPacket The PacketContainer representing the metadata packet
|
||||
* @param wrappedDataWatcher The WrappedDataWatcher containing the value list
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private static void setValueList(PacketContainer metaPacket, WrappedDataWatcher wrappedDataWatcher) {
|
||||
List<WrappedDataValue> wrappedDataValueList = Lists.newArrayList();
|
||||
wrappedDataWatcher.getWatchableObjects().stream().filter(Objects::nonNull).forEach(entry -> {
|
||||
|
||||
@@ -36,8 +36,17 @@ import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.inventory.meta.Damageable;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
/**
|
||||
* Utility class for various item-related operations.
|
||||
*/
|
||||
public class ItemUtils {
|
||||
|
||||
/**
|
||||
* Updates the lore of an NBTItem based on its custom NBT tags.
|
||||
*
|
||||
* @param nbtItem The NBTItem to update
|
||||
* @return The updated NBTItem
|
||||
*/
|
||||
public static NBTItem updateNBTItemLore(NBTItem nbtItem) {
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
if (cfCompound == null)
|
||||
@@ -84,6 +93,11 @@ public class ItemUtils {
|
||||
return nbtItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the lore of an ItemStack based on its custom NBT tags.
|
||||
*
|
||||
* @param itemStack The ItemStack to update
|
||||
*/
|
||||
public static void updateItemLore(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return;
|
||||
@@ -91,26 +105,104 @@ public class ItemUtils {
|
||||
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
public static void reduceHookDurability(ItemStack itemStack, boolean updateLore) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
/**
|
||||
* Reduces the durability of a fishing hook item.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack
|
||||
* @param updateLore Whether to update the lore after reducing durability
|
||||
*/
|
||||
public static void decreaseHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
if (rod == null || rod.getType() != Material.FISHING_ROD)
|
||||
return;
|
||||
NBTItem nbtItem = new NBTItem(itemStack);
|
||||
NBTItem nbtItem = new NBTItem(rod);
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
if (cfCompound != null && cfCompound.hasTag("hook_dur")) {
|
||||
int hookDur = cfCompound.getInteger("hook_dur");
|
||||
if (hookDur > 0) {
|
||||
cfCompound.setInteger("hook_dur", hookDur - 1);
|
||||
} else if (hookDur != -1) {
|
||||
cfCompound.removeKey("hook_id");
|
||||
cfCompound.removeKey("hook_dur");
|
||||
cfCompound.removeKey("hook_id");
|
||||
if (hookDur != -1) {
|
||||
hookDur = Math.max(0, hookDur - amount);
|
||||
if (hookDur > 0) {
|
||||
cfCompound.setInteger("hook_dur", hookDur);
|
||||
} else {
|
||||
cfCompound.removeKey("hook_id");
|
||||
cfCompound.removeKey("hook_dur");
|
||||
cfCompound.removeKey("hook_item");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updateLore) updateNBTItemLore(nbtItem);
|
||||
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
rod.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
public static void loseDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
/**
|
||||
* Increases the durability of a fishing hook by a specified amount and optionally updates its lore.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack to modify.
|
||||
* @param amount The amount by which to increase the durability.
|
||||
* @param updateLore Whether to update the lore of the fishing rod.
|
||||
*/
|
||||
public static void increaseHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
if (rod == null || rod.getType() != Material.FISHING_ROD)
|
||||
return;
|
||||
NBTItem nbtItem = new NBTItem(rod);
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
if (cfCompound != null && cfCompound.hasTag("hook_dur")) {
|
||||
int hookDur = cfCompound.getInteger("hook_dur");
|
||||
if (hookDur != -1) {
|
||||
String id = cfCompound.getString("hook_id");
|
||||
HookSetting setting = CustomFishingPlugin.get().getHookManager().getHookSetting(id);
|
||||
if (setting == null) {
|
||||
cfCompound.removeKey("hook_id");
|
||||
cfCompound.removeKey("hook_dur");
|
||||
cfCompound.removeKey("hook_item");
|
||||
} else {
|
||||
hookDur = Math.min(setting.getMaxDurability(), hookDur + amount);
|
||||
cfCompound.setInteger("hook_dur", hookDur);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updateLore) updateNBTItemLore(nbtItem);
|
||||
rod.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the durability of a fishing hook to a specific amount and optionally updates its lore.
|
||||
*
|
||||
* @param rod The fishing rod ItemStack to modify.
|
||||
* @param amount The new durability value to set.
|
||||
* @param updateLore Whether to update the lore of the fishing rod.
|
||||
*/
|
||||
public static void setHookDurability(ItemStack rod, int amount, boolean updateLore) {
|
||||
if (rod == null || rod.getType() != Material.FISHING_ROD)
|
||||
return;
|
||||
NBTItem nbtItem = new NBTItem(rod);
|
||||
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
|
||||
if (cfCompound != null && cfCompound.hasTag("hook_dur")) {
|
||||
int hookDur = cfCompound.getInteger("hook_dur");
|
||||
if (hookDur != -1) {
|
||||
String id = cfCompound.getString("hook_id");
|
||||
HookSetting setting = CustomFishingPlugin.get().getHookManager().getHookSetting(id);
|
||||
if (setting == null) {
|
||||
cfCompound.removeKey("hook_id");
|
||||
cfCompound.removeKey("hook_dur");
|
||||
cfCompound.removeKey("hook_item");
|
||||
} else {
|
||||
hookDur = Math.min(setting.getMaxDurability(), amount);
|
||||
cfCompound.setInteger("hook_dur", hookDur);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updateLore) updateNBTItemLore(nbtItem);
|
||||
rod.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the durability of an item and updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to reduce durability for
|
||||
* @param amount The amount by which to reduce durability
|
||||
* @param updateLore Whether to update the lore after reducing durability
|
||||
*/
|
||||
public static void decreaseDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return;
|
||||
int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY);
|
||||
@@ -145,7 +237,14 @@ public class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
/**
|
||||
* Increases the durability of an item and updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to increase durability for
|
||||
* @param amount The amount by which to increase durability
|
||||
* @param updateLore Whether to update the lore after increasing durability
|
||||
*/
|
||||
public static void increaseDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return;
|
||||
NBTItem nbtItem = new NBTItem(itemStack);
|
||||
@@ -167,6 +266,13 @@ public class ItemUtils {
|
||||
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the durability of an item and updates its lore.
|
||||
*
|
||||
* @param itemStack The ItemStack to set durability for
|
||||
* @param amount The new durability value
|
||||
* @param updateLore Whether to update the lore after setting durability
|
||||
*/
|
||||
public static void setDurability(ItemStack itemStack, int amount, boolean updateLore) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return;
|
||||
@@ -192,6 +298,12 @@ public class ItemUtils {
|
||||
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current durability of an item.
|
||||
*
|
||||
* @param itemStack The ItemStack to get durability from
|
||||
* @return The current durability value
|
||||
*/
|
||||
public static int getDurability(ItemStack itemStack) {
|
||||
if (!(itemStack.getItemMeta() instanceof Damageable damageable))
|
||||
return -1;
|
||||
@@ -206,6 +318,14 @@ public class ItemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a certain amount of an item to a player, handling stacking and item drops.
|
||||
*
|
||||
* @param player The player to give the item to
|
||||
* @param itemStack The ItemStack to give
|
||||
* @param amount The amount of items to give
|
||||
* @return The actual amount of items given
|
||||
*/
|
||||
public static int giveCertainAmountOfItem(Player player, ItemStack itemStack, int amount) {
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
|
||||
@@ -21,6 +21,13 @@ import org.bukkit.Location;
|
||||
|
||||
public class LocationUtils {
|
||||
|
||||
/**
|
||||
* Calculates the Euclidean distance between two locations in 3D space.
|
||||
*
|
||||
* @param location1 The first location
|
||||
* @param location2 The second location
|
||||
* @return The Euclidean distance between the two locations
|
||||
*/
|
||||
public static double getDistance(Location location1, Location location2) {
|
||||
return Math.sqrt(Math.pow(location2.getX() - location1.getX(), 2) +
|
||||
Math.pow(location2.getY() - location1.getY(), 2) +
|
||||
|
||||
@@ -24,8 +24,14 @@ import org.bukkit.configuration.MemorySection;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Utility class for working with NBT (Named Binary Tag) data.
|
||||
*/
|
||||
public class NBTUtils {
|
||||
|
||||
/**
|
||||
* Inner class representing a stack element used during NBT data conversion.
|
||||
*/
|
||||
public static class StackElement {
|
||||
final Map<String, Object> currentMap;
|
||||
final NBTCompound currentNbtCompound;
|
||||
@@ -36,6 +42,12 @@ public class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts data from a Bukkit YAML configuration to NBT tags.
|
||||
*
|
||||
* @param nbtCompound The target NBT compound
|
||||
* @param map The source map from Bukkit YAML
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setTagsFromBukkitYAML(NBTCompound nbtCompound, Map<String, Object> map) {
|
||||
|
||||
@@ -69,6 +81,7 @@ public class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper method
|
||||
private static void setListValue(String key, String value, NBTCompound nbtCompound) {
|
||||
String[] parts = getTypeAndData(value);
|
||||
String type = parts[0];
|
||||
@@ -89,6 +102,7 @@ public class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper method
|
||||
private static void setSingleValue(String key, String value, NBTCompound nbtCompound) {
|
||||
String[] parts = getTypeAndData(value);
|
||||
String type = parts[0];
|
||||
@@ -120,6 +134,12 @@ public class NBTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an NBT compound to a map of key-value pairs.
|
||||
*
|
||||
* @param nbtCompound The source NBT compound
|
||||
* @return A map representing the NBT data
|
||||
*/
|
||||
public static Map<String, Object> compoundToMap(ReadWriteNBT nbtCompound){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
for (String key : nbtCompound.getKeys()) {
|
||||
@@ -155,6 +175,12 @@ public class NBTUtils {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a value into type and data components.
|
||||
*
|
||||
* @param str The input value string
|
||||
* @return An array containing type and data strings
|
||||
*/
|
||||
private static String[] getTypeAndData(String str) {
|
||||
String[] parts = str.split("\\s+", 2);
|
||||
if (parts.length != 2) {
|
||||
@@ -165,6 +191,12 @@ public class NBTUtils {
|
||||
return new String[]{type, data};
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a value containing arrays into individual elements.
|
||||
*
|
||||
* @param value The input value containing arrays
|
||||
* @return An array of individual elements
|
||||
*/
|
||||
private static String[] splitValue(String value) {
|
||||
return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']'))
|
||||
.replaceAll("\\s", "")
|
||||
|
||||
@@ -28,6 +28,9 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* This class implements the VersionManager interface and is responsible for managing version-related information.
|
||||
*/
|
||||
public class VersionManagerImpl implements VersionManager {
|
||||
|
||||
private final boolean isNewerThan1_19_R2;
|
||||
@@ -41,9 +44,13 @@ public class VersionManagerImpl implements VersionManager {
|
||||
@SuppressWarnings("deprecation")
|
||||
public VersionManagerImpl(CustomFishingPluginImpl plugin) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// Get the server version
|
||||
serverVersion = plugin.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
String[] split = serverVersion.split("_");
|
||||
int main_ver = Integer.parseInt(split[1]);
|
||||
|
||||
// Determine if the server version is newer than 1_19_R2 and 1_20_R1
|
||||
if (main_ver >= 20) {
|
||||
isNewerThan1_19_R2 = true;
|
||||
isNewerThan1_20 = true;
|
||||
@@ -54,12 +61,20 @@ public class VersionManagerImpl implements VersionManager {
|
||||
isNewerThan1_19_R2 = false;
|
||||
isNewerThan1_20 = false;
|
||||
}
|
||||
|
||||
// Check if the server is Spigot
|
||||
String server_name = plugin.getServer().getName();
|
||||
this.isSpigot = server_name.equals("CraftBukkit");
|
||||
|
||||
// Check if the server is Folia
|
||||
try {
|
||||
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
|
||||
this.isFolia = true;
|
||||
} catch (ClassNotFoundException ignored) {}
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
|
||||
}
|
||||
|
||||
// Get the plugin version
|
||||
this.pluginVersion = plugin.getDescription().getVersion();
|
||||
}
|
||||
|
||||
@@ -93,6 +108,7 @@ public class VersionManagerImpl implements VersionManager {
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
// Method to asynchronously check for plugin updates
|
||||
@Override
|
||||
public CompletableFuture<Boolean> checkUpdate() {
|
||||
CompletableFuture<Boolean> updateFuture = new CompletableFuture<>();
|
||||
@@ -119,6 +135,7 @@ public class VersionManagerImpl implements VersionManager {
|
||||
return updateFuture;
|
||||
}
|
||||
|
||||
// Method to compare two version strings
|
||||
private boolean compareVer(String newV, String currentV) {
|
||||
if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -136,11 +136,11 @@ other-settings:
|
||||
thread-pool-settings:
|
||||
# The size of the core Thread pool, that is, the size of the Thread pool when there is no task to execute
|
||||
# Increase the size of corePoolSize when you are running a large server with many players fishing at the same time
|
||||
corePoolSize: 4
|
||||
corePoolSize: 8
|
||||
# The maximum number of threads allowed to be created in the Thread pool. The current number of threads in the Thread pool will not exceed this value
|
||||
maximumPoolSize: 8
|
||||
maximumPoolSize: 16
|
||||
# If a thread is idle for more than this attribute value, it will exit due to timeout
|
||||
keepAliveTime: 10
|
||||
keepAliveTime: 30
|
||||
|
||||
# Event priority: MONITOR HIGHEST HIGH NORMAL LOW LOWEST
|
||||
event-priority: NORMAL
|
||||
|
||||
@@ -7,11 +7,11 @@ double_loot_totem:
|
||||
4:
|
||||
- '*_STAIRS{face=east;half=top} OBSERVER{face=south} *_STAIRS{face=west;half=top}'
|
||||
3:
|
||||
- 'AIR CRYING_OBSIDIAN AIR'
|
||||
- 'AIR CRYING_OBSIDIAN AIR'
|
||||
2:
|
||||
- 'AIR *_LOG{axis=y}||*_PILLAR{axis=y} AIR'
|
||||
- 'AIR *_LOG{axis=y}||*_PILLAR{axis=y} AIR'
|
||||
1:
|
||||
- 'AIR ANVIL AIR'
|
||||
- 'AIR||GRASS||SNOW ANVIL AIR||GRASS||SNOW'
|
||||
effects:
|
||||
double_loot:
|
||||
type: multiple-loot
|
||||
|
||||
Reference in New Issue
Block a user