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:
42
core/.gitignore
vendored
Normal file
42
core/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
101
core/build.gradle.kts
Normal file
101
core/build.gradle.kts
Normal file
@@ -0,0 +1,101 @@
|
||||
val commitID: String by project
|
||||
|
||||
plugins {
|
||||
id("io.github.goooler.shadow") version "8.1.7"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://repo.xenondevs.xyz/releases") // invui
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // papi
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// platform
|
||||
compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT")
|
||||
// subprojects
|
||||
implementation(project(":common"))
|
||||
implementation(project(":api")) {
|
||||
exclude("dev.dejvokep", "boosted-yaml")
|
||||
}
|
||||
implementation(project(":compatibility"))
|
||||
// adventure
|
||||
implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}")
|
||||
implementation("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}")
|
||||
implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}")
|
||||
implementation("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") {
|
||||
exclude("com.google.code.gson", "gson")
|
||||
}
|
||||
// GUI
|
||||
implementation("xyz.xenondevs.invui:invui:${rootProject.properties["invui_version"]}") {
|
||||
exclude("org.jetbrains", "annotations")
|
||||
}
|
||||
// tag & component
|
||||
implementation("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}")
|
||||
implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}")
|
||||
// nms util
|
||||
implementation("com.github.Xiao-MoMi:Sparrow-Heart:${rootProject.properties["sparrow_heart_version"]}")
|
||||
// implementation(files("libs/Sparrow-Heart-${rootProject.properties["sparrow_heart_version"]}.jar"))
|
||||
// bstats
|
||||
compileOnly("org.bstats:bstats-bukkit:${rootProject.properties["bstats_version"]}")
|
||||
// config
|
||||
compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}")
|
||||
// serialization
|
||||
compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}")
|
||||
// database
|
||||
compileOnly("org.xerial:sqlite-jdbc:${rootProject.properties["sqlite_driver_version"]}")
|
||||
compileOnly("com.h2database:h2:${rootProject.properties["h2_driver_version"]}")
|
||||
compileOnly("org.mongodb:mongodb-driver-sync:${rootProject.properties["mongodb_driver_version"]}")
|
||||
compileOnly("com.zaxxer:HikariCP:${rootProject.properties["hikari_version"]}")
|
||||
compileOnly("redis.clients:jedis:${rootProject.properties["jedis_version"]}")
|
||||
// cloud command framework
|
||||
compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}")
|
||||
compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}")
|
||||
compileOnly("org.incendo:cloud-paper:${rootProject.properties["cloud_paper_version"]}")
|
||||
// expression
|
||||
compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}")
|
||||
// placeholder api
|
||||
compileOnly("me.clip:placeholderapi:${rootProject.properties["placeholder_api_version"]}")
|
||||
// lz4
|
||||
compileOnly("org.lz4:lz4-java:${rootProject.properties["lz4_version"]}")
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName = "CustomFishing-${rootProject.properties["project_version"]}-${commitID}.jar"
|
||||
destinationDirectory.set(file("$rootDir/target"))
|
||||
relocate("net.kyori", "net.momirealms.customfishing.libraries")
|
||||
relocate("org.incendo", "net.momirealms.customfishing.libraries")
|
||||
relocate("dev.dejvokep", "net.momirealms.customfishing.libraries")
|
||||
relocate ("org.apache.commons.pool2", "net.momirealms.customfishing.libraries.commonspool2")
|
||||
relocate ("com.mysql", "net.momirealms.customfishing.libraries.mysql")
|
||||
relocate ("org.mariadb", "net.momirealms.customfishing.libraries.mariadb")
|
||||
relocate ("com.zaxxer.hikari", "net.momirealms.customfishing.libraries.hikari")
|
||||
relocate ("com.mongodb", "net.momirealms.customfishing.libraries.mongodb")
|
||||
relocate ("org.bson", "net.momirealms.customfishing.libraries.bson")
|
||||
relocate ("org.bstats", "net.momirealms.customfishing.libraries.bstats")
|
||||
relocate ("com.github.benmanes.caffeine", "net.momirealms.customfishing.libraries.caffeine")
|
||||
relocate ("net.momirealms.sparrow.heart", "net.momirealms.customfishing.bukkit.nms")
|
||||
relocate ("com.saicone.rtag", "net.momirealms.customfishing.libraries.rtag")
|
||||
relocate ("xyz.xenondevs", "net.momirealms.customfishing.libraries")
|
||||
relocate ("net.objecthunter.exp4j", "net.momirealms.customfishing.libraries.exp4j")
|
||||
relocate ("net.jpountz", "net.momirealms.customfishing.libraries.jpountz") //lz4
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives(tasks.shadowJar)
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(17)
|
||||
dependsOn(tasks.clean)
|
||||
}
|
||||
BIN
core/libs/Sparrow-Heart-0.29.jar
Normal file
BIN
core/libs/Sparrow-Heart-0.29.jar
Normal file
Binary file not shown.
@@ -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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user