mirror of
https://github.com/Xiao-MoMi/Custom-Fishing.git
synced 2025-12-30 20:39:18 +00:00
I don't want to waste time on resolving conflicts
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class BukkitBootstrap extends JavaPlugin {
|
||||
|
||||
private BukkitCustomFishingPlugin plugin;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
this.plugin = new BukkitCustomFishingPluginImpl(this);
|
||||
this.plugin.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.plugin.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
this.plugin.disable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.event.CustomFishingReloadEvent;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.cooldown.CoolDownManager;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager;
|
||||
import net.momirealms.customfishing.api.util.EventUtils;
|
||||
import net.momirealms.customfishing.bukkit.action.BukkitActionManager;
|
||||
import net.momirealms.customfishing.bukkit.bag.BukkitBagManager;
|
||||
import net.momirealms.customfishing.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandManager;
|
||||
import net.momirealms.customfishing.bukkit.competition.BukkitCompetitionManager;
|
||||
import net.momirealms.customfishing.bukkit.config.BukkitConfigManager;
|
||||
import net.momirealms.customfishing.bukkit.effect.BukkitEffectManager;
|
||||
import net.momirealms.customfishing.bukkit.entity.BukkitEntityManager;
|
||||
import net.momirealms.customfishing.bukkit.event.BukkitEventManager;
|
||||
import net.momirealms.customfishing.bukkit.fishing.BukkitFishingManager;
|
||||
import net.momirealms.customfishing.bukkit.game.BukkitGameManager;
|
||||
import net.momirealms.customfishing.bukkit.hook.BukkitHookManager;
|
||||
import net.momirealms.customfishing.bukkit.integration.BukkitIntegrationManager;
|
||||
import net.momirealms.customfishing.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.customfishing.bukkit.loot.BukkitLootManager;
|
||||
import net.momirealms.customfishing.bukkit.market.BukkitMarketManager;
|
||||
import net.momirealms.customfishing.bukkit.requirement.BukkitRequirementManager;
|
||||
import net.momirealms.customfishing.bukkit.scheduler.BukkitSchedulerAdapter;
|
||||
import net.momirealms.customfishing.bukkit.sender.BukkitSenderFactory;
|
||||
import net.momirealms.customfishing.bukkit.statistic.BukkitStatisticsManager;
|
||||
import net.momirealms.customfishing.bukkit.storage.BukkitStorageManager;
|
||||
import net.momirealms.customfishing.bukkit.totem.BukkitTotemManager;
|
||||
import net.momirealms.customfishing.common.dependency.Dependency;
|
||||
import net.momirealms.customfishing.common.dependency.DependencyManagerImpl;
|
||||
import net.momirealms.customfishing.common.helper.VersionHelper;
|
||||
import net.momirealms.customfishing.common.locale.TranslationManager;
|
||||
import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender;
|
||||
import net.momirealms.customfishing.common.plugin.classpath.ReflectionClassPathAppender;
|
||||
import net.momirealms.customfishing.common.plugin.logging.JavaPluginLogger;
|
||||
import net.momirealms.customfishing.common.plugin.logging.PluginLogger;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BukkitCustomFishingPluginImpl extends BukkitCustomFishingPlugin {
|
||||
|
||||
private final ClassPathAppender classPathAppender;
|
||||
private final PluginLogger logger;
|
||||
private BukkitCommandManager commandManager;
|
||||
private Consumer<Object> debugger;
|
||||
|
||||
public BukkitCustomFishingPluginImpl(Plugin boostrap) {
|
||||
super(boostrap);
|
||||
VersionHelper.init(getServerVersion());
|
||||
this.scheduler = new BukkitSchedulerAdapter(this);
|
||||
this.classPathAppender = new ReflectionClassPathAppender(this);
|
||||
this.logger = new JavaPluginLogger(getBoostrap().getLogger());
|
||||
this.dependencyManager = new DependencyManagerImpl(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
this.dependencyManager.loadDependencies(
|
||||
List.of(
|
||||
Dependency.BOOSTED_YAML,
|
||||
Dependency.BSTATS_BASE, Dependency.BSTATS_BUKKIT,
|
||||
Dependency.CAFFEINE,
|
||||
Dependency.GEANTY_REF,
|
||||
Dependency.CLOUD_CORE, Dependency.CLOUD_SERVICES, Dependency.CLOUD_BUKKIT, Dependency.CLOUD_PAPER, Dependency.CLOUD_BRIGADIER, Dependency.CLOUD_MINECRAFT_EXTRAS,
|
||||
Dependency.GSON,
|
||||
Dependency.COMMONS_POOL_2,
|
||||
Dependency.JEDIS,
|
||||
Dependency.EXP4J,
|
||||
Dependency.MYSQL_DRIVER, Dependency.MARIADB_DRIVER,
|
||||
Dependency.SQLITE_DRIVER, Dependency.SLF4J_API, Dependency.SLF4J_SIMPLE,
|
||||
Dependency.H2_DRIVER,
|
||||
Dependency.MONGODB_DRIVER_CORE, Dependency.MONGODB_DRIVER_SYNC, Dependency.MONGODB_DRIVER_BSON,
|
||||
Dependency.HIKARI_CP,
|
||||
Dependency.LZ4
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
this.eventManager = new BukkitEventManager(this);
|
||||
this.configManager = new BukkitConfigManager(this);
|
||||
this.requirementManager = new BukkitRequirementManager(this);
|
||||
this.actionManager = new BukkitActionManager(this);
|
||||
this.senderFactory = new BukkitSenderFactory(this);
|
||||
this.placeholderManager = new BukkitPlaceholderManager(this);
|
||||
this.itemManager = new BukkitItemManager(this);
|
||||
this.competitionManager = new BukkitCompetitionManager(this);
|
||||
this.marketManager = new BukkitMarketManager(this);
|
||||
this.storageManager = new BukkitStorageManager(this);
|
||||
this.lootManager = new BukkitLootManager(this);
|
||||
this.coolDownManager = new CoolDownManager(this);
|
||||
this.entityManager = new BukkitEntityManager(this);
|
||||
this.blockManager = new BukkitBlockManager(this);
|
||||
this.statisticsManager = new BukkitStatisticsManager(this);
|
||||
this.effectManager = new BukkitEffectManager(this);
|
||||
this.hookManager = new BukkitHookManager(this);
|
||||
this.fishingManager = new BukkitFishingManager(this);
|
||||
this.bagManager = new BukkitBagManager(this);
|
||||
this.totemManager = new BukkitTotemManager(this);
|
||||
this.translationManager = new TranslationManager(this);
|
||||
this.integrationManager = new BukkitIntegrationManager(this);
|
||||
this.gameManager = new BukkitGameManager(this);
|
||||
this.commandManager = new BukkitCommandManager(this);
|
||||
this.commandManager.registerDefaultFeatures();
|
||||
|
||||
this.reload();
|
||||
if (ConfigManager.metrics()) new Metrics((JavaPlugin) getBoostrap(), 16648);
|
||||
if (ConfigManager.checkUpdate()) {
|
||||
VersionHelper.UPDATE_CHECKER.apply(this).thenAccept(result -> {
|
||||
if (!result) this.getPluginLogger().info("You are using the latest version.");
|
||||
else this.getPluginLogger().warn("Update is available: https://polymart.org/resource/2723");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
MechanicType.reset();
|
||||
|
||||
this.itemManager.unload();
|
||||
this.eventManager.unload();
|
||||
this.entityManager.unload();
|
||||
this.lootManager.unload();
|
||||
this.blockManager.unload();
|
||||
this.effectManager.unload();
|
||||
this.hookManager.unload();
|
||||
this.totemManager.unload();
|
||||
this.gameManager.unload();
|
||||
|
||||
// before ConfigManager
|
||||
this.placeholderManager.reload();
|
||||
this.configManager.reload();
|
||||
// after ConfigManager
|
||||
this.debugger = ConfigManager.debug() ? (s) -> logger.info("[DEBUG] " + s.toString()) : (s) -> {};
|
||||
|
||||
this.actionManager.reload();
|
||||
this.requirementManager.reload();
|
||||
this.coolDownManager.reload();
|
||||
this.translationManager.reload();
|
||||
this.marketManager.reload();
|
||||
this.competitionManager.reload();
|
||||
this.statisticsManager.reload();
|
||||
this.bagManager.reload();
|
||||
this.storageManager.reload();
|
||||
this.fishingManager.reload();
|
||||
|
||||
this.itemManager.load();
|
||||
this.eventManager.load();
|
||||
this.entityManager.load();
|
||||
this.lootManager.load();
|
||||
this.blockManager.load();
|
||||
this.effectManager.load();
|
||||
this.hookManager.load();
|
||||
this.totemManager.load();
|
||||
this.gameManager.load();
|
||||
|
||||
EventUtils.fireAndForget(new CustomFishingReloadEvent(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
this.eventManager.disable();
|
||||
this.configManager.disable();
|
||||
this.requirementManager.disable();
|
||||
this.actionManager.disable();
|
||||
this.placeholderManager.disable();
|
||||
this.itemManager.disable();
|
||||
this.competitionManager.disable();
|
||||
this.marketManager.disable();
|
||||
this.lootManager.disable();
|
||||
this.coolDownManager.disable();
|
||||
this.entityManager.disable();
|
||||
this.blockManager.disable();
|
||||
this.statisticsManager.disable();
|
||||
this.effectManager.disable();
|
||||
this.hookManager.disable();
|
||||
this.bagManager.disable();
|
||||
this.integrationManager.disable();
|
||||
this.storageManager.disable();
|
||||
this.commandManager.unregisterFeatures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceStream(String filePath) {
|
||||
return getBoostrap().getResource(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginLogger getPluginLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassPathAppender getClassPathAppender() {
|
||||
return classPathAppender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getDataDirectory() {
|
||||
return getBoostrap().getDataFolder().toPath().toAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerVersion() {
|
||||
return Bukkit.getServer().getBukkitVersion().split("-")[0];
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public String getPluginVersion() {
|
||||
return getBoostrap().getDescription().getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(Object message) {
|
||||
this.debugger.accept(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,938 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.action;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.action.*;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.Effect;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.placeholder.BukkitPlaceholderManager;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.MathValue;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.TextValue;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customfishing.bukkit.integration.VaultHook;
|
||||
import net.momirealms.customfishing.bukkit.util.LocationUtils;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.locale.TranslationManager;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.ClassUtils;
|
||||
import net.momirealms.customfishing.common.util.ListUtils;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import net.momirealms.customfishing.common.util.RandomUtils;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import net.momirealms.sparrow.heart.feature.armorstand.FakeArmorStand;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.ExperienceOrb;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class BukkitActionManager implements ActionManager<Player> {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, ActionFactory<Player>> actionFactoryMap = new HashMap<>();
|
||||
private static final String EXPANSION_FOLDER = "expansions/action";
|
||||
|
||||
public BukkitActionManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registerBuiltInActions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
this.actionFactoryMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
this.loadExpansions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerAction(String type, ActionFactory<Player> actionFactory) {
|
||||
if (this.actionFactoryMap.containsKey(type)) return false;
|
||||
this.actionFactoryMap.put(type, actionFactory);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterAction(String type) {
|
||||
return this.actionFactoryMap.remove(type) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ActionFactory<Player> getActionFactory(@NotNull String type) {
|
||||
return actionFactoryMap.get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAction(@NotNull String type) {
|
||||
return actionFactoryMap.containsKey(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player> parseAction(Section section) {
|
||||
if (section == null) return EmptyAction.INSTANCE;
|
||||
ActionFactory<Player> factory = getActionFactory(section.getString("type"));
|
||||
if (factory == null) {
|
||||
plugin.getPluginLogger().warn("Action type: " + section.getString("type") + " doesn't exist.");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
return factory.process(section.get("value"), section.getDouble("chance", 1d));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Action<Player>[] parseActions(Section section) {
|
||||
ArrayList<Action<Player>> actionList = new ArrayList<>();
|
||||
if (section != null)
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
Action<Player> action = parseAction(innerSection);
|
||||
if (action != null)
|
||||
actionList.add(action);
|
||||
}
|
||||
}
|
||||
return actionList.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action<Player> parseAction(@NotNull String type, @NotNull Object args) {
|
||||
ActionFactory<Player> factory = getActionFactory(type);
|
||||
if (factory == null) {
|
||||
plugin.getPluginLogger().warn("Action type: " + type + " doesn't exist.");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
return factory.process(args, 1);
|
||||
}
|
||||
|
||||
private void registerBuiltInActions() {
|
||||
this.registerMessageAction();
|
||||
this.registerCommandAction();
|
||||
this.registerActionBarAction();
|
||||
this.registerCloseInvAction();
|
||||
this.registerExpAction();
|
||||
this.registerFoodAction();
|
||||
this.registerChainAction();
|
||||
this.registerMoneyAction();
|
||||
this.registerItemAction();
|
||||
this.registerPotionAction();
|
||||
this.registerFishFindAction();
|
||||
this.registerPluginExpAction();
|
||||
this.registerSoundAction();
|
||||
this.registerHologramAction();
|
||||
this.registerFakeItemAction();
|
||||
this.registerTitleAction();
|
||||
}
|
||||
|
||||
private void registerMessageAction() {
|
||||
registerAction("message", (args, chance) -> {
|
||||
List<String> messages = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
List<String> replaced = plugin.getPlaceholderManager().parse(context.getHolder(), messages, context.placeholderMap());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(context.getHolder());
|
||||
for (String text : replaced) {
|
||||
audience.sendMessage(AdventureHelper.miniMessage(text));
|
||||
}
|
||||
};
|
||||
});
|
||||
registerAction("random-message", (args, chance) -> {
|
||||
List<String> messages = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
String random = messages.get(RandomUtils.generateRandomInt(0, messages.size() - 1));
|
||||
random = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), random, context.placeholderMap());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(context.getHolder());
|
||||
audience.sendMessage(AdventureHelper.miniMessage(random));
|
||||
};
|
||||
});
|
||||
registerAction("broadcast", (args, chance) -> {
|
||||
List<String> messages = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
List<String> replaced = plugin.getPlaceholderManager().parse(context.getHolder(), messages, context.placeholderMap());
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
for (String text : replaced) {
|
||||
audience.sendMessage(AdventureHelper.miniMessage(text));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
registerAction("message-nearby", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<String> messages = ListUtils.toList(section.get("message"));
|
||||
MathValue<Player> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
double realRange = range.evaluate(context);
|
||||
Player owner = context.getHolder();
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(
|
||||
owner,
|
||||
messages,
|
||||
context.placeholderMap()
|
||||
);
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
for (String text : replaced) {
|
||||
audience.sendMessage(AdventureHelper.miniMessage(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, location);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at message-nearby action which should be Section");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerCommandAction() {
|
||||
registerAction("command", (args, chance) -> {
|
||||
List<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), commands, context.placeholderMap());
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (String text : replaced) {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
|
||||
}
|
||||
}, context.arg(ContextKeys.LOCATION));
|
||||
};
|
||||
});
|
||||
registerAction("player-command", (args, chance) -> {
|
||||
List<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), commands, context.placeholderMap());
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (String text : replaced) {
|
||||
context.getHolder().performCommand(text);
|
||||
}
|
||||
}, context.arg(ContextKeys.LOCATION));
|
||||
};
|
||||
});
|
||||
registerAction("random-command", (args, chance) -> {
|
||||
List<String> commands = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
String random = commands.get(ThreadLocalRandom.current().nextInt(commands.size()));
|
||||
random = BukkitPlaceholderManager.getInstance().parse(context.getHolder(), random, context.placeholderMap());
|
||||
String finalRandom = random;
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom);
|
||||
}, context.arg(ContextKeys.LOCATION));
|
||||
};
|
||||
});
|
||||
registerAction("command-nearby", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<String> cmd = ListUtils.toList(section.get("command"));
|
||||
MathValue<Player> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player owner = context.getHolder();
|
||||
double realRange = range.evaluate(context);
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
List<String> replaced = BukkitPlaceholderManager.getInstance().parse(owner, cmd, context.placeholderMap());
|
||||
for (String text : replaced) {
|
||||
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, location);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at command-nearby action which should be Section");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerCloseInvAction() {
|
||||
registerAction("close-inv", (args, chance) -> condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
condition.getHolder().closeInventory();
|
||||
});
|
||||
}
|
||||
|
||||
private void registerActionBarAction() {
|
||||
registerAction("actionbar", (args, chance) -> {
|
||||
String text = (String) args;
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Audience audience = plugin.getSenderFactory().getAudience(context.getHolder());
|
||||
Component component = AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(context.getHolder(), text, context.placeholderMap()));
|
||||
audience.sendActionBar(component);
|
||||
};
|
||||
});
|
||||
registerAction("random-actionbar", (args, chance) -> {
|
||||
List<String> texts = ListUtils.toList(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
String random = texts.get(RandomUtils.generateRandomInt(0, texts.size() - 1));
|
||||
random = plugin.getPlaceholderManager().parse(context.getHolder(), random, context.placeholderMap());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(context.getHolder());
|
||||
audience.sendActionBar(AdventureHelper.miniMessage(random));
|
||||
};
|
||||
});
|
||||
registerAction("actionbar-nearby", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String actionbar = section.getString("actionbar");
|
||||
MathValue<Player> range = MathValue.auto(section.get("range"));
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player owner = context.getHolder();
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
double realRange = range.evaluate(context);
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, realRange, realRange, realRange, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= realRange) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
String replaced = plugin.getPlaceholderManager().parse(owner, actionbar, context.placeholderMap());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
audience.sendActionBar(AdventureHelper.miniMessage(replaced));
|
||||
}
|
||||
}
|
||||
}, location
|
||||
);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at actionbar-nearby action which should be Section");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerExpAction() {
|
||||
registerAction("mending", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
final Player player = context.getHolder();
|
||||
player.getLocation().getWorld().spawn(player.getLocation().clone().add(0,0.5,0), ExperienceOrb.class, e -> e.setExperience((int) value.evaluate(context)));
|
||||
};
|
||||
});
|
||||
registerAction("exp", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
final Player player = context.getHolder();
|
||||
player.giveExp((int) Math.round(value.evaluate(context)));
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
AdventureHelper.playSound(audience, Sound.sound(Key.key("minecraft:entity.experience_orb.pickup"), Sound.Source.PLAYER, 1, 1));
|
||||
};
|
||||
});
|
||||
registerAction("level", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
player.setLevel((int) Math.max(0, player.getLevel() + value.evaluate(context)));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void registerFoodAction() {
|
||||
registerAction("food", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
player.setFoodLevel((int) (player.getFoodLevel() + value.evaluate(context)));
|
||||
};
|
||||
});
|
||||
registerAction("saturation", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
player.setSaturation((float) (player.getSaturation() + value.evaluate(context)));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void registerItemAction() {
|
||||
registerAction("item-amount", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
boolean mainOrOff = section.getString("hand", "main").equalsIgnoreCase("main");
|
||||
int amount = section.getInt("amount", 1);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
ItemStack itemStack = mainOrOff ? player.getInventory().getItemInMainHand() : player.getInventory().getItemInOffHand();
|
||||
itemStack.setAmount(Math.max(0, itemStack.getAmount() + amount));
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at item-amount action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
registerAction("durability", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
EquipmentSlot slot = EquipmentSlot.valueOf(section.getString("slot", "hand").toUpperCase(Locale.ENGLISH));
|
||||
int amount = section.getInt("amount", 1);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
ItemStack itemStack = player.getInventory().getItem(slot);
|
||||
// if (amount > 0) {
|
||||
// ItemUtils.increaseDurability(itemStack, amount, true);
|
||||
// } else {
|
||||
// ItemUtils.decreaseDurability(context.getHolder(), itemStack, -amount, true);
|
||||
// }
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at durability action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
registerAction("give-item", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String id = section.getString("item");
|
||||
int amount = section.getInt("amount", 1);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player player = context.getHolder();
|
||||
ItemStack itemStack = plugin.getItemManager().buildAny(context, id);
|
||||
int maxStack = itemStack.getType().getMaxStackSize();
|
||||
int amountToGive = amount;
|
||||
while (amountToGive > 0) {
|
||||
int perStackSize = Math.min(maxStack, amountToGive);
|
||||
amountToGive -= perStackSize;
|
||||
ItemStack more = itemStack.clone();
|
||||
more.setAmount(perStackSize);
|
||||
PlayerUtils.dropItem(player, itemStack, true, true, false);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at give-item action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerChainAction() {
|
||||
registerAction("chain", (args, chance) -> {
|
||||
List<Action<Player>> actions = new ArrayList<>();
|
||||
if (args instanceof Section section) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
actions.add(parseAction(innerSection));
|
||||
}
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Action<Player> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
};
|
||||
});
|
||||
registerAction("delay", (args, chance) -> {
|
||||
List<Action<Player>> actions = new ArrayList<>();
|
||||
int delay;
|
||||
boolean async;
|
||||
if (args instanceof Section section) {
|
||||
delay = section.getInt("delay", 1);
|
||||
async = section.getBoolean("async", false);
|
||||
Section actionSection = section.getSection("actions");
|
||||
if (actionSection != null)
|
||||
for (Map.Entry<String, Object> entry : actionSection.getStringRouteMappedValues(false).entrySet())
|
||||
if (entry.getValue() instanceof Section innerSection)
|
||||
actions.add(parseAction(innerSection));
|
||||
} else {
|
||||
delay = 1;
|
||||
async = false;
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = context.arg(ContextKeys.LOCATION);
|
||||
if (async) {
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
for (Action<Player> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
plugin.getScheduler().sync().runLater(() -> {
|
||||
for (Action<Player> action : actions)
|
||||
action.trigger(context);
|
||||
}, delay, location);
|
||||
}
|
||||
};
|
||||
});
|
||||
registerAction("timer", (args, chance) -> {
|
||||
List<Action<Player>> actions = new ArrayList<>();
|
||||
int delay, duration, period;
|
||||
boolean async;
|
||||
if (args instanceof Section section) {
|
||||
delay = section.getInt("delay", 2);
|
||||
duration = section.getInt("duration", 20);
|
||||
period = section.getInt("period", 2);
|
||||
async = section.getBoolean("async", false);
|
||||
Section actionSection = section.getSection("actions");
|
||||
if (actionSection != null)
|
||||
for (Map.Entry<String, Object> entry : actionSection.getStringRouteMappedValues(false).entrySet())
|
||||
if (entry.getValue() instanceof Section innerSection)
|
||||
actions.add(parseAction(innerSection));
|
||||
} else {
|
||||
delay = 1;
|
||||
period = 1;
|
||||
async = false;
|
||||
duration = 20;
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = context.arg(ContextKeys.LOCATION);
|
||||
SchedulerTask task;
|
||||
if (async) {
|
||||
task = plugin.getScheduler().asyncRepeating(() -> {
|
||||
for (Action<Player> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
}, delay * 50L, period * 50L, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
task = plugin.getScheduler().sync().runRepeating(() -> {
|
||||
for (Action<Player> action : actions) {
|
||||
action.trigger(context);
|
||||
}
|
||||
}, delay, period, location);
|
||||
}
|
||||
plugin.getScheduler().asyncLater(task::cancel, duration * 50L, TimeUnit.MILLISECONDS);
|
||||
};
|
||||
});
|
||||
registerAction("conditional", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
Action<Player>[] actions = parseActions(section.getSection("actions"));
|
||||
Requirement<Player>[] requirements = plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), true);
|
||||
return condition -> {
|
||||
if (Math.random() > chance) return;
|
||||
for (Requirement<Player> requirement : requirements) {
|
||||
if (!requirement.isSatisfied(condition)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (Action<Player> action : actions) {
|
||||
action.trigger(condition);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at conditional action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
registerAction("priority", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<Pair<Requirement<Player>[], Action<Player>[]>> conditionActionPairList = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
Action<Player>[] actions = parseActions(inner.getSection("actions"));
|
||||
Requirement<Player>[] requirements = plugin.getRequirementManager().parseRequirements(inner.getSection("conditions"), false);
|
||||
conditionActionPairList.add(Pair.of(requirements, actions));
|
||||
}
|
||||
}
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
outer:
|
||||
for (Pair<Requirement<Player>[], Action<Player>[]> pair : conditionActionPairList) {
|
||||
if (pair.left() != null)
|
||||
for (Requirement<Player> requirement : pair.left()) {
|
||||
if (!requirement.isSatisfied(context)) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
if (pair.right() != null)
|
||||
for (Action<Player> action : pair.right()) {
|
||||
action.trigger(context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at priority action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerMoneyAction() {
|
||||
registerAction("give-money", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
VaultHook.deposit(context.getHolder(), value.evaluate(context));
|
||||
};
|
||||
});
|
||||
registerAction("take-money", (args, chance) -> {
|
||||
MathValue<Player> value = MathValue.auto(args);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
VaultHook.withdraw(context.getHolder(), value.evaluate(context));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void registerPotionAction() {
|
||||
registerAction("potion-effect", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
PotionEffect potionEffect = new PotionEffect(
|
||||
Objects.requireNonNull(PotionEffectType.getByName(section.getString("type", "BLINDNESS").toUpperCase(Locale.ENGLISH))),
|
||||
section.getInt("duration", 20),
|
||||
section.getInt("amplifier", 0)
|
||||
);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
context.getHolder().addPotionEffect(potionEffect);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at potion-effect action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerSoundAction() {
|
||||
registerAction("sound", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
Sound sound = Sound.sound(
|
||||
Key.key(section.getString("key")),
|
||||
Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)),
|
||||
section.getDouble("volume", 1.0).floatValue(),
|
||||
section.getDouble("pitch", 1.0).floatValue()
|
||||
);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Audience audience = plugin.getSenderFactory().getAudience(context.getHolder());
|
||||
AdventureHelper.playSound(audience, sound);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at sound action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void registerPluginExpAction() {
|
||||
registerAction("plugin-exp", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String pluginName = section.getString("plugin");
|
||||
MathValue<Player> value = MathValue.auto(section.get("exp"));
|
||||
String target = section.getString("target");
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Optional.ofNullable(plugin.getIntegrationManager().getLevelerProvider(pluginName)).ifPresentOrElse(it -> {
|
||||
it.addXp(context.getHolder(), target, value.evaluate(context));
|
||||
}, () -> plugin.getPluginLogger().warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."));
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at plugin-exp action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerTitleAction() {
|
||||
registerAction("title", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
TextValue<Player> title = TextValue.auto(section.getString("title", ""));
|
||||
TextValue<Player> subtitle = TextValue.auto(section.getString("subtitle", ""));
|
||||
int fadeIn = section.getInt("fade-in", 20);
|
||||
int stay = section.getInt("stay", 30);
|
||||
int fadeOut = section.getInt("fade-out", 10);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
final Player player = context.getHolder();
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
AdventureHelper.sendTitle(audience,
|
||||
AdventureHelper.miniMessage(title.render(context)),
|
||||
AdventureHelper.miniMessage(subtitle.render(context)),
|
||||
fadeIn, stay, fadeOut
|
||||
);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
registerAction("random-title", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<String> titles = section.getStringList("titles");
|
||||
if (titles.isEmpty()) titles.add("");
|
||||
List<String> subtitles = section.getStringList("subtitles");
|
||||
if (subtitles.isEmpty()) subtitles.add("");
|
||||
int fadeIn = section.getInt("fade-in", 20);
|
||||
int stay = section.getInt("stay", 30);
|
||||
int fadeOut = section.getInt("fade-out", 10);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
TextValue<Player> title = TextValue.auto(titles.get(RandomUtils.generateRandomInt(0, titles.size() - 1)));
|
||||
TextValue<Player> subtitle = TextValue.auto(subtitles.get(RandomUtils.generateRandomInt(0, subtitles.size() - 1)));
|
||||
final Player player = context.getHolder();
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
AdventureHelper.sendTitle(audience,
|
||||
AdventureHelper.miniMessage(title.render(context)),
|
||||
AdventureHelper.miniMessage(subtitle.render(context)),
|
||||
fadeIn, stay, fadeOut
|
||||
);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at random-title action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
registerAction("title-nearby", (args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
TextValue<Player> title = TextValue.auto(section.getString("title"));
|
||||
TextValue<Player> subtitle = TextValue.auto(section.getString("subtitle"));
|
||||
int fadeIn = section.getInt("fade-in", 20);
|
||||
int stay = section.getInt("stay", 30);
|
||||
int fadeOut = section.getInt("fade-out", 10);
|
||||
int range = section.getInt("range", 0);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Location location = requireNonNull(context.arg(ContextKeys.LOCATION));
|
||||
plugin.getScheduler().sync().run(() -> {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= range) {
|
||||
context.arg(ContextKeys.TEMP_NEAR_PLAYER, player.getName());
|
||||
Audience audience = plugin.getSenderFactory().getAudience(player);
|
||||
AdventureHelper.sendTitle(audience,
|
||||
AdventureHelper.miniMessage(title.render(context)),
|
||||
AdventureHelper.miniMessage(subtitle.render(context)),
|
||||
fadeIn, stay, fadeOut
|
||||
);
|
||||
}
|
||||
}
|
||||
}, location
|
||||
);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at title-nearby action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerFakeItemAction() {
|
||||
registerAction("fake-item", ((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
String itemID = section.getString("item", "");
|
||||
String[] split = itemID.split(":");
|
||||
if (split.length >= 2) itemID = split[split.length - 1];
|
||||
MathValue<Player> duration = MathValue.auto(section.get("duration", 20));
|
||||
boolean position = !section.getString("position", "player").equals("player");
|
||||
MathValue<Player> x = MathValue.auto(section.get("x", 0));
|
||||
MathValue<Player> y = MathValue.auto(section.get("y", 0));
|
||||
MathValue<Player> z = MathValue.auto(section.get("z", 0));
|
||||
MathValue<Player> yaw = MathValue.auto(section.get("yaw", 0));
|
||||
int range = section.getInt("range", 0);
|
||||
boolean opposite = section.getBoolean("opposite-yaw", false);
|
||||
String finalItemID = itemID;
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player owner = context.getHolder();
|
||||
Location location = position ? requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)).clone() : owner.getLocation().clone();
|
||||
location.add(x.evaluate(context), y.evaluate(context) - 1, z.evaluate(context));
|
||||
if (opposite) location.setYaw(-owner.getLocation().getYaw());
|
||||
else location.setYaw((float) yaw.evaluate(context));
|
||||
FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location);
|
||||
armorStand.invisible(true);
|
||||
armorStand.equipment(EquipmentSlot.HEAD, plugin.getItemManager().buildInternal(context, finalItemID));
|
||||
ArrayList<Player> viewers = new ArrayList<>();
|
||||
if (range > 0) {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= range) {
|
||||
viewers.add((Player) player);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewers.add(owner);
|
||||
}
|
||||
for (Player player : viewers) {
|
||||
armorStand.spawn(player);
|
||||
}
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
for (Player player : viewers) {
|
||||
if (player.isOnline() && player.isValid()) {
|
||||
armorStand.destroy(player);
|
||||
}
|
||||
}
|
||||
}, (long) (duration.evaluate(context) * 50), TimeUnit.MILLISECONDS);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at fake-item action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void registerHologramAction() {
|
||||
registerAction("hologram", ((args, chance) -> {
|
||||
if (args instanceof Section section) {
|
||||
TextValue<Player> text = TextValue.auto(section.getString("text", ""));
|
||||
MathValue<Player> duration = MathValue.auto(section.get("duration", 20));
|
||||
boolean position = section.getString("position", "other").equals("other");
|
||||
MathValue<Player> x = MathValue.auto(section.get("x", 0));
|
||||
MathValue<Player> y = MathValue.auto(section.get("y", 0));
|
||||
MathValue<Player> z = MathValue.auto(section.get("z", 0));
|
||||
int range = section.getInt("range", 16);
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
Player owner = context.getHolder();
|
||||
Location location = position ? requireNonNull(context.arg(ContextKeys.OTHER_LOCATION)).clone() : owner.getLocation().clone();
|
||||
location.add(x.evaluate(context), y.evaluate(context), z.evaluate(context));
|
||||
FakeArmorStand armorStand = SparrowHeart.getInstance().createFakeArmorStand(location);
|
||||
armorStand.invisible(true);
|
||||
armorStand.small(true);
|
||||
armorStand.name(AdventureHelper.miniMessageToJson(text.render(context)));
|
||||
ArrayList<Player> viewers = new ArrayList<>();
|
||||
if (range > 0) {
|
||||
for (Entity player : location.getWorld().getNearbyEntities(location, range, range, range, entity -> entity instanceof Player)) {
|
||||
double distance = LocationUtils.getDistance(player.getLocation(), location);
|
||||
if (distance <= range) {
|
||||
viewers.add((Player) player);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewers.add(owner);
|
||||
}
|
||||
for (Player player : viewers) {
|
||||
armorStand.spawn(player);
|
||||
}
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
for (Player player : viewers) {
|
||||
if (player.isOnline() && player.isValid()) {
|
||||
armorStand.destroy(player);
|
||||
}
|
||||
}
|
||||
}, (long) (duration.evaluate(context) * 50), TimeUnit.MILLISECONDS);
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at hologram action which is expected to be `Section`");
|
||||
return EmptyAction.INSTANCE;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void registerFishFindAction() {
|
||||
registerAction("fish-finder", (args, chance) -> {
|
||||
String surrounding = (String) args;
|
||||
return context -> {
|
||||
if (Math.random() > chance) return;
|
||||
String previous = context.arg(ContextKeys.SURROUNDING);
|
||||
context.arg(ContextKeys.SURROUNDING, surrounding);
|
||||
Collection<String> loots = plugin.getLootManager().getWeightedLoots(Effect.newInstance(), context).keySet();
|
||||
StringJoiner stringJoiner = new StringJoiner(TranslationManager.miniMessageTranslation(MessageConstants.COMMAND_FISH_FINDER_SPLIT_CHAR.build().key()));
|
||||
for (String loot : loots) {
|
||||
plugin.getLootManager().getLoot(loot).ifPresent(lootIns -> {
|
||||
if (lootIns.showInFinder()) {
|
||||
if (!lootIns.nick().equals("UNDEFINED")) {
|
||||
stringJoiner.add(lootIns.nick());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (previous == null) {
|
||||
context.remove(ContextKeys.SURROUNDING);
|
||||
} else {
|
||||
context.arg(ContextKeys.SURROUNDING, previous);
|
||||
}
|
||||
if (loots.isEmpty()) {
|
||||
plugin.getSenderFactory().wrap(context.getHolder()).sendMessage(TranslationManager.render(MessageConstants.COMMAND_FISH_FINDER_NO_LOOT.build()));
|
||||
} else {
|
||||
plugin.getSenderFactory().wrap(context.getHolder()).sendMessage(TranslationManager.render(MessageConstants.COMMAND_FISH_FINDER_POSSIBLE_LOOTS.arguments(AdventureHelper.miniMessage(stringJoiner.toString())).build()));
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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", "unchecked"})
|
||||
private void loadExpansions() {
|
||||
File expansionFolder = new File(plugin.getDataFolder(), EXPANSION_FOLDER);
|
||||
if (!expansionFolder.exists())
|
||||
expansionFolder.mkdirs();
|
||||
|
||||
List<Class<? extends ActionExpansion<Player>>> classes = new ArrayList<>();
|
||||
File[] expansionJars = expansionFolder.listFiles();
|
||||
if (expansionJars == null) return;
|
||||
for (File expansionJar : expansionJars) {
|
||||
if (expansionJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
Class<? extends ActionExpansion<Player>> expansionClass = (Class<? extends ActionExpansion<Player>>) ClassUtils.findClass(expansionJar, ActionExpansion.class);
|
||||
classes.add(expansionClass);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
plugin.getPluginLogger().warn("Failed to load expansion: " + expansionJar.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (Class<? extends ActionExpansion<Player>> expansionClass : classes) {
|
||||
ActionExpansion<Player> expansion = expansionClass.getDeclaredConstructor().newInstance();
|
||||
unregisterAction(expansion.getActionType());
|
||||
registerAction(expansion.getActionType(), expansion.getActionFactory());
|
||||
plugin.getPluginLogger().info("Loaded action expansion: " + expansion.getActionType() + "[" + expansion.getVersion() + "]" + " by " + expansion.getAuthor() );
|
||||
}
|
||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
|
||||
plugin.getPluginLogger().warn("Error occurred when creating expansion instance.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of InvUI, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021 NichtStudioCode
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.adventure;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Languages {
|
||||
|
||||
private static final Languages INSTANCE = new Languages();
|
||||
private final Map<String, Map<String, String>> translations = new HashMap<>();
|
||||
private Function<Player, Locale> languageProvider = Player::locale;
|
||||
private boolean serverSideTranslations = true;
|
||||
|
||||
private Languages() {
|
||||
}
|
||||
|
||||
public static Languages getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void addLanguage(@NotNull String lang, @NotNull Map<String, String> translations) {
|
||||
this.translations.put(lang, translations);
|
||||
}
|
||||
|
||||
public void loadLanguage(@NotNull String lang, @NotNull Reader reader) throws IOException {
|
||||
var translations = new HashMap<String, String>();
|
||||
try (var jsonReader = new JsonReader(reader)) {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
var key = jsonReader.nextName();
|
||||
var value = jsonReader.nextString();
|
||||
translations.put(key, value);
|
||||
}
|
||||
|
||||
addLanguage(lang, translations);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadLanguage(@NotNull String lang, @NotNull File file, @NotNull Charset charset) throws IOException {
|
||||
try (var reader = new FileReader(file, charset)) {
|
||||
loadLanguage(lang, reader);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getFormatString(@NotNull String lang, @NotNull String key) {
|
||||
var map = translations.get(lang);
|
||||
if (map == null)
|
||||
return null;
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
public void setLanguageProvider(@NotNull Function<Player, Locale> languageProvider) {
|
||||
this.languageProvider = languageProvider;
|
||||
}
|
||||
|
||||
public @NotNull Locale getLanguage(@NotNull Player player) {
|
||||
return languageProvider.apply(player);
|
||||
}
|
||||
|
||||
public void enableServerSideTranslations(boolean enable) {
|
||||
serverSideTranslations = enable;
|
||||
}
|
||||
|
||||
public boolean doesServerSideTranslations() {
|
||||
return serverSideTranslations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of InvUI, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021 NichtStudioCode
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.adventure;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
|
||||
public class ShadedAdventureComponentUtils {
|
||||
|
||||
private static final Style FORMATTING_TEMPLATE = Style.style()
|
||||
.color(NamedTextColor.WHITE)
|
||||
.decoration(TextDecoration.ITALIC, false)
|
||||
.decoration(TextDecoration.BOLD, false)
|
||||
.decoration(TextDecoration.STRIKETHROUGH, false)
|
||||
.decoration(TextDecoration.UNDERLINED, false)
|
||||
.decoration(TextDecoration.OBFUSCATED, false)
|
||||
.build();
|
||||
|
||||
public static Component withoutPreFormatting(Component component) {
|
||||
return component.style(component.style().merge(FORMATTING_TEMPLATE, Style.Merge.Strategy.IF_ABSENT_ON_TARGET));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of InvUI, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021 NichtStudioCode
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.adventure;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.xenondevs.inventoryaccess.component.ComponentWrapper;
|
||||
|
||||
public class ShadedAdventureComponentWrapper implements ComponentWrapper {
|
||||
|
||||
public static final ShadedAdventureComponentWrapper EMPTY = new ShadedAdventureComponentWrapper(Component.empty());
|
||||
|
||||
private final Component component;
|
||||
|
||||
public ShadedAdventureComponentWrapper(Component component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String serializeToJson() {
|
||||
return GsonComponentSerializer.gson().serialize(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ComponentWrapper localized(@NotNull String lang) {
|
||||
if (!Languages.getInstance().doesServerSideTranslations())
|
||||
return this;
|
||||
|
||||
return new ShadedAdventureComponentWrapper(ShadedAdventureShadedComponentLocalizer.getInstance().localize(lang, component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ComponentWrapper withoutPreFormatting() {
|
||||
return new ShadedAdventureComponentWrapper(ShadedAdventureComponentUtils.withoutPreFormatting(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ShadedAdventureComponentWrapper clone() {
|
||||
try {
|
||||
return (ShadedAdventureComponentWrapper) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of InvUI, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021 NichtStudioCode
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.adventure;
|
||||
|
||||
import net.kyori.adventure.text.*;
|
||||
|
||||
public class ShadedAdventureShadedComponentLocalizer extends ShadedComponentLocalizer<Component> {
|
||||
|
||||
private static final ShadedAdventureShadedComponentLocalizer INSTANCE = new ShadedAdventureShadedComponentLocalizer();
|
||||
|
||||
private ShadedAdventureShadedComponentLocalizer() {
|
||||
super(Component::text);
|
||||
}
|
||||
|
||||
public static ShadedAdventureShadedComponentLocalizer getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public Component localize(String lang, Component component) {
|
||||
if (!(component instanceof BuildableComponent))
|
||||
throw new IllegalStateException("Component is not a BuildableComponent");
|
||||
|
||||
return localize(lang, (BuildableComponent) component);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonExtendableApiUsage")
|
||||
private <C extends BuildableComponent<C, B>, B extends ComponentBuilder<C, B>> BuildableComponent<?, ?> localize(String lang, BuildableComponent<C, B> component) {
|
||||
ComponentBuilder<?, ?> builder;
|
||||
if (component instanceof TranslatableComponent) {
|
||||
builder = localizeTranslatable(lang, (TranslatableComponent) component).toBuilder();
|
||||
} else {
|
||||
builder = component.toBuilder();
|
||||
}
|
||||
|
||||
builder.mapChildrenDeep(child -> {
|
||||
if (child instanceof TranslatableComponent)
|
||||
return localizeTranslatable(lang, (TranslatableComponent) child);
|
||||
return child;
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private BuildableComponent<?, ?> localizeTranslatable(String lang, TranslatableComponent component) {
|
||||
var formatString = Languages.getInstance().getFormatString(lang, component.key());
|
||||
if (formatString == null)
|
||||
return component;
|
||||
|
||||
var children = decomposeFormatString(lang, formatString, component, component.args());
|
||||
return Component.textOfChildren(children.toArray(ComponentLike[]::new)).style(component.style());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of InvUI, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) 2021 NichtStudioCode
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.adventure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
abstract class ShadedComponentLocalizer<T> {
|
||||
|
||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([A-Za-z%]|$)");
|
||||
|
||||
private Function<String, T> componentCreator;
|
||||
|
||||
public ShadedComponentLocalizer(Function<String, T> componentCreator) {
|
||||
this.componentCreator = componentCreator;
|
||||
}
|
||||
|
||||
public void setComponentCreator(Function<String, T> componentCreator) {
|
||||
this.componentCreator = componentCreator;
|
||||
}
|
||||
|
||||
public abstract T localize(String lang, T component);
|
||||
|
||||
protected List<T> decomposeFormatString(String lang, String formatString, T component, List<T> args) {
|
||||
var matcher = FORMAT_PATTERN.matcher(formatString);
|
||||
|
||||
var components = new ArrayList<T>();
|
||||
var sb = new StringBuilder();
|
||||
var nextArgIdx = 0;
|
||||
|
||||
var i = 0;
|
||||
while (matcher.find(i)) {
|
||||
var start = matcher.start();
|
||||
var end = matcher.end();
|
||||
|
||||
// check for escaped %
|
||||
var matchedStr = formatString.substring(i, start);
|
||||
if ("%%".equals(matchedStr)) {
|
||||
sb.append('%');
|
||||
} else {
|
||||
// check for invalid format, only %s is supported
|
||||
var argType = matcher.group(2);
|
||||
if (!"s".equals(argType)) {
|
||||
throw new IllegalStateException("Unsupported format: '" + matchedStr + "'");
|
||||
}
|
||||
|
||||
// retrieve argument index
|
||||
var argIdxStr = matcher.group(1);
|
||||
var argIdx = argIdxStr == null ? nextArgIdx++ : Integer.parseInt(argIdxStr) - 1;
|
||||
|
||||
// validate argument index
|
||||
if (argIdx < 0)
|
||||
throw new IllegalStateException("Invalid argument index: " + argIdx);
|
||||
|
||||
// append the text before the argument
|
||||
sb.append(formatString, i, start);
|
||||
// add text component
|
||||
components.add(componentCreator.apply(sb.toString()));
|
||||
// add argument component
|
||||
components.add(args.size() <= argIdx ? componentCreator.apply("") : localize(lang, args.get(argIdx)));
|
||||
// clear string builder
|
||||
sb.setLength(0);
|
||||
}
|
||||
|
||||
// start next search after matcher end index
|
||||
i = end;
|
||||
}
|
||||
|
||||
// append the text after the last argument
|
||||
if (i < formatString.length()) {
|
||||
sb.append(formatString, i, formatString.length());
|
||||
components.add(componentCreator.apply(sb.toString()));
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.bag;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.event.FishingBagPreCollectEvent;
|
||||
import net.momirealms.customfishing.api.event.FishingLootSpawnEvent;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.bag.BagManager;
|
||||
import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.api.util.EventUtils;
|
||||
import net.momirealms.customfishing.bukkit.config.BukkitConfigManager;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryAction;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitBagManager implements BagManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<UUID, UserData> tempEditMap;
|
||||
private Action<Player>[] collectLootActions;
|
||||
private Action<Player>[] bagFullActions;
|
||||
private boolean bagStoreLoots;
|
||||
private boolean bagStoreRods;
|
||||
private boolean bagStoreBaits;
|
||||
private boolean bagStoreHooks;
|
||||
private boolean bagStoreUtils;
|
||||
private final HashSet<MechanicType> storedTypes = new HashSet<>();
|
||||
private boolean enable;
|
||||
private String bagTitle;
|
||||
private List<Material> bagWhiteListItems = new ArrayList<>();
|
||||
private Requirement<Player>[] collectRequirements;
|
||||
|
||||
public BukkitBagManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.tempEditMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
this.loadConfig();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
storedTypes.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
unload();
|
||||
this.plugin.getStorageManager().getDataSource().updateManyPlayersData(tempEditMap.values(), true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLootSpawn(FishingLootSpawnEvent event) {
|
||||
if (!enable || !bagStoreLoots) {
|
||||
return;
|
||||
}
|
||||
if (!event.summonEntity()) {
|
||||
return;
|
||||
}
|
||||
if (!(event.getEntity() instanceof Item itemEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
Context<Player> context = event.getContext();
|
||||
if (!RequirementManager.isSatisfied(context, collectRequirements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<UserData> onlineUser = plugin.getStorageManager().getOnlineUser(player.getUniqueId());
|
||||
if (onlineUser.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UserData userData = onlineUser.get();
|
||||
Inventory inventory = userData.holder().getInventory();
|
||||
ItemStack item = itemEntity.getItemStack();
|
||||
FishingBagPreCollectEvent preCollectEvent = new FishingBagPreCollectEvent(player, item, inventory);
|
||||
if (EventUtils.fireAndCheckCancel(preCollectEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cannotPut = PlayerUtils.putItemsToInventory(inventory, item, item.getAmount());
|
||||
// some are put into bag
|
||||
if (cannotPut != item.getAmount()) {
|
||||
ActionManager.trigger(context, collectLootActions);
|
||||
}
|
||||
// all are put
|
||||
if (cannotPut == 0) {
|
||||
event.summonEntity(false);
|
||||
return;
|
||||
}
|
||||
item.setAmount(cannotPut);
|
||||
itemEntity.setItemStack(item);
|
||||
ActionManager.trigger(context, bagFullActions);
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
Section config = BukkitConfigManager.getMainConfig().getSection("mechanics.fishing-bag");
|
||||
|
||||
enable = config.getBoolean("enable", true);
|
||||
bagTitle = config.getString("bag-title", "");
|
||||
bagStoreLoots = config.getBoolean("can-store-loot", false);
|
||||
bagStoreRods = config.getBoolean("can-store-rod", true);
|
||||
bagStoreBaits = config.getBoolean("can-store-bait", true);
|
||||
bagStoreHooks = config.getBoolean("can-store-hook", true);
|
||||
bagStoreUtils = config.getBoolean("can-store-util", true);
|
||||
bagWhiteListItems = config.getStringList("whitelist-items").stream().map(it -> Material.valueOf(it.toUpperCase(Locale.ENGLISH))).toList();
|
||||
collectLootActions = plugin.getActionManager().parseActions(config.getSection("collect-actions"));
|
||||
bagFullActions = plugin.getActionManager().parseActions(config.getSection("full-actions"));
|
||||
collectRequirements = plugin.getRequirementManager().parseRequirements(config.getSection("collect-requirements"), false);
|
||||
|
||||
if (bagStoreLoots) storedTypes.add(MechanicType.LOOT);
|
||||
if (bagStoreRods) storedTypes.add(MechanicType.ROD);
|
||||
if (bagStoreBaits) storedTypes.add(MechanicType.BAIT);
|
||||
if (bagStoreHooks) storedTypes.add(MechanicType.HOOK);
|
||||
if (bagStoreUtils) storedTypes.add(MechanicType.UTIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> openBag(Player viewer, UUID owner) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
if (enable) {
|
||||
Optional<UserData> onlineUser = plugin.getStorageManager().getOnlineUser(owner);
|
||||
onlineUser.ifPresentOrElse(data -> {
|
||||
viewer.openInventory(data.holder().getInventory());
|
||||
SparrowHeart.getInstance().updateInventoryTitle(viewer, AdventureHelper.componentToJson(AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(Bukkit.getOfflinePlayer(owner), bagTitle, Map.of("{uuid}", owner.toString(), "{player}", data.name())))));
|
||||
future.complete(true);
|
||||
}, () -> plugin.getStorageManager().getOfflineUserData(owner, true).thenAccept(result -> result.ifPresentOrElse(data -> {
|
||||
if (data.isLocked()) {
|
||||
future.completeExceptionally(new RuntimeException("Data is locked"));
|
||||
return;
|
||||
}
|
||||
this.tempEditMap.put(viewer.getUniqueId(), data);
|
||||
viewer.openInventory(data.holder().getInventory());
|
||||
SparrowHeart.getInstance().updateInventoryTitle(viewer, AdventureHelper.componentToJson(AdventureHelper.miniMessage(plugin.getPlaceholderManager().parse(Bukkit.getOfflinePlayer(owner), bagTitle, Map.of("{uuid}", owner.toString(), "{player}", data.name())))));
|
||||
future.complete(true);
|
||||
}, () -> future.complete(false))));
|
||||
} else {
|
||||
future.complete(false);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
return;
|
||||
final Player viewer = (Player) event.getPlayer();
|
||||
UserData userData = tempEditMap.remove(viewer.getUniqueId());
|
||||
if (userData == null)
|
||||
return;
|
||||
this.plugin.getStorageManager().saveUserData(userData, 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 (ignoreCancelled = true)
|
||||
public void onInvClick(InventoryClickEvent event) {
|
||||
if (!(event.getInventory().getHolder() instanceof FishingBagHolder))
|
||||
return;
|
||||
ItemStack movedItem = event.getCurrentItem();
|
||||
Inventory clicked = event.getClickedInventory();
|
||||
if (clicked != event.getWhoClicked().getInventory()) {
|
||||
if (event.getAction() != InventoryAction.HOTBAR_SWAP && event.getAction() != InventoryAction.HOTBAR_MOVE_AND_READD) {
|
||||
return;
|
||||
}
|
||||
movedItem = event.getWhoClicked().getInventory().getItem(event.getHotbarButton());
|
||||
}
|
||||
if (movedItem == null || movedItem.getType() == Material.AIR || bagWhiteListItems.contains(movedItem.getType()))
|
||||
return;
|
||||
String id = plugin.getItemManager().getItemID(movedItem);
|
||||
List<MechanicType> type = MechanicType.getTypeByID(id);
|
||||
if (type == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
for (MechanicType mechanicType : type) {
|
||||
if (storedTypes.contains(mechanicType)) {
|
||||
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) {
|
||||
UserData userData = tempEditMap.remove(event.getPlayer().getUniqueId());
|
||||
if (userData == null)
|
||||
return;
|
||||
plugin.getStorageManager().saveUserData(userData, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.block;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.integration.BlockProvider;
|
||||
import net.momirealms.customfishing.api.integration.ExternalProvider;
|
||||
import net.momirealms.customfishing.api.mechanic.block.*;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.MathValue;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import net.momirealms.customfishing.common.util.RandomUtils;
|
||||
import net.momirealms.customfishing.common.util.Tuple;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Container;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.block.data.Directional;
|
||||
import org.bukkit.block.data.Rotatable;
|
||||
import org.bukkit.block.data.type.NoteBlock;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.FallingBlock;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityChangeBlockEvent;
|
||||
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.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class BukkitBlockManager implements BlockManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, BlockProvider> blockProviders = new HashMap<>();
|
||||
private final HashMap<String, BlockConfig> blocks = new HashMap<>();
|
||||
private final HashMap<String, BlockDataModifierFactory> dataFactories = new HashMap<>();
|
||||
private final HashMap<String, BlockStateModifierFactory> stateFactories = new HashMap<>();
|
||||
private BlockProvider[] blockDetectArray;
|
||||
|
||||
public BukkitBlockManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registerInbuiltProperties();
|
||||
this.registerBlockProvider(new BlockProvider() {
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "vanilla";
|
||||
}
|
||||
@Override
|
||||
public BlockData blockData(@NotNull Context<Player> context, @NotNull String id, List<BlockDataModifier> modifiers) {
|
||||
BlockData blockData = Material.valueOf(id.toUpperCase(Locale.ENGLISH)).createBlockData();
|
||||
for (BlockDataModifier modifier : modifiers)
|
||||
modifier.apply(context, blockData);
|
||||
return blockData;
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
public String blockID(@NotNull Block block) {
|
||||
return block.getType().name();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
this.resetBlockDetectionOrder();
|
||||
for (BlockProvider provider : blockProviders.values()) {
|
||||
plugin.debug("Registered BlockProvider: " + provider.identifier());
|
||||
}
|
||||
plugin.debug("Loaded " + blocks.size() + " blocks");
|
||||
plugin.debug("Block order: " + Arrays.toString(Arrays.stream(blockDetectArray).map(ExternalProvider::identifier).toList().toArray(new String[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
this.blocks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
this.blockProviders.clear();
|
||||
}
|
||||
|
||||
private void resetBlockDetectionOrder() {
|
||||
ArrayList<BlockProvider> list = new ArrayList<>();
|
||||
for (String plugin : ConfigManager.blockDetectOrder()) {
|
||||
BlockProvider library = blockProviders.get(plugin);
|
||||
if (library != null)
|
||||
list.add(library);
|
||||
}
|
||||
this.blockDetectArray = list.toArray(new BlockProvider[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBlock(@NotNull BlockConfig block) {
|
||||
if (blocks.containsKey(block.id())) return false;
|
||||
blocks.put(block.id(), block);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Retrieve a custom string value stored in the entity's persistent data container.
|
||||
String temp = event.getEntity().getPersistentDataContainer().get(
|
||||
requireNonNull(NamespacedKey.fromString("block", plugin.getBoostrap())),
|
||||
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= blocks.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();
|
||||
event.getBlock().setType(Material.AIR);
|
||||
return;
|
||||
}
|
||||
|
||||
Context<Player> context = Context.player(player);
|
||||
Location location = event.getBlock().getLocation();
|
||||
|
||||
// Apply block state modifiers from the BlockConfig to the block 1 tick later.
|
||||
plugin.getScheduler().sync().runLater(() -> {
|
||||
BlockState state = location.getBlock().getState();
|
||||
for (BlockStateModifier modifier : blockConfig.stateModifiers()) {
|
||||
modifier.apply(context, state);
|
||||
}
|
||||
}, 1, location);
|
||||
}
|
||||
|
||||
public boolean registerBlockProvider(BlockProvider blockProvider) {
|
||||
if (this.blockProviders.containsKey(blockProvider.identifier())) return false;
|
||||
this.blockProviders.put(blockProvider.identifier(), blockProvider);
|
||||
this.resetBlockDetectionOrder();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unregisterBlockProvider(String identification) {
|
||||
boolean success = blockProviders.remove(identification) != null;
|
||||
if (success)
|
||||
this.resetBlockDetectionOrder();
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean registerBlockDataModifierBuilder(String type, BlockDataModifierFactory factory) {
|
||||
if (this.dataFactories.containsKey(type)) return false;
|
||||
this.dataFactories.put(type, factory);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean registerBlockStateModifierBuilder(String type, BlockStateModifierFactory factory) {
|
||||
if (stateFactories.containsKey(type)) return false;
|
||||
this.stateFactories.put(type, factory);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unregisterBlockDataModifierBuilder(String type) {
|
||||
return this.dataFactories.remove(type) != null;
|
||||
}
|
||||
|
||||
public boolean unregisterBlockStateModifierBuilder(String type) {
|
||||
return this.stateFactories.remove(type) != null;
|
||||
}
|
||||
|
||||
private void registerInbuiltProperties() {
|
||||
this.registerDirectional();
|
||||
this.registerStorage();
|
||||
this.registerRotatable();
|
||||
this.registerNoteBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FallingBlock summonBlockLoot(@NotNull Context<Player> context) {
|
||||
String id = context.arg(ContextKeys.ID);
|
||||
BlockConfig config = requireNonNull(blocks.get(id), "Block " + id + " not found");
|
||||
String blockID = config.blockID();
|
||||
BlockData blockData;
|
||||
if (blockID.contains(":")) {
|
||||
String[] split = blockID.split(":", 2);
|
||||
BlockProvider provider = requireNonNull(blockProviders.get(split[0]), "BlockProvider " + split[0] + " doesn't exist");
|
||||
blockData = requireNonNull(provider.blockData(context, split[1], config.dataModifier()), "Block " + split[1] + " doesn't exist");
|
||||
} else {
|
||||
blockData = blockProviders.get("vanilla").blockData(context, blockID, config.dataModifier());
|
||||
}
|
||||
Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION));
|
||||
Location playerLocation = requireNonNull(context.getHolder()).getLocation();
|
||||
FallingBlock fallingBlock = hookLocation.getWorld().spawn(hookLocation, FallingBlock.class, (fb -> fb.setBlockData(blockData)));
|
||||
fallingBlock.getPersistentDataContainer().set(
|
||||
requireNonNull(NamespacedKey.fromString("block", plugin.getBoostrap())),
|
||||
PersistentDataType.STRING,
|
||||
id + ";" + context.getHolder().getName()
|
||||
);
|
||||
Vector vector = playerLocation.subtract(hookLocation).toVector().multiply(1.2 - 1);
|
||||
vector = vector.setY((vector.getY() + 0.2) * 1.2);
|
||||
fallingBlock.setVelocity(vector);
|
||||
return fallingBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getBlockID(@NotNull Block block) {
|
||||
for (BlockProvider blockProvider : blockDetectArray) {
|
||||
String id = blockProvider.blockID(block);
|
||||
if (id != null) return id;
|
||||
}
|
||||
// Should not reach this because vanilla library would always work
|
||||
return "AIR";
|
||||
}
|
||||
|
||||
private void registerDirectional() {
|
||||
this.registerBlockDataModifierBuilder("directional-4", (args) -> (context, blockData) -> {
|
||||
boolean arg = (boolean) args;
|
||||
if (arg && blockData instanceof Directional directional) {
|
||||
directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 4)]);
|
||||
}
|
||||
});
|
||||
this.registerBlockDataModifierBuilder("directional-6", (args) -> (context, blockData) -> {
|
||||
boolean arg = (boolean) args;
|
||||
if (arg && blockData instanceof Directional directional) {
|
||||
directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0, 6)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerRotatable() {
|
||||
this.registerBlockDataModifierBuilder("rotatable", (args) -> {
|
||||
boolean arg = (boolean) args;
|
||||
return (context, blockData) -> {
|
||||
if (arg && blockData instanceof Rotatable rotatable) {
|
||||
rotatable.setRotation(BlockFace.values()[ThreadLocalRandom.current().nextInt(BlockFace.values().length)]);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void registerNoteBlock() {
|
||||
this.registerBlockDataModifierBuilder("noteblock", (args) -> {
|
||||
if (args instanceof Section section) {
|
||||
var instrument = Instrument.valueOf(section.getString("instrument"));
|
||||
var note = new Note(section.getInt("note"));
|
||||
return (context, blockData) -> {
|
||||
if (blockData instanceof NoteBlock noteBlock) {
|
||||
noteBlock.setNote(note);
|
||||
noteBlock.setInstrument(instrument);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at noteblock property which should be Section");
|
||||
return EmptyBlockDataModifier.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerStorage() {
|
||||
this.registerBlockStateModifierBuilder("storage", (args) -> {
|
||||
if (args instanceof Section section) {
|
||||
List<Tuple<MathValue<Player>, String, Pair<MathValue<Player>, MathValue<Player>>>> contents = new ArrayList<>();
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof ConfigurationSection inner) {
|
||||
String item = inner.getString("item");
|
||||
String[] split = inner.getString("amount","1~1").split("~");
|
||||
Pair<MathValue<Player>, MathValue<Player>> amountPair = Pair.of(MathValue.auto(split[0]), MathValue.auto(split[1]));
|
||||
MathValue<Player> chance = MathValue.auto(inner.get("chance", 1d));
|
||||
contents.add(Tuple.of(chance, item, amountPair));
|
||||
}
|
||||
}
|
||||
return (context, blockState) -> {
|
||||
if (blockState instanceof Container container) {
|
||||
setInventoryItems(contents, context, container.getInventory());
|
||||
}
|
||||
};
|
||||
} else {
|
||||
plugin.getPluginLogger().warn("Invalid value type: " + args.getClass().getSimpleName() + " found at storage property which should be Section");
|
||||
return EmptyBlockStateModifier.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setInventoryItems(
|
||||
List<Tuple<MathValue<Player>, String, Pair<MathValue<Player>, MathValue<Player>>>> contents,
|
||||
Context<Player> context,
|
||||
Inventory inventory
|
||||
) {
|
||||
LinkedList<Integer> unused = new LinkedList<>();
|
||||
for (int i = 0; i < inventory.getSize(); i++) {
|
||||
unused.add(i);
|
||||
}
|
||||
Collections.shuffle(unused);
|
||||
for (Tuple<MathValue<Player>, String, Pair<MathValue<Player>, MathValue<Player>>> tuple : contents) {
|
||||
if (tuple.left().evaluate(context) > Math.random()) {
|
||||
ItemStack itemStack = plugin.getItemManager().buildAny(context, tuple.mid());
|
||||
if (itemStack != null) {
|
||||
itemStack.setAmount(RandomUtils.generateRandomInt((int) tuple.right().left().evaluate(context), (int) (tuple.right().right().evaluate(context))));
|
||||
inventory.setItem(unused.pop(), itemStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.common.command.AbstractCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.sender.SenderFactory;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.incendo.cloud.bukkit.data.Selector;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class BukkitCommandFeature<C extends CommandSender> extends AbstractCommandFeature<C> {
|
||||
|
||||
public BukkitCommandFeature(CustomFishingCommandManager<C> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected SenderFactory<?, C> getSenderFactory() {
|
||||
return (SenderFactory<?, C>) BukkitCustomFishingPlugin.getInstance().getSenderFactory();
|
||||
}
|
||||
|
||||
public Pair<TranslatableComponent.Builder, Component> resolveSelector(Selector<? extends Entity> selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) {
|
||||
Collection<? extends Entity> entities = selector.values();
|
||||
if (entities.size() == 1) {
|
||||
return Pair.of(single, Component.text(entities.iterator().next().getName()));
|
||||
} else {
|
||||
return Pair.of(multiple, Component.text(entities.size()));
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<TranslatableComponent.Builder, Component> resolveSelector(Collection<? extends Entity> selector, TranslatableComponent.Builder single, TranslatableComponent.Builder multiple) {
|
||||
if (selector.size() == 1) {
|
||||
return Pair.of(single, Component.text(selector.iterator().next().getName()));
|
||||
} else {
|
||||
return Pair.of(multiple, Component.text(selector.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command;
|
||||
|
||||
import net.kyori.adventure.util.Index;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.feature.*;
|
||||
import net.momirealms.customfishing.common.command.AbstractCommandManager;
|
||||
import net.momirealms.customfishing.common.command.CommandFeature;
|
||||
import net.momirealms.customfishing.common.sender.Sender;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.SenderMapper;
|
||||
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
|
||||
import org.incendo.cloud.execution.ExecutionCoordinator;
|
||||
import org.incendo.cloud.paper.LegacyPaperCommandManager;
|
||||
import org.incendo.cloud.setting.ManagerSetting;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BukkitCommandManager extends AbstractCommandManager<CommandSender> {
|
||||
|
||||
private final List<CommandFeature<CommandSender>> FEATURES = List.of(
|
||||
new ReloadCommand(this),
|
||||
new SellFishCommand(this),
|
||||
new GetItemCommand(this),
|
||||
new GiveItemCommand(this),
|
||||
new ImportItemCommand(this),
|
||||
new EndCompetitionCommand(this),
|
||||
new StopCompetitionCommand(this),
|
||||
new StartCompetitionCommand(this),
|
||||
new OpenMarketCommand(this),
|
||||
new OpenBagCommand(this),
|
||||
new FishingBagCommand(this),
|
||||
new EditOnlineBagCommand(this),
|
||||
new EditOfflineBagCommand(this),
|
||||
new UnlockDataCommand(this),
|
||||
new ImportDataCommand(this),
|
||||
new ExportDataCommand(this),
|
||||
new AddStatisticsCommand(this),
|
||||
new SetStatisticsCommand(this),
|
||||
new ResetStatisticsCommand(this),
|
||||
new QueryStatisticsCommand(this)
|
||||
);
|
||||
|
||||
private final Index<String, CommandFeature<CommandSender>> INDEX = Index.create(CommandFeature::getFeatureID, FEATURES);
|
||||
|
||||
public BukkitCommandManager(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin, new LegacyPaperCommandManager<>(
|
||||
plugin.getBoostrap(),
|
||||
ExecutionCoordinator.simpleCoordinator(),
|
||||
SenderMapper.identity()
|
||||
));
|
||||
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
|
||||
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);
|
||||
if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
|
||||
manager.registerBrigadier();
|
||||
manager.brigadierManager().setNativeNumberSuggestions(true);
|
||||
} else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
|
||||
manager.registerAsynchronousCompletions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sender wrapSender(CommandSender sender) {
|
||||
return ((BukkitCustomFishingPlugin) plugin).getSenderFactory().wrap(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Index<String, CommandFeature<CommandSender>> getFeatures() {
|
||||
return INDEX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
import org.incendo.cloud.parser.standard.DoubleParser;
|
||||
import org.incendo.cloud.parser.standard.EnumParser;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
|
||||
public class AddStatisticsCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public AddStatisticsCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.required("id", StringParser.stringParser())
|
||||
.required("type", EnumParser.enumParser(FishingStatistics.Type.class))
|
||||
.required("value", DoubleParser.doubleParser(0))
|
||||
.handler(context -> {
|
||||
Player player = context.get("player");
|
||||
String id = context.get("id");
|
||||
FishingStatistics.Type type = context.get("type");
|
||||
double value = context.get("value");
|
||||
BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> {
|
||||
if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) {
|
||||
userData.statistics().addAmount(id, (int) value);
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName()));
|
||||
} else if (type == FishingStatistics.Type.MAX_SIZE) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_UNSUPPORTED);
|
||||
}
|
||||
}, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "statistics_add";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.parser.standard.UUIDParser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EditOfflineBagCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public EditOfflineBagCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.required("uuid", UUIDParser.uuidParser())
|
||||
.handler(context -> {
|
||||
Player admin = context.sender();
|
||||
UUID uuid = context.get("uuid");
|
||||
BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(admin, uuid).whenComplete((result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_BAG_EDIT_FAILURE_UNSAFE);
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_BAG_EDIT_FAILURE_NEVER_PLAYED);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "edit_offline_bag";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
|
||||
public class EditOnlineBagCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public EditOnlineBagCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.handler(context -> {
|
||||
Player admin = context.sender();
|
||||
Player online = context.get("player");
|
||||
BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(admin, online.getUniqueId());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "edit_online_bag";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public class EndCompetitionCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public EndCompetitionCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
FishingCompetition competition = BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getOnGoingCompetition();
|
||||
if (competition == null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NO_COMPETITION);
|
||||
} else {
|
||||
competition.end(true);
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_END_SUCCESS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "end_competition";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.DataStorageProvider;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.util.CompletableFutures;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class ExportDataCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ExportDataCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(ConsoleCommandSender.class)
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
if (!Bukkit.getOnlinePlayers().isEmpty()) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_FAILURE_PLAYER_ONLINE);
|
||||
return;
|
||||
}
|
||||
|
||||
BukkitCustomFishingPlugin plugin = BukkitCustomFishingPlugin.getInstance();
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_START);
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
|
||||
DataStorageProvider storageProvider = plugin.getStorageManager().getDataSource();
|
||||
|
||||
Set<UUID> uuids = storageProvider.getUniqueUsers();
|
||||
Set<CompletableFuture<Void>> futures = new HashSet<>();
|
||||
AtomicInteger userCount = new AtomicInteger(0);
|
||||
Map<UUID, String> out = Collections.synchronizedMap(new TreeMap<>());
|
||||
|
||||
int amount = uuids.size();
|
||||
for (UUID uuid : uuids) {
|
||||
futures.add(storageProvider.getPlayerData(uuid, false).thenAccept(it -> {
|
||||
if (it.isPresent()) {
|
||||
out.put(uuid, plugin.getStorageManager().toJson(it.get()));
|
||||
userCount.incrementAndGet();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
CompletableFuture<Void> overallFuture = CompletableFutures.allOf(futures);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
overallFuture.get(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (TimeoutException e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_PROGRESS, Component.text(userCount.get()), Component.text(amount));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
JsonObject outJson = new JsonObject();
|
||||
for (Map.Entry<UUID, String> entry : out.entrySet()) {
|
||||
outJson.addProperty(entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
|
||||
String formattedDate = formatter.format(new Date());
|
||||
File outFile = new File(plugin.getDataFolder(), "exported-" + formattedDate + ".json.gz");
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(outFile.toPath())), StandardCharsets.UTF_8))) {
|
||||
new GsonBuilder().disableHtmlEscaping().create().toJson(outJson, writer);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unexpected issue: ", e);
|
||||
}
|
||||
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_EXPORT_SUCCESS);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "data_export";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public class FishingBagCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public FishingBagCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.handler(context -> {
|
||||
BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(context.sender(), context.sender().getUniqueId()).whenComplete((result, e) -> {
|
||||
if (!result || e != null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_FAILURE_NOT_LOADED);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "fishingbag";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.context.CommandInput;
|
||||
import org.incendo.cloud.parser.standard.IntegerParser;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class GetItemCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public GetItemCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
|
||||
@Override
|
||||
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
|
||||
return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getItemManager().getItemIDs().stream().map(Suggestion::suggestion).toList());
|
||||
}
|
||||
}))
|
||||
.optional("amount", IntegerParser.integerParser(1, 6400))
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
final int amount = context.getOrDefault("amount", 1);
|
||||
final String id = context.get("id");
|
||||
final Player player = context.sender();
|
||||
try {
|
||||
ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player).arg(ContextKeys.ID, id), id);
|
||||
if (itemStack == null) {
|
||||
throw new RuntimeException("Unrecognized item id: " + id);
|
||||
}
|
||||
int amountToGive = amount;
|
||||
int maxStack = itemStack.getType().getMaxStackSize();
|
||||
while (amountToGive > 0) {
|
||||
int perStackSize = Math.min(maxStack, amountToGive);
|
||||
amountToGive -= perStackSize;
|
||||
ItemStack more = itemStack.clone();
|
||||
more.setAmount(perStackSize);
|
||||
PlayerUtils.dropItem(player, more, false, true, false);
|
||||
}
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_SUCCESS, Component.text(amount), Component.text(id));
|
||||
} catch (NullPointerException e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_FAILURE_NOT_EXIST, Component.text(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "get_item";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.context.CommandInput;
|
||||
import org.incendo.cloud.parser.standard.IntegerParser;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class GiveItemCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public GiveItemCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
|
||||
@Override
|
||||
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
|
||||
return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getItemManager().getItemIDs().stream().map(Suggestion::suggestion).toList());
|
||||
}
|
||||
}))
|
||||
.optional("amount", IntegerParser.integerParser(1, 6400))
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
final Player player = context.get("player");
|
||||
final int amount = context.getOrDefault("amount", 1);
|
||||
final String id = context.get("id");
|
||||
try {
|
||||
ItemStack itemStack = BukkitCustomFishingPlugin.getInstance().getItemManager().buildInternal(Context.player(player).arg(ContextKeys.ID, id), id);
|
||||
if (itemStack == null) {
|
||||
throw new RuntimeException("Unrecognized item id: " + id);
|
||||
}
|
||||
int amountToGive = amount;
|
||||
int maxStack = itemStack.getType().getMaxStackSize();
|
||||
while (amountToGive > 0) {
|
||||
int perStackSize = Math.min(maxStack, amountToGive);
|
||||
amountToGive -= perStackSize;
|
||||
ItemStack more = itemStack.clone();
|
||||
more.setAmount(perStackSize);
|
||||
PlayerUtils.dropItem(player, more, false, true, false);
|
||||
}
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_GIVE_SUCCESS, Component.text(player.getName()), Component.text(amount), Component.text(id));
|
||||
} catch (NullPointerException e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_FAILURE_NOT_EXIST, Component.text(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "give_item";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.DataStorageProvider;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.util.CompletableFutures;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class ImportDataCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ImportDataCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(ConsoleCommandSender.class)
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.required("file", StringParser.greedyFlagYieldingStringParser())
|
||||
.handler(context -> {
|
||||
if (!Bukkit.getOnlinePlayers().isEmpty()) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_PLAYER_ONLINE);
|
||||
return;
|
||||
}
|
||||
String fileName = context.get("file");
|
||||
BukkitCustomFishingPlugin plugin = BukkitCustomFishingPlugin.getInstance();
|
||||
File file = new File(plugin.getDataFolder(), fileName);
|
||||
if (!file.exists()) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_NOT_EXISTS);
|
||||
return;
|
||||
}
|
||||
if (!file.getName().endsWith(".json.gz")) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_FAILURE_INVALID_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_START);
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
|
||||
JsonObject data;
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(file.toPath())), StandardCharsets.UTF_8))) {
|
||||
data = new GsonBuilder().disableHtmlEscaping().create().fromJson(reader, JsonObject.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unexpected issue: ", e);
|
||||
}
|
||||
|
||||
DataStorageProvider storageProvider = plugin.getStorageManager().getDataSource();
|
||||
var entrySet = data.entrySet();
|
||||
int amount = entrySet.size();
|
||||
AtomicInteger userCount = new AtomicInteger(0);
|
||||
Set<CompletableFuture<Void>> futures = new HashSet<>();
|
||||
|
||||
for (Map.Entry<String, JsonElement> entry : entrySet) {
|
||||
UUID uuid = UUID.fromString(entry.getKey());
|
||||
if (entry.getValue() instanceof JsonPrimitive primitive) {
|
||||
PlayerData playerData = plugin.getStorageManager().fromJson(primitive.getAsString());
|
||||
futures.add(storageProvider.updateOrInsertPlayerData(uuid, playerData, true).thenAccept(it -> userCount.incrementAndGet()));
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<Void> overallFuture = CompletableFutures.allOf(futures);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
overallFuture.get(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (TimeoutException e) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_PROGRESS, Component.text(userCount.get()), Component.text(amount));
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_IMPORT_SUCCESS);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "data_import";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.bukkit.util.ItemStackUtils;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class ImportItemCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ImportItemCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.required("id", StringParser.stringParser())
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
Player player = context.sender();
|
||||
ItemStack item = player.getInventory().getItemInMainHand();
|
||||
String id = context.get("id");
|
||||
if (item.getType() == Material.AIR) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_IMPORT_FAILURE_NO_ITEM);
|
||||
return;
|
||||
}
|
||||
File saved = new File(BukkitCustomFishingPlugin.getInstance().getDataFolder(), "imported_items.yml");
|
||||
if (!saved.exists()) {
|
||||
try {
|
||||
saved.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
YamlDocument document = BukkitCustomFishingPlugin.getInstance().getConfigManager().loadData(saved);
|
||||
Map<String, Object> map = ItemStackUtils.itemStackToMap(item);
|
||||
document.set(id, map);
|
||||
try {
|
||||
document.save(saved);
|
||||
handleFeedback(context, MessageConstants.COMMAND_ITEM_IMPORT_SUCCESS, Component.text(id));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "import_item";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
|
||||
public class OpenBagCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public OpenBagCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
final Player player = context.get("player");
|
||||
BukkitCustomFishingPlugin.getInstance().getBagManager().openBag(player, player.getUniqueId()).whenComplete((result, e) -> {
|
||||
if (!result || e != null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_BAG_OPEN_FAILURE_NOT_LOADED, Component.text(player.getName()));
|
||||
} else {
|
||||
handleFeedback(context, MessageConstants.COMMAND_BAG_OPEN_SUCCESS, Component.text(player.getName()));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "open_bag";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
|
||||
public class OpenMarketCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public OpenMarketCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
final Player player = context.get("player");
|
||||
if (BukkitCustomFishingPlugin.getInstance().getMarketManager().openMarketGUI(player)) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_MARKET_OPEN_SUCCESS, Component.text(player.getName()));
|
||||
} else {
|
||||
handleFeedback(context, MessageConstants.COMMAND_MARKET_OPEN_FAILURE_NOT_LOADED, Component.text(player.getName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "open_market";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
import org.incendo.cloud.parser.standard.EnumParser;
|
||||
|
||||
public class QueryStatisticsCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public QueryStatisticsCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.required("type", EnumParser.enumParser(FishingStatistics.Type.class))
|
||||
.handler(context -> {
|
||||
Player player = context.get("player");
|
||||
FishingStatistics.Type type = context.get("type");
|
||||
BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> {
|
||||
if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_QUERY_AMOUNT, Component.text(userData.statistics().amountMap().toString()));
|
||||
} else if (type == FishingStatistics.Type.MAX_SIZE) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_QUERY_SIZE, Component.text(userData.statistics().sizeMap().toString()));
|
||||
}
|
||||
}, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "statistics_query";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ReloadCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.handler(context -> {
|
||||
long time1 = System.currentTimeMillis();
|
||||
BukkitCustomFishingPlugin.getInstance().reload();
|
||||
handleFeedback(context, MessageConstants.COMMAND_RELOAD_SUCCESS, Component.text(System.currentTimeMillis() - time1));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "reload";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
|
||||
public class ResetStatisticsCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public ResetStatisticsCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.handler(context -> {
|
||||
Player player = context.get("player");
|
||||
BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> {
|
||||
userData.statistics().reset();
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_RESET_SUCCESS, Component.text(player.getName()));
|
||||
}, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "statistics_reset";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public class SellFishCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public SellFishCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.senderType(Player.class)
|
||||
.handler(context -> {
|
||||
if (!BukkitCustomFishingPlugin.getInstance().getMarketManager().openMarketGUI(context.sender())) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_FAILURE_NOT_LOADED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "sellfish";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.statistic.FishingStatistics;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
import org.incendo.cloud.parser.standard.DoubleParser;
|
||||
import org.incendo.cloud.parser.standard.EnumParser;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
|
||||
public class SetStatisticsCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public SetStatisticsCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.required("id", StringParser.stringParser())
|
||||
.required("type", EnumParser.enumParser(FishingStatistics.Type.class))
|
||||
.required("value", DoubleParser.doubleParser(0))
|
||||
.handler(context -> {
|
||||
Player player = context.get("player");
|
||||
String id = context.get("id");
|
||||
FishingStatistics.Type type = context.get("type");
|
||||
double value = context.get("value");
|
||||
BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(player.getUniqueId()).ifPresentOrElse(userData -> {
|
||||
if (type == FishingStatistics.Type.AMOUNT_OF_FISH_CAUGHT) {
|
||||
userData.statistics().setAmount(id, (int) value);
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName()));
|
||||
} else if (type == FishingStatistics.Type.MAX_SIZE) {
|
||||
userData.statistics().setMaxSize(id, (float) value);
|
||||
handleFeedback(context, MessageConstants.COMMAND_STATISTICS_MODIFY_SUCCESS, Component.text(player.getName()));
|
||||
}
|
||||
}, () -> handleFeedback(context, MessageConstants.COMMAND_STATISTICS_FAILURE_NOT_LOADED));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "statistics_set";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.context.CommandInput;
|
||||
import org.incendo.cloud.parser.standard.StringParser;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class StartCompetitionCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public StartCompetitionCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.required("id", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() {
|
||||
@Override
|
||||
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
|
||||
return CompletableFuture.completedFuture(BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getCompetitionIDs().stream().map(Suggestion::suggestion).toList());
|
||||
}
|
||||
}))
|
||||
.optional("group", StringParser.stringParser())
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
String id = context.get("id");
|
||||
String group = context.getOrDefault("group", null);
|
||||
if (BukkitCustomFishingPlugin.getInstance().getCompetitionManager().startCompetition(id, true, group)) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_START_SUCCESS);
|
||||
} else {
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NOT_EXIST, Component.text(id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "start_competition";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
|
||||
public class StopCompetitionCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public StopCompetitionCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s").build())
|
||||
.handler(context -> {
|
||||
FishingCompetition competition = BukkitCustomFishingPlugin.getInstance().getCompetitionManager().getOnGoingCompetition();
|
||||
if (competition == null) {
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_FAILURE_NO_COMPETITION);
|
||||
} else {
|
||||
competition.stop(true);
|
||||
handleFeedback(context, MessageConstants.COMMAND_COMPETITION_STOP_SUCCESS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "stop_competition";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.command.BukkitCommandFeature;
|
||||
import net.momirealms.customfishing.common.command.CustomFishingCommandManager;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.parser.standard.UUIDParser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class UnlockDataCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public UnlockDataCommand(CustomFishingCommandManager<CommandSender> commandManager) {
|
||||
super(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(manager.flagBuilder("silent").withAliases("s"))
|
||||
.required("uuid", UUIDParser.uuidParser())
|
||||
.handler(context -> {
|
||||
UUID uuid = context.get("uuid");
|
||||
BukkitCustomFishingPlugin.getInstance().getStorageManager().getDataSource().lockOrUnlockPlayerData(uuid, false);
|
||||
handleFeedback(context, MessageConstants.COMMAND_DATA_UNLOCK_SUCCESS, Component.text(uuid.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "data_unlock";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition;
|
||||
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BukkitCompetitionManager implements CompetitionManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<CompetitionSchedule, CompetitionConfig> timeConfigMap;
|
||||
private final HashMap<String, CompetitionConfig> commandConfigMap;
|
||||
private Competition currentCompetition;
|
||||
private SchedulerTask timerCheckTask;
|
||||
private int nextCompetitionSeconds;
|
||||
|
||||
public BukkitCompetitionManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.timeConfigMap = new HashMap<>();
|
||||
this.commandConfigMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
loadConfig();
|
||||
this.timerCheckTask = plugin.getScheduler().asyncRepeating(
|
||||
this::timerCheck,
|
||||
1,
|
||||
1,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
plugin.debug("Loaded " + commandConfigMap.size() + " competitions");
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
if (this.timerCheckTask != null)
|
||||
this.timerCheckTask.cancel();
|
||||
if (currentCompetition != null && currentCompetition.isOnGoing())
|
||||
this.currentCompetition.stop(true);
|
||||
this.commandConfigMap.clear();
|
||||
this.timeConfigMap.clear();
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
if (this.timerCheckTask != null)
|
||||
this.timerCheckTask.cancel();
|
||||
if (currentCompetition != null && currentCompetition.isOnGoing())
|
||||
this.currentCompetition.stop(false);
|
||||
this.commandConfigMap.clear();
|
||||
this.timeConfigMap.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void loadConfig() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
for (String type : List.of("competition")) {
|
||||
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
|
||||
if (!typeFolder.exists()) {
|
||||
if (!typeFolder.mkdirs()) return;
|
||||
plugin.getBoostrap().saveResource("contents" + File.separator + type + File.separator + "default.yml", false);
|
||||
}
|
||||
fileDeque.push(typeFolder);
|
||||
while (!fileDeque.isEmpty()) {
|
||||
File file = fileDeque.pop();
|
||||
File[] files = file.listFiles();
|
||||
if (files == null) continue;
|
||||
for (File subFile : files) {
|
||||
if (subFile.isDirectory()) {
|
||||
fileDeque.push(subFile);
|
||||
} else if (subFile.isFile() && subFile.getName().endsWith(".yml")) {
|
||||
this.loadSingleFileCompetition(subFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSingleFileCompetition(File file) {
|
||||
YamlDocument document = plugin.getConfigManager().loadData(file);
|
||||
for (Map.Entry<String, Object> entry : document.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section section) {
|
||||
CompetitionConfig.Builder builder = CompetitionConfig.builder()
|
||||
.key(entry.getKey())
|
||||
.goal(CompetitionGoal.index().value(section.getString("goal", "TOTAL_SCORE").toLowerCase(Locale.ENGLISH)))
|
||||
.minPlayers(section.getInt("min-players", 0))
|
||||
.duration(section.getInt("duration", 300))
|
||||
.rewards(getPrizeActions(section.getSection("rewards")))
|
||||
.joinRequirements(plugin.getRequirementManager().parseRequirements(section.getSection("participate-requirements"), false))
|
||||
.joinActions(plugin.getActionManager().parseActions(section.getSection("participate-actions")))
|
||||
.startActions(plugin.getActionManager().parseActions(section.getSection("start-actions")))
|
||||
.endActions(plugin.getActionManager().parseActions(section.getSection("end-actions")))
|
||||
.skipActions(plugin.getActionManager().parseActions(section.getSection("skip-actions")));;
|
||||
if (section.getBoolean("bossbar.enable", false)) {
|
||||
builder.bossBarConfig(
|
||||
BossBarConfig.builder()
|
||||
.enable(true)
|
||||
.color(BossBar.Color.valueOf(section.getString("bossbar.color", "WHITE").toUpperCase(Locale.ENGLISH)))
|
||||
.overlay(BossBar.Overlay.valueOf(section.getString("bossbar.overlay", "PROGRESS").toUpperCase(Locale.ENGLISH)))
|
||||
.refreshRate(section.getInt("bossbar.refresh-rate", 20))
|
||||
.switchInterval(section.getInt("bossbar.switch-interval", 200))
|
||||
.showToAll(!section.getBoolean("bossbar.only-show-to-participants", true))
|
||||
.text(section.getStringList("bossbar.text").toArray(new String[0]))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
if (section.getBoolean("actionbar.enable", false)) {
|
||||
builder.actionBarConfig(
|
||||
ActionBarConfig.builder()
|
||||
.enable(true)
|
||||
.refreshRate(section.getInt("actionbar.refresh-rate", 5))
|
||||
.switchInterval(section.getInt("actionbar.switch-interval", 200))
|
||||
.showToAll(!section.getBoolean("actionbar.only-show-to-participants", true))
|
||||
.text(section.getStringList("actionbar.text").toArray(new String[0]))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
CompetitionConfig competitionConfig = builder.build();
|
||||
List<Pair<Integer, Integer>> timePairs = section.getStringList("start-time")
|
||||
.stream().map(it -> {
|
||||
String[] split = it.split(":");
|
||||
return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
|
||||
}).toList();
|
||||
List<Integer> weekdays = section.getIntList("start-weekday");
|
||||
if (weekdays.isEmpty()) {
|
||||
weekdays.addAll(List.of(1,2,3,4,5,6,7));
|
||||
}
|
||||
for (Integer weekday : weekdays) {
|
||||
for (Pair<Integer, Integer> timePair : timePairs) {
|
||||
CompetitionSchedule schedule = new CompetitionSchedule(weekday, timePair.left(), timePair.right(), 0);
|
||||
timeConfigMap.put(schedule, competitionConfig);
|
||||
}
|
||||
}
|
||||
commandConfigMap.put(entry.getKey(), competitionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets prize actions from a configuration section.
|
||||
*
|
||||
* @param section The configuration section containing prize actions.
|
||||
* @return A HashMap where keys are action names and values are arrays of Action objects.
|
||||
*/
|
||||
public HashMap<String, Action<Player>[]> getPrizeActions(Section section) {
|
||||
HashMap<String, Action<Player>[]> map = new HashMap<>();
|
||||
if (section == null) return map;
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
map.put(entry.getKey(), plugin.getActionManager().parseActions(innerSection));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the timer for the next competition and starts it if necessary.
|
||||
*/
|
||||
public void timerCheck() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
CompetitionSchedule competitionSchedule = new CompetitionSchedule(
|
||||
now.getDayOfWeek().getValue(),
|
||||
now.getHour(),
|
||||
now.getMinute(),
|
||||
now.getSecond()
|
||||
);
|
||||
int seconds = competitionSchedule.getTotalSeconds();
|
||||
int nextCompetitionTime = 7 * 24 * 60 * 60;
|
||||
for (CompetitionSchedule schedule : timeConfigMap.keySet()) {
|
||||
nextCompetitionTime = Math.min(nextCompetitionTime, schedule.getTimeDelta(seconds));
|
||||
}
|
||||
this.nextCompetitionSeconds = nextCompetitionTime;
|
||||
CompetitionConfig config = timeConfigMap.get(competitionSchedule);
|
||||
if (config != null) {
|
||||
startCompetition(config, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startCompetition(String competition, boolean force, String serverGroup) {
|
||||
CompetitionConfig config = commandConfigMap.get(competition);
|
||||
if (config == null) {
|
||||
return false;
|
||||
}
|
||||
return startCompetition(config, force, serverGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (currentCompetition == null) return null;
|
||||
return currentCompetition.isOnGoing() ? currentCompetition : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startCompetition(CompetitionConfig config, boolean force, @Nullable String serverGroup) {
|
||||
if (!force) {
|
||||
int players = Bukkit.getOnlinePlayers().size();
|
||||
if (players < config.minPlayersToStart()) {
|
||||
ActionManager.trigger(Context.player(null), config.skipActions());
|
||||
return false;
|
||||
}
|
||||
start(config);
|
||||
return true;
|
||||
} else if (serverGroup == null) {
|
||||
start(config);
|
||||
return true;
|
||||
} else {
|
||||
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||
out.writeUTF(serverGroup);
|
||||
out.writeUTF("competition");
|
||||
out.writeUTF("start");
|
||||
out.writeUTF(config.key());
|
||||
RedisManager.getInstance().publishRedisMessage(Arrays.toString(out.toByteArray()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void start(CompetitionConfig config) {
|
||||
if (getOnGoingCompetition() != null) {
|
||||
// END
|
||||
currentCompetition.end(true);
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
// start one second later
|
||||
this.currentCompetition = new Competition(plugin, config);
|
||||
this.currentCompetition.start(true);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
// start instantly
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
this.currentCompetition = new Competition(plugin, config);
|
||||
this.currentCompetition.start(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of seconds until the next competition.
|
||||
*
|
||||
* @return The number of seconds until the next competition.
|
||||
*/
|
||||
@Override
|
||||
public int getNextCompetitionInSeconds() {
|
||||
return nextCompetitionSeconds;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CompetitionConfig getCompetition(String key) {
|
||||
return commandConfigMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCompetitionIDs() {
|
||||
return commandConfigMap.keySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.event.CompetitionEvent;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionGoal;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.RankingProvider;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.bukkit.competition.actionbar.ActionBarManager;
|
||||
import net.momirealms.customfishing.bukkit.competition.bossbar.BossBarManager;
|
||||
import net.momirealms.customfishing.bukkit.competition.ranking.LocalRankingProvider;
|
||||
import net.momirealms.customfishing.bukkit.competition.ranking.RedisRankingProvider;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.locale.TranslationManager;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
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.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Competition implements FishingCompetition {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final CompetitionConfig config;
|
||||
private SchedulerTask competitionTimerTask;
|
||||
private final CompetitionGoal goal;
|
||||
private final Context<Player> publicContext;
|
||||
private final RankingProvider rankingProvider;
|
||||
private float progress;
|
||||
private int remainingTime;
|
||||
private long startTime;
|
||||
private BossBarManager bossBarManager;
|
||||
private ActionBarManager actionBarManager;
|
||||
|
||||
public Competition(BukkitCustomFishingPlugin plugin, CompetitionConfig config) {
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.goal = config.goal() == CompetitionGoal.RANDOM ? CompetitionGoal.getRandom() : config.goal();
|
||||
if (ConfigManager.redisRanking()) this.rankingProvider = new RedisRankingProvider();
|
||||
else this.rankingProvider = new LocalRankingProvider();
|
||||
this.publicContext = Context.player(null, true);
|
||||
this.publicContext.arg(ContextKeys.GOAL, goal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(boolean triggerEvent) {
|
||||
this.progress = 1;
|
||||
this.remainingTime = this.config.durationInSeconds();
|
||||
this.startTime = Instant.now().getEpochSecond();
|
||||
|
||||
this.arrangeTimerTask();
|
||||
if (this.config.bossBarConfig() != null && this.config.bossBarConfig().enabled()) {
|
||||
this.bossBarManager = new BossBarManager(this.config.bossBarConfig(), this);
|
||||
this.bossBarManager.load();
|
||||
}
|
||||
if (this.config.actionBarConfig() != null && this.config.actionBarConfig().enabled()) {
|
||||
this.actionBarManager = new ActionBarManager(this.config.actionBarConfig(), this);
|
||||
this.actionBarManager.load();
|
||||
}
|
||||
|
||||
this.updatePublicPlaceholders();
|
||||
ActionManager.trigger(this.publicContext, this.config.startActions());
|
||||
this.rankingProvider.clear();
|
||||
if (triggerEvent) {
|
||||
this.plugin.getScheduler().async().execute(() -> {
|
||||
CompetitionEvent competitionStartEvent = new CompetitionEvent(CompetitionEvent.State.START, this);
|
||||
Bukkit.getPluginManager().callEvent(competitionStartEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean triggerEvent) {
|
||||
if (this.competitionTimerTask != null)
|
||||
this.competitionTimerTask.cancel();
|
||||
if (this.bossBarManager != null)
|
||||
this.bossBarManager.unload();
|
||||
if (this.actionBarManager != null)
|
||||
this.actionBarManager.unload();
|
||||
this.rankingProvider.clear();
|
||||
this.remainingTime = 0;
|
||||
if (triggerEvent) {
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
CompetitionEvent competitionEvent = new CompetitionEvent(CompetitionEvent.State.STOP, this);
|
||||
Bukkit.getPluginManager().callEvent(competitionEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(boolean triggerEvent) {
|
||||
// mark it as ended
|
||||
this.remainingTime = 0;
|
||||
|
||||
// cancel some sub tasks
|
||||
if (competitionTimerTask != null)
|
||||
this.competitionTimerTask.cancel();
|
||||
if (this.bossBarManager != null)
|
||||
this.bossBarManager.unload();
|
||||
if (this.actionBarManager != null)
|
||||
this.actionBarManager.unload();
|
||||
|
||||
// give prizes
|
||||
HashMap<String, Action<Player>[]> rewardsMap = config.rewards();
|
||||
if (rankingProvider.getSize() != 0 && rewardsMap != null) {
|
||||
Iterator<Pair<String, Double>> iterator = rankingProvider.getIterator();
|
||||
int i = 1;
|
||||
while (iterator.hasNext()) {
|
||||
Pair<String, Double> competitionPlayer = iterator.next();
|
||||
this.publicContext.arg(ContextKeys.of(i + "_player", String.class), competitionPlayer.left());
|
||||
this.publicContext.arg(ContextKeys.of(i + "_score", String.class), String.format("%.2f", competitionPlayer.right()));
|
||||
if (i < rewardsMap.size()) {
|
||||
Player player = Bukkit.getPlayer(competitionPlayer.left());
|
||||
if (player != null) {
|
||||
ActionManager.trigger(Context.player(player).combine(this.publicContext), rewardsMap.get(String.valueOf(i)));
|
||||
}
|
||||
} else {
|
||||
Action<Player>[] actions = rewardsMap.get("participation");
|
||||
if (actions != null) {
|
||||
Player player = Bukkit.getPlayer(competitionPlayer.left()); {
|
||||
if (player != null) {
|
||||
ActionManager.trigger(Context.player(player).combine(this.publicContext), actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// end actions
|
||||
ActionManager.trigger(publicContext, config.endActions());
|
||||
|
||||
// call event
|
||||
if (triggerEvent) {
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
CompetitionEvent competitionEndEvent = new CompetitionEvent(CompetitionEvent.State.END, this);
|
||||
Bukkit.getPluginManager().callEvent(competitionEndEvent);
|
||||
});
|
||||
}
|
||||
|
||||
// 1 seconds delay for other servers to read the redis data
|
||||
plugin.getScheduler().asyncLater(this.rankingProvider::clear, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void arrangeTimerTask() {
|
||||
this.competitionTimerTask = this.plugin.getScheduler().asyncRepeating(() -> {
|
||||
if (decreaseTime()) {
|
||||
end(true);
|
||||
return;
|
||||
}
|
||||
updatePublicPlaceholders();
|
||||
}, 1, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void updatePublicPlaceholders() {
|
||||
for (int i = 1; i < ConfigManager.placeholderLimit() + 1; i++) {
|
||||
Optional<String> player = Optional.ofNullable(this.rankingProvider.getPlayerAt(i));
|
||||
if (player.isPresent()) {
|
||||
this.publicContext.arg(ContextKeys.of(i + "_player", String.class), player.get());
|
||||
this.publicContext.arg(ContextKeys.of(i + "_score", String.class), String.format("%.2f", this.rankingProvider.getScoreAt(i)));
|
||||
} else {
|
||||
this.publicContext.arg(ContextKeys.of(i + "_player", String.class), TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_PLAYER.build().key()));
|
||||
this.publicContext.arg(ContextKeys.of(i + "_score", String.class), TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_SCORE.build().key()));
|
||||
}
|
||||
}
|
||||
this.publicContext.arg(ContextKeys.HOUR, remainingTime < 3600 ? "" : (remainingTime / 3600) + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_HOUR.build().key()));
|
||||
this.publicContext.arg(ContextKeys.MINUTE, remainingTime < 60 ? "" : (remainingTime % 3600) / 60 + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_MINUTE.build().key()));
|
||||
this.publicContext.arg(ContextKeys.SECOND, remainingTime == 0 ? "" : remainingTime % 60 + TranslationManager.miniMessageTranslation(MessageConstants.FORMAT_SECOND.build().key()));
|
||||
this.publicContext.arg(ContextKeys.SECONDS, remainingTime);
|
||||
}
|
||||
|
||||
@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.durationInSeconds();
|
||||
remainingTime = (int) (duration - (current - startTime));
|
||||
progress = (float) remainingTime / duration;
|
||||
return remainingTime <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshData(Player player, double score) {
|
||||
// if player join for the first time, trigger join actions
|
||||
if (!hasPlayerJoined(player)) {
|
||||
ActionManager.trigger(Context.player(player).combine(publicContext), config.joinActions());
|
||||
}
|
||||
|
||||
// show competition info
|
||||
if (this.bossBarManager != null)
|
||||
this.bossBarManager.showBossBarTo(player);
|
||||
if (this.actionBarManager != null)
|
||||
this.actionBarManager.showActionBarTo(player);
|
||||
|
||||
// refresh data
|
||||
this.goal.refreshScore(rankingProvider, player, score);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPlayerJoined(OfflinePlayer player) {
|
||||
return rankingProvider.getPlayerRank(player.getName()) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRemainingTime() {
|
||||
return remainingTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompetitionConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompetitionGoal getGoal() {
|
||||
return goal;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RankingProvider getRanking() {
|
||||
return rankingProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context<Player> getPublicContext() {
|
||||
return publicContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition;
|
||||
|
||||
public class CompetitionSchedule {
|
||||
|
||||
private final int weekday;
|
||||
private final int hour;
|
||||
private final int minute;
|
||||
private final int second;
|
||||
|
||||
public CompetitionSchedule(int weekday, int hour, int minute, int second) {
|
||||
this.weekday = weekday;
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the weekday associated with this time schedule.
|
||||
*
|
||||
* @return The weekday value (e.g., 1 for Monday, 2 for Tuesday, etc.).
|
||||
*/
|
||||
public int getWeekday() {
|
||||
return weekday;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hour of the day associated with this time schedule.
|
||||
*
|
||||
* @return The hour value (0-23).
|
||||
*/
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minute of the hour associated with this time schedule.
|
||||
*
|
||||
* @return The minute value (0-59).
|
||||
*/
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the second of the minute associated with this time schedule.
|
||||
*
|
||||
* @return The second value (0-59).
|
||||
*/
|
||||
public int getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total number of seconds represented by this time schedule.
|
||||
*
|
||||
* @return The total number of seconds.
|
||||
*/
|
||||
public int getTotalSeconds() {
|
||||
return second +
|
||||
minute * 60 +
|
||||
hour * 60 * 60 +
|
||||
weekday * 24 * 60 * 60;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time difference (delta) in seconds between this time schedule and a given total seconds value.
|
||||
*
|
||||
* @param totalSeconds The total seconds value to compare against.
|
||||
* @return The time difference in seconds.
|
||||
*/
|
||||
public int getTimeDelta(int totalSeconds) {
|
||||
int thisSeconds = getTotalSeconds();
|
||||
if (thisSeconds >= totalSeconds) {
|
||||
return thisSeconds - totalSeconds;
|
||||
} else {
|
||||
return (7 * 24 * 60 * 60) - (totalSeconds - thisSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 7;
|
||||
int result = 1;
|
||||
result = prime * result + weekday;
|
||||
result = prime * result + hour;
|
||||
result = prime * result + minute;
|
||||
result = prime * result + second;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
CompetitionSchedule other = (CompetitionSchedule) obj;
|
||||
if (weekday != other.weekday)
|
||||
return false;
|
||||
if (hour != other.hour)
|
||||
return false;
|
||||
if (minute != other.minute)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.actionbar;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig;
|
||||
import net.momirealms.customfishing.bukkit.competition.Competition;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ActionBarManager implements Listener {
|
||||
|
||||
private static final ConcurrentHashMap<UUID, ActionBarSender> senderMap = new ConcurrentHashMap<>();
|
||||
private final ActionBarConfig actionBarConfig;
|
||||
private final Competition competition;
|
||||
|
||||
public ActionBarManager(ActionBarConfig actionBarConfig, Competition competition) {
|
||||
this.actionBarConfig = actionBarConfig;
|
||||
this.competition = competition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the ActionBar manager, registering events and showing ActionBar messages to online players.
|
||||
*/
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap());
|
||||
if (actionBarConfig.showToAll()) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition);
|
||||
if (!sender.isVisible()) {
|
||||
sender.show();
|
||||
}
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the ActionBar manager, unregistering events and hiding ActionBar messages for all players.
|
||||
*/
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
for (ActionBarSender ActionBarSender : senderMap.values()) {
|
||||
ActionBarSender.hide();
|
||||
}
|
||||
senderMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the PlayerQuitEvent to hide ActionBar messages for a player when they quit the game.
|
||||
*
|
||||
* @param event The PlayerQuitEvent.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
ActionBarSender sender = senderMap.remove(player.getUniqueId());
|
||||
if (sender != null) {
|
||||
if (sender.isVisible())
|
||||
sender.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the PlayerJoinEvent to show ActionBar messages to players when they join the game.
|
||||
*
|
||||
* @param event The PlayerJoinEvent.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
BukkitCustomFishingPlugin.getInstance().getScheduler().asyncLater(() -> {
|
||||
boolean hasJoined = competition.hasPlayerJoined(player);
|
||||
if ((hasJoined || actionBarConfig.showToAll())
|
||||
&& !senderMap.containsKey(player.getUniqueId())) {
|
||||
ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition);
|
||||
if (!sender.isVisible()) {
|
||||
sender.show();
|
||||
}
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an ActionBar message to a specific player.
|
||||
*
|
||||
* @param player The player to show the ActionBar message to.
|
||||
*/
|
||||
public void showActionBarTo(Player player) {
|
||||
ActionBarSender sender = senderMap.get(player.getUniqueId());
|
||||
if (sender == null) {
|
||||
sender = new ActionBarSender(player, actionBarConfig, competition);
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
if (!sender.isVisible())
|
||||
sender.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.actionbar;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.ActionBarConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.DynamicText;
|
||||
import net.momirealms.customfishing.bukkit.competition.Competition;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.locale.TranslationManager;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ActionBarSender {
|
||||
|
||||
private final Player player;
|
||||
private final Audience audience;
|
||||
private int refreshTimer;
|
||||
private int switchTimer;
|
||||
private int counter;
|
||||
private final DynamicText[] texts;
|
||||
private SchedulerTask senderTask;
|
||||
private final ActionBarConfig config;
|
||||
private boolean isShown;
|
||||
private final Competition competition;
|
||||
private final Context<Player> privateContext;
|
||||
|
||||
public ActionBarSender(Player player, ActionBarConfig config, Competition competition) {
|
||||
this.player = player;
|
||||
this.audience = BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(player);
|
||||
this.config = config;
|
||||
this.privateContext = Context.player(player);
|
||||
this.isShown = false;
|
||||
this.competition = competition;
|
||||
this.updatePrivatePlaceholders();
|
||||
String[] str = config.texts();
|
||||
texts = new DynamicText[str.length];
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
texts[i] = new DynamicText(player, str[i]);
|
||||
texts[i].update(privateContext.placeholderMap());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void updatePrivatePlaceholders() {
|
||||
this.privateContext.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", competition.getRanking().getPlayerScore(player.getName())));
|
||||
int rank = competition.getRanking().getPlayerRank(player.getName());
|
||||
this.privateContext.arg(ContextKeys.RANK, rank != -1 ? String.valueOf(rank) : TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_RANK.build().key()));
|
||||
this.privateContext.combine(competition.getPublicContext());
|
||||
}
|
||||
|
||||
public void show() {
|
||||
this.isShown = true;
|
||||
senderTask = BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> {
|
||||
switchTimer++;
|
||||
if (switchTimer > config.switchInterval()) {
|
||||
switchTimer = 0;
|
||||
counter++;
|
||||
}
|
||||
if (refreshTimer < config.refreshRate()){
|
||||
refreshTimer++;
|
||||
} else {
|
||||
refreshTimer = 0;
|
||||
DynamicText text = texts[counter % (texts.length)];
|
||||
updatePrivatePlaceholders();
|
||||
text.update(this.privateContext.placeholderMap());
|
||||
audience.sendActionBar(AdventureHelper.miniMessage(text.getLatestValue()));
|
||||
}
|
||||
}, 50, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
if (senderTask != null)
|
||||
senderTask.cancel();
|
||||
this.isShown = false;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return this.isShown;
|
||||
}
|
||||
|
||||
public ActionBarConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.bossbar;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig;
|
||||
import net.momirealms.customfishing.bukkit.competition.Competition;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BossBarManager implements Listener {
|
||||
|
||||
private static final ConcurrentHashMap<UUID, BossBarSender> senderMap = new ConcurrentHashMap<>();
|
||||
private final BossBarConfig bossBarConfig;
|
||||
private final Competition competition;
|
||||
|
||||
public BossBarManager(BossBarConfig bossBarConfig, Competition competition) {
|
||||
this.bossBarConfig = bossBarConfig;
|
||||
this.competition = competition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the boss bar manager, registering events and showing boss bars to online players.
|
||||
*/
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, BukkitCustomFishingPlugin.getInstance().getBoostrap());
|
||||
if (bossBarConfig.showToAll()) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
BossBarSender sender = new BossBarSender(player, bossBarConfig, competition);
|
||||
if (!sender.isVisible()) {
|
||||
sender.show();
|
||||
}
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the boss bar manager, unregistering events and hiding boss bars for all players.
|
||||
*/
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
for (BossBarSender bossBarSender : senderMap.values()) {
|
||||
bossBarSender.hide();
|
||||
}
|
||||
senderMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the PlayerQuitEvent to hide the boss bar for a player when they quit the game.
|
||||
*
|
||||
* @param event The PlayerQuitEvent.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
BossBarSender sender = senderMap.remove(player.getUniqueId());
|
||||
if (sender != null) {
|
||||
if (sender.isVisible())
|
||||
sender.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the PlayerJoinEvent to show boss bars to players when they join the game.
|
||||
*
|
||||
* @param event The PlayerJoinEvent.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
BukkitCustomFishingPlugin.getInstance().getScheduler().asyncLater(() -> {
|
||||
boolean hasJoined = competition.hasPlayerJoined(player);
|
||||
if ((hasJoined || bossBarConfig.showToAll())
|
||||
&& !senderMap.containsKey(player.getUniqueId())) {
|
||||
BossBarSender sender = new BossBarSender(player, bossBarConfig, competition);
|
||||
if (!sender.isVisible()) {
|
||||
sender.show();
|
||||
}
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a boss bar to a specific player.
|
||||
*
|
||||
* @param player The player to show the boss bar to.
|
||||
*/
|
||||
public void showBossBarTo(Player player) {
|
||||
BossBarSender sender = senderMap.get(player.getUniqueId());
|
||||
if (sender == null) {
|
||||
sender = new BossBarSender(player, bossBarConfig, competition);
|
||||
senderMap.put(player.getUniqueId(), sender);
|
||||
}
|
||||
if (!sender.isVisible())
|
||||
sender.show();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.bossbar;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.info.BossBarConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.DynamicText;
|
||||
import net.momirealms.customfishing.bukkit.competition.Competition;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.locale.MessageConstants;
|
||||
import net.momirealms.customfishing.common.locale.TranslationManager;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BossBarSender {
|
||||
|
||||
private final Player player;
|
||||
private final Audience audience;
|
||||
private int refreshTimer;
|
||||
private int switchTimer;
|
||||
private int counter;
|
||||
private final DynamicText[] texts;
|
||||
private SchedulerTask senderTask;
|
||||
private final BossBar bossBar;
|
||||
private final BossBarConfig config;
|
||||
private boolean isShown;
|
||||
private final Competition competition;
|
||||
private final Context<Player> privateContext;
|
||||
|
||||
public BossBarSender(Player player, BossBarConfig config, Competition competition) {
|
||||
this.player = player;
|
||||
this.audience = BukkitCustomFishingPlugin.getInstance().getSenderFactory().getAudience(player);
|
||||
this.config = config;
|
||||
this.isShown = false;
|
||||
this.competition = competition;
|
||||
this.privateContext = Context.player(player);
|
||||
this.updatePrivatePlaceholders();
|
||||
String[] str = config.texts();
|
||||
texts = new DynamicText[str.length];
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
texts[i] = new DynamicText(player, str[i]);
|
||||
texts[i].update(privateContext.placeholderMap());
|
||||
}
|
||||
bossBar = BossBar.bossBar(
|
||||
AdventureHelper.miniMessage(texts[0].getLatestValue()),
|
||||
competition.getProgress(),
|
||||
config.color(),
|
||||
config.overlay(),
|
||||
Set.of()
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
private void updatePrivatePlaceholders() {
|
||||
this.privateContext.arg(ContextKeys.SCORE_FORMATTED, String.format("%.2f", competition.getRanking().getPlayerScore(player.getName())));
|
||||
int rank = competition.getRanking().getPlayerRank(player.getName());
|
||||
this.privateContext.arg(ContextKeys.RANK, rank != -1 ? String.valueOf(rank) : TranslationManager.miniMessageTranslation(MessageConstants.COMPETITION_NO_RANK.build().key()));
|
||||
this.privateContext.combine(competition.getPublicContext());
|
||||
}
|
||||
|
||||
public void show() {
|
||||
this.isShown = true;
|
||||
this.bossBar.addViewer(audience);
|
||||
this.senderTask = BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> {
|
||||
switchTimer++;
|
||||
if (switchTimer > config.switchInterval()) {
|
||||
switchTimer = 0;
|
||||
counter++;
|
||||
}
|
||||
if (refreshTimer < config.refreshRate()){
|
||||
refreshTimer++;
|
||||
} else {
|
||||
refreshTimer = 0;
|
||||
DynamicText text = texts[counter % (texts.length)];
|
||||
updatePrivatePlaceholders();
|
||||
if (text.update(privateContext.placeholderMap())) {
|
||||
bossBar.name(AdventureHelper.miniMessage(text.getLatestValue()));
|
||||
}
|
||||
bossBar.progress(competition.getProgress());
|
||||
}
|
||||
}, 50, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return this.isShown;
|
||||
}
|
||||
|
||||
public BossBarConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
this.bossBar.removeViewer(audience);
|
||||
if (senderTask != null) senderTask.cancel();
|
||||
this.isShown = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.ranking;
|
||||
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.RankingProvider;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implementation of the Ranking interface that manages the ranking of competition players locally.
|
||||
*/
|
||||
public class LocalRankingProvider implements RankingProvider {
|
||||
|
||||
private final Set<CompetitionPlayer> competitionPlayers;
|
||||
|
||||
public LocalRankingProvider() {
|
||||
competitionPlayers = Collections.synchronizedSet(new TreeSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a competition player to the ranking.
|
||||
*
|
||||
* @param competitionPlayer The CompetitionPlayer to add.
|
||||
*/
|
||||
@Override
|
||||
public void addPlayer(CompetitionPlayer competitionPlayer) {
|
||||
competitionPlayers.add(competitionPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a competition player from the ranking.
|
||||
*
|
||||
* @param player player's name
|
||||
*/
|
||||
@Override
|
||||
public void removePlayer(String player) {
|
||||
competitionPlayers.removeIf(e -> e.getPlayer().equals(player));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a competition player from the ranking.
|
||||
*
|
||||
* @param competitionPlayer The CompetitionPlayer to remove.
|
||||
*/
|
||||
public void removePlayer(CompetitionPlayer competitionPlayer) {
|
||||
competitionPlayers.removeIf(e -> e.equals(competitionPlayer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the list of competition players.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
competitionPlayers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a competition player by their name.
|
||||
*
|
||||
* @param player The name of the player to retrieve.
|
||||
* @return The CompetitionPlayer object if found, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
public CompetitionPlayer getCompetitionPlayer(String player) {
|
||||
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
|
||||
if (competitionPlayer.getPlayer().equals(player)) {
|
||||
return competitionPlayer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompetitionPlayer getCompetitionPlayer(int rank) {
|
||||
int i = 1;
|
||||
int size = getSize();
|
||||
if (rank > size) return null;
|
||||
for (CompetitionPlayer player : competitionPlayers) {
|
||||
if (rank == i) {
|
||||
return player;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for iterating over items of player names and scores.
|
||||
*
|
||||
* @return An iterator for items of player names and scores.
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Pair<String, Double>> getIterator() {
|
||||
List<Pair<String, Double>> players = new ArrayList<>();
|
||||
for (CompetitionPlayer competitionPlayer: competitionPlayers){
|
||||
players.add(Pair.of(competitionPlayer.getPlayer(), competitionPlayer.getScore()));
|
||||
}
|
||||
return players.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of competition players.
|
||||
*
|
||||
* @return The number of competition players.
|
||||
*/
|
||||
@Override
|
||||
public int getSize() {
|
||||
return competitionPlayers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rank of a player based on their name.
|
||||
*
|
||||
* @param player The name of the player to get the rank for.
|
||||
* @return The rank of the player, or -1 if the player is not found.
|
||||
*/
|
||||
@Override
|
||||
public int getPlayerRank(String player) {
|
||||
int index = 1;
|
||||
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
|
||||
if (competitionPlayer.getPlayer().equals(player)) {
|
||||
return index;
|
||||
}else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score of a player based on their name.
|
||||
*
|
||||
* @param player The name of the player to get the score for.
|
||||
* @return The score of the player, or 0 if the player is not found.
|
||||
*/
|
||||
@Override
|
||||
public double getPlayerScore(String player) {
|
||||
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
|
||||
if (competitionPlayer.getPlayer().equals(player)) {
|
||||
return competitionPlayer.getScore();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of a player at a given index.
|
||||
*
|
||||
* @param i The index of the player to retrieve.
|
||||
* @return The name of the player at the specified index, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
public String getPlayerAt(int i) {
|
||||
int index = 1;
|
||||
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
|
||||
if (index == i) {
|
||||
return competitionPlayer.getPlayer();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score of a player at a given index.
|
||||
*
|
||||
* @param i The index of the player to retrieve.
|
||||
* @return The score of the player at the specified index, or 0 if not found.
|
||||
*/
|
||||
@Override
|
||||
public double getScoreAt(int i) {
|
||||
int index = 1;
|
||||
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
|
||||
if (index == i) {
|
||||
return competitionPlayer.getScore();
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the data for a player by adding a score to their existing score or creating a new player.
|
||||
*
|
||||
* @param player The name of the player to update or create.
|
||||
* @param score The score to add to the player's existing score or set as their initial score.
|
||||
*/
|
||||
@Override
|
||||
public void refreshData(String player, double score) {
|
||||
CompetitionPlayer competitionPlayer = getCompetitionPlayer(player);
|
||||
if (competitionPlayer != null) {
|
||||
removePlayer(competitionPlayer);
|
||||
competitionPlayer.addScore(score);
|
||||
addPlayer(competitionPlayer);
|
||||
} else {
|
||||
competitionPlayer = new CompetitionPlayer(player, score);
|
||||
addPlayer(competitionPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data for a player, updating their score or creating a new player.
|
||||
*
|
||||
* @param player The name of the player to update or create.
|
||||
* @param score The score to set for the player.
|
||||
*/
|
||||
@Override
|
||||
public void setData(String player, double score) {
|
||||
CompetitionPlayer competitionPlayer = getCompetitionPlayer(player);
|
||||
if (competitionPlayer != null) {
|
||||
removePlayer(competitionPlayer);
|
||||
competitionPlayer.setScore(score);
|
||||
addPlayer(competitionPlayer);
|
||||
} else {
|
||||
competitionPlayer = new CompetitionPlayer(player, score);
|
||||
addPlayer(competitionPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.competition.ranking;
|
||||
|
||||
import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer;
|
||||
import net.momirealms.customfishing.api.mechanic.competition.RankingProvider;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.resps.Tuple;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class RedisRankingProvider implements RankingProvider {
|
||||
|
||||
/**
|
||||
* Clears the ranking data by removing all players and scores.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
jedis.del("cf_competition_" + ConfigManager.serverGroup());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a competition player by their name from the Redis ranking.
|
||||
*
|
||||
* @param player The name of the player to retrieve.
|
||||
* @return The CompetitionPlayer object if found, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
public CompetitionPlayer getCompetitionPlayer(String player) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
Double score = jedis.zscore("cf_competition_" + ConfigManager.serverGroup(), player);
|
||||
if (score == null || score == 0) return null;
|
||||
return new CompetitionPlayer(player, Float.parseFloat(score.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompetitionPlayer getCompetitionPlayer(int rank) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
List<Tuple> player = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1);
|
||||
if (player == null || player.isEmpty()) return null;
|
||||
return new CompetitionPlayer(player.get(0).getElement(), player.get(0).getScore());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPlayer(CompetitionPlayer competitionPlayer) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
jedis.zincrby("cf_competition_" + ConfigManager.serverGroup(), competitionPlayer.getScore(), competitionPlayer.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlayer(String player) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
jedis.zrem("cf_competition_" + ConfigManager.serverGroup(), player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for iterating over items of player names and scores in descending order.
|
||||
*
|
||||
* @return An iterator for items of player names and scores.
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Pair<String, Double>> getIterator() {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
List<Tuple> players = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), 0, -1);
|
||||
return players.stream().map(it -> Pair.of(it.getElement(), it.getScore())).toList().iterator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of players in the Redis ranking.
|
||||
*
|
||||
* @return The number of players in the ranking.
|
||||
*/
|
||||
@Override
|
||||
public int getSize() {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
long size = jedis.zcard("cf_competition_" + ConfigManager.serverGroup());
|
||||
return (int) size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rank of a player based on their name in descending order (1-based).
|
||||
*
|
||||
* @param player The name of the player to get the rank for.
|
||||
* @return The rank of the player, or -1 if the player is not found.
|
||||
*/
|
||||
@Override
|
||||
public int getPlayerRank(String player) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
Long rank = jedis.zrevrank("cf_competition_" + ConfigManager.serverGroup(), player);
|
||||
if (rank == null)
|
||||
return -1;
|
||||
return (int) (rank + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score of a player based on their name from the Redis ranking.
|
||||
*
|
||||
* @param player The name of the player to get the score for.
|
||||
* @return The score of the player, or 0 if the player is not found.
|
||||
*/
|
||||
@Override
|
||||
public double getPlayerScore(String player) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
Double rank = jedis.zscore("cf_competition_" + ConfigManager.serverGroup(), player);
|
||||
if (rank == null)
|
||||
return 0;
|
||||
return rank.floatValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the data for a player in the Redis ranking by adding a score to their existing score or creating a new player.
|
||||
*
|
||||
* @param player The name of the player to update or create.
|
||||
* @param score The score to add to the player's existing score or set as their initial score.
|
||||
*/
|
||||
@Override
|
||||
public void refreshData(String player, double score) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
jedis.zincrby("cf_competition_" + ConfigManager.serverGroup(), score, player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data for a player in the Redis ranking, updating their score or creating a new player.
|
||||
*
|
||||
* @param player The name of the player to update or create.
|
||||
* @param score The score to set for the player.
|
||||
*/
|
||||
@Override
|
||||
public void setData(String player, double score) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
jedis.zadd("cf_competition_" + ConfigManager.serverGroup(), score, player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the player at a given rank in descending order.
|
||||
*
|
||||
* @param rank The rank of the player to retrieve (1-based).
|
||||
* @return The name of the player at the specified rank, or null if not found.
|
||||
*/
|
||||
@Override
|
||||
public String getPlayerAt(int rank) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
List<String> player = jedis.zrevrange("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1);
|
||||
if (player == null || player.isEmpty()) return null;
|
||||
return player.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score of the player at a given rank in descending order.
|
||||
*
|
||||
* @param rank The rank of the player to retrieve (1-based).
|
||||
* @return The score of the player at the specified rank, or 0 if not found.
|
||||
*/
|
||||
@Override
|
||||
public double getScoreAt(int rank) {
|
||||
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
|
||||
List<Tuple> players = jedis.zrevrangeWithScores("cf_competition_" + ConfigManager.serverGroup(), rank - 1, rank -1);
|
||||
if (players == null || players.isEmpty()) return 0;
|
||||
return players.get(0).getScore();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.effect;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectManager;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectModifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BukkitEffectManager implements EffectManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, EffectModifier> effectModifiers = new HashMap<>();
|
||||
|
||||
public BukkitEffectManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.effectModifiers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
plugin.debug("Loaded " + effectModifiers.size() + " effects");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerEffectModifier(EffectModifier effect, MechanicType type) {
|
||||
if (effectModifiers.containsKey(effect.id())) return false;
|
||||
this.effectModifiers.put(type.getType() + ":" + effect.id(), effect);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EffectModifier> getEffectModifier(String id, MechanicType type) {
|
||||
return Optional.ofNullable(this.effectModifiers.get(type.getType() + ":" + id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.entity;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.integration.EntityProvider;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.entity.EntityConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.entity.EntityManager;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class BukkitEntityManager implements EntityManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, EntityProvider> entityProviders = new HashMap<>();
|
||||
private final HashMap<String, EntityConfig> entities = new HashMap<>();
|
||||
|
||||
public BukkitEntityManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.registerEntityProvider(new EntityProvider() {
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "vanilla";
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
public Entity spawn(@NotNull Location location, @NotNull String id, @NotNull Map<String, Object> propertyMap) {
|
||||
return location.getWorld().spawnEntity(location, EntityType.valueOf(id.toUpperCase(Locale.ENGLISH)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (EntityProvider provider : entityProviders.values()) {
|
||||
plugin.debug("Registered EntityProvider: " + provider.identifier());
|
||||
}
|
||||
plugin.debug("Loaded " + entities.size() + " entities");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.entities.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
unload();
|
||||
this.entityProviders.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EntityConfig> getEntity(String id) {
|
||||
return Optional.ofNullable(this.entities.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerEntity(EntityConfig entity) {
|
||||
if (entities.containsKey(entity.id())) return false;
|
||||
this.entities.put(entity.id(), entity);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean registerEntityProvider(EntityProvider entityProvider) {
|
||||
if (entityProviders.containsKey(entityProvider.identifier())) return false;
|
||||
else entityProviders.put(entityProvider.identifier(), entityProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unregisterEntityProvider(String id) {
|
||||
return entityProviders.remove(id) != null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Entity summonEntityLoot(Context<Player> context) {
|
||||
String id = context.arg(ContextKeys.ID);
|
||||
EntityConfig config = requireNonNull(entities.get(id), "Entity " + id + " not found");
|
||||
Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION));
|
||||
Location playerLocation = requireNonNull(context.getHolder().getLocation());
|
||||
String entityID = config.entityID();
|
||||
Entity entity;
|
||||
if (entityID.contains(":")) {
|
||||
String[] split = entityID.split(":", 2);
|
||||
EntityProvider provider = requireNonNull(entityProviders.get(split[0]), "EntityProvider " + split[0] + " doesn't exist");
|
||||
entity = requireNonNull(provider.spawn(hookLocation, split[1], config.propertyMap()), "Entity " + entityID + " doesn't exist");
|
||||
} else {
|
||||
entity = entityProviders.get("vanilla").spawn(hookLocation, entityID, config.propertyMap());
|
||||
}
|
||||
double d0 = playerLocation.getX() - hookLocation.getX();
|
||||
double d1 = playerLocation.getY() - hookLocation.getY();
|
||||
double d2 = playerLocation.getZ() - hookLocation.getZ();
|
||||
double d3 = config.horizontalVector().evaluate(context);
|
||||
double d4 = config.verticalVector().evaluate(context);
|
||||
Vector vector = new Vector(d0 * 0.1D * d3, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D * d4, d2 * 0.1D * d3);
|
||||
entity.setVelocity(vector);
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.event;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.event.EventCarrier;
|
||||
import net.momirealms.customfishing.api.mechanic.event.EventManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerItemConsumeEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BukkitEventManager implements EventManager, Listener {
|
||||
|
||||
private final HashMap<String, EventCarrier> carriers = new HashMap<>();
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
|
||||
public BukkitEventManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.carriers.clear();
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, this.plugin.getBoostrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EventCarrier> getEventCarrier(String id, MechanicType type) {
|
||||
return Optional.ofNullable(this.carriers.get(type.getType() + ":" + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerEventCarrier(EventCarrier carrier) {
|
||||
if (this.carriers.containsKey(carrier.id())) return false;
|
||||
this.carriers.put(carrier.type().getType() + ":" + carrier.id(), carrier);
|
||||
return true;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInteract(PlayerInteractEvent event) {
|
||||
if (event.getHand() != EquipmentSlot.HAND)
|
||||
return;
|
||||
if (event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_AIR && event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK)
|
||||
return;
|
||||
ItemStack itemStack = event.getPlayer().getInventory().getItemInMainHand();
|
||||
if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0)
|
||||
return;
|
||||
String id = this.plugin.getItemManager().getItemID(itemStack);
|
||||
Context<Player> context = Context.player(event.getPlayer());
|
||||
Block clicked = event.getClickedBlock();
|
||||
context.arg(ContextKeys.OTHER_LOCATION, clicked == null ? event.getPlayer().getLocation() : clicked.getLocation());
|
||||
trigger(context, id, MechanicType.UTIL, ActionTrigger.INTERACT);
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onConsumeItem(PlayerItemConsumeEvent event) {
|
||||
Context<Player> context = Context.player(event.getPlayer());
|
||||
trigger(context, plugin.getItemManager().getItemID(event.getItem()), MechanicType.LOOT, ActionTrigger.CONSUME);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.fishing;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.event.FishingHookStateEvent;
|
||||
import net.momirealms.customfishing.api.event.RodCastEvent;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.fishing.CustomFishingHook;
|
||||
import net.momirealms.customfishing.api.mechanic.fishing.FishingGears;
|
||||
import net.momirealms.customfishing.api.mechanic.fishing.FishingManager;
|
||||
import net.momirealms.customfishing.api.mechanic.fishing.hook.VanillaMechanic;
|
||||
import net.momirealms.customfishing.api.mechanic.game.AbstractGamingPlayer;
|
||||
import net.momirealms.customfishing.api.mechanic.game.GamingPlayer;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager;
|
||||
import net.momirealms.customfishing.api.util.EventUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.FishHook;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BukkitFishingManager implements FishingManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final ConcurrentHashMap<UUID, CustomFishingHook> castHooks = new ConcurrentHashMap<>();
|
||||
|
||||
public BukkitFishingManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomFishingHook> getFishHook(Player player) {
|
||||
return getFishHook(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomFishingHook> getFishHook(UUID player) {
|
||||
return Optional.ofNullable(castHooks.get(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getOwner(FishHook hook) {
|
||||
if (hook.getOwnerUniqueId() != null) {
|
||||
return Optional.ofNullable(Bukkit.getPlayer(hook.getOwnerUniqueId()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onFishMONITOR(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.MONITOR) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onFishHIGHEST(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.HIGHEST) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
|
||||
public void onFishHIGH(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.HIGH) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onFishNORMAL(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.NORMAL) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onFishLOW(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.LOW) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
public void onFishLOWEST(PlayerFishEvent event) {
|
||||
if (ConfigManager.eventPriority() != EventPriority.LOWEST) return;
|
||||
this.selectState(event);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onItemHeldChange(PlayerItemHeldEvent event) {
|
||||
if (getFishHook(event.getPlayer()).isPresent()) {
|
||||
this.destroy(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onSwapItem(PlayerSwapHandItemsEvent event) {
|
||||
getFishHook(event.getPlayer()).ifPresent(hook -> {
|
||||
Optional<GamingPlayer> optionalGamingPlayer = hook.getGamingPlayer();
|
||||
if (optionalGamingPlayer.isPresent()) {
|
||||
optionalGamingPlayer.get().handleSwapHand();
|
||||
event.setCancelled(true);
|
||||
} else {
|
||||
this.destroy(event.getPlayer().getUniqueId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onJump(PlayerMoveEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
if (event.getFrom().getY() < event.getTo().getY() && player.isOnGround()) {
|
||||
getFishHook(player).flatMap(CustomFishingHook::getGamingPlayer).ifPresent(gamingPlayer -> {
|
||||
if (gamingPlayer.handleJump()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onQuit(PlayerQuitEvent event) {
|
||||
this.destroy(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = false)
|
||||
public void onLeftClick(PlayerInteractEvent event) {
|
||||
if (event.getAction() != Action.LEFT_CLICK_AIR)
|
||||
return;
|
||||
if (event.getMaterial() != Material.FISHING_ROD)
|
||||
return;
|
||||
if (event.getHand() != EquipmentSlot.HAND)
|
||||
return;
|
||||
getFishHook(event.getPlayer()).ifPresent(hook -> {
|
||||
Optional<GamingPlayer> optionalGamingPlayer = hook.getGamingPlayer();
|
||||
if (optionalGamingPlayer.isPresent()) {
|
||||
if (((AbstractGamingPlayer) optionalGamingPlayer.get()).internalLeftClick()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent event) {
|
||||
if (!event.isSneaking()) return;
|
||||
getFishHook(event.getPlayer()).ifPresent(hook -> {
|
||||
Optional<GamingPlayer> optionalGamingPlayer = hook.getGamingPlayer();
|
||||
if (optionalGamingPlayer.isPresent()) {
|
||||
if (optionalGamingPlayer.get().handleSneak()) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChat(AsyncPlayerChatEvent event) {
|
||||
if (event.isCancelled()) return;
|
||||
getFishHook(event.getPlayer()).ifPresent(hook -> {
|
||||
Optional<GamingPlayer> optionalGamingPlayer = hook.getGamingPlayer();
|
||||
if (optionalGamingPlayer.isPresent()) {
|
||||
if (optionalGamingPlayer.get().handleChat(event.getMessage())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void selectState(PlayerFishEvent event) {
|
||||
switch (event.getState()) {
|
||||
case FISHING -> onCastRod(event);
|
||||
case REEL_IN, CAUGHT_FISH -> onReelIn(event);
|
||||
case CAUGHT_ENTITY -> onCaughtEntity(event);
|
||||
//case CAUGHT_FISH -> onCaughtFish(event);
|
||||
case BITE -> onBite(event);
|
||||
case IN_GROUND -> onInGround(event);
|
||||
case FAILED_ATTEMPT -> onFailedAttempt(event);
|
||||
// case LURED 1.20.5+
|
||||
}
|
||||
}
|
||||
|
||||
// for vanilla mechanics
|
||||
private void onFailedAttempt(PlayerFishEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
getFishHook(player).ifPresent(hook -> {
|
||||
if (hook.getCurrentHookMechanic() instanceof VanillaMechanic vanillaMechanic) {
|
||||
vanillaMechanic.onFailedAttempt();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// for vanilla mechanics
|
||||
private void onBite(PlayerFishEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
getFishHook(player).ifPresent(hook -> {
|
||||
if (hook.getCurrentHookMechanic() instanceof VanillaMechanic vanillaMechanic) {
|
||||
vanillaMechanic.onBite();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onCaughtEntity(PlayerFishEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
Optional<CustomFishingHook> hook = getFishHook(player);
|
||||
if (hook.isPresent()) {
|
||||
Entity entity = event.getCaught();
|
||||
if (entity != null && entity.getPersistentDataContainer().get(
|
||||
Objects.requireNonNull(NamespacedKey.fromString("temp-entity", plugin.getBoostrap())),
|
||||
PersistentDataType.STRING
|
||||
) != null) {
|
||||
event.setCancelled(true);
|
||||
hook.get().onReelIn();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
ItemStack itemStack = player.getInventory().getItemInMainHand();
|
||||
if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand();
|
||||
if (plugin.getItemManager().hasCustomDurability(itemStack)) {
|
||||
event.getHook().pullHookedEntity();
|
||||
event.getHook().remove();
|
||||
event.setCancelled(true);
|
||||
plugin.getItemManager().decreaseDurability(player, itemStack, event.getCaught() instanceof Item ? 3 : 5, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onReelIn(PlayerFishEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
getFishHook(player).ifPresent(hook -> {
|
||||
event.setCancelled(true);
|
||||
Optional<GamingPlayer> gamingPlayer = hook.getGamingPlayer();
|
||||
if (gamingPlayer.isPresent()) {
|
||||
((AbstractGamingPlayer) gamingPlayer.get()).internalRightClick();
|
||||
return;
|
||||
}
|
||||
hook.onReelIn();
|
||||
});
|
||||
}
|
||||
|
||||
// private void onCaughtFish(PlayerFishEvent event) {
|
||||
// Player player = event.getPlayer();
|
||||
// getFishHook(player).ifPresent(hook -> {
|
||||
// Optional<GamingPlayer> gamingPlayer = hook.getGamingPlayer();
|
||||
// if (gamingPlayer.isPresent()) {
|
||||
// if (gamingPlayer.get().handleRightClick()) {
|
||||
// event.setCancelled(true);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// event.setCancelled(true);
|
||||
// hook.onReelIn();
|
||||
// });
|
||||
// }
|
||||
|
||||
private void onCastRod(PlayerFishEvent event) {
|
||||
FishHook hook = event.getHook();
|
||||
Player player = event.getPlayer();
|
||||
Context<Player> context = Context.player(player);
|
||||
FishingGears gears = new FishingGears(context);
|
||||
if (!RequirementManager.isSatisfied(context, ConfigManager.mechanicRequirements())) {
|
||||
this.destroy(player.getUniqueId());
|
||||
return;
|
||||
}
|
||||
if (!gears.canFish()) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
if (EventUtils.fireAndCheckCancel(new RodCastEvent(event, gears))) {
|
||||
return;
|
||||
}
|
||||
plugin.debug(context);
|
||||
CustomFishingHook customHook = new CustomFishingHook(plugin, hook, gears, context);
|
||||
this.castHooks.put(player.getUniqueId(), customHook);
|
||||
}
|
||||
|
||||
private void onInGround(PlayerFishEvent event) {
|
||||
final Player player = event.getPlayer();
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
ItemStack itemStack = player.getInventory().getItemInMainHand();
|
||||
if (itemStack.getType() != Material.FISHING_ROD) itemStack = player.getInventory().getItemInOffHand();
|
||||
if (itemStack.getType() == Material.FISHING_ROD) {
|
||||
if (plugin.getItemManager().hasCustomDurability(itemStack)) {
|
||||
event.setCancelled(true);
|
||||
event.getHook().remove();
|
||||
plugin.getItemManager().decreaseDurability(player, itemStack, 2, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHookStateChange(FishingHookStateEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
getFishHook(player).ifPresent(hook -> {
|
||||
switch (event.getState()) {
|
||||
case BITE -> hook.onBite();
|
||||
case LAND -> hook.onLand();
|
||||
case ESCAPE -> hook.onEscape();
|
||||
case LURE -> hook.onLure();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(UUID uuid) {
|
||||
this.getFishHook(uuid).ifPresent(hook -> {
|
||||
hook.destroy();
|
||||
this.castHooks.remove(uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.hook;
|
||||
|
||||
import com.saicone.rtag.item.ItemTagStream;
|
||||
import net.jpountz.lz4.LZ4Compressor;
|
||||
import net.jpountz.lz4.LZ4Factory;
|
||||
import net.jpountz.lz4.LZ4FastDecompressor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectModifier;
|
||||
import net.momirealms.customfishing.api.mechanic.hook.HookConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.hook.HookManager;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.CustomDurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.DurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.VanillaDurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BukkitHookManager implements HookManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, HookConfig> hooks = new HashMap<>();
|
||||
private LZ4Factory factory;
|
||||
|
||||
public BukkitHookManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.factory = LZ4Factory.fastestInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
plugin.debug("Loaded " + hooks.size() + " hooks");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerHook(HookConfig hook) {
|
||||
if (hooks.containsKey(hook.id())) return false;
|
||||
hooks.put(hook.id(), hook);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<HookConfig> getHook(String id) {
|
||||
return Optional.ofNullable(hooks.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getHookID(ItemStack rod) {
|
||||
if (rod == null || rod.getType() != Material.FISHING_ROD || rod.getAmount() == 0)
|
||||
return Optional.empty();
|
||||
|
||||
Item<ItemStack> wrapped = plugin.getItemManager().wrap(rod);
|
||||
return wrapped.getTag("CustomFishing", "hook_id").map(o -> (String) o);
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onDragDrop(InventoryClickEvent event) {
|
||||
final Player player = (Player) event.getWhoClicked();
|
||||
if (event.getClickedInventory() != player.getInventory())
|
||||
return;
|
||||
if (player.getGameMode() != GameMode.SURVIVAL)
|
||||
return;
|
||||
ItemStack clicked = event.getCurrentItem();
|
||||
if (clicked == null || clicked.getType() != Material.FISHING_ROD)
|
||||
return;
|
||||
if (plugin.getFishingManager().getFishHook(player).isPresent())
|
||||
return;
|
||||
ItemStack cursor = event.getCursor();
|
||||
if (cursor.getType() == Material.AIR) {
|
||||
if (event.getClick() != ClickType.RIGHT) {
|
||||
return;
|
||||
}
|
||||
Item<ItemStack> wrapped = plugin.getItemManager().wrap(clicked);
|
||||
if (!wrapped.hasTag("CustomFishing", "hook_id")) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
String id = (String) wrapped.getTag("CustomFishing", "hook_id").orElseThrow();
|
||||
byte[] hookItemBase64 = (byte[]) wrapped.getTag("CustomFishing", "hook_stack").orElse(null);
|
||||
int damage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0);
|
||||
ItemStack itemStack;
|
||||
if (hookItemBase64 != null) {
|
||||
itemStack = bytesToHook(hookItemBase64);
|
||||
} else {
|
||||
itemStack = plugin.getItemManager().buildInternal(Context.player(player), id);
|
||||
}
|
||||
plugin.getItemManager().setDurability(player, itemStack, damage);
|
||||
|
||||
wrapped.removeTag("CustomFishing", "hook_id");
|
||||
wrapped.removeTag("CustomFishing", "hook_stack");
|
||||
wrapped.removeTag("CustomFishing", "hook_damage");
|
||||
wrapped.removeTag("CustomFishing", "hook_max_damage");
|
||||
|
||||
event.setCursor(itemStack);
|
||||
|
||||
List<String> previousLore = wrapped.lore().orElse(new ArrayList<>());
|
||||
List<String> newLore = new ArrayList<>();
|
||||
for (String previous : previousLore) {
|
||||
Component component = AdventureHelper.jsonToComponent(previous);
|
||||
if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf") && scoreComponent.objective().equals("hook")) {
|
||||
continue;
|
||||
}
|
||||
newLore.add(previous);
|
||||
}
|
||||
wrapped.lore(newLore);
|
||||
|
||||
wrapped.load();
|
||||
return;
|
||||
}
|
||||
|
||||
String hookID = plugin.getItemManager().getItemID(cursor);
|
||||
Optional<HookConfig> setting = getHook(hookID);
|
||||
if (setting.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context<Player> context = Context.player(player);
|
||||
HookConfig hookConfig = setting.get();
|
||||
Optional<EffectModifier> modifier = plugin.getEffectManager().getEffectModifier(hookID, MechanicType.HOOK);
|
||||
if (modifier.isPresent()) {
|
||||
if (!RequirementManager.isSatisfied(context, modifier.get().requirements())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event.setCancelled(true);
|
||||
|
||||
ItemStack clonedHook = cursor.clone();
|
||||
clonedHook.setAmount(1);
|
||||
cursor.setAmount(cursor.getAmount() - 1);
|
||||
|
||||
Item<ItemStack> wrapped = plugin.getItemManager().wrap(clicked);
|
||||
String previousHookID = (String) wrapped.getTag("CustomFishing", "hook_id").orElse(null);
|
||||
if (previousHookID != null) {
|
||||
int previousHookDamage = (int) wrapped.getTag("CustomFishing", "hook_damage").orElse(0);
|
||||
ItemStack previousItemStack;
|
||||
byte[] stackBytes = (byte[]) wrapped.getTag("CustomFishing", "hook_stack").orElse(null);
|
||||
if (stackBytes != null) {
|
||||
previousItemStack = bytesToHook(stackBytes);
|
||||
} else {
|
||||
previousItemStack = plugin.getItemManager().buildInternal(Context.player(player), previousHookID);
|
||||
}
|
||||
if (previousItemStack != null) {
|
||||
plugin.getItemManager().setDurability(player, previousItemStack, previousHookDamage);
|
||||
if (cursor.getAmount() == 0) {
|
||||
event.setCursor(previousItemStack);
|
||||
} else {
|
||||
PlayerUtils.giveItem(player, previousItemStack, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item<ItemStack> wrappedHook = plugin.getItemManager().wrap(clonedHook);
|
||||
DurabilityItem durabilityItem;
|
||||
if (wrappedHook.hasTag("CustomFishing", "max_dur")) {
|
||||
durabilityItem = new CustomDurabilityItem(wrappedHook);
|
||||
} else {
|
||||
durabilityItem = new VanillaDurabilityItem(wrappedHook);
|
||||
}
|
||||
|
||||
wrapped.setTag(hookID, "CustomFishing", "hook_id");
|
||||
wrapped.setTag(hookToBytes(clonedHook), "CustomFishing", "hook_stack");
|
||||
wrapped.setTag(durabilityItem.damage(), "CustomFishing", "hook_damage");
|
||||
wrapped.setTag(durabilityItem.maxDamage(), "CustomFishing", "hook_max_damage");
|
||||
|
||||
List<String> previousLore = wrapped.lore().orElse(new ArrayList<>());
|
||||
List<String> newLore = new ArrayList<>();
|
||||
List<String> durabilityLore = new ArrayList<>();
|
||||
for (String previous : previousLore) {
|
||||
Component component = AdventureHelper.jsonToComponent(previous);
|
||||
if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) {
|
||||
if (scoreComponent.objective().equals("hook")) {
|
||||
continue;
|
||||
} else if (scoreComponent.objective().equals("durability")) {
|
||||
durabilityLore.add(previous);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newLore.add(previous);
|
||||
}
|
||||
for (String lore : hookConfig.lore()) {
|
||||
ScoreComponent.Builder builder = Component.score().name("cf").objective("hook");
|
||||
builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(durabilityItem.maxDamage() - durabilityItem.damage())).replace("{max}", String.valueOf(durabilityItem.maxDamage()))));
|
||||
newLore.add(AdventureHelper.componentToJson(builder.build()));
|
||||
}
|
||||
newLore.addAll(durabilityLore);
|
||||
wrapped.lore(newLore);
|
||||
wrapped.load();
|
||||
}
|
||||
|
||||
private byte[] hookToBytes(ItemStack hook) {
|
||||
try {
|
||||
byte[] data = ItemTagStream.INSTANCE.toBytes(hook);
|
||||
int decompressedLength = data.length;
|
||||
LZ4Compressor compressor = factory.fastCompressor();
|
||||
int maxCompressedLength = compressor.maxCompressedLength(decompressedLength);
|
||||
byte[] compressed = new byte[maxCompressedLength];
|
||||
int compressedLength = compressor.compress(data, 0, decompressedLength, compressed, 0, maxCompressedLength);
|
||||
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream);
|
||||
outputStream.writeInt(decompressedLength);
|
||||
outputStream.write(compressed, 0, compressedLength);
|
||||
outputStream.close();
|
||||
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack bytesToHook(byte[] bytes) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(bytes));
|
||||
int decompressedLength = inputStream.readInt();
|
||||
byte[] compressed = new byte[inputStream.available()];
|
||||
inputStream.readFully(compressed);
|
||||
|
||||
LZ4FastDecompressor decompressor = factory.fastDecompressor();
|
||||
byte[] restored = new byte[decompressedLength];
|
||||
decompressor.decompress(compressed, 0, restored, 0, decompressedLength);
|
||||
|
||||
return ItemTagStream.INSTANCE.fromBytes(restored);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.integration;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.integration.*;
|
||||
import net.momirealms.customfishing.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.customfishing.bukkit.entity.BukkitEntityManager;
|
||||
import net.momirealms.customfishing.bukkit.integration.block.ItemsAdderBlockProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.block.OraxenBlockProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.enchant.AdvancedEnchantmentsProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.enchant.VanillaEnchantmentsProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.entity.ItemsAdderEntityProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.entity.MythicEntityProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.item.*;
|
||||
import net.momirealms.customfishing.bukkit.integration.level.*;
|
||||
import net.momirealms.customfishing.bukkit.integration.papi.CompetitionPapi;
|
||||
import net.momirealms.customfishing.bukkit.integration.papi.CustomFishingPapi;
|
||||
import net.momirealms.customfishing.bukkit.integration.papi.StatisticsPapi;
|
||||
import net.momirealms.customfishing.bukkit.integration.quest.BattlePassQuest;
|
||||
import net.momirealms.customfishing.bukkit.integration.quest.BetonQuestQuest;
|
||||
import net.momirealms.customfishing.bukkit.integration.quest.ClueScrollsQuest;
|
||||
import net.momirealms.customfishing.bukkit.integration.season.AdvancedSeasonsProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.season.CustomCropsSeasonProvider;
|
||||
import net.momirealms.customfishing.bukkit.integration.season.RealisticSeasonsProvider;
|
||||
import net.momirealms.customfishing.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class BukkitIntegrationManager implements IntegrationManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, LevelerProvider> levelerProviders = new HashMap<>();
|
||||
private final HashMap<String, EnchantmentProvider> enchantmentProviders = new HashMap<>();
|
||||
private SeasonProvider seasonProvider;
|
||||
|
||||
public BukkitIntegrationManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
this.enchantmentProviders.clear();
|
||||
this.levelerProviders.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
registerEnchantmentProvider(new VanillaEnchantmentsProvider());
|
||||
if (isHooked("ItemsAdder")) {
|
||||
registerItemProvider(new ItemsAdderItemProvider());
|
||||
registerBlockProvider(new ItemsAdderBlockProvider());
|
||||
registerEntityProvider(new ItemsAdderEntityProvider());
|
||||
}
|
||||
if (isHooked("MMOItems")) {
|
||||
registerItemProvider(new MMOItemsItemProvider());
|
||||
}
|
||||
if (isHooked("Oraxen")) {
|
||||
registerItemProvider(new OraxenItemProvider());
|
||||
registerBlockProvider(new OraxenBlockProvider());
|
||||
}
|
||||
if (isHooked("Zaphkiel")) {
|
||||
registerItemProvider(new ZaphkielItemProvider());
|
||||
}
|
||||
if (isHooked("NeigeItems")) {
|
||||
registerItemProvider(new NeigeItemsItemProvider());
|
||||
}
|
||||
if (isHooked("MythicMobs")) {
|
||||
registerItemProvider(new MythicMobsItemProvider());
|
||||
registerEntityProvider(new MythicEntityProvider());
|
||||
}
|
||||
if (isHooked("EcoJobs")) {
|
||||
registerLevelerProvider(new EcoJobsLevelerProvider());
|
||||
}
|
||||
if (isHooked("EcoSkills")) {
|
||||
registerLevelerProvider(new EcoSkillsLevelerProvider());
|
||||
}
|
||||
if (isHooked("Jobs")) {
|
||||
registerLevelerProvider(new JobsRebornLevelerProvider());
|
||||
}
|
||||
if (isHooked("MMOCore")) {
|
||||
registerLevelerProvider(new MMOCoreLevelerProvider());
|
||||
}
|
||||
if (isHooked("mcMMO")) {
|
||||
try {
|
||||
registerItemProvider(new McMMOTreasureProvider());
|
||||
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||
plugin.getPluginLogger().warn("Failed to initialize mcMMO Treasure");
|
||||
}
|
||||
registerLevelerProvider(new McMMOLevelerProvider());
|
||||
}
|
||||
if (isHooked("AureliumSkills")) {
|
||||
registerLevelerProvider(new AureliumSkillsProvider());
|
||||
}
|
||||
if (isHooked("AuraSkills")) {
|
||||
registerLevelerProvider(new AuraSkillsLevelerProvider());
|
||||
}
|
||||
if (isHooked("AdvancedEnchantments")) {
|
||||
registerEnchantmentProvider(new AdvancedEnchantmentsProvider());
|
||||
}
|
||||
if (isHooked("RealisticSeasons")) {
|
||||
registerSeasonProvider(new RealisticSeasonsProvider());
|
||||
} else if (isHooked("AdvancedSeasons")) {
|
||||
registerSeasonProvider(new AdvancedSeasonsProvider());
|
||||
} else if (isHooked("CustomCrops")) {
|
||||
registerSeasonProvider(new CustomCropsSeasonProvider());
|
||||
}
|
||||
if (isHooked("Vault")) {
|
||||
VaultHook.initialize();
|
||||
}
|
||||
if (isHooked("BattlePass")){
|
||||
BattlePassQuest battlePassQuest = new BattlePassQuest();
|
||||
battlePassQuest.register();
|
||||
}
|
||||
if (isHooked("ClueScrolls")) {
|
||||
ClueScrollsQuest clueScrollsQuest = new ClueScrollsQuest();
|
||||
clueScrollsQuest.register();
|
||||
}
|
||||
if (isHooked("BetonQuest")) {
|
||||
BetonQuestQuest.register();
|
||||
}
|
||||
if (isHooked("PlaceholderAPI")) {
|
||||
new CustomFishingPapi(plugin).load();
|
||||
new CompetitionPapi(plugin).load();
|
||||
new StatisticsPapi(plugin).load();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isHooked(String hooked) {
|
||||
if (Bukkit.getPluginManager().getPlugin(hooked) != null) {
|
||||
plugin.getPluginLogger().info(hooked + " hooked!");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerLevelerProvider(@NotNull LevelerProvider leveler) {
|
||||
if (levelerProviders.containsKey(leveler.identifier())) return false;
|
||||
levelerProviders.put(leveler.identifier(), leveler);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterLevelerProvider(@NotNull String id) {
|
||||
return levelerProviders.remove(id) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerEnchantmentProvider(@NotNull EnchantmentProvider enchantment) {
|
||||
if (enchantmentProviders.containsKey(enchantment.identifier())) return false;
|
||||
enchantmentProviders.put(enchantment.identifier(), enchantment);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterEnchantmentProvider(@NotNull String id) {
|
||||
return enchantmentProviders.remove(id) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LevelerProvider getLevelerProvider(String plugin) {
|
||||
return levelerProviders.get(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public EnchantmentProvider getEnchantmentProvider(String id) {
|
||||
return enchantmentProviders.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, Short>> getEnchantments(ItemStack itemStack) {
|
||||
ArrayList<Pair<String, Short>> list = new ArrayList<>();
|
||||
for (EnchantmentProvider enchantmentProvider : enchantmentProviders.values()) {
|
||||
list.addAll(enchantmentProvider.getEnchants(itemStack));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SeasonProvider getSeasonProvider() {
|
||||
return seasonProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerSeasonProvider(@NotNull SeasonProvider season) {
|
||||
if (this.seasonProvider != null) return false;
|
||||
this.seasonProvider = season;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterSeasonProvider() {
|
||||
if (this.seasonProvider == null) return false;
|
||||
this.seasonProvider = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerEntityProvider(@NotNull EntityProvider entity) {
|
||||
return ((BukkitEntityManager) plugin.getEntityManager()).registerEntityProvider(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterEntityProvider(@NotNull String id) {
|
||||
return ((BukkitEntityManager) plugin.getEntityManager()).unregisterEntityProvider(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerItemProvider(@NotNull ItemProvider item) {
|
||||
return ((BukkitItemManager) plugin.getItemManager()).registerItemProvider(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterItemProvider(@NotNull String id) {
|
||||
return ((BukkitItemManager) plugin.getItemManager()).unregisterItemProvider(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerBlockProvider(@NotNull BlockProvider block) {
|
||||
return ((BukkitBlockManager) plugin.getBlockManager()).registerBlockProvider(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterBlockProvider(@NotNull String id) {
|
||||
return ((BukkitBlockManager) plugin.getBlockManager()).unregisterBlockProvider(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item;
|
||||
|
||||
import com.saicone.rtag.RtagItem;
|
||||
import net.momirealms.customfishing.bukkit.item.impl.ComponentItemFactory;
|
||||
import net.momirealms.customfishing.bukkit.item.impl.UniversalItemFactory;
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import net.momirealms.customfishing.common.item.ItemFactory;
|
||||
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class BukkitItemFactory extends ItemFactory<CustomFishingPlugin, RtagItem, ItemStack> {
|
||||
|
||||
protected BukkitItemFactory(CustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
public static BukkitItemFactory create(CustomFishingPlugin plugin) {
|
||||
Objects.requireNonNull(plugin, "plugin");
|
||||
switch (plugin.getServerVersion()) {
|
||||
case "1.17", "1.17.1",
|
||||
"1.18", "1.18.1", "1.18.2",
|
||||
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
|
||||
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4" -> {
|
||||
return new UniversalItemFactory(plugin);
|
||||
}
|
||||
case "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2" -> {
|
||||
return new ComponentItemFactory(plugin);
|
||||
}
|
||||
default -> throw new IllegalStateException("Unsupported server version: " + plugin.getServerVersion());
|
||||
}
|
||||
}
|
||||
|
||||
public Item<ItemStack> wrap(ItemStack item) {
|
||||
Objects.requireNonNull(item, "item");
|
||||
return wrap(new RtagItem(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTag(RtagItem item, Object value, Object... path) {
|
||||
item.set(value, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Object> getTag(RtagItem item, Object... path) {
|
||||
return Optional.ofNullable(item.get(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasTag(RtagItem item, Object... path) {
|
||||
return item.hasTag(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeTag(RtagItem item, Object... path) {
|
||||
return item.remove(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(RtagItem item) {
|
||||
item.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack load(RtagItem item) {
|
||||
return item.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack getItem(RtagItem item) {
|
||||
return item.getItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack loadCopy(RtagItem item) {
|
||||
return item.loadCopy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.sound.Sound;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.integration.ExternalProvider;
|
||||
import net.momirealms.customfishing.api.integration.ItemProvider;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem;
|
||||
import net.momirealms.customfishing.api.mechanic.item.ItemManager;
|
||||
import net.momirealms.customfishing.api.util.EventUtils;
|
||||
import net.momirealms.customfishing.bukkit.integration.item.CustomFishingItemProvider;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.CustomDurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.DurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.item.damage.VanillaDurabilityItem;
|
||||
import net.momirealms.customfishing.bukkit.util.ItemStackUtils;
|
||||
import net.momirealms.customfishing.bukkit.util.LocationUtils;
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.FishHook;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.*;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.inventory.InventoryPickupItemEvent;
|
||||
import org.bukkit.event.inventory.PrepareAnvilEvent;
|
||||
import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
|
||||
import org.bukkit.event.player.PlayerItemDamageEvent;
|
||||
import org.bukkit.event.player.PlayerItemMendEvent;
|
||||
import org.bukkit.inventory.AnvilInventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class BukkitItemManager implements ItemManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, ItemProvider> itemProviders = new HashMap<>();
|
||||
private final HashMap<String, CustomFishingItem> items = new HashMap<>();
|
||||
private final BukkitItemFactory factory;
|
||||
private ItemProvider[] itemDetectArray;
|
||||
|
||||
public BukkitItemManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.factory = BukkitItemFactory.create(plugin);
|
||||
this.registerItemProvider(new ItemProvider() {
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack buildItem(@NotNull Player player, @NotNull String id) {
|
||||
return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH)));
|
||||
}
|
||||
@NotNull
|
||||
@Override
|
||||
public String itemID(@NotNull ItemStack itemStack) {
|
||||
return itemStack.getType().name();
|
||||
}
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "vanilla";
|
||||
}
|
||||
});
|
||||
this.registerItemProvider(new CustomFishingItemProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
this.items.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
this.resetItemDetectionOrder();
|
||||
for (ItemProvider provider : itemProviders.values()) {
|
||||
plugin.debug("Registered ItemProvider: " + provider.identifier());
|
||||
}
|
||||
plugin.debug("Loaded " + items.size() + " items");
|
||||
plugin.debug("Item order: " + Arrays.toString(Arrays.stream(itemDetectArray).map(ExternalProvider::identifier).toList().toArray(new String[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerItem(@NotNull CustomFishingItem item) {
|
||||
if (items.containsKey(item.id())) return false;
|
||||
items.put(item.id(), item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ItemStack buildInternal(@NotNull Context<Player> context, @NotNull String id) {
|
||||
// CustomFishingItem item = requireNonNull(items.get(id), () -> "No item found for " + id);
|
||||
CustomFishingItem item = items.get(id);
|
||||
if (item == null) return null;
|
||||
return build(context, item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ItemStack build(@NotNull Context<Player> context, @NotNull CustomFishingItem item) {
|
||||
ItemStack itemStack = getOriginalStack(context.getHolder(), item.material());
|
||||
if (itemStack.getType() == Material.AIR) return itemStack;
|
||||
Item<ItemStack> wrappedItemStack = factory.wrap(itemStack);
|
||||
for (BiConsumer<Item<ItemStack>, Context<Player>> consumer : item.tagConsumers()) {
|
||||
consumer.accept(wrappedItemStack, context);
|
||||
}
|
||||
return wrappedItemStack.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack buildAny(@NotNull Context<Player> context, @NotNull String item) {
|
||||
return getOriginalStack(context.getHolder(), item);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getItemID(@NotNull ItemStack itemStack) {
|
||||
if (itemStack.getType() == Material.AIR)
|
||||
return "AIR";
|
||||
for (ItemProvider library : itemDetectArray) {
|
||||
String id = library.itemID(itemStack);
|
||||
if (id != null)
|
||||
return id;
|
||||
}
|
||||
// should not reach this because vanilla library would always work
|
||||
return "AIR";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCustomFishingItemID(@NotNull ItemStack itemStack) {
|
||||
return (String) factory.wrap(itemStack).getTag("CustomFishing", "id").orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public org.bukkit.entity.Item dropItemLoot(@NotNull Context<Player> context, ItemStack rod, FishHook hook) {
|
||||
String id = requireNonNull(context.arg(ContextKeys.ID));
|
||||
ItemStack itemStack;
|
||||
if (id.equals("vanilla")) {
|
||||
itemStack = SparrowHeart.getInstance().getFishingLoot(context.getHolder(), hook, rod).stream().findAny().orElseThrow(() -> new RuntimeException("new EntityItem would throw if for whatever reason (mostly shitty datapacks) the fishing loot turns out to be empty"));
|
||||
} else {
|
||||
itemStack = requireNonNull(buildInternal(context, id));
|
||||
}
|
||||
|
||||
if (itemStack.getType() == Material.AIR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Player player = context.getHolder();
|
||||
Location playerLocation = player.getLocation();
|
||||
Location hookLocation = requireNonNull(context.arg(ContextKeys.OTHER_LOCATION));
|
||||
|
||||
double d0 = playerLocation.getX() - hookLocation.getX();
|
||||
double d1 = playerLocation.getY() - hookLocation.getY();
|
||||
double d2 = playerLocation.getZ() - hookLocation.getZ();
|
||||
Vector vector = new Vector(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D);
|
||||
|
||||
org.bukkit.entity.Item itemEntity = hookLocation.getWorld().dropItem(hookLocation, itemStack);
|
||||
|
||||
itemEntity.setInvulnerable(true);
|
||||
// prevent from being killed by lava
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
if (itemEntity.isValid())
|
||||
itemEntity.setInvulnerable(false);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
|
||||
itemEntity.setVelocity(vector);
|
||||
|
||||
return itemEntity;
|
||||
}
|
||||
|
||||
private ItemStack getOriginalStack(Player player, String material) {
|
||||
if (!material.contains(":")) {
|
||||
try {
|
||||
return new ItemStack(Material.valueOf(material.toUpperCase(Locale.ENGLISH)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getPluginLogger().severe("material " + material + " not exists", e);
|
||||
return new ItemStack(Material.PAPER);
|
||||
}
|
||||
} else {
|
||||
String[] split = material.split(":", 2);
|
||||
ItemProvider provider = requireNonNull(itemProviders.get(split[0]), "Item provider: " + split[0] + " not found");
|
||||
return requireNonNull(provider.buildItem(player, split[0]), "Item: " + split[0] + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
private void resetItemDetectionOrder() {
|
||||
ArrayList<ItemProvider> list = new ArrayList<>();
|
||||
for (String plugin : ConfigManager.itemDetectOrder()) {
|
||||
ItemProvider provider = itemProviders.get(plugin);
|
||||
if (provider != null)
|
||||
list.add(provider);
|
||||
}
|
||||
this.itemDetectArray = list.toArray(new ItemProvider[0]);
|
||||
}
|
||||
|
||||
public boolean registerItemProvider(ItemProvider item) {
|
||||
if (itemProviders.containsKey(item.identifier())) return false;
|
||||
itemProviders.put(item.identifier(), item);
|
||||
this.resetItemDetectionOrder();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean unregisterItemProvider(String id) {
|
||||
boolean success = itemProviders.remove(id) != null;
|
||||
if (success)
|
||||
this.resetItemDetectionOrder();
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCustomDurability(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0)
|
||||
return false;
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
return wrapped.hasTag("CustomFishing", "max_dur");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseDurability(Player player, ItemStack itemStack, int amount, boolean incorrectUsage) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0)
|
||||
return;
|
||||
if (!incorrectUsage) {
|
||||
int unBreakingLevel = itemStack.getEnchantmentLevel(Enchantment.DURABILITY);
|
||||
if (Math.random() > (double) 1 / (unBreakingLevel + 1)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
if (wrapped.unbreakable())
|
||||
return;
|
||||
|
||||
ItemMeta previousMeta = itemStack.getItemMeta().clone();
|
||||
PlayerItemDamageEvent itemDamageEvent = new PlayerItemDamageEvent(player, itemStack, amount);
|
||||
if (EventUtils.fireAndCheckCancel(itemDamageEvent)) {
|
||||
plugin.debug("Another plugin modified the item from `PlayerItemDamageEvent` called by CustomFishing");
|
||||
return;
|
||||
}
|
||||
if (!itemStack.getItemMeta().equals(previousMeta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DurabilityItem durabilityItem = wrapDurabilityItem(wrapped);
|
||||
int damage = durabilityItem.damage();
|
||||
if (damage + amount >= durabilityItem.maxDamage()) {
|
||||
plugin.getSenderFactory().getAudience(player).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1));
|
||||
itemStack.setAmount(0);
|
||||
return;
|
||||
}
|
||||
|
||||
durabilityItem.damage(damage + amount);
|
||||
wrapped.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDurability(Player player, ItemStack itemStack, int damage) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0)
|
||||
return;
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
if (wrapped.unbreakable())
|
||||
return;
|
||||
DurabilityItem wrappedDurability = wrapDurabilityItem(wrapped);
|
||||
if (damage >= wrappedDurability.maxDamage()) {
|
||||
if (player != null)
|
||||
plugin.getSenderFactory().getAudience(player).playSound(Sound.sound(Key.key("minecraft:entity.item.break"), Sound.Source.PLAYER, 1, 1));
|
||||
itemStack.setAmount(0);
|
||||
return;
|
||||
}
|
||||
wrappedDurability.damage(damage);
|
||||
wrapped.load();
|
||||
}
|
||||
|
||||
public DurabilityItem wrapDurabilityItem(Item<ItemStack> wrapped) {
|
||||
if (wrapped.hasTag("CustomFishing", "max_dur")) {
|
||||
return new CustomDurabilityItem(wrapped);
|
||||
} else {
|
||||
return new VanillaDurabilityItem(wrapped);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onMending(PlayerItemMendEvent event) {
|
||||
ItemStack itemStack = event.getItem();
|
||||
if (!hasCustomDurability(itemStack)) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
if (wrapped.unbreakable())
|
||||
return;
|
||||
DurabilityItem wrappedDurability = wrapDurabilityItem(wrapped);
|
||||
setDurability(event.getPlayer(), itemStack, Math.max(wrappedDurability.damage() - event.getRepairAmount(), 0));
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onAnvil(PrepareAnvilEvent event) {
|
||||
AnvilInventory anvil = event.getInventory();
|
||||
ItemStack first = anvil.getFirstItem();
|
||||
ItemStack second = anvil.getSecondItem();
|
||||
if (first != null && second != null
|
||||
&& first.getType() == Material.FISHING_ROD && second.getType() == Material.FISHING_ROD && event.getResult() != null
|
||||
&& hasCustomDurability(first)) {
|
||||
Item<ItemStack> wrapped1 = factory.wrap(anvil.getResult());
|
||||
DurabilityItem wrappedDurability1 = wrapDurabilityItem(wrapped1);
|
||||
|
||||
Item<ItemStack> wrapped2 = factory.wrap(second);
|
||||
DurabilityItem wrappedDurability2 = wrapDurabilityItem(wrapped2);
|
||||
|
||||
int durability2 = wrappedDurability2.maxDamage() - wrappedDurability2.damage();
|
||||
int damage1 = Math.max(wrappedDurability1.damage() - durability2, 0);
|
||||
wrappedDurability1.damage(damage1);
|
||||
event.setResult(wrapped1.load());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInvPickItem(InventoryPickupItemEvent event) {
|
||||
ItemStack itemStack = event.getItem().getItemStack();
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
if (wrapped.hasTag("owner")) {
|
||||
wrapped.removeTag("owner");
|
||||
itemStack.setItemMeta(wrapped.getItem().getItemMeta());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onPlaceBlock(BlockPlaceEvent event) {
|
||||
ItemStack itemStack = event.getItemInHand();
|
||||
if (itemStack.getType() == Material.AIR || itemStack.getAmount() == 0 || !itemStack.hasItemMeta()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Item<ItemStack> wrapped = factory.wrap(itemStack);
|
||||
if (wrapped.hasTag("CustomFishing")) {
|
||||
if (!wrapped.hasTag("CustomFishing", "placeable") || ((int) wrapped.getTag("CustomFishing", "placeable").get()) != 1) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
Block block = event.getBlock();
|
||||
if (block.getState() instanceof Skull) {
|
||||
PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer();
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(1);
|
||||
pdc.set(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING, ItemStackUtils.toBase64(cloned));
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onBreakBlock(BlockBreakEvent event) {
|
||||
final Block block = event.getBlock();
|
||||
if (block.getState() instanceof Skull) {
|
||||
PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer();
|
||||
String base64 = pdc.get(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING);
|
||||
if (base64 != null) {
|
||||
ItemStack itemStack = ItemStackUtils.fromBase64(base64);
|
||||
event.setDropItems(false);
|
||||
block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onPiston(BlockPistonExtendEvent event) {
|
||||
handlePiston(event, event.getBlocks());
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onPiston(BlockPistonRetractEvent event) {
|
||||
handlePiston(event, event.getBlocks());
|
||||
}
|
||||
|
||||
private void handlePiston(Cancellable event, List<Block> blockList) {
|
||||
for (Block block : blockList) {
|
||||
if (block.getState() instanceof Skull) {
|
||||
PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer();
|
||||
if (pdc.has(new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation())), PersistentDataType.STRING)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onExplosion(BlockExplodeEvent event) {
|
||||
handleExplosion(event.blockList());
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onExplosion(EntityExplodeEvent event) {
|
||||
handleExplosion(event.blockList());
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onPickUpItem(PlayerAttemptPickupItemEvent event) {
|
||||
String owner = event.getItem().getPersistentDataContainer().get(requireNonNull(NamespacedKey.fromString("owner", plugin.getBoostrap())), PersistentDataType.STRING);
|
||||
if (owner != null) {
|
||||
if (!owner.equals(event.getPlayer().getName())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExplosion(List<Block> blocks) {
|
||||
ArrayList<Block> blockToRemove = new ArrayList<>();
|
||||
for (Block block : blocks) {
|
||||
if (block.getState() instanceof Skull) {
|
||||
PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer();
|
||||
var nk = new NamespacedKey(plugin.getBoostrap(), LocationUtils.toChunkPosString(block.getLocation()));
|
||||
String base64 = pdc.get(nk, PersistentDataType.STRING);
|
||||
if (base64 != null) {
|
||||
ItemStack itemStack = ItemStackUtils.fromBase64(base64);
|
||||
block.getLocation().getWorld().dropItemNaturally(block.getLocation(), itemStack);
|
||||
blockToRemove.add(block);
|
||||
block.setType(Material.AIR);
|
||||
pdc.remove(nk);
|
||||
}
|
||||
}
|
||||
}
|
||||
blocks.removeAll(blockToRemove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitItemFactory getFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemProvider[] getItemProviders() {
|
||||
return itemProviders.values().toArray(new ItemProvider[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getItemIDs() {
|
||||
return items.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item<ItemStack> wrap(ItemStack itemStack) {
|
||||
return factory.wrap(itemStack);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item.damage;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CustomDurabilityItem implements DurabilityItem {
|
||||
|
||||
private final Item<ItemStack> item;
|
||||
|
||||
public CustomDurabilityItem(Item<ItemStack> item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int value) {
|
||||
int customMaxDamage = (int) item.getTag("CustomFishing", "max_dur").get();
|
||||
int maxDamage = item.maxDamage().get();
|
||||
double ratio = (double) maxDamage / (double) customMaxDamage;
|
||||
int fakeDamage = (int) (value * ratio);
|
||||
item.damage(fakeDamage);
|
||||
item.setTag(customMaxDamage - value, "CustomFishing", "cur_dur");
|
||||
List<String> durabilityLore = ConfigManager.durabilityLore();
|
||||
List<String> previousLore = item.lore().orElse(new ArrayList<>());
|
||||
List<String> newLore = new ArrayList<>();
|
||||
for (String previous : previousLore) {
|
||||
Component component = AdventureHelper.jsonToComponent(previous);
|
||||
if (component instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("cf")) {
|
||||
if (scoreComponent.objective().equals("durability")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newLore.add(previous);
|
||||
}
|
||||
for (String lore : durabilityLore) {
|
||||
ScoreComponent.Builder builder = Component.score().name("cf").objective("durability");
|
||||
builder.append(AdventureHelper.miniMessage(lore.replace("{dur}", String.valueOf(customMaxDamage - value)).replace("{max}", String.valueOf(customMaxDamage))));
|
||||
newLore.add(AdventureHelper.componentToJson(builder.build()));
|
||||
}
|
||||
item.lore(newLore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int damage() {
|
||||
int customMaxDamage = (int) item.getTag("CustomFishing", "max_dur").get();
|
||||
return customMaxDamage - (int) item.getTag("CustomFishing", "cur_dur").orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxDamage() {
|
||||
return (int) item.getTag("CustomFishing", "max_dur").get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item.damage;
|
||||
|
||||
public interface DurabilityItem {
|
||||
|
||||
void damage(int value);
|
||||
|
||||
int damage();
|
||||
|
||||
int maxDamage();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item.damage;
|
||||
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class VanillaDurabilityItem implements DurabilityItem {
|
||||
|
||||
private final Item<ItemStack> item;
|
||||
|
||||
public VanillaDurabilityItem(Item<ItemStack> item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(int value) {
|
||||
item.damage(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int damage() {
|
||||
return item.damage().orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxDamage() {
|
||||
return item.maxDamage().get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item.impl;
|
||||
|
||||
import com.saicone.rtag.RtagItem;
|
||||
import com.saicone.rtag.data.ComponentType;
|
||||
import net.momirealms.customfishing.bukkit.item.BukkitItemFactory;
|
||||
import net.momirealms.customfishing.common.item.ComponentKeys;
|
||||
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.common.util.Key;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ComponentItemFactory extends BukkitItemFactory {
|
||||
|
||||
public ComponentItemFactory(CustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customModelData(RtagItem item, Integer data) {
|
||||
if (data == null) {
|
||||
item.removeComponent(ComponentKeys.CUSTOM_MODEL_DATA);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> customModelData(RtagItem item) {
|
||||
if (!item.hasComponent(ComponentKeys.CUSTOM_MODEL_DATA)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.CUSTOM_MODEL_DATA,
|
||||
item.getComponent(ComponentKeys.CUSTOM_MODEL_DATA)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayName(RtagItem item, String json) {
|
||||
if (json == null) {
|
||||
item.removeComponent(ComponentKeys.CUSTOM_NAME);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.CUSTOM_NAME, json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> displayName(RtagItem item) {
|
||||
if (!item.hasComponent(ComponentKeys.CUSTOM_NAME)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(String) ComponentType.encodeJava(
|
||||
ComponentKeys.CUSTOM_NAME,
|
||||
item.getComponent(ComponentKeys.CUSTOM_NAME)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skull(RtagItem item, String skullData) {
|
||||
final Map<String, Object> profile = Map.of(
|
||||
"properties", List.of(
|
||||
Map.of(
|
||||
"name", "textures",
|
||||
"value", skullData
|
||||
)
|
||||
)
|
||||
);
|
||||
item.setComponent("minecraft:profile", profile);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Optional<List<String>> lore(RtagItem item) {
|
||||
if (!item.hasComponent(ComponentKeys.LORE)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(List<String>) ComponentType.encodeJava(
|
||||
ComponentKeys.LORE,
|
||||
item.getComponent(ComponentKeys.LORE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void lore(RtagItem item, List<String> lore) {
|
||||
if (lore == null || lore.isEmpty()) {
|
||||
item.removeComponent(ComponentKeys.LORE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.LORE, lore);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean unbreakable(RtagItem item) {
|
||||
return item.isUnbreakable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unbreakable(RtagItem item, boolean unbreakable) {
|
||||
item.setUnbreakable(unbreakable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Boolean> glint(RtagItem item) {
|
||||
return Optional.ofNullable((Boolean) item.getComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glint(RtagItem item, Boolean glint) {
|
||||
item.setComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE, glint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> damage(RtagItem item) {
|
||||
if (!item.hasComponent(ComponentKeys.DAMAGE)) return Optional.empty();
|
||||
return Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.DAMAGE,
|
||||
item.getComponent(ComponentKeys.DAMAGE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void damage(RtagItem item, Integer damage) {
|
||||
if (damage == null) damage = 0;
|
||||
item.setComponent(ComponentKeys.DAMAGE, damage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> maxDamage(RtagItem item) {
|
||||
if (!item.hasComponent(ComponentKeys.MAX_DAMAGE)) return Optional.of((int) item.getItem().getType().getMaxDurability());
|
||||
return Optional.ofNullable(
|
||||
(Integer) ComponentType.encodeJava(
|
||||
ComponentKeys.MAX_DAMAGE,
|
||||
item.getComponent(ComponentKeys.MAX_DAMAGE)
|
||||
).orElse(null)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxDamage(RtagItem item, Integer damage) {
|
||||
if (damage == null) {
|
||||
item.removeComponent(ComponentKeys.MAX_DAMAGE);
|
||||
} else {
|
||||
item.setComponent(ComponentKeys.MAX_DAMAGE, damage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enchantments(RtagItem item, Map<Key, Short> enchantments) {
|
||||
Map<String, Integer> enchants = new HashMap<>();
|
||||
for (Map.Entry<Key, Short> entry : enchantments.entrySet()) {
|
||||
enchants.put(entry.getKey().toString(), Integer.valueOf(entry.getValue()));
|
||||
}
|
||||
item.setComponent(ComponentKeys.ENCHANTMENTS, enchants);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void storedEnchantments(RtagItem item, Map<Key, Short> enchantments) {
|
||||
Map<String, Integer> enchants = new HashMap<>();
|
||||
for (Map.Entry<Key, Short> entry : enchantments.entrySet()) {
|
||||
enchants.put(entry.getKey().toString(), Integer.valueOf(entry.getValue()));
|
||||
}
|
||||
item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, enchants);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEnchantment(RtagItem item, Key enchantment, int level) {
|
||||
Object enchant = item.getComponent(ComponentKeys.ENCHANTMENTS);
|
||||
Map<String, Integer> map = SparrowHeart.getInstance().itemEnchantmentsToMap(enchant);
|
||||
map.put(enchantment.toString(), level);
|
||||
item.setComponent(ComponentKeys.ENCHANTMENTS, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addStoredEnchantment(RtagItem item, Key enchantment, int level) {
|
||||
Object enchant = item.getComponent(ComponentKeys.STORED_ENCHANTMENTS);
|
||||
Map<String, Integer> map = SparrowHeart.getInstance().itemEnchantmentsToMap(enchant);
|
||||
map.put(enchantment.toString(), level);
|
||||
item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemFlags(RtagItem item, List<String> flags) {
|
||||
throw new UnsupportedOperationException("This feature is not available on 1.20.5+");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.item.impl;
|
||||
|
||||
import com.saicone.rtag.RtagItem;
|
||||
import com.saicone.rtag.tag.TagBase;
|
||||
import com.saicone.rtag.tag.TagCompound;
|
||||
import com.saicone.rtag.tag.TagList;
|
||||
import net.momirealms.customfishing.bukkit.item.BukkitItemFactory;
|
||||
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.common.util.Key;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UniversalItemFactory extends BukkitItemFactory {
|
||||
|
||||
public UniversalItemFactory(CustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayName(RtagItem item, String json) {
|
||||
if (json != null) {
|
||||
item.set(json, "display", "Name");
|
||||
} else {
|
||||
item.remove("display", "Name");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<String> displayName(RtagItem item) {
|
||||
if (!item.hasTag("display", "Name")) return Optional.empty();
|
||||
return Optional.of(item.get("display", "Name"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customModelData(RtagItem item, Integer data) {
|
||||
if (data == null) {
|
||||
item.remove("CustomModelData");
|
||||
} else {
|
||||
item.set(data, "CustomModelData");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> customModelData(RtagItem item) {
|
||||
if (!item.hasTag("CustomModelData")) return Optional.empty();
|
||||
return Optional.of(item.get("CustomModelData"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skull(RtagItem item, String skullData) {
|
||||
if (skullData == null) {
|
||||
item.remove("SkullOwner");
|
||||
} else {
|
||||
item.set(List.of(Map.of("Value", skullData)), "SkullOwner", "Properties", "textures");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<List<String>> lore(RtagItem item) {
|
||||
if (!item.hasTag("display", "Lore")) return Optional.empty();
|
||||
return Optional.of(item.get("display", "Lore"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void lore(RtagItem item, List<String> lore) {
|
||||
if (lore == null || lore.isEmpty()) {
|
||||
item.remove("display", "Lore");
|
||||
} else {
|
||||
item.set(lore, "display", "Lore");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean unbreakable(RtagItem item) {
|
||||
return item.isUnbreakable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unbreakable(RtagItem item, boolean unbreakable) {
|
||||
item.setUnbreakable(unbreakable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Boolean> glint(RtagItem item) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void glint(RtagItem item, Boolean glint) {
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> damage(RtagItem item) {
|
||||
if (!item.hasTag("Damage")) return Optional.empty();
|
||||
return Optional.of(item.get("Damage"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void damage(RtagItem item, Integer damage) {
|
||||
item.set(damage, "Damage");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<Integer> maxDamage(RtagItem item) {
|
||||
// if (!item.hasTag("CustomFishing", "max_dur")) return Optional.empty();
|
||||
// return Optional.of(item.get("CustomFishing", "max_dur"));
|
||||
return Optional.of((int) item.getItem().getType().getMaxDurability());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxDamage(RtagItem item, Integer damage) {
|
||||
// if (damage == null) {
|
||||
// item.remove("CustomFishing", "max_dur");
|
||||
// } else {
|
||||
// item.set(damage, "CustomFishing", "max_dur");
|
||||
// }
|
||||
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enchantments(RtagItem item, Map<Key, Short> enchantments) {
|
||||
ArrayList<Object> tags = new ArrayList<>();
|
||||
for (Map.Entry<Key, Short> entry : enchantments.entrySet()) {
|
||||
tags.add((Map.of("id", entry.getKey().toString(), "lvl", entry.getValue())));
|
||||
}
|
||||
item.set(tags, "Enchantments");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void storedEnchantments(RtagItem item, Map<Key, Short> enchantments) {
|
||||
ArrayList<Object> tags = new ArrayList<>();
|
||||
for (Map.Entry<Key, Short> entry : enchantments.entrySet()) {
|
||||
tags.add((Map.of("id", entry.getKey().toString(), "lvl", entry.getValue())));
|
||||
}
|
||||
item.set(tags, "StoredEnchantments");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addEnchantment(RtagItem item, Key enchantment, int level) {
|
||||
Object enchantments = item.getExact("Enchantments");
|
||||
if (enchantments != null) {
|
||||
for (Object enchant : TagList.getValue(enchantments)) {
|
||||
if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) {
|
||||
TagCompound.set(enchant, "lvl", TagBase.newTag(level));
|
||||
return;
|
||||
}
|
||||
}
|
||||
item.add(Map.of("id", enchantment.toString(), "lvl", (short) level), "Enchantments");
|
||||
} else {
|
||||
item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) level)), "Enchantments");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addStoredEnchantment(RtagItem item, Key enchantment, int level) {
|
||||
Object enchantments = item.getExact("StoredEnchantments");
|
||||
if (enchantments != null) {
|
||||
for (Object enchant : TagList.getValue(enchantments)) {
|
||||
if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) {
|
||||
TagCompound.set(enchant, "lvl", TagBase.newTag(level));
|
||||
return;
|
||||
}
|
||||
}
|
||||
item.add(Map.of("id", enchantment.toString(), "lvl", (short) level), "StoredEnchantments");
|
||||
} else {
|
||||
item.set(List.of(Map.of("id", enchantment.toString(), "lvl", (short) level)), "StoredEnchantments");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemFlags(RtagItem item, List<String> flags) {
|
||||
if (flags == null || flags.isEmpty()) {
|
||||
item.remove("HideFlags");
|
||||
return;
|
||||
}
|
||||
int f = 0;
|
||||
for (String flag : flags) {
|
||||
ItemFlag itemFlag = ItemFlag.valueOf(flag);
|
||||
f = f | 1 << itemFlag.ordinal();
|
||||
}
|
||||
item.set(f, "HideFlags");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.loot;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.Effect;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.Loot;
|
||||
import net.momirealms.customfishing.api.mechanic.loot.LootManager;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.ConditionalElement;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import net.momirealms.customfishing.common.util.WeightUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class BukkitLootManager implements LootManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, Loot> lootMap = new HashMap<>();
|
||||
private final HashMap<String, List<String>> groupMembersMap = new HashMap<>();
|
||||
private final LinkedHashMap<String, ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player>> lootConditions = new LinkedHashMap<>();
|
||||
|
||||
public BukkitLootManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.lootMap.clear();
|
||||
this.groupMembersMap.clear();
|
||||
this.lootConditions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
plugin.debug("Loaded " + lootMap.size() + " loots");
|
||||
for (Map.Entry<String, List<String>> entry : groupMembersMap.entrySet()) {
|
||||
plugin.debug("Group: {" + entry.getKey() + "} Members: " + entry.getValue());
|
||||
}
|
||||
File file = new File(plugin.getDataFolder(), "loot-conditions.yml");
|
||||
if (!file.exists()) {
|
||||
plugin.getBoostrap().saveResource("loot-conditions.yml", false);
|
||||
}
|
||||
YamlDocument lootConditionsConfig = plugin.getConfigManager().loadData(file);
|
||||
for (Map.Entry<String, Object> entry : lootConditionsConfig.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section section) {
|
||||
lootConditions.put(entry.getKey(), parseLootConditions(section));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player> parseLootConditions(Section section) {
|
||||
Section subSection = section.getSection("sub-groups");
|
||||
if (subSection == null) {
|
||||
return new ConditionalElement<>(
|
||||
plugin.getConfigManager().parseWeightOperation(section.getStringList("list")),
|
||||
Map.of(),
|
||||
plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false)
|
||||
);
|
||||
} else {
|
||||
HashMap<String, ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player>> subElements = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : subSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
subElements.put(entry.getKey(), parseLootConditions(innerSection));
|
||||
}
|
||||
}
|
||||
return new ConditionalElement<>(
|
||||
plugin.getConfigManager().parseWeightOperation(section.getStringList("list")),
|
||||
subElements,
|
||||
plugin.getRequirementManager().parseRequirements(section.getSection("conditions"), false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerLoot(@NotNull Loot loot) {
|
||||
if (lootMap.containsKey(loot.id())) return false;
|
||||
this.lootMap.put(loot.id(), loot);
|
||||
for (String group : loot.lootGroup()) {
|
||||
addGroupMember(group, loot.id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addGroupMember(String group, String member) {
|
||||
List<String> members = groupMembersMap.get(group);
|
||||
if (members == null) {
|
||||
members = new ArrayList<>(List.of(member));
|
||||
groupMembersMap.put(group, members);
|
||||
} else {
|
||||
members.add(member);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getGroupMembers(String key) {
|
||||
return Optional.ofNullable(groupMembersMap.get(key)).orElse(List.of());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<Loot> getLoot(String key) {
|
||||
return Optional.ofNullable(lootMap.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, Double> getWeightedLoots(Effect effect, Context<Player> context) {
|
||||
HashMap<String, Double> lootWeightMap = new HashMap<>();
|
||||
for (ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player> conditionalElement : lootConditions.values()) {
|
||||
modifyWeightMap(lootWeightMap, context, conditionalElement);
|
||||
}
|
||||
for (Pair<String, BiFunction<Context<Player>, Double, Double>> pair : effect.weightOperations()) {
|
||||
double previous = lootWeightMap.getOrDefault(pair.left(), 0d);
|
||||
if (previous > 0)
|
||||
lootWeightMap.put(pair.left(), pair.right().apply(context, previous));
|
||||
}
|
||||
for (Pair<String, BiFunction<Context<Player>, Double, Double>> pair : effect.weightOperationsIgnored()) {
|
||||
double previous = lootWeightMap.getOrDefault(pair.left(), 0d);
|
||||
lootWeightMap.put(pair.left(), pair.right().apply(context, previous));
|
||||
}
|
||||
return lootWeightMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Loot getNextLoot(Effect effect, Context<Player> context) {
|
||||
HashMap<String, Double> lootWeightMap = new HashMap<>();
|
||||
for (ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player> conditionalElement : lootConditions.values()) {
|
||||
modifyWeightMap(lootWeightMap, context, conditionalElement);
|
||||
}
|
||||
for (Pair<String, BiFunction<Context<Player>, Double, Double>> pair : effect.weightOperations()) {
|
||||
double previous = lootWeightMap.getOrDefault(pair.left(), 0d);
|
||||
if (previous > 0)
|
||||
lootWeightMap.put(pair.left(), pair.right().apply(context, previous));
|
||||
}
|
||||
for (Pair<String, BiFunction<Context<Player>, Double, Double>> pair : effect.weightOperationsIgnored()) {
|
||||
double previous = lootWeightMap.getOrDefault(pair.left(), 0d);
|
||||
lootWeightMap.put(pair.left(), pair.right().apply(context, previous));
|
||||
}
|
||||
String lootID = WeightUtils.getRandom(lootWeightMap);
|
||||
return Optional.ofNullable(lootID)
|
||||
.map(id -> getLoot(lootID).orElseThrow(() -> new RuntimeException("Could not find loot " + lootID)))
|
||||
.orElseThrow(() -> new RuntimeException("No loot available. " + context));
|
||||
}
|
||||
|
||||
private void modifyWeightMap(Map<String, Double> weightMap, Context<Player> context, ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player> conditionalElement) {
|
||||
if (conditionalElement == null) return;
|
||||
if (RequirementManager.isSatisfied(context, conditionalElement.getRequirements())) {
|
||||
for (Pair<String, BiFunction<Context<Player>, Double, Double>> modifierPair : conditionalElement.getElement()) {
|
||||
double previous = weightMap.getOrDefault(modifierPair.left(), 0d);
|
||||
weightMap.put(modifierPair.left(), modifierPair.right().apply(context, previous));
|
||||
}
|
||||
for (ConditionalElement<List<Pair<String, BiFunction<Context<Player>, Double, Double>>>, Player> sub : conditionalElement.getSubElements().values()) {
|
||||
modifyWeightMap(weightMap, context, sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.market;
|
||||
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.action.Action;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionManager;
|
||||
import net.momirealms.customfishing.api.mechanic.config.GUIItemParser;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.item.CustomFishingItem;
|
||||
import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder;
|
||||
import net.momirealms.customfishing.api.mechanic.market.MarketManager;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.MathValue;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.TextValue;
|
||||
import net.momirealms.customfishing.api.storage.data.EarningData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.bukkit.config.BukkitConfigManager;
|
||||
import net.momirealms.customfishing.bukkit.item.BukkitItemFactory;
|
||||
import net.momirealms.customfishing.common.item.Item;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.ShulkerBox;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.*;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||
import org.bukkit.inventory.meta.BundleMeta;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class BukkitMarketManager implements MarketManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
|
||||
private final HashMap<String, MathValue<Player>> priceMap;
|
||||
private String formula;
|
||||
private MathValue<Player> earningsLimit;
|
||||
private boolean allowItemWithNoPrice;
|
||||
protected boolean sellFishingBag;
|
||||
|
||||
protected TextValue<Player> title;
|
||||
protected String[] layout;
|
||||
protected final HashMap<Character, CustomFishingItem> decorativeIcons;
|
||||
protected final ConcurrentHashMap<UUID, MarketGUI> marketGUICache;
|
||||
|
||||
protected char itemSlot;
|
||||
protected char sellSlot;
|
||||
protected char sellAllSlot;
|
||||
|
||||
protected CustomFishingItem sellIconAllowItem;
|
||||
protected CustomFishingItem sellIconDenyItem;
|
||||
protected CustomFishingItem sellIconLimitItem;
|
||||
protected CustomFishingItem sellAllIconAllowItem;
|
||||
protected CustomFishingItem sellAllIconDenyItem;
|
||||
protected CustomFishingItem sellAllIconLimitItem;
|
||||
protected Action<Player>[] sellDenyActions;
|
||||
protected Action<Player>[] sellAllowActions;
|
||||
protected Action<Player>[] sellLimitActions;
|
||||
protected Action<Player>[] sellAllDenyActions;
|
||||
protected Action<Player>[] sellAllAllowActions;
|
||||
protected Action<Player>[] sellAllLimitActions;
|
||||
|
||||
private SchedulerTask resetEarningsTask;
|
||||
private int cachedDate;
|
||||
|
||||
private boolean allowBundle;
|
||||
private boolean allowShulkerBox;
|
||||
|
||||
public BukkitMarketManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.priceMap = new HashMap<>();
|
||||
this.decorativeIcons = new HashMap<>();
|
||||
this.marketGUICache = new ConcurrentHashMap<>();
|
||||
this.cachedDate = getRealTimeDate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
this.loadConfig();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
this.resetEarningsTask = plugin.getScheduler().asyncRepeating(() -> {
|
||||
int now = getRealTimeDate();
|
||||
if (this.cachedDate != now) {
|
||||
this.cachedDate = now;
|
||||
for (UserData userData : plugin.getStorageManager().getOnlineUsers()) {
|
||||
userData.earningData().refresh();
|
||||
}
|
||||
}
|
||||
}, 1, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private int getRealTimeDate() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
this.priceMap.clear();
|
||||
this.decorativeIcons.clear();
|
||||
if (this.resetEarningsTask != null)
|
||||
this.resetEarningsTask.cancel();
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
Section config = BukkitConfigManager.getMainConfig().getSection("mechanics.market");
|
||||
|
||||
this.formula = config.getString("price-formula", "{base} + {bonus} * {size}");
|
||||
this.layout = config.getStringList("layout").toArray(new String[0]);
|
||||
this.title = TextValue.auto(config.getString("title", "market.title"));
|
||||
this.itemSlot = config.getString("item-slot.symbol", "I").charAt(0);
|
||||
this.allowItemWithNoPrice = config.getBoolean("item-slot.allow-items-with-no-price", true);
|
||||
this.allowBundle = config.getBoolean("allow-bundle", true);
|
||||
this.allowShulkerBox = config.getBoolean("allow-shulker-box", true);
|
||||
|
||||
Section sellAllSection = config.getSection("sell-all-icons");
|
||||
if (sellAllSection != null) {
|
||||
this.sellAllSlot = sellAllSection.getString("symbol", "S").charAt(0);
|
||||
this.sellFishingBag = sellAllSection.getBoolean("fishingbag", true);
|
||||
|
||||
this.sellAllIconAllowItem = new GUIItemParser("allow", sellAllSection.getSection("allow-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
this.sellAllIconDenyItem = new GUIItemParser("deny", sellAllSection.getSection("deny-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
this.sellAllIconLimitItem = new GUIItemParser("limit", sellAllSection.getSection("limit-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
|
||||
this.sellAllAllowActions = plugin.getActionManager().parseActions(sellAllSection.getSection("allow-icon.action"));
|
||||
this.sellAllDenyActions = plugin.getActionManager().parseActions(sellAllSection.getSection("deny-icon.action"));
|
||||
this.sellAllLimitActions = plugin.getActionManager().parseActions(sellAllSection.getSection("limit-icon.action"));
|
||||
}
|
||||
|
||||
Section sellSection = config.getSection("sell-icons");
|
||||
if (sellSection == null) {
|
||||
// for old config compatibility
|
||||
sellSection = config.getSection("functional-icons");
|
||||
}
|
||||
if (sellSection != null) {
|
||||
this.sellSlot = sellSection.getString("symbol", "B").charAt(0);
|
||||
|
||||
this.sellIconAllowItem = new GUIItemParser("allow", sellSection.getSection("allow-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
this.sellIconDenyItem = new GUIItemParser("deny", sellSection.getSection("deny-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
this.sellIconLimitItem = new GUIItemParser("limit", sellSection.getSection("limit-icon"), plugin.getConfigManager().getFormatFunctions()).getItem();
|
||||
|
||||
this.sellAllowActions = plugin.getActionManager().parseActions(sellSection.getSection("allow-icon.action"));
|
||||
this.sellDenyActions = plugin.getActionManager().parseActions(sellSection.getSection("deny-icon.action"));
|
||||
this.sellLimitActions = plugin.getActionManager().parseActions(sellSection.getSection("limit-icon.action"));
|
||||
}
|
||||
|
||||
this.earningsLimit = config.getBoolean("limitation.enable", true) ? MathValue.auto(config.getString("limitation.earnings", "10000")) : MathValue.plain(-1);
|
||||
|
||||
// Load item prices from the configuration
|
||||
Section priceSection = config.getSection("item-price");
|
||||
if (priceSection != null) {
|
||||
for (Map.Entry<String, Object> entry : priceSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
this.priceMap.put(entry.getKey(), MathValue.auto(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Load decorative icons from the configuration
|
||||
Section decorativeSection = config.getSection("decorative-icons");
|
||||
if (decorativeSection != null) {
|
||||
for (Map.Entry<String, Object> entry : decorativeSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section innerSection) {
|
||||
char symbol = Objects.requireNonNull(innerSection.getString("symbol")).charAt(0);
|
||||
decorativeIcons.put(symbol, new GUIItemParser("gui", innerSection, plugin.getConfigManager().getFormatFunctions()).getItem());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the market GUI for a player
|
||||
*
|
||||
* @param player player
|
||||
*/
|
||||
@Override
|
||||
public boolean openMarketGUI(Player player) {
|
||||
Optional<UserData> optionalUserData = plugin.getStorageManager().getOnlineUser(player.getUniqueId());
|
||||
if (optionalUserData.isEmpty()) {
|
||||
plugin.getPluginLogger().warn("Player " + player.getName() + "'s market data has not been loaded yet.");
|
||||
return false;
|
||||
}
|
||||
Context<Player> context = Context.player(player);
|
||||
MarketGUI gui = new MarketGUI(this, context, optionalUserData.get().earningData());
|
||||
gui.addElement(new MarketGUIElement(itemSlot, new ItemStack(Material.AIR)));
|
||||
gui.addElement(new MarketDynamicGUIElement(sellSlot, new ItemStack(Material.AIR)));
|
||||
gui.addElement(new MarketDynamicGUIElement(sellAllSlot, new ItemStack(Material.AIR)));
|
||||
for (Map.Entry<Character, CustomFishingItem> entry : decorativeIcons.entrySet()) {
|
||||
gui.addElement(new MarketGUIElement(entry.getKey(), entry.getValue().build(context)));
|
||||
}
|
||||
gui.build().refresh().show();
|
||||
marketGUICache.put(player.getUniqueId(), gui);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
return;
|
||||
if (!(event.getInventory().getHolder() instanceof MarketGUIHolder))
|
||||
return;
|
||||
MarketGUI gui = marketGUICache.remove(player.getUniqueId());
|
||||
if (gui != null)
|
||||
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 = marketGUICache.remove(event.getPlayer().getUniqueId());
|
||||
if (gui != null)
|
||||
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())
|
||||
return;
|
||||
Inventory inventory = event.getInventory();
|
||||
if (!(inventory.getHolder() instanceof MarketGUIHolder))
|
||||
return;
|
||||
Player player = (Player) event.getWhoClicked();
|
||||
MarketGUI gui = marketGUICache.get(player.getUniqueId());
|
||||
if (gui == null) {
|
||||
event.setCancelled(true);
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
|
||||
MarketGUIElement element = gui.getElement(itemSlot);
|
||||
if (element == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Integer> slots = element.getSlots();
|
||||
for (int dragSlot : event.getRawSlots()) {
|
||||
if (!slots.contains(dragSlot)) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getScheduler().sync().runLater(gui::refresh, 1, player.getLocation());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles inventory click events.
|
||||
*
|
||||
* @param event The InventoryClickEvent that triggered this method.
|
||||
*/
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onClickInv(InventoryClickEvent event) {
|
||||
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;
|
||||
|
||||
MarketGUI gui = marketGUICache.get(player.getUniqueId());
|
||||
if (gui == null) {
|
||||
event.setCancelled(true);
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
|
||||
EarningData earningData = gui.earningData;
|
||||
earningData.refresh();
|
||||
double earningLimit = earningLimit(gui.context);
|
||||
|
||||
if (clickedInv != player.getInventory()) {
|
||||
int slot = event.getSlot();
|
||||
MarketGUIElement element = gui.getElement(slot);
|
||||
if (element == null) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.getSymbol() == itemSlot) {
|
||||
if (!allowItemWithNoPrice) {
|
||||
if (event.getAction() == InventoryAction.HOTBAR_MOVE_AND_READD || event.getAction() == InventoryAction.HOTBAR_SWAP) {
|
||||
ItemStack moved = player.getInventory().getItem(event.getHotbarButton());
|
||||
double price = getItemPrice(gui.context, moved);
|
||||
if (price <= 0) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
if (element.getSymbol() == sellSlot) {
|
||||
|
||||
Pair<Integer, Double> pair = getItemsToSell(gui.context, gui.getItemsInGUI());
|
||||
double totalWorth = pair.right();
|
||||
gui.context.arg(ContextKeys.MONEY, money(totalWorth))
|
||||
.arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth))
|
||||
.arg(ContextKeys.REST, money(earningLimit - earningData.earnings))
|
||||
.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)))
|
||||
.arg(ContextKeys.SOLD_ITEM_AMOUNT, pair.left());
|
||||
|
||||
if (totalWorth > 0) {
|
||||
if (earningLimit != -1 && (earningLimit - earningData.earnings) < totalWorth) {
|
||||
// Can't earn more money
|
||||
ActionManager.trigger(gui.context, sellLimitActions);
|
||||
} else {
|
||||
// Clear items and update earnings
|
||||
clearWorthyItems(gui.context, gui.getItemsInGUI());
|
||||
earningData.earnings += totalWorth;
|
||||
gui.context.arg(ContextKeys.REST, money(earningLimit - earningData.earnings));
|
||||
gui.context.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)));
|
||||
ActionManager.trigger(gui.context, sellAllowActions);
|
||||
}
|
||||
} else {
|
||||
// Nothing to sell
|
||||
ActionManager.trigger(gui.context, sellDenyActions);
|
||||
}
|
||||
} else if (element.getSymbol() == sellAllSlot) {
|
||||
ArrayList<ItemStack> itemStacksToSell = new ArrayList<>(List.of(gui.context.getHolder().getInventory().getStorageContents()));
|
||||
if (sellFishingBag) {
|
||||
Optional<UserData> optionalUserData = BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(gui.context.getHolder().getUniqueId());
|
||||
optionalUserData.ifPresent(userData -> itemStacksToSell.addAll(List.of(userData.holder().getInventory().getStorageContents())));
|
||||
}
|
||||
Pair<Integer, Double> pair = getItemsToSell(gui.context, itemStacksToSell);
|
||||
double totalWorth = pair.right();
|
||||
gui.context.arg(ContextKeys.MONEY, money(totalWorth))
|
||||
.arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth))
|
||||
.arg(ContextKeys.REST, money(earningLimit - earningData.earnings))
|
||||
.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)))
|
||||
.arg(ContextKeys.SOLD_ITEM_AMOUNT, pair.left());
|
||||
|
||||
if (totalWorth > 0) {
|
||||
if (earningLimit != -1 && (earningLimit - earningData.earnings) < totalWorth) {
|
||||
// Can't earn more money
|
||||
ActionManager.trigger(gui.context, sellAllLimitActions);
|
||||
} else {
|
||||
// Clear items and update earnings
|
||||
clearWorthyItems(gui.context, itemStacksToSell);
|
||||
earningData.earnings += totalWorth;
|
||||
gui.context.arg(ContextKeys.REST, money(earningLimit - earningData.earnings));
|
||||
gui.context.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)));
|
||||
ActionManager.trigger(gui.context, sellAllAllowActions);
|
||||
}
|
||||
} else {
|
||||
// Nothing to sell
|
||||
ActionManager.trigger(gui.context, sellAllDenyActions);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle interactions with the player's inventory
|
||||
ItemStack current = event.getCurrentItem();
|
||||
if (!allowItemWithNoPrice) {
|
||||
double price = getItemPrice(gui.context, current);
|
||||
if (price <= 0) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)
|
||||
&& (current != null && current.getType() != Material.AIR)) {
|
||||
event.setCancelled(true);
|
||||
MarketGUIElement element = gui.getElement(itemSlot);
|
||||
if (element == null) return;
|
||||
for (int slot : element.getSlots()) {
|
||||
ItemStack itemStack = gui.inventory.getItem(slot);
|
||||
if (itemStack != null && itemStack.getType() != Material.AIR) {
|
||||
if (current.getType() == itemStack.getType()
|
||||
&& itemStack.getAmount() != itemStack.getType().getMaxStackSize()
|
||||
&& current.getItemMeta().equals(itemStack.getItemMeta())
|
||||
) {
|
||||
int left = itemStack.getType().getMaxStackSize() - itemStack.getAmount();
|
||||
if (current.getAmount() <= left) {
|
||||
itemStack.setAmount(itemStack.getAmount() + current.getAmount());
|
||||
current.setAmount(0);
|
||||
break;
|
||||
} else {
|
||||
current.setAmount(current.getAmount() - left);
|
||||
itemStack.setAmount(itemStack.getType().getMaxStackSize());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gui.inventory.setItem(slot, current.clone());
|
||||
current.setAmount(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the GUI
|
||||
plugin.getScheduler().sync().runLater(gui::refresh, 1, player.getLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public double getItemPrice(Context<Player> context, ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return 0;
|
||||
|
||||
Item<ItemStack> wrapped = ((BukkitItemFactory) plugin.getItemManager().getFactory()).wrap(itemStack);
|
||||
double price = (double) wrapped.getTag("Price").orElse(0d);
|
||||
if (price != 0) {
|
||||
// If a custom price is defined in the ItemStack's NBT data, use it.
|
||||
return price * itemStack.getAmount();
|
||||
}
|
||||
|
||||
if (allowBundle && itemStack.getItemMeta() instanceof BundleMeta bundleMeta) {
|
||||
Pair<Integer, Double> pair = getItemsToSell(context, bundleMeta.getItems());
|
||||
return pair.right();
|
||||
}
|
||||
|
||||
if (allowShulkerBox && itemStack.getItemMeta() instanceof BlockStateMeta stateMeta) {
|
||||
if (stateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
|
||||
Pair<Integer, Double> pair = getItemsToSell(context, Arrays.stream(shulkerBox.getInventory().getStorageContents()).filter(Objects::nonNull).toList());
|
||||
return pair.right();
|
||||
}
|
||||
}
|
||||
|
||||
// If no custom price is defined, attempt to fetch the price from a predefined price map.
|
||||
String itemID = itemStack.getType().name();
|
||||
Optional<Integer> optionalCMD = wrapped.customModelData();
|
||||
if (optionalCMD.isPresent()) {
|
||||
itemID = itemID + ":" + optionalCMD.get();
|
||||
}
|
||||
|
||||
MathValue<Player> formula = priceMap.get(itemID);
|
||||
if (formula == null) return 0;
|
||||
|
||||
return formula.evaluate(context) * itemStack.getAmount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormula() {
|
||||
return formula;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double earningLimit(Context<Player> context) {
|
||||
return earningsLimit.evaluate(context);
|
||||
}
|
||||
|
||||
public Pair<Integer, Double> getItemsToSell(Context<Player> context, List<ItemStack> itemStacks) {
|
||||
int amount = 0;
|
||||
double worth = 0d;
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
double price = getItemPrice(context, itemStack);
|
||||
if (price > 0 && itemStack != null) {
|
||||
amount += itemStack.getAmount();
|
||||
worth += price;
|
||||
}
|
||||
}
|
||||
return Pair.of(amount, worth);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public void clearWorthyItems(Context<Player> context, List<ItemStack> itemStacks) {
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
double price = getItemPrice(context, itemStack);
|
||||
if (price > 0 && itemStack != null) {
|
||||
if (allowBundle && itemStack.getItemMeta() instanceof BundleMeta bundleMeta) {
|
||||
clearWorthyItems(context, bundleMeta.getItems());
|
||||
itemStack.setItemMeta(bundleMeta);
|
||||
continue;
|
||||
}
|
||||
if (allowShulkerBox && itemStack.getItemMeta() instanceof BlockStateMeta stateMeta) {
|
||||
if (stateMeta.getBlockState() instanceof ShulkerBox shulkerBox) {
|
||||
clearWorthyItems(context, Arrays.stream(shulkerBox.getInventory().getStorageContents()).filter(Objects::nonNull).toList());
|
||||
stateMeta.setBlockState(shulkerBox);
|
||||
itemStack.setItemMeta(stateMeta);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
itemStack.setAmount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String money(double money) {
|
||||
String str = String.format("%.2f", money);
|
||||
return str.replace(",", ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.market;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class MarketDynamicGUIElement extends MarketGUIElement {
|
||||
|
||||
public MarketDynamicGUIElement(char symbol, ItemStack itemStack) {
|
||||
super(symbol, itemStack);
|
||||
}
|
||||
|
||||
public void setItemStack(ItemStack itemStack) {
|
||||
super.itemStack = itemStack;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.market;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.market.MarketGUIHolder;
|
||||
import net.momirealms.customfishing.api.storage.data.EarningData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.bukkit.util.PlayerUtils;
|
||||
import net.momirealms.customfishing.common.helper.AdventureHelper;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import net.momirealms.sparrow.heart.SparrowHeart;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class MarketGUI {
|
||||
|
||||
private final HashMap<Character, MarketGUIElement> itemsCharMap;
|
||||
private final HashMap<Integer, MarketGUIElement> itemsSlotMap;
|
||||
private final BukkitMarketManager manager;
|
||||
protected final Inventory inventory;
|
||||
protected final Context<Player> context;
|
||||
protected final EarningData earningData;
|
||||
|
||||
public MarketGUI(BukkitMarketManager manager, Context<Player> context, EarningData earningData) {
|
||||
this.manager = manager;
|
||||
this.context = context;
|
||||
this.earningData = earningData;
|
||||
this.itemsCharMap = new HashMap<>();
|
||||
this.itemsSlotMap = new HashMap<>();
|
||||
var holder = new MarketGUIHolder();
|
||||
this.inventory = Bukkit.createInventory(holder, manager.layout.length * 9);
|
||||
holder.setInventory(this.inventory);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
int line = 0;
|
||||
for (String content : manager.layout) {
|
||||
for (int index = 0; index < 9; index++) {
|
||||
char symbol;
|
||||
if (index < content.length()) symbol = content.charAt(index);
|
||||
else symbol = ' ';
|
||||
MarketGUIElement element = itemsCharMap.get(symbol);
|
||||
if (element != null) {
|
||||
element.addSlot(index + line * 9);
|
||||
itemsSlotMap.put(index + line * 9, element);
|
||||
}
|
||||
}
|
||||
line++;
|
||||
}
|
||||
for (Map.Entry<Integer, MarketGUIElement> entry : itemsSlotMap.entrySet()) {
|
||||
this.inventory.setItem(entry.getKey(), entry.getValue().getItemStack().clone());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public MarketGUI addElement(MarketGUIElement... elements) {
|
||||
for (MarketGUIElement element : elements) {
|
||||
itemsCharMap.put(element.getSymbol(), element);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MarketGUI build() {
|
||||
init();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
context.getHolder().openInventory(inventory);
|
||||
SparrowHeart.getInstance().updateInventoryTitle(context.getHolder(), AdventureHelper.componentToJson(AdventureHelper.miniMessage(manager.title.render(context))));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MarketGUIElement getElement(int slot) {
|
||||
return itemsSlotMap.get(slot);
|
||||
}
|
||||
|
||||
@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 earningLimit = manager.earningLimit(context);
|
||||
MarketDynamicGUIElement sellElement = (MarketDynamicGUIElement) getElement(manager.sellSlot);
|
||||
if (sellElement != null && !sellElement.getSlots().isEmpty()) {
|
||||
Pair<Integer, Double> pair = manager.getItemsToSell(context, getItemsInGUI());
|
||||
double totalWorth = pair.right();
|
||||
int soldAmount = pair.left();
|
||||
context.arg(ContextKeys.MONEY, manager.money(totalWorth))
|
||||
.arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth))
|
||||
.arg(ContextKeys.REST, manager.money(earningLimit - earningData.earnings))
|
||||
.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)))
|
||||
.arg(ContextKeys.SOLD_ITEM_AMOUNT, soldAmount);
|
||||
if (totalWorth <= 0) {
|
||||
sellElement.setItemStack(manager.sellIconDenyItem.build(context));
|
||||
} else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) {
|
||||
sellElement.setItemStack(manager.sellIconLimitItem.build(context));
|
||||
} else {
|
||||
sellElement.setItemStack(manager.sellIconAllowItem.build(context));
|
||||
}
|
||||
}
|
||||
|
||||
MarketDynamicGUIElement sellAllElement = (MarketDynamicGUIElement) getElement(manager.sellAllSlot);
|
||||
if (sellAllElement != null && !sellAllElement.getSlots().isEmpty()) {
|
||||
ArrayList<ItemStack> itemStacksToSell = new ArrayList<>(List.of(context.getHolder().getInventory().getStorageContents()));
|
||||
if (manager.sellFishingBag) {
|
||||
Optional<UserData> optionalUserData = BukkitCustomFishingPlugin.getInstance().getStorageManager().getOnlineUser(context.getHolder().getUniqueId());
|
||||
optionalUserData.ifPresent(userData -> itemStacksToSell.addAll(List.of(userData.holder().getInventory().getStorageContents())));
|
||||
}
|
||||
Pair<Integer, Double> pair = manager.getItemsToSell(context, itemStacksToSell);
|
||||
double totalWorth = pair.right();
|
||||
int soldAmount = pair.left();
|
||||
context.arg(ContextKeys.MONEY, manager.money(totalWorth))
|
||||
.arg(ContextKeys.MONEY_FORMATTED, String.format("%.2f", totalWorth))
|
||||
.arg(ContextKeys.REST, manager.money(earningLimit - earningData.earnings))
|
||||
.arg(ContextKeys.REST_FORMATTED, String.format("%.2f", (earningLimit - earningData.earnings)))
|
||||
.arg(ContextKeys.SOLD_ITEM_AMOUNT, soldAmount);
|
||||
if (totalWorth <= 0) {
|
||||
sellAllElement.setItemStack(manager.sellAllIconAllowItem.build(context));
|
||||
} else if (earningLimit != -1 && (earningLimit - earningData.earnings < totalWorth)) {
|
||||
sellAllElement.setItemStack(manager.sellAllIconLimitItem.build(context));
|
||||
} else {
|
||||
sellAllElement.setItemStack(manager.sellAllIconAllowItem.build(context));
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, MarketGUIElement> entry : itemsSlotMap.entrySet()) {
|
||||
if (entry.getValue() instanceof MarketDynamicGUIElement dynamicGUIElement) {
|
||||
this.inventory.setItem(entry.getKey(), dynamicGUIElement.getItemStack().clone());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<ItemStack> getItemsInGUI() {
|
||||
MarketGUIElement itemElement = getElement(manager.itemSlot);
|
||||
if (itemElement == null) return List.of();
|
||||
return itemElement.getSlots().stream().map(inventory::getItem).filter(Objects::nonNull).toList();
|
||||
}
|
||||
|
||||
public int getEmptyItemSlot() {
|
||||
MarketGUIElement itemElement = getElement(manager.itemSlot);
|
||||
if (itemElement == null) {
|
||||
return -1;
|
||||
}
|
||||
for (int slot : itemElement.getSlots()) {
|
||||
ItemStack itemStack = inventory.getItem(slot);
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void returnItems() {
|
||||
MarketGUIElement itemElement = getElement(manager.itemSlot);
|
||||
if (itemElement == null) {
|
||||
return;
|
||||
}
|
||||
for (int slot : itemElement.getSlots()) {
|
||||
ItemStack itemStack = inventory.getItem(slot);
|
||||
if (itemStack != null && itemStack.getType() != Material.AIR) {
|
||||
PlayerUtils.giveItem(context.getHolder(), itemStack, itemStack.getAmount());
|
||||
inventory.setItem(slot, new ItemStack(Material.AIR));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.market;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MarketGUIElement {
|
||||
|
||||
private final char symbol;
|
||||
private final List<Integer> slots;
|
||||
protected ItemStack itemStack;
|
||||
|
||||
public MarketGUIElement(char symbol, ItemStack itemStack) {
|
||||
this.symbol = symbol;
|
||||
this.itemStack = itemStack;
|
||||
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.clone();
|
||||
}
|
||||
|
||||
// Getter method to retrieve the list of slots where this element can appear
|
||||
public List<Integer> getSlots() {
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.scheduler;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.bukkit.scheduler.impl.BukkitExecutor;
|
||||
import net.momirealms.customfishing.bukkit.scheduler.impl.FoliaExecutor;
|
||||
import net.momirealms.customfishing.common.helper.VersionHelper;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.AbstractJavaScheduler;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor;
|
||||
import org.bukkit.Location;
|
||||
|
||||
public class BukkitSchedulerAdapter extends AbstractJavaScheduler<Location> {
|
||||
protected RegionExecutor<Location> sync;
|
||||
|
||||
public BukkitSchedulerAdapter(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
if (VersionHelper.isFolia()) {
|
||||
this.sync = new FoliaExecutor(plugin.getBoostrap());
|
||||
} else {
|
||||
this.sync = new BukkitExecutor(plugin.getBoostrap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegionExecutor<Location> sync() {
|
||||
return this.sync;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.scheduler.impl;
|
||||
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class BukkitExecutor implements RegionExecutor<Location> {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
public BukkitExecutor(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Runnable r, Location l) {
|
||||
Bukkit.getScheduler().runTask(plugin, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runLater(Runnable r, long delayTicks, Location l) {
|
||||
if (delayTicks == 0) {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
r.run();
|
||||
return () -> {};
|
||||
} else {
|
||||
return Bukkit.getScheduler().runTask(plugin, r)::cancel;
|
||||
}
|
||||
}
|
||||
return Bukkit.getScheduler().runTaskLater(plugin, r, delayTicks)::cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) {
|
||||
return Bukkit.getScheduler().runTaskTimer(plugin, r, delayTicks, period)::cancel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.scheduler.impl;
|
||||
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.RegionExecutor;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class FoliaExecutor implements RegionExecutor<Location> {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
public FoliaExecutor(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Runnable r, Location l) {
|
||||
Optional.ofNullable(l).ifPresentOrElse(loc -> Bukkit.getRegionScheduler().execute(plugin, loc, r), () -> Bukkit.getGlobalRegionScheduler().execute(plugin, r));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runLater(Runnable r, long delayTicks, Location l) {
|
||||
if (l == null) {
|
||||
if (delayTicks == 0) {
|
||||
return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> r.run(), delayTicks)::cancel;
|
||||
} else {
|
||||
return Bukkit.getGlobalRegionScheduler().run(plugin, scheduledTask -> r.run())::cancel;
|
||||
}
|
||||
} else {
|
||||
if (delayTicks == 0) {
|
||||
return Bukkit.getRegionScheduler().run(plugin, l, scheduledTask -> r.run())::cancel;
|
||||
} else {
|
||||
return Bukkit.getRegionScheduler().runDelayed(plugin, l, scheduledTask -> r.run(), delayTicks)::cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask runRepeating(Runnable r, long delayTicks, long period, Location l) {
|
||||
if (l == null) {
|
||||
return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> r.run(), delayTicks, period)::cancel;
|
||||
} else {
|
||||
return Bukkit.getRegionScheduler().runAtFixedRate(plugin, l, scheduledTask -> r.run(), delayTicks, period)::cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.sender;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.common.sender.Sender;
|
||||
import net.momirealms.customfishing.common.sender.SenderFactory;
|
||||
import net.momirealms.customfishing.common.util.Tristate;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.RemoteConsoleCommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitSenderFactory extends SenderFactory<BukkitCustomFishingPlugin, CommandSender> {
|
||||
private final BukkitAudiences audiences;
|
||||
|
||||
public BukkitSenderFactory(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
this.audiences = BukkitAudiences.create(plugin.getBoostrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return sender.getName();
|
||||
}
|
||||
return Sender.CONSOLE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUniqueId(CommandSender sender) {
|
||||
if (sender instanceof Player) {
|
||||
return ((Player) sender).getUniqueId();
|
||||
}
|
||||
return Sender.CONSOLE_UUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Audience getAudience(CommandSender sender) {
|
||||
return this.audiences.sender(sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendMessage(CommandSender sender, Component message) {
|
||||
// we can safely send async for players and the console - otherwise, send it sync
|
||||
if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
|
||||
getAudience(sender).sendMessage(message);
|
||||
} else {
|
||||
getPlugin().getScheduler().executeSync(() -> getAudience(sender).sendMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Tristate getPermissionValue(CommandSender sender, String node) {
|
||||
if (sender.hasPermission(node)) {
|
||||
return Tristate.TRUE;
|
||||
} else if (sender.isPermissionSet(node)) {
|
||||
return Tristate.FALSE;
|
||||
} else {
|
||||
return Tristate.UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasPermission(CommandSender sender, String node) {
|
||||
return sender.hasPermission(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCommand(CommandSender sender, String command) {
|
||||
getPlugin().getBoostrap().getServer().dispatchCommand(sender, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isConsole(CommandSender sender) {
|
||||
return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
this.audiences.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.statistic;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.statistic.StatisticsManager;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
public class BukkitStatisticsManager implements StatisticsManager {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final Map<String, List<String>> categoryMap = new HashMap<>();
|
||||
|
||||
public BukkitStatisticsManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
this.loadCategoriesFromPluginFolder();
|
||||
for (Map.Entry<String, List<String>> entry : categoryMap.entrySet()) {
|
||||
plugin.debug("Category: {" + entry.getKey() + "} Members: " + entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
this.categoryMap.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public void loadCategoriesFromPluginFolder() {
|
||||
Deque<File> fileDeque = new ArrayDeque<>();
|
||||
for (String type : List.of("category")) {
|
||||
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
|
||||
if (!typeFolder.exists()) {
|
||||
if (!typeFolder.mkdirs()) return;
|
||||
plugin.getBoostrap().saveResource("contents" + File.separator + type + File.separator + "default.yml", false);
|
||||
}
|
||||
fileDeque.push(typeFolder);
|
||||
while (!fileDeque.isEmpty()) {
|
||||
File file = fileDeque.pop();
|
||||
File[] files = file.listFiles();
|
||||
if (files == null) continue;
|
||||
for (File subFile : files) {
|
||||
if (subFile.isDirectory()) {
|
||||
fileDeque.push(subFile);
|
||||
} else if (subFile.isFile() && subFile.getName().endsWith(".yml")) {
|
||||
this.loadSingleFile(subFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSingleFile(File file) {
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
for (String key : config.getKeys(false)) {
|
||||
categoryMap.put(key, config.getStringList(key));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getCategoryMembers(String key) {
|
||||
return categoryMap.getOrDefault(key, List.of());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.storage.DataStorageProvider;
|
||||
import net.momirealms.customfishing.api.storage.StorageManager;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.nosql.MongoDBProvider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.nosql.RedisManager;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.sql.H2Provider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.sql.MariaDBProvider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.sql.MySQLProvider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.database.sql.SQLiteProvider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.file.JsonProvider;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.file.YAMLProvider;
|
||||
import net.momirealms.customfishing.common.helper.GsonHelper;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BukkitStorageManager implements StorageManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private DataStorageProvider dataSource;
|
||||
private StorageType previousType;
|
||||
private final ConcurrentHashMap<UUID, UserData> onlineUserMap;
|
||||
private final HashSet<UUID> locked;
|
||||
private boolean hasRedis;
|
||||
private RedisManager redisManager;
|
||||
private String serverID;
|
||||
private SchedulerTask timerSaveTask;
|
||||
|
||||
public BukkitStorageManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.locked = new HashSet<>();
|
||||
this.onlineUserMap = new ConcurrentHashMap<>();
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
YamlDocument config = plugin.getConfigManager().loadConfig("database.yml");
|
||||
this.serverID = 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();
|
||||
this.previousType = storageType;
|
||||
switch (storageType) {
|
||||
case H2 -> this.dataSource = new H2Provider(plugin);
|
||||
case JSON -> this.dataSource = new JsonProvider(plugin);
|
||||
case YAML -> this.dataSource = new YAMLProvider(plugin);
|
||||
case SQLite -> this.dataSource = new SQLiteProvider(plugin);
|
||||
case MySQL -> this.dataSource = new MySQLProvider(plugin);
|
||||
case MariaDB -> this.dataSource = new MariaDBProvider(plugin);
|
||||
case MongoDB -> this.dataSource = new MongoDBProvider(plugin);
|
||||
}
|
||||
if (this.dataSource != null) this.dataSource.initialize(config);
|
||||
else plugin.getPluginLogger().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(config);
|
||||
}
|
||||
|
||||
// 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.cancel();
|
||||
}
|
||||
|
||||
// Schedule periodic data saving if dataSaveInterval is configured
|
||||
if (ConfigManager.dataSaveInterval() > 0)
|
||||
this.timerSaveTask = this.plugin.getScheduler().asyncRepeating(
|
||||
() -> {
|
||||
long time1 = System.currentTimeMillis();
|
||||
this.dataSource.updateManyPlayersData(this.onlineUserMap.values(), !ConfigManager.lockData());
|
||||
if (ConfigManager.logDataSaving())
|
||||
plugin.getPluginLogger().info("Data Saved for online players. Took " + (System.currentTimeMillis() - time1) + "ms.");
|
||||
},
|
||||
ConfigManager.dataSaveInterval(),
|
||||
ConfigManager.dataSaveInterval(),
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the storage manager and cleans up resources.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
HandlerList.unregisterAll(this);
|
||||
this.dataSource.updateManyPlayersData(onlineUserMap.values(), true);
|
||||
this.onlineUserMap.clear();
|
||||
if (this.dataSource != null)
|
||||
this.dataSource.disable();
|
||||
if (this.redisManager != null)
|
||||
this.redisManager.disable();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getServerID() {
|
||||
return serverID;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<UserData> getOnlineUser(UUID uuid) {
|
||||
return Optional.ofNullable(onlineUserMap.get(uuid));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<UserData> getOnlineUsers() {
|
||||
return onlineUserMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<UserData>> getOfflineUserData(UUID uuid, boolean lock) {
|
||||
CompletableFuture<Optional<PlayerData>> optionalDataFuture = dataSource.getPlayerData(uuid, lock);
|
||||
return optionalDataFuture.thenCompose(optionalUser -> {
|
||||
if (optionalUser.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
PlayerData data = optionalUser.get();
|
||||
return CompletableFuture.completedFuture(Optional.of(UserData.builder()
|
||||
.data(data)
|
||||
.build()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> saveUserData(UserData userData, boolean unlock) {
|
||||
return dataSource.updatePlayerData(userData.uuid(), userData.toPlayerData(), unlock);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataStorageProvider 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();
|
||||
UUID uuid = player.getUniqueId();
|
||||
locked.add(uuid);
|
||||
if (!hasRedis) {
|
||||
waitLock(uuid, 1);
|
||||
} else {
|
||||
plugin.getScheduler().asyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(changeServer -> {
|
||||
if (!changeServer) {
|
||||
waitLock(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();
|
||||
UUID uuid = player.getUniqueId();
|
||||
if (locked.contains(uuid))
|
||||
return;
|
||||
|
||||
UserData onlineUser = onlineUserMap.remove(uuid);
|
||||
if (onlineUser == null) return;
|
||||
PlayerData data = onlineUser.toPlayerData();
|
||||
|
||||
if (hasRedis) {
|
||||
redisManager.setChangeServer(uuid).thenRun(
|
||||
() -> redisManager.updatePlayerData(uuid, data, true).thenRun(
|
||||
() -> dataSource.updatePlayerData(uuid, data, true).thenAccept(
|
||||
result -> {
|
||||
if (result) locked.remove(uuid);
|
||||
})));
|
||||
} else {
|
||||
dataSource.updatePlayerData(uuid, data, true).thenAccept(
|
||||
result -> {
|
||||
if (result) locked.remove(uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable task for asynchronously retrieving data from Redis.
|
||||
* Retries up to 6 times and cancels the task if the player is offline.
|
||||
*/
|
||||
private class RedisGetDataTask implements Runnable {
|
||||
|
||||
private final UUID uuid;
|
||||
private int triedTimes;
|
||||
private final SchedulerTask task;
|
||||
|
||||
public RedisGetDataTask(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
this.task = plugin.getScheduler().asyncRepeating(this, 0, 333, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
triedTimes++;
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline()) {
|
||||
// offline
|
||||
task.cancel();
|
||||
return;
|
||||
}
|
||||
if (triedTimes >= 6) {
|
||||
waitLock(uuid, 3);
|
||||
return;
|
||||
}
|
||||
redisManager.getPlayerData(uuid, false).thenAccept(optionalData -> {
|
||||
if (optionalData.isPresent()) {
|
||||
addOnlineUser(player, optionalData.get());
|
||||
task.cancel();
|
||||
if (ConfigManager.lockData()) dataSource.lockOrUnlockPlayerData(uuid, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private void waitLock(UUID uuid, int times) {
|
||||
plugin.getScheduler().asyncLater(() -> {
|
||||
var player = Bukkit.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline())
|
||||
return;
|
||||
if (times > 3) {
|
||||
plugin.getPluginLogger().warn("Tried 3 times when getting data for " + uuid + ". Giving up.");
|
||||
return;
|
||||
}
|
||||
this.dataSource.getPlayerData(uuid, ConfigManager.lockData()).thenAccept(optionalData -> {
|
||||
// Data should not be empty
|
||||
if (optionalData.isEmpty()) {
|
||||
plugin.getPluginLogger().severe("Unexpected error: Data is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (optionalData.get().locked()) {
|
||||
waitLock(uuid, times + 1);
|
||||
} else {
|
||||
try {
|
||||
addOnlineUser(player, optionalData.get());
|
||||
} catch (Exception e) {
|
||||
plugin.getPluginLogger().severe("Unexpected error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void addOnlineUser(Player player, PlayerData playerData) {
|
||||
this.locked.remove(player.getUniqueId());
|
||||
this.onlineUserMap.put(player.getUniqueId(), UserData.builder()
|
||||
.data(playerData)
|
||||
// update the name
|
||||
.name(player.getName())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRedisEnabled() {
|
||||
return hasRedis;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RedisManager getRedisManager() {
|
||||
return redisManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public byte[] toBytes(@NotNull PlayerData data) {
|
||||
return toJson(data).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toJson(@NotNull PlayerData data) {
|
||||
return GsonHelper.get().toJson(data);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PlayerData fromJson(String json) {
|
||||
try {
|
||||
return GsonHelper.get().fromJson(json, PlayerData.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
plugin.getPluginLogger().severe("Failed to parse PlayerData from json");
|
||||
plugin.getPluginLogger().info("Json: " + json);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PlayerData fromBytes(byte[] data) {
|
||||
return fromJson(new String(data, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.DataStorageProvider;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
|
||||
import java.time.Instant;
|
||||
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 DataStorageProvider {
|
||||
|
||||
protected BukkitCustomFishingPlugin plugin;
|
||||
|
||||
public AbstractStorage(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(YamlDocument config) {
|
||||
// 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 UserData> users, boolean unlock) {
|
||||
for (UserData user : users) {
|
||||
this.updatePlayerData(user.uuid(), user.toPlayerData(), unlock);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.nosql;
|
||||
|
||||
import com.mongodb.*;
|
||||
import com.mongodb.client.*;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage;
|
||||
import org.bson.Document;
|
||||
import org.bson.UuidRepresentation;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.Binary;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class MongoDBProvider extends AbstractStorage {
|
||||
|
||||
private MongoClient mongoClient;
|
||||
private MongoDatabase database;
|
||||
private String collectionPrefix;
|
||||
|
||||
public MongoDBProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(YamlDocument config) {
|
||||
Section section = config.getSection("MongoDB");
|
||||
if (section == null) {
|
||||
plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
|
||||
return;
|
||||
}
|
||||
|
||||
collectionPrefix = section.getString("collection-prefix", "customfishing");
|
||||
var settings = MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD);
|
||||
if (!section.getString("connection-uri", "").equals("")) {
|
||||
settings.applyConnectionString(new ConnectionString(section.getString("connection-uri", "")));
|
||||
mongoClient = MongoClients.create(settings.build());
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.contains("user")) {
|
||||
MongoCredential credential = MongoCredential.createCredential(
|
||||
section.getString("user", "root"),
|
||||
section.getString("database", "minecraft"),
|
||||
section.getString("password", "password").toCharArray()
|
||||
);
|
||||
settings.credential(credential);
|
||||
}
|
||||
|
||||
settings.applyToClusterSettings(builder -> builder.hosts(Collections.singletonList(new ServerAddress(
|
||||
section.getString("host", "localhost"),
|
||||
section.getInt("port", 27017)
|
||||
))));
|
||||
this.mongoClient = MongoClients.create(settings.build());
|
||||
this.database = mongoClient.getDatabase(section.getString("database", "minecraft"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
if (this.mongoClient != null) {
|
||||
this.mongoClient.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.MongoDB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
Document doc = collection.find(Filters.eq("uuid", uuid)).first();
|
||||
if (doc == null) {
|
||||
if (Bukkit.getPlayer(uuid) != null) {
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
var data = PlayerData.empty();
|
||||
data.uuid(uuid);
|
||||
future.complete(Optional.of(data));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
} else {
|
||||
Binary binary = (Binary) doc.get("data");
|
||||
PlayerData data = plugin.getStorageManager().fromBytes(binary.getData());
|
||||
data.uuid(uuid);
|
||||
if (doc.getInteger("lock") != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= doc.getInteger("lock")) {
|
||||
data.locked(true);
|
||||
future.complete(Optional.of(data));
|
||||
return;
|
||||
}
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(data));
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Document query = new Document("uuid", uuid);
|
||||
Bson updates = Updates.combine(
|
||||
Updates.set("lock", unlock ? 0 : getCurrentSeconds()),
|
||||
Updates.set("data", new Binary(plugin.getStorageManager().toBytes(playerData))));
|
||||
UpdateOptions options = new UpdateOptions().upsert(true);
|
||||
UpdateResult result = collection.updateOne(query, updates, options);
|
||||
future.complete(result.wasAcknowledged());
|
||||
} catch (MongoException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends UserData> users, boolean unlock) {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
int lock = unlock ? 0 : getCurrentSeconds();
|
||||
var list = users.stream().map(it -> new UpdateOneModel<Document>(
|
||||
new Document("uuid", it.uuid()),
|
||||
Updates.combine(
|
||||
Updates.set("lock", lock),
|
||||
Updates.set("data", new Binary(plugin.getStorageManager().toBytes(it.toPlayerData())))
|
||||
),
|
||||
new UpdateOptions().upsert(true)
|
||||
)
|
||||
).toList();
|
||||
if (list.isEmpty()) return;
|
||||
collection.bulkWrite(list);
|
||||
} catch (MongoException e) {
|
||||
plugin.getPluginLogger().warn("Failed to update data for online players", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockOrUnlockPlayerData(UUID uuid, boolean lock) {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Document query = new Document("uuid", uuid);
|
||||
Bson updates = Updates.combine(Updates.set("lock", !lock ? 0 : getCurrentSeconds()));
|
||||
UpdateOptions options = new UpdateOptions().upsert(true);
|
||||
collection.updateOne(query, updates, options);
|
||||
} catch (MongoException e) {
|
||||
plugin.getPluginLogger().warn("Failed to lock data for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers() {
|
||||
// no legacy files
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Bson projectionFields = Projections.fields(Projections.include("uuid"));
|
||||
try (MongoCursor<Document> cursor = collection.find().projection(projectionFields).iterator()) {
|
||||
while (cursor.hasNext()) {
|
||||
uuids.add(cursor.next().get("uuid", UUID.class));
|
||||
}
|
||||
}
|
||||
} catch (MongoException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get unique data.", e);
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.nosql;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import redis.clients.jedis.*;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
import redis.clients.jedis.params.XReadParams;
|
||||
import redis.clients.jedis.resps.StreamEntry;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class RedisManager extends AbstractStorage {
|
||||
|
||||
private static RedisManager instance;
|
||||
private final static String STREAM = "customfishing";
|
||||
private JedisPool jedisPool;
|
||||
private String password;
|
||||
private int port;
|
||||
private String host;
|
||||
private boolean useSSL;
|
||||
private BlockingThreadTask threadTask;
|
||||
private boolean isNewerThan5;
|
||||
|
||||
public RedisManager(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
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(YamlDocument config) {
|
||||
Section section = config.getSection("Redis");
|
||||
if (section == null) {
|
||||
plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
|
||||
return;
|
||||
}
|
||||
|
||||
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||
jedisPoolConfig.setTestWhileIdle(true);
|
||||
jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000));
|
||||
jedisPoolConfig.setNumTestsPerEvictionRun(-1);
|
||||
jedisPoolConfig.setMinEvictableIdleDuration(Duration.ofMillis(section.getInt("MinEvictableIdleTimeMillis", 1800000)));
|
||||
jedisPoolConfig.setMaxTotal(section.getInt("MaxTotal",8));
|
||||
jedisPoolConfig.setMaxIdle(section.getInt("MaxIdle",8));
|
||||
jedisPoolConfig.setMinIdle(section.getInt("MinIdle",1));
|
||||
jedisPoolConfig.setMaxWait(Duration.ofMillis(section.getInt("MaxWaitMillis")));
|
||||
|
||||
password = section.getString("password", "");
|
||||
port = section.getInt("port", 6379);
|
||||
host = section.getString("host", "localhost");
|
||||
useSSL = section.getBoolean("use-ssl", false);
|
||||
|
||||
if (password.isBlank()) {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, host, port, 0, useSSL);
|
||||
} else {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, host, port, 0, password, useSSL);
|
||||
}
|
||||
String info;
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
info = jedis.info();
|
||||
plugin.getPluginLogger().info("Redis server connected.");
|
||||
} catch (JedisException e) {
|
||||
plugin.getPluginLogger().warn("Failed to connect redis.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
String version = parseRedisVersion(info);
|
||||
if (isRedisNewerThan5(version)) {
|
||||
// For Redis 5.0+
|
||||
this.threadTask = new BlockingThreadTask();
|
||||
this.isNewerThan5 = true;
|
||||
} else {
|
||||
// For Redis 2.0+
|
||||
this.subscribe();
|
||||
this.isNewerThan5 = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the Redis connection by closing the JedisPool.
|
||||
*/
|
||||
@Override
|
||||
public void disable() {
|
||||
if (threadTask != null)
|
||||
threadTask.stop();
|
||||
if (jedisPool != null && !jedisPool.isClosed())
|
||||
jedisPool.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to Redis on a specified channel.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
public void publishRedisMessage(@NotNull String message) {
|
||||
if (isNewerThan5) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
HashMap<String, String> messages = new HashMap<>();
|
||||
messages.put("value", message);
|
||||
jedis.xadd(getStream(), StreamEntryID.NEW_ENTRY, messages);
|
||||
}
|
||||
} else {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.publish(getStream(), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() ?
|
||||
new Jedis(host, port, 0, useSSL) :
|
||||
new Jedis(host, port, DefaultJedisClientConfig
|
||||
.builder()
|
||||
.password(password)
|
||||
.timeoutMillis(0)
|
||||
.ssl(useSSL)
|
||||
.build())
|
||||
) {
|
||||
jedis.connect();
|
||||
jedis.subscribe(new JedisPubSub() {
|
||||
@Override
|
||||
public void onMessage(String channel, String message) {
|
||||
if (!channel.equals(getStream())) {
|
||||
return;
|
||||
}
|
||||
handleMessage(message);
|
||||
}
|
||||
}, getStream());
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void handleMessage(String message) {
|
||||
ByteArrayDataInput input = ByteStreams.newDataInput(message.getBytes(StandardCharsets.UTF_8));
|
||||
String server = input.readUTF();
|
||||
if (!ConfigManager.serverGroup().equals(server))
|
||||
return;
|
||||
String type = input.readUTF();
|
||||
if (type.equals("competition")) {
|
||||
String action = input.readUTF();
|
||||
switch (action) {
|
||||
case "start" -> {
|
||||
plugin.getCompetitionManager().startCompetition(input.readUTF(), true, null);
|
||||
}
|
||||
case "end" -> {
|
||||
if (plugin.getCompetitionManager().getOnGoingCompetition() != null)
|
||||
plugin.getCompetitionManager().getOnGoingCompetition().end(true);
|
||||
}
|
||||
case "stop" -> {
|
||||
if (plugin.getCompetitionManager().getOnGoingCompetition() != null)
|
||||
plugin.getCompetitionManager().getOnGoingCompetition().stop(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.Redis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().async().execute(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(
|
||||
getRedisKey("cf_server", uuid),
|
||||
10,
|
||||
new byte[0]
|
||||
);
|
||||
}
|
||||
future.complete(null);
|
||||
});
|
||||
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().async().execute(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
byte[] key = getRedisKey("cf_server", uuid);
|
||||
if (jedis.get(key) != null) {
|
||||
jedis.del(key);
|
||||
future.complete(true);
|
||||
} else {
|
||||
future.complete(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
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>>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
byte[] key = getRedisKey("cf_data", uuid);
|
||||
byte[] data = jedis.get(key);
|
||||
jedis.del(key);
|
||||
if (data != null) {
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(data)));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
});
|
||||
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>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(
|
||||
getRedisKey("cf_data", uuid),
|
||||
10,
|
||||
plugin.getStorageManager().toBytes(playerData)
|
||||
);
|
||||
future.complete(true);
|
||||
} catch (Exception e) {
|
||||
future.complete(false);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers() {
|
||||
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);
|
||||
}
|
||||
|
||||
public static String getStream() {
|
||||
return STREAM;
|
||||
}
|
||||
|
||||
private boolean isRedisNewerThan5(String version) {
|
||||
String[] split = version.split("\\.");
|
||||
int major = Integer.parseInt(split[0]);
|
||||
if (major < 7) {
|
||||
plugin.getPluginLogger().warn(String.format("Detected that you are running an outdated Redis server. v%s. ", version));
|
||||
plugin.getPluginLogger().warn("It's recommended to update to avoid security vulnerabilities!");
|
||||
}
|
||||
return major >= 5;
|
||||
}
|
||||
|
||||
private String parseRedisVersion(String info) {
|
||||
for (String line : info.split("\n")) {
|
||||
if (line.startsWith("redis_version:")) {
|
||||
return line.split(":")[1];
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public class BlockingThreadTask {
|
||||
|
||||
private boolean stopped;
|
||||
|
||||
public void stop() {
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
public BlockingThreadTask() {
|
||||
Thread thread = new Thread(() -> {
|
||||
var map = new HashMap<String, StreamEntryID>();
|
||||
map.put(getStream(), StreamEntryID.LAST_ENTRY);
|
||||
while (!this.stopped) {
|
||||
try {
|
||||
var connection = getJedis();
|
||||
if (connection != null) {
|
||||
var messages = connection.xread(XReadParams.xReadParams().count(1).block(2000), map);
|
||||
connection.close();
|
||||
if (messages != null && !messages.isEmpty()) {
|
||||
for (Map.Entry<String, List<StreamEntry>> message : messages) {
|
||||
if (message.getKey().equals(getStream())) {
|
||||
var value = message.getValue().get(0).getFields().get("value");
|
||||
handleMessage(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getPluginLogger().warn("Failed to connect redis. Try reconnecting 10s later",e);
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException ex) {
|
||||
this.stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public abstract class AbstractHikariDatabase extends AbstractSQLDatabase {
|
||||
|
||||
private HikariDataSource dataSource;
|
||||
private final String driverClass;
|
||||
private final String sqlBrand;
|
||||
|
||||
public AbstractHikariDatabase(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
this.driverClass = getStorageType() == StorageType.MariaDB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver";
|
||||
this.sqlBrand = getStorageType() == StorageType.MariaDB ? "MariaDB" : "MySQL";
|
||||
try {
|
||||
Class.forName(this.driverClass);
|
||||
} catch (ClassNotFoundException e1) {
|
||||
if (getStorageType() == StorageType.MariaDB) {
|
||||
plugin.getPluginLogger().warn("No MariaDB driver is found");
|
||||
} else if (getStorageType() == StorageType.MySQL) {
|
||||
try {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
} catch (ClassNotFoundException e2) {
|
||||
plugin.getPluginLogger().warn("No MySQL driver is found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(YamlDocument config) {
|
||||
Section section = config.getSection(sqlBrand);
|
||||
|
||||
if (section == null) {
|
||||
plugin.getPluginLogger().warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
|
||||
return;
|
||||
}
|
||||
|
||||
super.tablePrefix = section.getString("table-prefix", "customfishing");
|
||||
HikariConfig hikariConfig = new HikariConfig();
|
||||
hikariConfig.setUsername(section.getString("user", "root"));
|
||||
hikariConfig.setPassword(section.getString("password", "pa55w0rd"));
|
||||
hikariConfig.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s",
|
||||
sqlBrand.toLowerCase(Locale.ENGLISH),
|
||||
section.getString("host", "localhost"),
|
||||
section.getString("port", "3306"),
|
||||
section.getString("database", "minecraft"),
|
||||
section.getString("connection-parameters")
|
||||
));
|
||||
hikariConfig.setDriverClassName(driverClass);
|
||||
hikariConfig.setMaximumPoolSize(section.getInt("Pool-Settings.max-pool-size", 10));
|
||||
hikariConfig.setMinimumIdle(section.getInt("Pool-Settings.min-idle", 10));
|
||||
hikariConfig.setMaxLifetime(section.getLong("Pool-Settings.max-lifetime", 180000L));
|
||||
hikariConfig.setConnectionTimeout(section.getLong("Pool-Settings.time-out", 20000L));
|
||||
hikariConfig.setPoolName("CustomFishingHikariPool");
|
||||
try {
|
||||
hikariConfig.setKeepaliveTime(section.getLong("Pool-Settings.keep-alive-time", 60000L));
|
||||
} catch (NoSuchMethodError ignored) {
|
||||
}
|
||||
|
||||
final Properties properties = new Properties();
|
||||
properties.putAll(
|
||||
Map.of("cachePrepStmts", "true",
|
||||
"prepStmtCacheSize", "250",
|
||||
"prepStmtCacheSqlLimit", "2048",
|
||||
"useServerPrepStmts", "true",
|
||||
"useLocalSessionState", "true",
|
||||
"useLocalTransactionState", "true"
|
||||
));
|
||||
properties.putAll(
|
||||
Map.of(
|
||||
"rewriteBatchedStatements", "true",
|
||||
"cacheResultSetMetadata", "true",
|
||||
"cacheServerConfiguration", "true",
|
||||
"elideSetAutoCommits", "true",
|
||||
"maintainTimeStats", "false")
|
||||
);
|
||||
hikariConfig.setDataSourceProperties(properties);
|
||||
dataSource = new HikariDataSource(hikariConfig);
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
if (dataSource != null && !dataSource.isClosed())
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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;
|
||||
|
||||
public AbstractSQLDatabase(BukkitCustomFishingPlugin plugin) {
|
||||
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));
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
for (String tableCreationStatement : databaseSchema) {
|
||||
statement.execute(tableCreationStatement);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to create tables", e);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get sql connection", e);
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get schema resource", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getBoostrap().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;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
ResultSet rs = statement.executeQuery();
|
||||
if (rs.next()) {
|
||||
final Blob blob = rs.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
PlayerData data = plugin.getStorageManager().fromBytes(dataByteArray);
|
||||
data.uuid(uuid);
|
||||
if (lock) {
|
||||
int lockValue = rs.getInt(2);
|
||||
if (lockValue != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= lockValue) {
|
||||
connection.close();
|
||||
data.locked(true);
|
||||
future.complete(Optional.of(data));
|
||||
plugin.getPluginLogger().warn("Player " + uuid + "'s data is locked. Retrying...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(data));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
// the player is online
|
||||
var data = PlayerData.empty();
|
||||
data.uuid(uuid);
|
||||
insertPlayerData(uuid, data, lock);
|
||||
future.complete(Optional.of(data));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setInt(1, unlock ? 0 : getCurrentSeconds());
|
||||
statement.setBlob(2, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
|
||||
statement.setString(3, uuid.toString());
|
||||
statement.executeUpdate();
|
||||
future.complete(true);
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to update " + uuid + "'s data.", e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends UserData> users, boolean unlock) {
|
||||
String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"));
|
||||
try (Connection connection = getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (UserData user : users) {
|
||||
statement.setInt(1, unlock ? 0 : getCurrentSeconds());
|
||||
statement.setBlob(2, new ByteArrayInputStream(plugin.getStorageManager().toBytes(user.toPlayerData())));
|
||||
statement.setString(3, user.uuid().toString());
|
||||
statement.addBatch();
|
||||
}
|
||||
statement.executeBatch();
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
connection.rollback();
|
||||
plugin.getPluginLogger().warn("Failed to update data for online players", e);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get connection when saving online players' data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
statement.setInt(2, lock ? getCurrentSeconds() : 0);
|
||||
statement.setBlob(3, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to insert " + uuid + "'s data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockOrUnlockPlayerData(UUID uuid, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_LOCK_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setInt(1, lock ? getCurrentSeconds() : 0);
|
||||
statement.setString(2, uuid.toString());
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to lock " + uuid + "'s data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updateOrInsertPlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
ResultSet rs = statement.executeQuery();
|
||||
if (rs.next()) {
|
||||
updatePlayerData(uuid, playerData, unlock).thenRun(() -> future.complete(true));
|
||||
} else {
|
||||
insertPlayerData(uuid, playerData, !unlock);
|
||||
future.complete(true);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers() {
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
try (Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_ALL_UUID, getTableName("data")))) {
|
||||
try (ResultSet rs = statement.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
UUID uuid = UUID.fromString(rs.getString("uuid"));
|
||||
uuids.add(uuid);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get unique data.", e);
|
||||
}
|
||||
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`";
|
||||
public static final String SQL_UPDATE_BY_UUID = "UPDATE `%s` SET `lock` = ?, `data` = ? WHERE `uuid` = ?";
|
||||
public static final String SQL_LOCK_BY_UUID = "UPDATE `%s` SET `lock` = ? WHERE `uuid` = ?";
|
||||
public static final String SQL_INSERT_DATA_BY_UUID = "INSERT INTO `%s`(`uuid`, `lock`, `data`) VALUES(?, ?, ?)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.common.dependency.Dependency;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* An implementation of AbstractSQLDatabase that uses the H2 embedded database for player data storage.
|
||||
*/
|
||||
public class H2Provider extends AbstractSQLDatabase {
|
||||
|
||||
private Object connectionPool;
|
||||
private Method disposeMethod;
|
||||
private Method getConnectionMethod;
|
||||
|
||||
public H2Provider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the H2 database and connection pool based on the configuration.
|
||||
*/
|
||||
@Override
|
||||
public void initialize(YamlDocument config) {
|
||||
File databaseFile = new File(plugin.getDataFolder(), config.getString("H2.file", "data.db"));
|
||||
super.tablePrefix = config.getString("H2.table-prefix", "customfishing");
|
||||
|
||||
final String url = String.format("jdbc:h2:%s", databaseFile.getAbsolutePath());
|
||||
ClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.H2_DRIVER));
|
||||
try {
|
||||
Class<?> connectionClass = classLoader.loadClass("org.h2.jdbcx.JdbcConnectionPool");
|
||||
Method createPoolMethod = connectionClass.getMethod("create", String.class, String.class, String.class);
|
||||
this.connectionPool = createPoolMethod.invoke(null, url, "sa", "");
|
||||
this.disposeMethod = connectionClass.getMethod("dispose");
|
||||
this.getConnectionMethod = connectionClass.getMethod("getConnection");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
if (connectionPool != null) {
|
||||
try {
|
||||
disposeMethod.invoke(connectionPool);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.H2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
return (Connection) getConnectionMethod.invoke(connectionPool);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
|
||||
public class MariaDBProvider extends AbstractHikariDatabase {
|
||||
|
||||
public MariaDBProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.MariaDB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
|
||||
public class MySQLProvider extends AbstractHikariDatabase {
|
||||
|
||||
public MySQLProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.MySQL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.database.sql;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.user.UserData;
|
||||
import net.momirealms.customfishing.common.dependency.Dependency;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class SQLiteProvider extends AbstractSQLDatabase {
|
||||
|
||||
private Connection connection;
|
||||
private File databaseFile;
|
||||
private Constructor<?> connectionConstructor;
|
||||
|
||||
public SQLiteProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(YamlDocument config) {
|
||||
ClassLoader classLoader = plugin.getDependencyManager().obtainClassLoaderWith(EnumSet.of(Dependency.SQLITE_DRIVER, Dependency.SLF4J_SIMPLE, Dependency.SLF4J_API));
|
||||
try {
|
||||
Class<?> connectionClass = classLoader.loadClass("org.sqlite.jdbc4.JDBC4Connection");
|
||||
connectionConstructor = connectionClass.getConstructor(String.class, String.class, Properties.class);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.databaseFile = new File(plugin.getDataFolder(), config.getString("SQLite.file", "data") + ".db");
|
||||
super.tablePrefix = config.getString("SQLite.table-prefix", "customfishing");
|
||||
super.createTableIfNotExist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed())
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
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()) {
|
||||
return connection;
|
||||
}
|
||||
try {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("foreign_keys", Boolean.toString(true));
|
||||
properties.setProperty("encoding", "'UTF-8'");
|
||||
properties.setProperty("synchronous", "FULL");
|
||||
connection = (Connection) this.connectionConstructor.newInstance("jdbc:sqlite:" + databaseFile.toString(), databaseFile.toString(), properties);
|
||||
return connection;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
if (e.getCause() instanceof SQLException) {
|
||||
throw (SQLException) e.getCause();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
ResultSet rs = statement.executeQuery();
|
||||
if (rs.next()) {
|
||||
final byte[] dataByteArray = rs.getBytes("data");
|
||||
PlayerData data = plugin.getStorageManager().fromBytes(dataByteArray);
|
||||
data.uuid(uuid);
|
||||
int lockValue = rs.getInt(2);
|
||||
if (lockValue != 0 && getCurrentSeconds() - ConfigManager.dataSaveInterval() <= lockValue) {
|
||||
connection.close();
|
||||
data.locked(true);
|
||||
future.complete(Optional.of(data));
|
||||
return;
|
||||
}
|
||||
if (lock) lockOrUnlockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(data));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
data.uuid(uuid);
|
||||
insertPlayerData(uuid, data, lock);
|
||||
future.complete(Optional.of(data));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get " + uuid + "'s data.", e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean unlock) {
|
||||
var future = new CompletableFuture<Boolean>();
|
||||
plugin.getScheduler().async().execute(() -> {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setInt(1, unlock ? 0 : getCurrentSeconds());
|
||||
statement.setBytes(2, plugin.getStorageManager().toBytes(playerData));
|
||||
statement.setString(3, uuid.toString());
|
||||
statement.executeUpdate();
|
||||
future.complete(true);
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to update " + uuid + "'s data.", e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateManyPlayersData(Collection<? extends UserData> users, boolean unlock) {
|
||||
String sql = String.format(SqlConstants.SQL_UPDATE_BY_UUID, getTableName("data"));
|
||||
try (Connection connection = getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (UserData user : users) {
|
||||
statement.setInt(1, unlock ? 0 : getCurrentSeconds());
|
||||
statement.setBytes(2, plugin.getStorageManager().toBytes(user.toPlayerData()));
|
||||
statement.setString(3, user.uuid().toString());
|
||||
statement.addBatch();
|
||||
}
|
||||
statement.executeBatch();
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
connection.rollback();
|
||||
plugin.getPluginLogger().warn("Failed to update bag data for online players", e);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to get connection when saving online players' data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
statement.setInt(2, lock ? getCurrentSeconds() : 0);
|
||||
statement.setBytes(3, plugin.getStorageManager().toBytes(playerData));
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
plugin.getPluginLogger().warn("Failed to insert " + uuid + "'s data.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.file;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
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 JsonProvider extends AbstractStorage {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public JsonProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
if (!folder.exists()) folder.mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
return StorageType.JSON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
File file = getPlayerDataFile(uuid);
|
||||
PlayerData playerData;
|
||||
if (file.exists()) {
|
||||
playerData = readFromJsonFile(file, PlayerData.class);
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
playerData = PlayerData.empty();
|
||||
playerData.uuid(uuid);
|
||||
} else {
|
||||
playerData = null;
|
||||
}
|
||||
return CompletableFuture.completedFuture(Optional.ofNullable(playerData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) {
|
||||
this.saveToJsonFile(playerData, getPlayerDataFile(uuid));
|
||||
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)) {
|
||||
gson.toJson(obj, file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
fis.read(fileBytes);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fileBytes;
|
||||
}
|
||||
|
||||
// Retrieve a set of unique user UUIDs based on JSON data files in the 'data' folder.
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers() {
|
||||
// No legacy files
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
if (folder.exists()) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
uuids.add(UUID.fromString(file.getName().substring(file.getName().length() - 5)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.storage.method.file;
|
||||
|
||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.storage.StorageType;
|
||||
import net.momirealms.customfishing.api.storage.data.EarningData;
|
||||
import net.momirealms.customfishing.api.storage.data.InventoryData;
|
||||
import net.momirealms.customfishing.api.storage.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.storage.data.StatisticData;
|
||||
import net.momirealms.customfishing.bukkit.storage.method.AbstractStorage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class YAMLProvider extends AbstractStorage {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public YAMLProvider(BukkitCustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
if (!folder.exists()) folder.mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageType getStorageType() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
File dataFile = getPlayerDataFile(uuid);
|
||||
if (!dataFile.exists()) {
|
||||
if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
data.uuid(uuid);
|
||||
return CompletableFuture.completedFuture(Optional.of(data));
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
}
|
||||
YamlDocument data = plugin.getConfigManager().loadData(dataFile);
|
||||
PlayerData playerData = PlayerData.builder()
|
||||
.bag(new InventoryData(data.getString("bag", ""), data.getInt("size", 9)))
|
||||
.earnings(new EarningData(data.getDouble("earnings"), data.getInt("date")))
|
||||
.statistics(getStatistics(data.getSection("stats")))
|
||||
.name(data.getString("name", ""))
|
||||
.build();
|
||||
return CompletableFuture.completedFuture(Optional.of(playerData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> updatePlayerData(UUID uuid, PlayerData playerData, boolean ignore) {
|
||||
YamlConfiguration data = new YamlConfiguration();
|
||||
data.set("name", playerData.name());
|
||||
data.set("bag", playerData.bagData().serialized);
|
||||
data.set("size", playerData.bagData().size);
|
||||
data.set("date", playerData.earningData().date);
|
||||
data.set("earnings", playerData.earningData().earnings);
|
||||
ConfigurationSection section = data.createSection("stats");
|
||||
ConfigurationSection amountSection = section.createSection("amount");
|
||||
ConfigurationSection sizeSection = section.createSection("size");
|
||||
for (Map.Entry<String, Integer> entry : playerData.statistics().amountMap.entrySet()) {
|
||||
amountSection.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, Float> entry : playerData.statistics().sizeMap.entrySet()) {
|
||||
sizeSection.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
try {
|
||||
data.save(getPlayerDataFile(uuid));
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().warn("Failed to save player data", e);
|
||||
}
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers() {
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
if (folder.exists()) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
uuids.add(UUID.fromString(file.getName().substring(0, file.getName().length() - 4)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
|
||||
private StatisticData getStatistics(Section section) {
|
||||
HashMap<String, Integer> amountMap = new HashMap<>();
|
||||
HashMap<String, Float> sizeMap = new HashMap<>();
|
||||
if (section == null) {
|
||||
return new StatisticData(amountMap, sizeMap);
|
||||
}
|
||||
Section amountSection = section.getSection("amount");
|
||||
if (amountSection != null) {
|
||||
for (Map.Entry<String, Object> entry : amountSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
amountMap.put(entry.getKey(), (Integer) entry.getValue());
|
||||
}
|
||||
}
|
||||
Section sizeSection = section.getSection("size");
|
||||
if (sizeSection != null) {
|
||||
for (Map.Entry<String, Object> entry : sizeSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
sizeMap.put(entry.getKey(), ((Double) entry.getValue()).floatValue());
|
||||
}
|
||||
}
|
||||
return new StatisticData(amountMap, sizeMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.totem;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.context.ContextKeys;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.TotemConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.TotemParticle;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ActivatedTotem {
|
||||
|
||||
private final List<SchedulerTask> subTasks;
|
||||
private final Location coreLocation;
|
||||
private final TotemConfig totemConfig;
|
||||
private final long expireTime;
|
||||
private final Context<Player> context;
|
||||
private final double radius;
|
||||
|
||||
public ActivatedTotem(Player activator, Location coreLocation, TotemConfig config) {
|
||||
this.context = Context.player(activator, true)
|
||||
.arg(ContextKeys.LOCATION, coreLocation)
|
||||
.arg(ContextKeys.X, coreLocation.getBlockX())
|
||||
.arg(ContextKeys.Y, coreLocation.getBlockY())
|
||||
.arg(ContextKeys.Z, coreLocation.getBlockZ())
|
||||
.arg(ContextKeys.ID, config.id());
|
||||
this.subTasks = new ArrayList<>();
|
||||
this.expireTime = (long) (System.currentTimeMillis() + config.duration().evaluate(context) * 1000L);
|
||||
this.coreLocation = coreLocation.clone().add(0.5,0,0.5);
|
||||
this.totemConfig = config;
|
||||
this.radius = config.radius().evaluate(context);
|
||||
for (TotemParticle particleSetting : config.particleSettings()) {
|
||||
this.subTasks.add(particleSetting.start(coreLocation, radius));
|
||||
}
|
||||
}
|
||||
|
||||
public TotemConfig getTotemConfig() {
|
||||
return totemConfig;
|
||||
}
|
||||
|
||||
public Location getCoreLocation() {
|
||||
return coreLocation;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
for (SchedulerTask task : this.subTasks) {
|
||||
task.cancel();
|
||||
}
|
||||
this.subTasks.clear();
|
||||
}
|
||||
|
||||
public long getExpireTime() {
|
||||
return this.expireTime;
|
||||
}
|
||||
|
||||
public double getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
public void doTimerAction() {
|
||||
this.context.arg(ContextKeys.TIME_LEFT, String.valueOf((expireTime - System.currentTimeMillis())/1000));
|
||||
BukkitCustomFishingPlugin.getInstance().getEventManager().getEventCarrier(totemConfig.id(), MechanicType.TOTEM)
|
||||
.ifPresent(carrier -> carrier.trigger(context, ActionTrigger.TIMER));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.totem;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.event.TotemActivateEvent;
|
||||
import net.momirealms.customfishing.api.mechanic.MechanicType;
|
||||
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
|
||||
import net.momirealms.customfishing.api.mechanic.config.ConfigManager;
|
||||
import net.momirealms.customfishing.api.mechanic.context.Context;
|
||||
import net.momirealms.customfishing.api.mechanic.effect.EffectModifier;
|
||||
import net.momirealms.customfishing.api.mechanic.requirement.RequirementManager;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.TotemConfig;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.TotemManager;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.block.TotemBlock;
|
||||
import net.momirealms.customfishing.api.util.SimpleLocation;
|
||||
import net.momirealms.customfishing.bukkit.util.LocationUtils;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BukkitTotemManager implements TotemManager, Listener {
|
||||
|
||||
private final BukkitCustomFishingPlugin plugin;
|
||||
private final HashMap<String, List<TotemConfig>> block2Totem = new HashMap<>();
|
||||
private final HashMap<String, TotemConfig> id2Totem = new HashMap<>();
|
||||
private final List<String> allMaterials = Arrays.stream(Material.values()).map(Enum::name).toList();
|
||||
private final ConcurrentHashMap<SimpleLocation, ActivatedTotem> activatedTotems = new ConcurrentHashMap<>();
|
||||
private SchedulerTask timerCheckTask;
|
||||
|
||||
public BukkitTotemManager(BukkitCustomFishingPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin.getBoostrap());
|
||||
this.timerCheckTask = plugin.getScheduler().asyncRepeating(() -> {
|
||||
long time = System.currentTimeMillis();
|
||||
ArrayList<SimpleLocation> removed = new ArrayList<>();
|
||||
for (Map.Entry<SimpleLocation, ActivatedTotem> entry : activatedTotems.entrySet()) {
|
||||
if (time > entry.getValue().getExpireTime()) {
|
||||
removed.add(entry.getKey());
|
||||
entry.getValue().cancel();
|
||||
} else {
|
||||
entry.getValue().doTimerAction();
|
||||
}
|
||||
}
|
||||
for (SimpleLocation simpleLocation : removed) {
|
||||
activatedTotems.remove(simpleLocation);
|
||||
}
|
||||
}, 1, 1, TimeUnit.SECONDS);
|
||||
plugin.debug("Loaded " + id2Totem.size() + " totems");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
HandlerList.unregisterAll(this);
|
||||
for (ActivatedTotem activatedTotem : this.activatedTotems.values())
|
||||
activatedTotem.cancel();
|
||||
this.activatedTotems.clear();
|
||||
if (this.timerCheckTask != null)
|
||||
this.timerCheckTask.cancel();
|
||||
this.block2Totem.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getActivatedTotems(Location location) {
|
||||
Collection<String> activated = new ArrayList<>();
|
||||
double nearest = Double.MAX_VALUE;
|
||||
String nearestTotemID = null;
|
||||
for (ActivatedTotem activatedTotem : activatedTotems.values()) {
|
||||
double distance = LocationUtils.getDistance(activatedTotem.getCoreLocation(), location);
|
||||
if (distance < activatedTotem.getRadius()) {
|
||||
activated.add(activatedTotem.getTotemConfig().id());
|
||||
if (nearest > distance) {
|
||||
nearest = distance;
|
||||
nearestTotemID = activatedTotem.getTotemConfig().id();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestTotemID == null) return List.of();
|
||||
if (!ConfigManager.allowMultipleTotemType()) {
|
||||
if (ConfigManager.allowSameTotemType()) {
|
||||
String finalNearestTotemID = nearestTotemID;
|
||||
activated.removeIf(element -> !element.equals(finalNearestTotemID));
|
||||
return activated;
|
||||
} else {
|
||||
return List.of(nearestTotemID);
|
||||
}
|
||||
} else {
|
||||
if (ConfigManager.allowSameTotemType()) {
|
||||
return activated;
|
||||
} else {
|
||||
return new HashSet<>(activated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onBreakTotemCore(BlockBreakEvent event) {
|
||||
if (event.isCancelled())
|
||||
return;
|
||||
Location location = event.getBlock().getLocation();
|
||||
SimpleLocation simpleLocation = SimpleLocation.of(location);
|
||||
ActivatedTotem activatedTotem = activatedTotems.remove(simpleLocation);
|
||||
if (activatedTotem != null)
|
||||
activatedTotem.cancel();
|
||||
}
|
||||
|
||||
@EventHandler (ignoreCancelled = true)
|
||||
public void onInteractBlock(PlayerInteractEvent event) {
|
||||
if (
|
||||
event.isBlockInHand() ||
|
||||
event.getAction() != org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK ||
|
||||
event.getHand() != EquipmentSlot.HAND
|
||||
)
|
||||
return;
|
||||
|
||||
Block block = event.getClickedBlock();
|
||||
assert block != null;
|
||||
String id = plugin.getBlockManager().getBlockID(block);
|
||||
List<TotemConfig> configs = block2Totem.get(id);
|
||||
if (configs == null)
|
||||
return;
|
||||
TotemConfig config = null;
|
||||
for (TotemConfig temp : configs) {
|
||||
if (temp.isRightPattern(block.getLocation())) {
|
||||
config = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (config == null)
|
||||
return;
|
||||
|
||||
String totemID = config.id();
|
||||
final Player player = event.getPlayer();;
|
||||
Context<Player> context = Context.player(player);
|
||||
Optional<EffectModifier> optionalEffectModifier = plugin.getEffectManager().getEffectModifier(totemID, MechanicType.TOTEM);
|
||||
if (optionalEffectModifier.isPresent()) {
|
||||
if (!RequirementManager.isSatisfied(context, optionalEffectModifier.get().requirements())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TotemActivateEvent totemActivateEvent = new TotemActivateEvent(player, block.getLocation(), config);
|
||||
Bukkit.getPluginManager().callEvent(totemActivateEvent);
|
||||
if (totemActivateEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getEventManager().trigger(context, totemID, MechanicType.TOTEM, ActionTrigger.ACTIVATE);
|
||||
|
||||
Location location = block.getLocation();
|
||||
ActivatedTotem activatedTotem = new ActivatedTotem(player, location, config);
|
||||
SimpleLocation simpleLocation = SimpleLocation.of(location);
|
||||
ActivatedTotem previous = this.activatedTotems.put(simpleLocation, activatedTotem);
|
||||
if (previous != null) {
|
||||
previous.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerTotem(TotemConfig totem) {
|
||||
if (id2Totem.containsKey(totem.id())) {
|
||||
return false;
|
||||
}
|
||||
HashSet<String> coreMaterials = new HashSet<>();
|
||||
for (TotemBlock totemBlock : totem.totemCore()) {
|
||||
String text = totemBlock.getTypeCondition().getRawText();
|
||||
if (text.startsWith("*")) {
|
||||
String sub = text.substring(1);
|
||||
coreMaterials.addAll(allMaterials.stream().filter(it -> it.endsWith(sub)).toList());
|
||||
} else if (text.endsWith("*")) {
|
||||
String sub = text.substring(0, text.length() - 1);
|
||||
coreMaterials.addAll(allMaterials.stream().filter(it -> it.startsWith(sub)).toList());
|
||||
} else {
|
||||
coreMaterials.add(text);
|
||||
}
|
||||
}
|
||||
for (String material : coreMaterials) {
|
||||
List<TotemConfig> configs = this.block2Totem.getOrDefault(material, new ArrayList<>());
|
||||
configs.add(totem);
|
||||
this.block2Totem.put(material, configs);
|
||||
}
|
||||
id2Totem.put(totem.id(), totem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Optional<TotemConfig> getTotem(String id) {
|
||||
return Optional.ofNullable(id2Totem.get(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.totem.particle;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DustParticleSetting extends ParticleSetting {
|
||||
|
||||
private final Particle.DustOptions dustOptions;
|
||||
|
||||
public DustParticleSetting(
|
||||
String formulaHorizontal,
|
||||
String formulaVertical,
|
||||
Particle particle,
|
||||
double interval,
|
||||
List<Pair<Double, Double>> ranges,
|
||||
int delayTicks,
|
||||
int periodTicks,
|
||||
Particle.DustOptions dustOptions
|
||||
) {
|
||||
super(formulaHorizontal, formulaVertical, particle, interval, ranges, delayTicks, periodTicks);
|
||||
this.dustOptions = dustOptions;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public SchedulerTask start(Location location, double radius) {
|
||||
World world = location.getWorld();
|
||||
return BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> {
|
||||
for (Pair<Double, Double> range : ranges) {
|
||||
for (double theta = range.left(); theta <= range.right(); theta += interval) {
|
||||
double r = expressionHorizontal.setVariable("theta", theta).setVariable("radius", radius).evaluate();
|
||||
double x = r * Math.cos(theta) + 0.5;
|
||||
double z = r * Math.sin(theta) + 0.5;
|
||||
double y = expressionVertical.setVariable("theta", theta).setVariable("radius", radius).evaluate();
|
||||
world.spawnParticle(particle, location.clone().add(x, y, z), 1,0,0,0, 0, dustOptions);
|
||||
}
|
||||
}
|
||||
}, delay * 50L, period * 50L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.totem.particle;
|
||||
|
||||
import net.momirealms.customfishing.api.BukkitCustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.mechanic.totem.TotemParticle;
|
||||
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerTask;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import net.objecthunter.exp4j.Expression;
|
||||
import net.objecthunter.exp4j.ExpressionBuilder;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ParticleSetting implements TotemParticle {
|
||||
|
||||
protected final Expression expressionHorizontal;
|
||||
protected final Expression expressionVertical;
|
||||
protected final double interval;
|
||||
protected int delay;
|
||||
protected int period;
|
||||
protected final Particle particle;
|
||||
List<Pair<Double, Double>> ranges;
|
||||
|
||||
public ParticleSetting(
|
||||
String formulaHorizontal,
|
||||
String formulaVertical,
|
||||
Particle particle,
|
||||
double interval,
|
||||
List<Pair<Double, Double>> ranges,
|
||||
int delayTicks,
|
||||
int periodTicks
|
||||
) {
|
||||
this.interval = interval * Math.PI / 180;
|
||||
this.particle = particle;
|
||||
this.delay = delayTicks;
|
||||
this.period = periodTicks;
|
||||
this.ranges = ranges;
|
||||
this.expressionHorizontal = new ExpressionBuilder(formulaHorizontal)
|
||||
.variables("theta", "radius")
|
||||
.build();
|
||||
this.expressionVertical = new ExpressionBuilder(formulaVertical)
|
||||
.variables("theta", "radius")
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public SchedulerTask start(Location location, double radius) {
|
||||
World world = location.getWorld();
|
||||
return BukkitCustomFishingPlugin.getInstance().getScheduler().asyncRepeating(() -> {
|
||||
for (Pair<Double, Double> range : ranges) {
|
||||
for (double theta = range.left(); theta <= range.right(); theta += interval) {
|
||||
double r = expressionHorizontal.setVariable("theta", theta).setVariable("radius", radius).evaluate();
|
||||
double x = r * Math.cos(theta) + 0.5;
|
||||
double z = r * Math.sin(theta) + 0.5;
|
||||
double y = expressionVertical.setVariable("theta", theta).setVariable("radius", radius).evaluate();
|
||||
world.spawnParticle(particle, location.clone().add(x, y, z), 1,0,0,0);
|
||||
}
|
||||
}
|
||||
}, delay * 50L, period * 50L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.util;
|
||||
|
||||
import com.saicone.rtag.item.ItemTagStream;
|
||||
import dev.dejvokep.boostedyaml.block.implementation.Section;
|
||||
import net.momirealms.customfishing.api.mechanic.item.ItemEditor;
|
||||
import net.momirealms.customfishing.api.mechanic.item.tag.TagMap;
|
||||
import net.momirealms.customfishing.api.mechanic.item.tag.TagValueType;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.MathValue;
|
||||
import net.momirealms.customfishing.api.mechanic.misc.value.TextValue;
|
||||
import net.momirealms.customfishing.common.util.ArrayUtils;
|
||||
import net.momirealms.customfishing.common.util.Pair;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import static net.momirealms.customfishing.api.util.TagUtils.toTypeAndData;
|
||||
import static net.momirealms.customfishing.common.util.ArrayUtils.splitValue;
|
||||
|
||||
public class ItemStackUtils {
|
||||
|
||||
private ItemStackUtils() {}
|
||||
|
||||
public static ItemStack fromBase64(String base64) {
|
||||
if (base64 == null || base64.isEmpty())
|
||||
return new ItemStack(Material.AIR);
|
||||
ByteArrayInputStream inputStream;
|
||||
try {
|
||||
inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(base64));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return new ItemStack(Material.AIR);
|
||||
}
|
||||
ItemStack stack = null;
|
||||
try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
|
||||
stack = (ItemStack) dataInput.readObject();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
public static String toBase64(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() == Material.AIR)
|
||||
return "";
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
|
||||
dataOutput.writeObject(itemStack);
|
||||
byte[] byteArr = outputStream.toByteArray();
|
||||
dataOutput.close();
|
||||
outputStream.close();
|
||||
return Base64Coder.encodeLines(byteArr);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> itemStackToMap(ItemStack itemStack) {
|
||||
Map<String, Object> map = ItemTagStream.INSTANCE.toMap(itemStack);
|
||||
map.remove("rtagDataVersion");
|
||||
map.remove("count");
|
||||
map.remove("id");
|
||||
map.put("material", itemStack.getType().name().toLowerCase(Locale.ENGLISH));
|
||||
map.put("amount", itemStack.getAmount());
|
||||
Object tag = map.remove("tags");
|
||||
if (tag != null) {
|
||||
map.put("nbt", tag);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void sectionToMap(Section section, Map<String, Object> outPut) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Section inner) {
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
outPut.put(entry.getKey(), map);
|
||||
sectionToMap(inner, map);
|
||||
} else {
|
||||
outPut.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public static void sectionToComponentEditor(Section section, List<ItemEditor> itemEditors) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
String component = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Section inner) {
|
||||
Map<String, Object> innerMap = new HashMap<>();
|
||||
sectionToMap(inner, innerMap);
|
||||
TagMap tagMap = TagMap.of(innerMap);
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, tagMap.apply(context));
|
||||
}));
|
||||
} else if (value instanceof List<?> list) {
|
||||
Object first = list.get(0);
|
||||
if (first instanceof Map<?,?>) {
|
||||
ArrayList<TagMap> output = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
Map<String, Object> innerMap = (Map<String, Object>) o;
|
||||
TagMap tagMap = TagMap.of(innerMap);
|
||||
output.add(tagMap);
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Map<String, Object>> maps = output.stream().map(unparsed -> unparsed.apply(context)).toList();
|
||||
item.setComponent(component, maps);
|
||||
}));
|
||||
} else if (first instanceof String str) {
|
||||
Pair<TagValueType, String> pair = toTypeAndData(str);
|
||||
switch (pair.left()) {
|
||||
case INT -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Integer> integers = values.stream().map(unparsed -> (int) unparsed.evaluate(context)).toList();
|
||||
item.setComponent(component, integers);
|
||||
}));
|
||||
}
|
||||
case BYTE -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Byte> bytes = values.stream().map(unparsed -> (byte) unparsed.evaluate(context)).toList();
|
||||
item.setComponent(component, bytes);
|
||||
}));
|
||||
}
|
||||
case LONG -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Long> longs = values.stream().map(unparsed -> (long) unparsed.evaluate(context)).toList();
|
||||
item.setComponent(component, longs);
|
||||
}));
|
||||
}
|
||||
case FLOAT -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Float> floats = values.stream().map(unparsed -> (float) unparsed.evaluate(context)).toList();
|
||||
item.setComponent(component, floats);
|
||||
}));
|
||||
}
|
||||
case DOUBLE -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Double> doubles = values.stream().map(unparsed -> (double) unparsed.evaluate(context)).toList();
|
||||
item.setComponent(component, doubles);
|
||||
}));
|
||||
}
|
||||
case STRING -> {
|
||||
List<TextValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(TextValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<String> texts = values.stream().map(unparsed -> unparsed.render(context)).toList();
|
||||
item.setComponent(component, texts);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, list);
|
||||
}));
|
||||
}
|
||||
} else if (value instanceof String str) {
|
||||
Pair<TagValueType, String> pair = toTypeAndData(str);
|
||||
switch (pair.left()) {
|
||||
case INT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (int) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case BYTE -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (byte) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case FLOAT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (float) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case LONG -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (long) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case SHORT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (short) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case DOUBLE -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, (double) mathValue.evaluate(context));
|
||||
}));
|
||||
}
|
||||
case STRING -> {
|
||||
TextValue<Player> textValue = TextValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, textValue.render(context));
|
||||
}));
|
||||
}
|
||||
case INTARRAY -> {
|
||||
String[] split = splitValue(str);
|
||||
int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray();
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, array);
|
||||
}));
|
||||
}
|
||||
case BYTEARRAY -> {
|
||||
String[] split = splitValue(str);
|
||||
byte[] bytes = new byte[split.length];
|
||||
for (int i = 0; i < split.length; i++){
|
||||
bytes[i] = Byte.parseByte(split[i]);
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, bytes);
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.setComponent(component, value);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ugly codes, remaining improvements
|
||||
public static void sectionToTagEditor(Section section, List<ItemEditor> itemEditors, String... route) {
|
||||
for (Map.Entry<String, Object> entry : section.getStringRouteMappedValues(false).entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
String key = entry.getKey();
|
||||
String[] currentRoute = ArrayUtils.appendElementToArray(route, key);
|
||||
if (value instanceof Section inner) {
|
||||
sectionToTagEditor(inner, itemEditors, currentRoute);
|
||||
} else if (value instanceof List<?> list) {
|
||||
Object first = list.get(0);
|
||||
if (first instanceof Map<?, ?>) {
|
||||
List<TagMap> maps = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
Map<String, Object> map = (Map<String, Object>) o;
|
||||
maps.add(TagMap.of(map));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Map<String, Object>> parsed = maps.stream().map(render -> render.apply(context)).toList();
|
||||
item.set(parsed, (Object[]) currentRoute);
|
||||
}));
|
||||
} else {
|
||||
if (first instanceof String str) {
|
||||
Pair<TagValueType, String> pair = toTypeAndData(str);
|
||||
switch (pair.left()) {
|
||||
case INT -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Integer> integers = values.stream().map(unparsed -> (int) unparsed.evaluate(context)).toList();
|
||||
item.set(integers, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case BYTE -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Byte> bytes = values.stream().map(unparsed -> (byte) unparsed.evaluate(context)).toList();
|
||||
item.set(bytes, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case LONG -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Long> longs = values.stream().map(unparsed -> (long) unparsed.evaluate(context)).toList();
|
||||
item.set(longs, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case FLOAT -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Float> floats = values.stream().map(unparsed -> (float) unparsed.evaluate(context)).toList();
|
||||
item.set(floats, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case DOUBLE -> {
|
||||
List<MathValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(MathValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<Double> doubles = values.stream().map(unparsed -> (double) unparsed.evaluate(context)).toList();
|
||||
item.set(doubles, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case STRING -> {
|
||||
List<TextValue<Player>> values = new ArrayList<>();
|
||||
for (Object o : list) {
|
||||
values.add(TextValue.auto(toTypeAndData((String) o).right()));
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
List<String> texts = values.stream().map(unparsed -> unparsed.render(context)).toList();
|
||||
item.set(texts, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set(list, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else if (value instanceof String str) {
|
||||
Pair<TagValueType, String> pair = toTypeAndData(str);
|
||||
switch (pair.left()) {
|
||||
case INT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((int) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case BYTE -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((byte) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case LONG -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((long) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case SHORT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((short) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case DOUBLE -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((double) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case FLOAT -> {
|
||||
MathValue<Player> mathValue = MathValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set((float) mathValue.evaluate(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case STRING -> {
|
||||
TextValue<Player> textValue = TextValue.auto(pair.right());
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set(textValue.render(context), (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case INTARRAY -> {
|
||||
String[] split = splitValue(str);
|
||||
int[] array = Arrays.stream(split).mapToInt(Integer::parseInt).toArray();
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set(array, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
case BYTEARRAY -> {
|
||||
String[] split = splitValue(str);
|
||||
byte[] bytes = new byte[split.length];
|
||||
for (int i = 0; i < split.length; i++){
|
||||
bytes[i] = Byte.parseByte(split[i]);
|
||||
}
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set(bytes, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
itemEditors.add(((item, context) -> {
|
||||
item.set(value, (Object[]) currentRoute);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
|
||||
public class LocationUtils {
|
||||
|
||||
private 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) +
|
||||
Math.pow(location2.getZ() - location1.getZ(), 2)
|
||||
);
|
||||
}
|
||||
|
||||
public static Location getAnyLocationInstance() {
|
||||
return new Location(Bukkit.getWorlds().get(0), 0, 64, 0);
|
||||
}
|
||||
|
||||
public static String toChunkPosString(Location location) {
|
||||
return (location.getBlockX() % 16) + "_" + location.getBlockY() + "_" + (location.getBlockZ() % 16);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) <2022> <XiaoMoMi>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.momirealms.customfishing.bukkit.util;
|
||||
|
||||
import net.momirealms.customfishing.common.util.RandomUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class PlayerUtils {
|
||||
|
||||
public static void dropItem(@NotNull Player player, @NotNull ItemStack itemStack, boolean retainOwnership, boolean noPickUpDelay, boolean throwRandomly) {
|
||||
requireNonNull(player, "player");
|
||||
requireNonNull(itemStack, "itemStack");
|
||||
Location location = player.getLocation().clone();
|
||||
Item item = player.getWorld().dropItem(player.getEyeLocation().clone().subtract(new Vector(0,0.3,0)), itemStack);
|
||||
item.setPickupDelay(noPickUpDelay ? 0 : 40);
|
||||
if (retainOwnership) {
|
||||
item.setThrower(player.getUniqueId());
|
||||
}
|
||||
if (throwRandomly) {
|
||||
double d1 = RandomUtils.generateRandomDouble(0,1) * 0.5f;
|
||||
double d2 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2);
|
||||
item.setVelocity(new Vector(-Math.sin(d2) * d1, 0.2f, Math.cos(d2) * d1));
|
||||
} else {
|
||||
double d1 = Math.sin(location.getPitch() * (Math.PI/180));
|
||||
double d2 = RandomUtils.generateRandomDouble(0, 0.02);
|
||||
double d3 = RandomUtils.generateRandomDouble(0,1) * (Math.PI * 2);
|
||||
Vector vector = location.getDirection().multiply(0.3).setY(-d1 * 0.3 + 0.1 + (RandomUtils.generateRandomDouble(0,1) - RandomUtils.generateRandomDouble(0,1)) * 0.1);
|
||||
vector.add(new Vector(Math.cos(d3) * d2, 0, Math.sin(d3) * d2));
|
||||
item.setVelocity(vector);
|
||||
}
|
||||
}
|
||||
|
||||
public static int putItemsToInventory(Inventory inventory, ItemStack itemStack, int amount) {
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
int maxStackSize = itemStack.getMaxStackSize();
|
||||
for (ItemStack other : inventory.getStorageContents()) {
|
||||
if (other != null) {
|
||||
if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) {
|
||||
if (other.getAmount() < maxStackSize) {
|
||||
int delta = maxStackSize - other.getAmount();
|
||||
if (amount > delta) {
|
||||
other.setAmount(maxStackSize);
|
||||
amount -= delta;
|
||||
} else {
|
||||
other.setAmount(amount + other.getAmount());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (amount > 0) {
|
||||
for (ItemStack other : inventory.getStorageContents()) {
|
||||
if (other == null) {
|
||||
if (amount > maxStackSize) {
|
||||
amount -= maxStackSize;
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(maxStackSize);
|
||||
inventory.addItem(cloned);
|
||||
} else {
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(amount);
|
||||
inventory.addItem(cloned);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public static int giveItem(Player player, ItemStack itemStack, int amount) {
|
||||
PlayerInventory inventory = player.getInventory();
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
int maxStackSize = itemStack.getMaxStackSize();
|
||||
if (amount > maxStackSize * 100) {
|
||||
amount = maxStackSize * 100;
|
||||
}
|
||||
int actualAmount = amount;
|
||||
for (ItemStack other : inventory.getStorageContents()) {
|
||||
if (other != null) {
|
||||
if (other.getType() == itemStack.getType() && other.getItemMeta().equals(meta)) {
|
||||
if (other.getAmount() < maxStackSize) {
|
||||
int delta = maxStackSize - other.getAmount();
|
||||
if (amount > delta) {
|
||||
other.setAmount(maxStackSize);
|
||||
amount -= delta;
|
||||
} else {
|
||||
other.setAmount(amount + other.getAmount());
|
||||
return actualAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (amount > 0) {
|
||||
for (ItemStack other : inventory.getStorageContents()) {
|
||||
if (other == null) {
|
||||
if (amount > maxStackSize) {
|
||||
amount -= maxStackSize;
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(maxStackSize);
|
||||
inventory.addItem(cloned);
|
||||
} else {
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(amount);
|
||||
inventory.addItem(cloned);
|
||||
return actualAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (amount > 0) {
|
||||
for (int i = 0; i < amount / maxStackSize; i++) {
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(maxStackSize);
|
||||
player.getWorld().dropItem(player.getLocation(), cloned);
|
||||
}
|
||||
int left = amount % maxStackSize;
|
||||
if (left != 0) {
|
||||
ItemStack cloned = itemStack.clone();
|
||||
cloned.setAmount(left);
|
||||
player.getWorld().dropItem(player.getLocation(), cloned);
|
||||
}
|
||||
}
|
||||
|
||||
return actualAmount;
|
||||
}
|
||||
}
|
||||
186
core/src/main/resources/commands.yml
Normal file
186
core/src/main/resources/commands.yml
Normal file
@@ -0,0 +1,186 @@
|
||||
#
|
||||
# Don't change this
|
||||
#
|
||||
config-version: "${config_version}"
|
||||
|
||||
#
|
||||
# For safety reasons, editing this file requires a restart to apply
|
||||
#
|
||||
|
||||
# A command to reload the plugin
|
||||
# Usage: [COMMAND]
|
||||
reload:
|
||||
enable: true
|
||||
permission: customfishing.command.reload
|
||||
usage:
|
||||
- /customfishing reload
|
||||
- /cfishing reload
|
||||
|
||||
# A command designed for players to sell fish
|
||||
# Usage: [COMMAND]
|
||||
sellfish:
|
||||
enable: true
|
||||
permission: customfishing.sellfish
|
||||
usage:
|
||||
- /sellfish
|
||||
|
||||
# A command designed for players to open the fishing bag
|
||||
# Usage: [COMMAND]
|
||||
fishingbag:
|
||||
enable: true
|
||||
permission: fishingbag.user
|
||||
usage:
|
||||
- /fishingbag
|
||||
|
||||
# A command to get items
|
||||
# Usage: [COMMAND] [id]
|
||||
get_item:
|
||||
enable: true
|
||||
permission: customfishing.command.getitem
|
||||
usage:
|
||||
- /customfishing items get
|
||||
- /cfishing items get
|
||||
|
||||
# A command to give items
|
||||
# Usage: [COMMAND] [id]
|
||||
give_item:
|
||||
enable: true
|
||||
permission: customfishing.command.giveitem
|
||||
usage:
|
||||
- /customfishing items give
|
||||
- /cfishing items give
|
||||
|
||||
# A command to import items
|
||||
# Usage: [COMMAND] [type] [id]
|
||||
import_item:
|
||||
enable: true
|
||||
permission: customfishing.command.importitem
|
||||
usage:
|
||||
- /customfishing items import
|
||||
- /cfishing items import
|
||||
|
||||
# A command to stop the competition
|
||||
# Usage: [COMMAND]
|
||||
stop_competition:
|
||||
enable: true
|
||||
permission: customfishing.command.competition
|
||||
usage:
|
||||
- /customfishing competition stop
|
||||
- /cfishing competition stop
|
||||
|
||||
# A command to end the competition
|
||||
# Usage: [COMMAND] [type] [id]
|
||||
end_competition:
|
||||
enable: true
|
||||
permission: customfishing.command.competition
|
||||
usage:
|
||||
- /customfishing competition end
|
||||
- /cfishing competition end
|
||||
|
||||
# A command to start competitions
|
||||
# Usage: [COMMAND] [id]
|
||||
start_competition:
|
||||
enable: true
|
||||
permission: customfishing.command.competition
|
||||
usage:
|
||||
- /customfishing competition start
|
||||
- /cfishing competition start
|
||||
|
||||
# A command to open market for players
|
||||
# Usage: [COMMAND] [player]
|
||||
open_market:
|
||||
enable: true
|
||||
permission: customfishing.command.open.market
|
||||
usage:
|
||||
- /customfishing open market
|
||||
- /cfishing open market
|
||||
|
||||
# A command to open bag for players
|
||||
# Usage: [COMMAND] [player]
|
||||
open_bag:
|
||||
enable: true
|
||||
permission: customfishing.command.open.bag
|
||||
usage:
|
||||
- /customfishing open bag
|
||||
- /cfishing open bag
|
||||
|
||||
# A command to edit bag contents
|
||||
# Usage: [COMMAND] [player]
|
||||
edit_online_bag:
|
||||
enable: true
|
||||
permission: customfishing.command.edit.bag
|
||||
usage:
|
||||
- /customfishing fishingbag edit-online
|
||||
- /cfishing fishingbag edit-online
|
||||
|
||||
# A command to edit bag contents
|
||||
# Usage: [COMMAND] [uuid]
|
||||
edit_offline_bag:
|
||||
enable: true
|
||||
permission: customfishing.command.edit.bag
|
||||
usage:
|
||||
- /customfishing fishingbag edit-offline
|
||||
- /cfishing fishingbag edit-offline
|
||||
|
||||
# A command to unlock those locked data
|
||||
# Usage: [COMMAND] [uuid]
|
||||
data_unlock:
|
||||
enable: true
|
||||
permission: customfishing.command.data
|
||||
usage:
|
||||
- /customfishing data unlock
|
||||
- /cfishing data unlock
|
||||
|
||||
# A command to export the data
|
||||
# Usage: [COMMAND]
|
||||
data_export:
|
||||
enable: true
|
||||
permission: customfishing.command.data
|
||||
usage:
|
||||
- /customfishing data export
|
||||
- /cfishing data export
|
||||
|
||||
# A command to import the data
|
||||
# Usage: [COMMAND] [file]
|
||||
data_import:
|
||||
enable: true
|
||||
permission: customfishing.command.data
|
||||
usage:
|
||||
- /customfishing data import
|
||||
- /cfishing data import
|
||||
|
||||
# A command to set a player's fishing statistics
|
||||
# Usage: [COMMAND] [player] [id] [type] [value]
|
||||
statistics_set:
|
||||
enable: true
|
||||
permission: customfishing.command.statistics
|
||||
usage:
|
||||
- /customfishing statistics set
|
||||
- /cfishing statistics set
|
||||
|
||||
# A command to reset a player's fishing statistics
|
||||
# Usage: [COMMAND] [player]
|
||||
statistics_reset:
|
||||
enable: true
|
||||
permission: customfishing.command.statistics
|
||||
usage:
|
||||
- /customfishing statistics reset
|
||||
- /cfishing statistics reset
|
||||
|
||||
# A command to query a player's fishing statistics
|
||||
# Usage: [COMMAND] [player] [type]
|
||||
statistics_query:
|
||||
enable: true
|
||||
permission: customfishing.command.statistics
|
||||
usage:
|
||||
- /customfishing statistics query
|
||||
- /cfishing statistics query
|
||||
|
||||
# A command to manually add a player's fishing statistics
|
||||
# Usage: [COMMAND] [player] [id] [type] [value]
|
||||
statistics_add:
|
||||
enable: true
|
||||
permission: customfishing.command.statistics
|
||||
usage:
|
||||
- /customfishing statistics add
|
||||
- /cfishing statistics add
|
||||
451
core/src/main/resources/config.yml
Normal file
451
core/src/main/resources/config.yml
Normal file
@@ -0,0 +1,451 @@
|
||||
# Don"t change this
|
||||
config-version: '${config_version}'
|
||||
|
||||
# Debug
|
||||
debug: false
|
||||
# BStats
|
||||
metrics: true
|
||||
# Check updates
|
||||
update-checker: true
|
||||
|
||||
# Mechanic settings
|
||||
mechanics:
|
||||
# Specifies the conditions required for the plugin mechanics to work.
|
||||
# Here, the type is !world, which implies the plugin won't work in
|
||||
# the world named 'blacklist_world'.
|
||||
mechanic-requirements:
|
||||
world_requirement:
|
||||
type: '!world'
|
||||
value:
|
||||
- blacklist_world
|
||||
# If you want to let some players skip games, you can set requirements that used to skip games
|
||||
# We used `impossible` requirement here, so players should play the game if there exists
|
||||
skip-game-requirements:
|
||||
impossible_requirement:
|
||||
type: 'impossible'
|
||||
# Requirements for enabling auto-fishing
|
||||
auto-fishing-requirements:
|
||||
impossible_requirement:
|
||||
type: 'impossible'
|
||||
|
||||
# Configures global effects. This is useful if you want to give all the players certain effects based on certain conditions
|
||||
global-effects:
|
||||
effect_1:
|
||||
type: conditional
|
||||
conditions:
|
||||
competition:
|
||||
ongoing: true
|
||||
id:
|
||||
- weekend_competition
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time-multiplier
|
||||
value: 0.85
|
||||
|
||||
# Configures global events for hook/bait/rod/loot
|
||||
# which would help you reduce duplicated lines
|
||||
global-events:
|
||||
hook: {}
|
||||
bait: {}
|
||||
loot:
|
||||
new_size_record:
|
||||
conditional_size_record_action:
|
||||
type: conditional
|
||||
value:
|
||||
conditions:
|
||||
has-stats: true
|
||||
actions:
|
||||
actionbar_action:
|
||||
type: actionbar
|
||||
value: '<#FFD700>[New Record]</#FFD700> <#FFFFF0>You caught a(n) {nick} which is <#FFA500>{size_formatted}cm</#FFA500> long!</#FFFFF0>'
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: "minecraft:block.note_block.cow_bell"
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
delayed_sound:
|
||||
type: delay
|
||||
value:
|
||||
delay: 2
|
||||
actions:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: "minecraft:block.note_block.bell"
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
success:
|
||||
conditional_size_info_action:
|
||||
type: conditional
|
||||
value:
|
||||
conditions:
|
||||
has-size: true
|
||||
has-stats: true
|
||||
actions:
|
||||
actionbar_action:
|
||||
type: actionbar
|
||||
value: '<gray>You caught a(n) {nick} which is <#F5F5F5>{size_formatted}cm</#F5F5F5> long!</gray> <#C0C0C0>(Best record: {record_formatted}cm)</#C0C0C0>'
|
||||
title_action:
|
||||
type: random-title
|
||||
value:
|
||||
titles:
|
||||
- '<green>GG!</green>'
|
||||
- '<green>Good Job!</green>'
|
||||
subtitles:
|
||||
- 'You caught a(n) {nick}'
|
||||
- 'Whoa! Nice catch!'
|
||||
- 'Oh {nick} here we go!'
|
||||
- 'Let''s see what it is!'
|
||||
fade-in: 20
|
||||
stay: 30
|
||||
fade-out: 10
|
||||
chance: 1.0
|
||||
failure:
|
||||
title_action:
|
||||
type: random-title
|
||||
value:
|
||||
titles:
|
||||
- '<red>Be concentrated!</red>'
|
||||
- '<red>What a pity!</red>'
|
||||
- '<red>Try next time!</red>'
|
||||
- '<red>Bad luck</red>'
|
||||
subtitles:
|
||||
- 'The fish escaped...'
|
||||
fade-in: 20
|
||||
stay: 30
|
||||
fade-out: 10
|
||||
chance: 1.0
|
||||
rod:
|
||||
land:
|
||||
priority_action:
|
||||
type: priority
|
||||
value:
|
||||
priority_1:
|
||||
conditions:
|
||||
lava-fishing: true
|
||||
actions:
|
||||
fake_item_action:
|
||||
type: fake-item
|
||||
value:
|
||||
duration: 35
|
||||
position: other
|
||||
item: lava_effect
|
||||
priority_2:
|
||||
conditions:
|
||||
lava-fishing: false
|
||||
actions:
|
||||
fake_item_action:
|
||||
type: fake-item
|
||||
value:
|
||||
duration: 35
|
||||
position: other
|
||||
item: water_effect
|
||||
|
||||
# Global properties which would help you reduce duplicated lines
|
||||
global-loot-property:
|
||||
show-in-fishfinder: true
|
||||
disable-stat: false
|
||||
disable-game: false
|
||||
instant-game: false
|
||||
|
||||
# Fishing bag is where players can store their baits, utils, hooks and rods (Loot optional)
|
||||
fishing-bag:
|
||||
enable: true
|
||||
# Fishing bag container title
|
||||
bag-title: '<blue>{player}''s Fishing Bag</blue>'
|
||||
# Other whitelist-items
|
||||
whitelist-items:
|
||||
- fishing_rod
|
||||
# Decide the items that can be stored in bag
|
||||
can-store-loot: false
|
||||
can-store-rod: true
|
||||
can-store-bait: true
|
||||
can-store-hook: true
|
||||
can-store-util: true
|
||||
# Requirements for automatically collecting
|
||||
collect-requirements:
|
||||
permission: fishingbag.collectloot
|
||||
# Actions to do if fishing loots are automatically collected into bag
|
||||
collect-actions:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: "minecraft:item.armor.equip_leather"
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
hologram_action:
|
||||
type: hologram
|
||||
value:
|
||||
duration: 40
|
||||
text: '{nick} <#B0E0E6><b>has been stored into bag</#B0E0E6>'
|
||||
position: other
|
||||
y: 1
|
||||
# Actions to do if the fishing bag is full
|
||||
full-actions:
|
||||
conditional_action:
|
||||
type: conditional
|
||||
value:
|
||||
conditions:
|
||||
condition_1:
|
||||
type: cooldown
|
||||
value:
|
||||
key: fishing_bag_full_notice
|
||||
time: 60000
|
||||
actions:
|
||||
message_action:
|
||||
type: message
|
||||
value: "<#EEE8AA>[Fishing Bag]</#EEE8AA> Your fishing bag has been full."
|
||||
market:
|
||||
# Market GUI title
|
||||
title: '<gradient:#A52A2A:#800000:#A52A2A>Fish Market</gradient>'
|
||||
# Whether to enable limitations
|
||||
limitation:
|
||||
enable: true
|
||||
earnings: '10000' # You can use expressions here
|
||||
# Market menu layout
|
||||
layout:
|
||||
- 'AAAAAAAAA'
|
||||
- 'AIIIIIIIA'
|
||||
- 'AIIIIIIIA'
|
||||
- 'AIIIIIIIA'
|
||||
- 'AAAABAAAA'
|
||||
# Price formula (For CustomFishing loots)
|
||||
price-formula: '{base} + {bonus} * {size}'
|
||||
# Allow player to sell fish in bundles
|
||||
allow-bundle: true
|
||||
# Allow player to sell fish in shulker boxes
|
||||
allow-shulker-box: true
|
||||
# Item price (For vanilla items & other plugin items that have CustomModelData)
|
||||
item-price:
|
||||
# Vanilla Items
|
||||
COD: 10
|
||||
PUFFERFISH: 10
|
||||
SALMON: 10
|
||||
TROPICAL_FISH: 10
|
||||
# PAPER (CustomModelData: 999)
|
||||
PAPER:999: 5
|
||||
# Slots to put items in
|
||||
item-slot:
|
||||
symbol: 'I'
|
||||
allow-items-with-no-price: true
|
||||
# This is an icon that allows players to sell all the fish from their inventory and fishingbag
|
||||
# You can enable it by putting the symbol into layout
|
||||
sell-all-icons:
|
||||
symbol: 'S'
|
||||
# Should the fish in fishing bag be sold
|
||||
fishingbag: true
|
||||
allow-icon:
|
||||
material: IRON_BLOCK
|
||||
display:
|
||||
name: '<#00CED1><b>● <!b>Ship the fish'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:#48D1CC:#E6E6FA>You will get <green>{money_formatted} coins</green> from the fish in inventory and bag</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:block.amethyst_block.place'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
message_action:
|
||||
type: message
|
||||
value: 'You earned {money_formatted} coins from the fish! You can get {rest_formatted} more coins from market today'
|
||||
command_action:
|
||||
type: command
|
||||
value: 'money give {player} {money}'
|
||||
# Requires Vault and any economy plugin
|
||||
# money_action:
|
||||
# type: give-money
|
||||
# value: '{money}'
|
||||
deny-icon:
|
||||
material: REDSTONE_BLOCK
|
||||
display:
|
||||
name: '<red><b>● <!b>Denied trade'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:red:#E6E6FA>Nothing to sell!</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:entity.villager.no'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
limit-icon:
|
||||
material: REDSTONE_BLOCK
|
||||
display:
|
||||
name: '<red><b>● <!b>Denied trade'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:red:#E6E6FA>The worth of items exceeds the money that can be earned for the rest of today!</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:block.anvil.land'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Sell icon
|
||||
sell-icons:
|
||||
symbol: 'B'
|
||||
allow-icon:
|
||||
material: IRON_BLOCK
|
||||
display:
|
||||
name: '<#00CED1><b>● <!b>Ship the fish'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:#48D1CC:#E6E6FA>You will get <green>{money_formatted} coins</green> from the fish</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:block.amethyst_block.place'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
message_action:
|
||||
type: message
|
||||
value: 'You earned {money_formatted} coins from the fish! You can get {rest_formatted} more coins from market today'
|
||||
command_action:
|
||||
type: command
|
||||
value: 'money give {player} {money}'
|
||||
# Requires Vault and any economy plugin
|
||||
# money_action:
|
||||
# type: give-money
|
||||
# value: '{money}'
|
||||
deny-icon:
|
||||
material: REDSTONE_BLOCK
|
||||
display:
|
||||
name: '<red><b>● <!b>Denied trade'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:red:#E6E6FA>Nothing to sell!</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:entity.villager.no'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
limit-icon:
|
||||
material: REDSTONE_BLOCK
|
||||
display:
|
||||
name: '<red><b>● <!b>Denied trade'
|
||||
lore:
|
||||
- '<font:uniform><gradient:#E6E6FA:red:#E6E6FA>The worth of items exceeds the money that can be earned for the rest of today!</gradient></font>'
|
||||
action:
|
||||
sound_action:
|
||||
type: sound
|
||||
value:
|
||||
key: 'minecraft:block.anvil.land'
|
||||
source: 'player'
|
||||
volume: 1
|
||||
pitch: 1
|
||||
# Decorative icons
|
||||
decorative-icons:
|
||||
glass-pane:
|
||||
symbol: 'A'
|
||||
material: BLACK_STAINED_GLASS_PANE
|
||||
display:
|
||||
name: ' '
|
||||
# This section would take effect if you set "override-vanilla" to true
|
||||
# That also means vanilla mechanics for example lure enchantment
|
||||
# would no longer take effect, so you have to configure its effect in CustomFishing
|
||||
fishing-wait-time:
|
||||
# override vanilla mechanic
|
||||
override-vanilla: false
|
||||
# ticks
|
||||
min-wait-time: 100
|
||||
max-wait-time: 600
|
||||
# Lava fishing settings
|
||||
# To modify vanilla fishing time, you should edit paper-world-defaults.yml where there's a section called fishing-time-range
|
||||
lava-fishing:
|
||||
enable: true
|
||||
# ticks
|
||||
min-wait-time: 100
|
||||
max-wait-time: 600
|
||||
void-fishing:
|
||||
enable: true
|
||||
# ticks
|
||||
min-wait-time: 100
|
||||
max-wait-time: 600
|
||||
# Size settings
|
||||
size:
|
||||
# Some effects would increase/decrease size so the option decides whether they could ignore the limit
|
||||
restricted-size-range: true
|
||||
# Competition settings
|
||||
competition:
|
||||
# Use redis for cross server data synchronization
|
||||
redis-ranking: false
|
||||
# Server group
|
||||
server-group: default
|
||||
# Increase this value would allow you to use more placeholders like {4_player} {5_score} in sacrifice of some performance
|
||||
placeholder-limit: 3
|
||||
# If a player could get multiple loots from fishing, should the loots spawn at the same time or have delay for each (measured in ticks)
|
||||
multiple-loot-spawn-delay: 4
|
||||
# Totem settings
|
||||
totem:
|
||||
# Is it allowed for different types of totems to take effect at the same time
|
||||
allow-multiple-type: true
|
||||
# Is it allowed for totems of the same type to take effect cumulatively
|
||||
allow-same-type: false
|
||||
# Enable fake bait casting animation
|
||||
bait-animation: true
|
||||
|
||||
# Other settings
|
||||
other-settings:
|
||||
# It's recommended to use MiniMessage format. If you insist on using legacy color code "&", enable the support below.
|
||||
# Disable this would improve performance
|
||||
legacy-color-code-support: true
|
||||
# Fishing event priority: MONITOR HIGHEST HIGH NORMAL LOW LOWEST
|
||||
event-priority: NORMAL
|
||||
# Save the data from cache to file periodically to minimize the data loss if server crashes
|
||||
# -1 to disable
|
||||
data-saving-interval: 600
|
||||
# Log the consumption of time on data saving
|
||||
log-data-saving: true
|
||||
# Lock player's data if a player is playing on a server that connected to database
|
||||
# If you can ensure low database link latency and fast processing, you can consider disabling this option to improve performance
|
||||
lock-data: true
|
||||
# Requires PlaceholderAPI to work
|
||||
placeholder-register:
|
||||
# Requires server expansion
|
||||
'{date}': '%server_time_yyyy-MM-dd-HH:mm:ss%'
|
||||
# Requires player expansion
|
||||
'{yaw}': '%player_yaw%'
|
||||
# CustomFishing supports using items/blocks from other plugins
|
||||
# If items share the same id, they would inherit the effects
|
||||
# Check the wiki for examples
|
||||
item-detection-order:
|
||||
- CustomFishing
|
||||
- vanilla
|
||||
block-detection-order:
|
||||
- vanilla
|
||||
# Custom durability format
|
||||
custom-durability-format:
|
||||
- ''
|
||||
- '<gray>Durability</gray><white>: {dur} <gray>/</gray> {max}</white>'
|
||||
# Offset characters
|
||||
# Never edit this unless you know what you are doing
|
||||
offset-characters:
|
||||
font: customfishing:offset_chars
|
||||
'1':
|
||||
'2':
|
||||
'4':
|
||||
'8':
|
||||
'16':
|
||||
'32':
|
||||
'64':
|
||||
'128':
|
||||
'-1':
|
||||
'-2':
|
||||
'-4':
|
||||
'-8':
|
||||
'-16':
|
||||
'-32':
|
||||
'-64':
|
||||
'-128':
|
||||
95
core/src/main/resources/contents/bait/default.yml
Normal file
95
core/src/main/resources/contents/bait/default.yml
Normal file
@@ -0,0 +1,95 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
# use Vanilla items as bait
|
||||
BOOK:
|
||||
tag: false
|
||||
material: book
|
||||
requirements:
|
||||
requirement_1:
|
||||
type: rod
|
||||
value:
|
||||
- magical_rod
|
||||
not-met-actions:
|
||||
action_message:
|
||||
type: message
|
||||
value:
|
||||
- 'This bait can only be used on <#7B68EE>Magical Fishing Rod'
|
||||
|
||||
simple_bait:
|
||||
material: paper
|
||||
display:
|
||||
name: '<b><#00BFFF>Simple lures'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray>Made from natural ingredients, it attracts'
|
||||
- '<gray>fish in a calm and steady manner. It''s the'
|
||||
- '<gray>go-to choice for those who prefer a '
|
||||
- '<gray>straightforward and reliable fishing experience.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Reduce fishing difficulty'
|
||||
- ''
|
||||
custom-model-data: 50001
|
||||
effects:
|
||||
effect_1:
|
||||
type: difficulty
|
||||
value: -10
|
||||
|
||||
magnetic_bait:
|
||||
material: paper
|
||||
display:
|
||||
name: '<b><red>Magn<blue>etic <gray>lures'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray>Its radiant shimmer and unique energy pulse'
|
||||
- '<gray>prove irresistible to curious fish, drawing'
|
||||
- '<gray>them in with unprecedented speed. This is '
|
||||
- '<gray>not just a bait, it''s a spectacle that fish'
|
||||
- '<gray>can''t help but investigate.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Reduce wait time'
|
||||
- '<gray> - More time for fishing'
|
||||
- ''
|
||||
custom-model-data: 50002
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time-multiplier
|
||||
value: 0.9
|
||||
effect_2:
|
||||
type: game-time
|
||||
value: 2
|
||||
|
||||
wild_bait:
|
||||
material: paper
|
||||
display:
|
||||
name: '<b><#2E8B57>Wild lures'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray>Crafted for the fearless angler, the Wild '
|
||||
- '<gray>Attraction Bait is an infusion of potent '
|
||||
- '<gray>natural ingredients, exuding an irresistible'
|
||||
- '<gray>aroma for large aquatic beasts.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Increase fishing difficulty'
|
||||
- '<gray> - Increase the size of fish caught'
|
||||
- ''
|
||||
custom-model-data: 50003
|
||||
effects:
|
||||
effect_1:
|
||||
type: difficulty
|
||||
value: +20
|
||||
effect_2:
|
||||
type: size-multiplier
|
||||
value: 1.5
|
||||
effect_3:
|
||||
type: game-time
|
||||
value: -2
|
||||
92
core/src/main/resources/contents/block/default.yml
Normal file
92
core/src/main/resources/contents/block/default.yml
Normal file
@@ -0,0 +1,92 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
apple_crate:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
nick: Apple Crate
|
||||
block: barrel
|
||||
velocity:
|
||||
horizontal: 1.07
|
||||
vertical: 1.5
|
||||
properties:
|
||||
directional: true
|
||||
storage:
|
||||
apple_1:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_2:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_3:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_4:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_5:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_6:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_7:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
apple_8:
|
||||
item: APPLE
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_crate:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
nick: Carrot Crate
|
||||
block: barrel
|
||||
velocity:
|
||||
horizontal: 1.07
|
||||
vertical: 1.5
|
||||
properties:
|
||||
directional: true
|
||||
storage:
|
||||
carrot_1:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_2:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_3:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_4:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_5:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_6:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_7:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
carrot_8:
|
||||
item: Carrot
|
||||
amount: 1~2
|
||||
chance: 0.8
|
||||
106
core/src/main/resources/contents/category/default.yml
Normal file
106
core/src/main/resources/contents/category/default.yml
Normal file
@@ -0,0 +1,106 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
# The concepts of categories and groups are different.
|
||||
# Categories are only used to classify fish based on certain rules,
|
||||
# which only affect the return value of placeholder and do not affect any gameplay
|
||||
|
||||
normal_fish:
|
||||
- tuna_fish
|
||||
- pike_fish
|
||||
- gold_fish
|
||||
- perch_fish
|
||||
- mullet_fish
|
||||
- sardine_fish
|
||||
- carp_fish
|
||||
- cat_fish
|
||||
- octopus
|
||||
- sunfish
|
||||
- red_snapper_fish
|
||||
- salmon_void_fish
|
||||
- woodskip_fish
|
||||
- sturgeon_fish
|
||||
|
||||
sliver_star_fish:
|
||||
- tuna_fish_silver_star
|
||||
- pike_fish_silver_star
|
||||
- gold_fish_silver_star
|
||||
- perch_fish_silver_star
|
||||
- mullet_fish_silver_star
|
||||
- sardine_fish_silver_star
|
||||
- carp_fish_silver_star
|
||||
- cat_fish_silver_star
|
||||
- octopus_silver_star
|
||||
- sunfish_silver_star
|
||||
- red_snapper_fish_silver_star
|
||||
- salmon_void_fish_silver_star
|
||||
- woodskip_fish_silver_star
|
||||
- sturgeon_fish_silver_star
|
||||
|
||||
golden_star_fish:
|
||||
- tuna_fish_golden_star
|
||||
- pike_fish_golden_star
|
||||
- gold_fish_golden_star
|
||||
- perch_fish_golden_star
|
||||
- mullet_fish_golden_star
|
||||
- sardine_fish_golden_star
|
||||
- carp_fish_golden_star
|
||||
- cat_fish_golden_star
|
||||
- octopus_golden_star
|
||||
- sunfish_golden_star
|
||||
- red_snapper_fish_golden_star
|
||||
- salmon_void_fish_golden_star
|
||||
- woodskip_fish_golden_star
|
||||
- sturgeon_fish_golden_star
|
||||
|
||||
river_fish:
|
||||
- gold_fish
|
||||
- gold_fish_silver_star
|
||||
- gold_fish_golden_star
|
||||
- perch_fish
|
||||
- perch_fish_silver_star
|
||||
- perch_fish_golden_star
|
||||
- mullet_fish
|
||||
- mullet_fish_silver_star
|
||||
- mullet_fish_golden_star
|
||||
- carp_fish
|
||||
- carp_fish_silver_star
|
||||
- carp_fish_golden_star
|
||||
- cat_fish
|
||||
- cat_fish_silver_star
|
||||
- cat_fish_silver_star
|
||||
- woodskip_fish
|
||||
- woodskip_fish_silver_star
|
||||
- woodskip_fish_golden_star
|
||||
- sturgeon_fish
|
||||
- sturgeon_fish_silver_star
|
||||
- sturgeon_fish_golden_star
|
||||
|
||||
ocean_fish:
|
||||
- tuna_fish
|
||||
- tuna_fish_silver_star
|
||||
- tuna_fish_golden_star
|
||||
- pike_fish
|
||||
- pike_fish_silver_star
|
||||
- pike_fish_golden_star
|
||||
- sardine_fish
|
||||
- sardine_fish_silver_star
|
||||
- sardine_fish_golden_star
|
||||
- octopus
|
||||
- octopus_silver_star
|
||||
- octopus_golden_star
|
||||
- sunfish
|
||||
- sunfish_silver_star
|
||||
- sunfish_golden_star
|
||||
- red_snapper_fish
|
||||
- red_snapper_fish_silver_star
|
||||
- red_snapper_fish_golden_star
|
||||
- blue_jellyfish
|
||||
- blue_jellyfish_silver_star
|
||||
- blue_jellyfish_golden_star
|
||||
- pink_jellyfish
|
||||
- pink_jellyfish_silver_star
|
||||
- pink_jellyfish_golden_star
|
||||
133
core/src/main/resources/contents/competition/default.yml
Normal file
133
core/src/main/resources/contents/competition/default.yml
Normal file
@@ -0,0 +1,133 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
weekend_competition:
|
||||
# TOTAL_SCORE
|
||||
# CATCH_AMOUNT
|
||||
# MAX_SIZE
|
||||
# TOTAL_SIZE
|
||||
# RANDOM
|
||||
goal: CATCH_AMOUNT
|
||||
|
||||
# Optional
|
||||
# 1-7
|
||||
start-weekday:
|
||||
- 6
|
||||
- 7
|
||||
|
||||
# Optional
|
||||
start-time:
|
||||
- '9:30'
|
||||
- '14:30'
|
||||
- '20:00'
|
||||
|
||||
# Seconds
|
||||
duration: 300
|
||||
|
||||
# Min players to start the competition
|
||||
min-players: 2
|
||||
skip-actions:
|
||||
broadcast:
|
||||
type: broadcast
|
||||
value:
|
||||
- 'The number of players is not enough for the fishing competition to be started as scheduled.'
|
||||
|
||||
bossbar:
|
||||
enable: true
|
||||
color: WHITE
|
||||
overlay: PROGRESS
|
||||
text:
|
||||
- '<gray>[<#87CEFA>🎣<gray>] <gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{seconds}s <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Your Rank: <#E6E6FA>{rank} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>No.1 Player: <#E6E6FA>{1_player}'
|
||||
- '<gray>[<#87CEFA>🎣<gray>] <gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{minute}{second} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Your Score: <#E6E6FA>{score} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>No.1 Score: <#E6E6FA>{1_score}'
|
||||
- '<gray>[<#87CEFA>🎣<gray>] <gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{minute}{second} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Winning condition: <#E6E6FA>{goal}'
|
||||
refresh-rate: 20
|
||||
switch-interval: 200
|
||||
only-show-to-participants: true
|
||||
|
||||
actionbar:
|
||||
enable: false
|
||||
text:
|
||||
- '<gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{seconds}s <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Your Rank: <#E6E6FA>{rank} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>No.1 Player: <#E6E6FA>{1_player}'
|
||||
- '<gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{minute}{second} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Your Score: <#E6E6FA>{score} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>No.1 Score: <#E6E6FA>{1_score}'
|
||||
- '<gradient:#F0F8FF:#87CEFA:#F0F8FF>Time Left: <#E6E6FA>{minute}{second} <gray>| <gradient:#F0F8FF:#87CEFA:#F0F8FF>Winning condition: <#E6E6FA>{goal}'
|
||||
refresh-rate: 5
|
||||
switch-interval: 200
|
||||
only-show-to-participants: true
|
||||
|
||||
start-actions:
|
||||
broadcast:
|
||||
type: broadcast
|
||||
value:
|
||||
- '<#D4F2E7>◤─────────────────────────◥'
|
||||
- ''
|
||||
- ' <gray>[<#87CEFA>🎣<gray>] <gradient:#F0F8FF:#87CEFA:#F0F8FF>Fishing Competition'
|
||||
- ''
|
||||
- ' <#E1FFFF>Objectives:'
|
||||
- ' <#B0C4DE>Catch as many fish as possible'
|
||||
- ' <#B0C4DE>Start fishing to participate!'
|
||||
- ''
|
||||
- '<#D4F2E7>◣─────────────────────────◢'
|
||||
end-actions:
|
||||
broadcast:
|
||||
type: broadcast
|
||||
value:
|
||||
- '<#D4F2E7>◤─────────────────────────◥'
|
||||
- ''
|
||||
- ' <gray>[<#87CEFA>🎣<gray>] <gradient:#F0F8FF:#87CEFA:#F0F8FF>Fishing Competition'
|
||||
- ''
|
||||
- ' <#E1FFFF>Results:'
|
||||
- ' <gradient:#FFF8DC:#FFD700:#FFF8DC>No.①: {1_player} - {1_score}'
|
||||
- ' <gradient:#F5FFFA:#F5F5F5:#F5FFFA>No.②: {2_player} - {2_score}'
|
||||
- ' <gradient:#D2B48C:#CD853F:#D2B48C>No.③: {3_player} - {3_score}'
|
||||
- ''
|
||||
- '<#D4F2E7>◣─────────────────────────◢'
|
||||
participate-actions:
|
||||
message:
|
||||
type: message
|
||||
value:
|
||||
- 'You have joined the competition. Good luck!'
|
||||
|
||||
# Requirements for participating the competition
|
||||
participate-requirements: {}
|
||||
|
||||
# Rewards
|
||||
rewards:
|
||||
1:
|
||||
command_action:
|
||||
type: command
|
||||
value:
|
||||
- 'money give {player} 200'
|
||||
messages_action:
|
||||
type: message
|
||||
value:
|
||||
- '<#FF4500>[1st] Congratulations! You got the first prize!'
|
||||
2:
|
||||
command_action:
|
||||
type: command
|
||||
value:
|
||||
- 'money give {player} 100'
|
||||
messages_action:
|
||||
type: message
|
||||
value:
|
||||
- '<#FF4500>[2nd] Just miss the opportunity, try next time!'
|
||||
3:
|
||||
command_action:
|
||||
type: command
|
||||
value:
|
||||
- 'money give {player} 100'
|
||||
messages_action:
|
||||
type: message
|
||||
value:
|
||||
- '<#FF4500>[3rd] Just miss the opportunity, try next time!'
|
||||
participation:
|
||||
command_action:
|
||||
type: command
|
||||
value:
|
||||
- 'money give {player} 10'
|
||||
messages_action:
|
||||
type: message
|
||||
value:
|
||||
- '<#FF4500>Thanks for participation!'
|
||||
71
core/src/main/resources/contents/enchant/default.yml
Normal file
71
core/src/main/resources/contents/enchant/default.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
# Vanilla/EcoEnchants: minecraft:enchant:level
|
||||
# AdvancedEnchantments: AE:enchant:level
|
||||
minecraft:luck_of_the_sea:1:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+2
|
||||
- golden_star:+1
|
||||
minecraft:luck_of_the_sea:2:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+4
|
||||
- golden_star:+2
|
||||
minecraft:luck_of_the_sea:3:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+6
|
||||
- golden_star:+3
|
||||
minecraft:lure:1:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: conditional
|
||||
conditions:
|
||||
"||":
|
||||
in-lava: true
|
||||
in-void: true
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time
|
||||
value: -80
|
||||
minecraft:lure:2:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: conditional
|
||||
conditions:
|
||||
"||":
|
||||
in-lava: true
|
||||
in-void: true
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time
|
||||
value: -160
|
||||
minecraft:lure:3:
|
||||
requirements: {}
|
||||
effects:
|
||||
effect_1:
|
||||
type: conditional
|
||||
conditions:
|
||||
"||":
|
||||
in-lava: true
|
||||
in-void: true
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time
|
||||
value: -240
|
||||
47
core/src/main/resources/contents/entity/default.yml
Normal file
47
core/src/main/resources/contents/entity/default.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
skeleton:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
disable-game: true
|
||||
entity: skeleton
|
||||
nick: <white>Skeleton</white>
|
||||
velocity:
|
||||
horizontal: 1.1
|
||||
vertical: 1.3
|
||||
|
||||
wither_skeleton:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
disable-game: true
|
||||
entity: wither_skeleton
|
||||
nick: <black>Wither Skeleton</black>
|
||||
velocity:
|
||||
horizontal: 1.1
|
||||
vertical: 1.2
|
||||
|
||||
magma_cube:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
disable-game: true
|
||||
entity: magma_cube
|
||||
nick: <red>Magma Cube</black>
|
||||
velocity:
|
||||
horizontal: 1.1
|
||||
vertical: 1.3
|
||||
|
||||
skeletalknight:
|
||||
show-in-fishfinder: false
|
||||
disable-stat: true
|
||||
disable-game: true
|
||||
entity: MythicMobs:SkeletalKnight
|
||||
nick: Skeletal Knight
|
||||
velocity:
|
||||
horizontal: 1.1
|
||||
vertical: 1.2
|
||||
properties:
|
||||
level: 0
|
||||
33
core/src/main/resources/contents/hook/default.yml
Normal file
33
core/src/main/resources/contents/hook/default.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
delicate_hook:
|
||||
material: SHEARS
|
||||
display:
|
||||
name: '<#1E90FF>Delicate hook'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - An embodiment of craftsmanship and allure, the hook'
|
||||
- '<gray> - is not your average piece of tackle. Polished to'
|
||||
- '<gray> - perfection and intricately designed, it gleams in the'
|
||||
- '<gray> - water, irresistibly drawing high-quality fish closer.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Increase the chance of getting high quality fish'
|
||||
max-durability: 16
|
||||
custom-model-data: 50000
|
||||
# {dur} / {max}
|
||||
lore-on-rod:
|
||||
- ''
|
||||
- '<#7FFFAA>Equipped hook:'
|
||||
- '<gray> - Delicate hook: <white>{dur}</white> times left'
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+1
|
||||
- golden_star:+1
|
||||
1289
core/src/main/resources/contents/item/default.yml
Normal file
1289
core/src/main/resources/contents/item/default.yml
Normal file
File diff suppressed because it is too large
Load Diff
1525
core/src/main/resources/contents/minigame/default.yml
Normal file
1525
core/src/main/resources/contents/minigame/default.yml
Normal file
File diff suppressed because it is too large
Load Diff
212
core/src/main/resources/contents/rod/default.yml
Normal file
212
core/src/main/resources/contents/rod/default.yml
Normal file
@@ -0,0 +1,212 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
# Vanilla fishing rod properties
|
||||
FISHING_ROD:
|
||||
tag: false
|
||||
material: fishing_rod
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time-multiplier
|
||||
value: 1.5
|
||||
|
||||
# Custom rods
|
||||
beginner_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#EEE9E9>Beginner's Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Specially designed for those new to the art '
|
||||
- '<gray> - of fishing, the Beginner''s Fishing Rod is a'
|
||||
- '<gray> - novice angler''s best friend.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Increase the waiting time'
|
||||
- '<gray> - Reduces the challenge of fishing'
|
||||
custom-model-data: 50001
|
||||
max-durability: 64
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time-multiplier
|
||||
value: 1.5
|
||||
effect_2:
|
||||
type: difficulty
|
||||
value: -8
|
||||
|
||||
silver_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#C0C0C0>Silver Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Elegantly crafted with a gleaming finish, the'
|
||||
- '<gray> - Silver Star Fishing Rod is the dream of every'
|
||||
- '<gray> - angler seeking silver-star quality fish.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Increase the chance of getting silver star fish'
|
||||
custom-model-data: 50002
|
||||
max-durability: 96
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+8
|
||||
|
||||
golden_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#FFD700>Golden Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Glistening under the sunlight and exuding an'
|
||||
- '<gray> - aura of luxury, the Golden Fishing Rod is the'
|
||||
- '<gray> - epitome of high-tier angling gear.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Increase the chance of getting golden star fish'
|
||||
custom-model-data: 50003
|
||||
max-durability: 80
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- golden_star:+3
|
||||
|
||||
star_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#FFFF00>Star Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Carved from stardust and bathed in cosmic energy,'
|
||||
- '<gray> - the Star Fishing Rod is a testament to the wonders'
|
||||
- '<gray> - of the universe. Its endless magical reservoir ensures'
|
||||
- '<gray> - that time seems to stand still, granting anglers what'
|
||||
- '<gray> - feels like an eternity to perfect their catch.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - +15s Game Time'
|
||||
custom-model-data: 50004
|
||||
max-durability: 128
|
||||
effects:
|
||||
effect_1:
|
||||
type: game-time
|
||||
value: 15
|
||||
|
||||
bone_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#CD2626>Bone Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Forged from the bones of age-old skeletons and'
|
||||
- '<gray> - imbued with dark enchantments, the Skeleton Fishing'
|
||||
- '<gray> - Rod is not for the faint-hearted. It allows the '
|
||||
- '<gray> - brave or the mad to fish within the scorching '
|
||||
- '<gray> - confines of lava, defying the usual limitations of'
|
||||
- '<gray> - regular rods.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Fish in lava'
|
||||
- '<gray> - Attract skeletons'
|
||||
custom-model-data: 50005
|
||||
max-durability: 32
|
||||
effects:
|
||||
effect_1:
|
||||
type: lava-fishing
|
||||
|
||||
magical_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: '<b><#7B68EE>Magical Fishing Rod'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - A product of arcane innovation, this unique'
|
||||
- '<gray> - fishing rod uses enchanted books as bait.'
|
||||
- '<gray> - Its intricate reel mechanism and magical '
|
||||
- '<gray> - allure demand the exp level of its user,'
|
||||
- '<gray> - consuming experience levels upon each cast.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Get an enchantment book from fishing.'
|
||||
- '<gray> - The waiting time is very long.'
|
||||
- ''
|
||||
- '<#CD5C5C>Requirements:'
|
||||
- '<gray> - 1x book bait'
|
||||
- '<gray> - 10 exp levels'
|
||||
custom-model-data: 50006
|
||||
max-durability: 16
|
||||
requirements:
|
||||
requirement_1:
|
||||
type: level
|
||||
value: 1
|
||||
not-met-actions:
|
||||
action_1:
|
||||
type: message
|
||||
value:
|
||||
- 'You don''t have enough levels to control the rod.'
|
||||
requirement_2:
|
||||
type: bait
|
||||
value:
|
||||
- BOOK
|
||||
not-met-actions:
|
||||
action_message:
|
||||
type: message
|
||||
value:
|
||||
- 'You have to hold a book as bait.'
|
||||
events:
|
||||
cast:
|
||||
action_level:
|
||||
type: level
|
||||
value: -1
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod-ignore-conditions
|
||||
value:
|
||||
- enchantments:+10
|
||||
effect_2:
|
||||
type: wait-time-multiplier
|
||||
value: 3
|
||||
|
||||
master_rod:
|
||||
material: fishing_rod
|
||||
display:
|
||||
name: "<b><#FFFF00>Master's Fishing Rod"
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Wielded only by the most skilled of anglers,'
|
||||
- '<gray> - the Master''s Fishing Rod represents the pinnacle'
|
||||
- '<gray> - of fishing craftsmanship. Meticulously crafted '
|
||||
- '<gray> - for precision, it significantly reduces the '
|
||||
- '<gray> - time it takes for a fish to bite.'
|
||||
- ''
|
||||
- '<#FFD700>Effects:'
|
||||
- '<gray> - Reduce the waiting time'
|
||||
- '<gray> - Increase the challenge of fishing'
|
||||
- '<gray> - Higher chance of getting quality fish'
|
||||
custom-model-data: 50007
|
||||
max-durability: 128
|
||||
effects:
|
||||
effect_1:
|
||||
type: wait-time-multiplier
|
||||
value: 0.95
|
||||
effect_2:
|
||||
type: difficulty
|
||||
value: +10
|
||||
effect_3:
|
||||
type: group-mod
|
||||
value:
|
||||
- silver_star:+5
|
||||
- golden_star:+3
|
||||
299
core/src/main/resources/contents/totem/default.yml
Normal file
299
core/src/main/resources/contents/totem/default.yml
Normal file
@@ -0,0 +1,299 @@
|
||||
double_loot_totem:
|
||||
radius: 8
|
||||
duration: 300
|
||||
pattern:
|
||||
core: 3,1,2 # layer:3 line:1 index:2 -> CRYING_OBSIDIAN
|
||||
layer:
|
||||
4:
|
||||
- '*_STAIRS{face=east;half=top} OBSERVER{face=south} *_STAIRS{face=west;half=top}'
|
||||
3:
|
||||
- 'AIR CRYING_OBSIDIAN AIR'
|
||||
2:
|
||||
- 'AIR *_LOG{axis=y}||*_PILLAR{axis=y} AIR'
|
||||
1:
|
||||
- 'AIR||GRASS||SNOW ANVIL AIR||GRASS||SNOW'
|
||||
effects:
|
||||
effect_1:
|
||||
type: multiple-loot
|
||||
value: 1.0
|
||||
requirements:
|
||||
requirement_1:
|
||||
type: item-in-hand
|
||||
value:
|
||||
hand: main
|
||||
item: NAUTILUS_SHELL
|
||||
amount: 1
|
||||
not-met-actions:
|
||||
action_1:
|
||||
type: message
|
||||
value:
|
||||
- '<#BA55D3>[TotemActivation]</#BA55D3> <#F5FFFA>Hold a nautilus shell in main hand to activate the totem.'
|
||||
events:
|
||||
timer:
|
||||
actionbar_action:
|
||||
type: actionbar-nearby
|
||||
value:
|
||||
actionbar: '<#BA55D3>[Double Loot Totem]</#BA55D3> Time Left: {time_left} seconds'
|
||||
range: 16
|
||||
hologram_action_1:
|
||||
type: hologram
|
||||
value:
|
||||
duration: 20
|
||||
text: '<#BA55D3>[Double Loot Totem]</#BA55D3>'
|
||||
position: other
|
||||
range: 16
|
||||
y: 3.3
|
||||
x: 0
|
||||
z: 0
|
||||
hologram_action_2:
|
||||
type: hologram
|
||||
value:
|
||||
duration: 20
|
||||
text: 'Time Left: {time_left} seconds'
|
||||
position: other
|
||||
range: 16
|
||||
y: 2.8
|
||||
x: 0
|
||||
z: 0
|
||||
activate:
|
||||
remove_item_action:
|
||||
type: item-amount
|
||||
value:
|
||||
hand: main
|
||||
amount: -1
|
||||
broadcast_action:
|
||||
type: message-nearby
|
||||
value:
|
||||
message:
|
||||
- '<#BA55D3>[TotemActivation]</#BA55D3> <#F5FFFA>{player}</#F5FFFA> <#F5F5F5>activated a totem nearby!</#F5F5F5>'
|
||||
range: 32
|
||||
particles:
|
||||
particle_01:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 186,85,211
|
||||
scale: 0.8
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 30
|
||||
range:
|
||||
- 0~359
|
||||
task:
|
||||
period: 5
|
||||
delay: 0
|
||||
particle_02:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 148,0,211
|
||||
scale: 1.1
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 30
|
||||
range:
|
||||
- 6~365
|
||||
task:
|
||||
period: 5
|
||||
delay: 1
|
||||
particle_03:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 147,112,219
|
||||
scale: 1.5
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 30
|
||||
range:
|
||||
- 12~371
|
||||
task:
|
||||
period: 5
|
||||
delay: 2
|
||||
particle_04:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 123,104,238
|
||||
scale: 2
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 30
|
||||
range:
|
||||
- 18~377
|
||||
task:
|
||||
period: 5
|
||||
delay: 3
|
||||
particle_05:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 30,144,255
|
||||
scale: 2.5
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 30
|
||||
range:
|
||||
- 24~383
|
||||
task:
|
||||
period: 5
|
||||
delay: 4
|
||||
particle_06:
|
||||
type: DRIPPING_OBSIDIAN_TEAR
|
||||
options:
|
||||
color: 30,144,255
|
||||
scale: 3
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 90
|
||||
range:
|
||||
- 0~359
|
||||
task:
|
||||
period: 40
|
||||
delay: 0
|
||||
particle_07:
|
||||
type: DRIPPING_OBSIDIAN_TEAR
|
||||
options:
|
||||
color: 30,144,255
|
||||
scale: 3
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 90
|
||||
range:
|
||||
- 22.5~382
|
||||
task:
|
||||
period: 40
|
||||
delay: 10
|
||||
particle_08:
|
||||
type: DRIPPING_OBSIDIAN_TEAR
|
||||
options:
|
||||
color: 30,144,255
|
||||
scale: 3
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 90
|
||||
range:
|
||||
- 45~404
|
||||
task:
|
||||
period: 40
|
||||
delay: 20
|
||||
particle_09:
|
||||
type: DRIPPING_OBSIDIAN_TEAR
|
||||
options:
|
||||
color: 30,144,255
|
||||
scale: 3
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius}'
|
||||
vertical: '3'
|
||||
theta:
|
||||
draw-interval: 90
|
||||
range:
|
||||
- 67.5~427
|
||||
task:
|
||||
period: 40
|
||||
delay: 20
|
||||
golden_star_totem:
|
||||
radius: 10
|
||||
duration: 120
|
||||
pattern:
|
||||
core: 4,2,2 # layer:4 line:2 index:2 -> DAYLIGHT_DETECTOR
|
||||
layer:
|
||||
4:
|
||||
- 'AIR AIR AIR'
|
||||
- 'AIR DAYLIGHT_DETECTOR AIR'
|
||||
- 'AIR AIR AIR'
|
||||
3:
|
||||
- 'AIR AIR AIR'
|
||||
- 'AIR LIGHTNING_ROD AIR'
|
||||
- 'AIR AIR AIR'
|
||||
2:
|
||||
- 'AIR AIR AIR'
|
||||
- 'AIR GOLD_BLOCK AIR'
|
||||
- 'AIR AIR AIR'
|
||||
1:
|
||||
- 'AIR GOLD_BLOCK AIR'
|
||||
- 'GOLD_BLOCK GOLD_BLOCK GOLD_BLOCK'
|
||||
- 'AIR GOLD_BLOCK AIR'
|
||||
particles:
|
||||
particle_01:
|
||||
type: REDSTONE
|
||||
options:
|
||||
color: 255,215,0
|
||||
scale: 3
|
||||
polar-coordinates-formula:
|
||||
horizontal: '{radius} + sin({theta} * 5) * 2.5'
|
||||
vertical: '-2.5'
|
||||
theta:
|
||||
draw-interval: 5
|
||||
range:
|
||||
- 0~360
|
||||
task:
|
||||
period: 5
|
||||
delay: 0
|
||||
effects:
|
||||
effect_1:
|
||||
type: group-mod
|
||||
value:
|
||||
- golden_star:+15
|
||||
requirements:
|
||||
requirement_1:
|
||||
type: item-in-hand
|
||||
value:
|
||||
hand: main
|
||||
item: GOLD_INGOT
|
||||
amount: 1
|
||||
not-met-actions:
|
||||
action_1:
|
||||
type: message
|
||||
value:
|
||||
- '<#BA55D3>[TotemActivation]</#BA55D3> <#F5FFFA>Hold a gold ingot in main hand to activate the totem.'
|
||||
events:
|
||||
timer:
|
||||
actionbar_action:
|
||||
type: actionbar-nearby
|
||||
value:
|
||||
actionbar: '<#FFFF00>[Golden Star Totem]</#FFFF00> Time Left: {time_left} seconds'
|
||||
range: 16
|
||||
hologram_action_1:
|
||||
type: hologram
|
||||
value:
|
||||
duration: 20
|
||||
text: '<#FFFF00>[Golden Star Totem]</#FFFF00>'
|
||||
position: other
|
||||
range: 16
|
||||
y: 1
|
||||
x: 0
|
||||
z: 0
|
||||
hologram_action_2:
|
||||
type: hologram
|
||||
value:
|
||||
duration: 20
|
||||
text: 'Time Left: {time_left} seconds'
|
||||
position: other
|
||||
range: 16
|
||||
y: 0.5
|
||||
x: 0
|
||||
z: 0
|
||||
activate:
|
||||
remove_item_action:
|
||||
type: item-amount
|
||||
value:
|
||||
hand: main
|
||||
amount: -1
|
||||
broadcast_action:
|
||||
type: message-nearby
|
||||
value:
|
||||
message:
|
||||
- '<#FFFF00>[TotemActivation]</#FFFF00> <#F5FFFA>{player}</#F5FFFA> <#F5F5F5>activated a totem nearby!</#F5F5F5>'
|
||||
range: 32
|
||||
47
core/src/main/resources/contents/util/default.yml
Normal file
47
core/src/main/resources/contents/util/default.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
# Note: These are the default configurations of the plugin
|
||||
# and do not necessarily mean that players can have a good
|
||||
# gaming experience. We hope that you will create
|
||||
# customized configurations based on your own ideas,
|
||||
# allowing players to experience the uniqueness of your server.
|
||||
|
||||
fishfinder:
|
||||
material: PAPER
|
||||
display:
|
||||
name: '<#1E90FF>Fish Finder'
|
||||
lore:
|
||||
- ''
|
||||
- '<#7FFFD4>Desciption:'
|
||||
- '<gray> - Compact yet powerful, it scans the waters'
|
||||
- '<gray> - with precision, revealing the variety of'
|
||||
- '<gray> - fish species lurking below the surface.'
|
||||
- '<gray> - Whether you''re in uncharted territories or'
|
||||
- '<gray> - familiar waters, this device ensures you''re'
|
||||
- '<gray> - always informed about your aquatic neighbors.'
|
||||
- ''
|
||||
custom-model-data: 50000
|
||||
events:
|
||||
interact:
|
||||
conditional_action:
|
||||
type: conditional
|
||||
value:
|
||||
conditions:
|
||||
condition_1:
|
||||
type: cooldown
|
||||
value:
|
||||
key: fishfinder
|
||||
time: 3000
|
||||
not-met-actions:
|
||||
action_1:
|
||||
type: message
|
||||
value:
|
||||
- 'Slow down! You are using the fish finder too frequently.'
|
||||
actions:
|
||||
action_1:
|
||||
type: fish-finder
|
||||
value: water
|
||||
water_effect:
|
||||
material: PAPER
|
||||
custom-model-data: 49998
|
||||
lava_effect:
|
||||
material: PAPER
|
||||
custom-model-data: 49999
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user