9
0
mirror of https://github.com/Xiao-MoMi/Custom-Fishing.git synced 2025-12-25 09:59:19 +00:00

2.0-backup-1

This commit is contained in:
XiaoMoMi
2023-09-01 02:23:12 +08:00
parent ce9829aeee
commit d60de693bb
416 changed files with 16225 additions and 18659 deletions

42
plugin/.gitignore vendored Normal file
View 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

85
plugin/build.gradle.kts Normal file
View File

@@ -0,0 +1,85 @@
dependencies {
// server
compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
// command
compileOnly("dev.jorel:commandapi-bukkit-core:9.1.0")
// packet
compileOnly("com.comphenix.protocol:ProtocolLib:5.0.0")
// papi
compileOnly("me.clip:placeholderapi:2.11.3")
compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.1.0")
// config
compileOnly("dev.dejvokep:boosted-yaml:1.3.1")
// mythic
compileOnly("io.lumine:Mythic-Dist:5.3.5")
compileOnly("net.Indyuce:MMOItems-API:6.9.2-SNAPSHOT")
compileOnly("io.lumine:MythicLib-dist:1.6-SNAPSHOT")
compileOnly("net.Indyuce:MMOCore-API:1.12-SNAPSHOT")
// Gson
compileOnly("com.google.code.gson:gson:2.10.1")
// eco
compileOnly("com.willfp:eco:6.65.4")
compileOnly("com.willfp:EcoJobs:3.29.1")
compileOnly("com.willfp:EcoSkills:3.17.1")
compileOnly("com.willfp:libreforge:4.29.1")
// database
compileOnly("org.xerial:sqlite-jdbc:3.42.0.0")
compileOnly("com.h2database:h2:2.2.220")
compileOnly("org.mongodb:mongodb-driver-sync:4.10.2")
compileOnly("com.zaxxer:HikariCP:5.0.1")
compileOnly("redis.clients:jedis:4.4.3")
// others
compileOnly("com.github.LoneDev6:api-itemsadder:3.5.0b")
compileOnly("com.github.oraxen:oraxen:1.159.0")
compileOnly("pers.neige.neigeitems:NeigeItems:1.15.9")
compileOnly("com.github.Zrips:Jobs:4.17.2")
compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21")
// local jars
compileOnly(files("libs/AdvancedEnchantments-api.jar"))
compileOnly(files("libs/RealisticSeasons-api.jar"))
compileOnly(files("libs/CustomCrops-api.jar"))
compileOnly(files("libs/mcMMO-api.jar"))
// api module
implementation(project(":api"))
// adventure
implementation("net.kyori:adventure-api:4.14.0")
implementation("net.kyori:adventure-platform-bukkit:4.3.0")
implementation("net.kyori:adventure-text-minimessage:4.14.0")
implementation("net.kyori:adventure-text-serializer-legacy:4.14.0")
// nbt
implementation("de.tr7zw:item-nbt-api:2.11.3")
// bStats
implementation("org.bstats:bstats-bukkit:3.0.1")
// local lib
implementation(files("libs/BiomeAPI.jar"))
implementation(files("libs/ProtectionLib.jar"))
// anvil
implementation("net.wesjd:anvilgui:1.7.0-SNAPSHOT")
}
tasks {
shadowJar {
relocate ("de.tr7zw", "net.momirealms.customfishing.libraries")
relocate ("net.kyori", "net.momirealms.customfishing.libraries")
relocate ("net.wesjd", "net.momirealms.customfishing.libraries")
relocate ("org.bstats", "net.momirealms.customfishing.libraries.bstats")
relocate ("net.momirealms.biomeapi", "net.momirealms.customfishing.libraries.biomeapi")
relocate ("net.momirealms.protectionlib", "net.momirealms.customfishing.libraries.protectionlib")
}
}

Binary file not shown.

BIN
plugin/libs/BiomeAPI.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
plugin/libs/mcMMO-api.jar Normal file

Binary file not shown.

View File

@@ -0,0 +1,211 @@
package net.momirealms.customfishing;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import de.tr7zw.changeme.nbtapi.utils.MinecraftVersion;
import de.tr7zw.changeme.nbtapi.utils.VersionChecker;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.api.util.ReflectionUtils;
import net.momirealms.customfishing.command.CommandManagerImpl;
import net.momirealms.customfishing.compatibility.IntegrationManagerImpl;
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
import net.momirealms.customfishing.libraries.libraryloader.LibraryLoader;
import net.momirealms.customfishing.mechanic.action.ActionManagerImpl;
import net.momirealms.customfishing.mechanic.bag.BagManagerImpl;
import net.momirealms.customfishing.mechanic.block.BlockManagerImpl;
import net.momirealms.customfishing.mechanic.competition.CompetitionManagerImpl;
import net.momirealms.customfishing.mechanic.effect.EffectManagerImpl;
import net.momirealms.customfishing.mechanic.fishing.FishingManagerImpl;
import net.momirealms.customfishing.mechanic.game.GameManagerImpl;
import net.momirealms.customfishing.mechanic.item.ItemManagerImpl;
import net.momirealms.customfishing.mechanic.loot.LootManagerImpl;
import net.momirealms.customfishing.mechanic.market.MarketManagerImpl;
import net.momirealms.customfishing.mechanic.mob.MobManagerImpl;
import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl;
import net.momirealms.customfishing.scheduler.SchedulerImpl;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.setting.Locale;
import net.momirealms.customfishing.storage.StorageManagerImpl;
import net.momirealms.customfishing.version.VersionManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.lang.reflect.Field;
import java.util.TimeZone;
public class CustomFishingPluginImpl extends CustomFishingPlugin {
private static ProtocolManager protocolManager;
public CustomFishingPluginImpl() {
super();
}
@Override
public void onLoad() {
this.loadDependencies();
}
@Override
public void onEnable() {
protocolManager = ProtocolLibrary.getProtocolManager();
this.versionManager = new VersionManagerImpl(this);
this.disableNBTAPILogs();
ReflectionUtils.load();
this.actionManager = new ActionManagerImpl(this);
this.adventure = new AdventureManagerImpl(this);
this.bagManager = new BagManagerImpl(this);
this.blockManager = new BlockManagerImpl(this);
this.commandManager = new CommandManagerImpl(this);
this.effectManager = new EffectManagerImpl(this);
this.fishingManager = new FishingManagerImpl(this);
this.gameManager = new GameManagerImpl(this);
this.integrationManager = new IntegrationManagerImpl(this);
this.itemManager = new ItemManagerImpl(this);
this.lootManager = new LootManagerImpl(this);
this.marketManager = new MarketManagerImpl(this);
this.mobManager = new MobManagerImpl(this);
this.placeholderManager = new PlaceholderManagerImpl(this);
this.requirementManager = new RequirementManagerImpl(this);
this.scheduler = new SchedulerImpl(this);
this.storageManager = new StorageManagerImpl(this);
this.competitionManager = new CompetitionManagerImpl(this);
this.reload();
if (Config.updateChecker)
this.versionManager.checkUpdate().thenAccept(result -> {
if (!result) this.getAdventure().sendConsoleMessage("[CustomFishing] You are using the latest version.");
else this.getAdventure().sendConsoleMessage("[CustomFishing] Update is available: <u>https://polymart.org/resource/customfishing.2723<!u>");
});
}
@Override
public void onDisable() {
((AdventureManagerImpl) this.adventure).close();
((BagManagerImpl) this.bagManager).disable();
((BlockManagerImpl) this.blockManager).disable();
((EffectManagerImpl) this.effectManager).disable();
((FishingManagerImpl) this.fishingManager).disable();
((GameManagerImpl) this.gameManager).disable();
((ItemManagerImpl) this.itemManager).disable();
((LootManagerImpl) this.lootManager).disable();
((MarketManagerImpl) this.marketManager).disable();
((MobManagerImpl) this.mobManager).disable();
((RequirementManagerImpl) this.requirementManager).disable();
((SchedulerImpl) this.scheduler).shutdown();
((IntegrationManagerImpl) this.integrationManager).disable();
((StorageManagerImpl) this.storageManager).disable();
((CompetitionManagerImpl) this.competitionManager).disable();
((PlaceholderManagerImpl) this.placeholderManager).disable();
}
@Override
public void reload() {
Config.load();
Locale.load();
((SchedulerImpl) this.scheduler).reload();
((RequirementManagerImpl) this.requirementManager).unload();
((RequirementManagerImpl) this.requirementManager).load();
((ItemManagerImpl) this.itemManager).unload();
((ItemManagerImpl) this.itemManager).load();
((LootManagerImpl) this.lootManager).unload();
((LootManagerImpl) this.lootManager).load();
((FishingManagerImpl) this.fishingManager).unload();
((FishingManagerImpl) this.fishingManager).load();
((EffectManagerImpl) this.effectManager).unload();
((EffectManagerImpl) this.effectManager).load();
((MarketManagerImpl) this.marketManager).unload();
((MarketManagerImpl) this.marketManager).load();
((BagManagerImpl) this.bagManager).unload();
((BagManagerImpl) this.bagManager).load();
((BlockManagerImpl) this.blockManager).unload();
((BlockManagerImpl) this.blockManager).load();
((GameManagerImpl) this.gameManager).unload();
((GameManagerImpl) this.gameManager).load();
((MobManagerImpl) this.mobManager).unload();
((MobManagerImpl) this.mobManager).load();
((CompetitionManagerImpl) this.competitionManager).unload();
((CompetitionManagerImpl) this.competitionManager).load();
((StorageManagerImpl) this.storageManager).reload();
this.commandManager.loadCommands();
}
private void loadDependencies() {
String libRepo = TimeZone.getDefault().getID().startsWith("Asia") ?
"https://maven.aliyun.com/repository/public/" : "https://repo.maven.apache.org/maven2/";
LibraryLoader.loadDependencies(
"org.apache.commons:commons-pool2:2.11.1", libRepo,
"redis.clients:jedis:4.4.2", libRepo,
"dev.dejvokep:boosted-yaml:1.3.1", libRepo,
"com.zaxxer:HikariCP:5.0.1", libRepo,
"net.objecthunter:exp4j:0.4.8", libRepo,
"org.mariadb.jdbc:mariadb-java-client:3.1.4", libRepo,
"mysql:mysql-connector-java:8.0.30", libRepo,
"commons-io:commons-io:2.13.0", libRepo,
"com.google.code.gson:gson:2.10.1", libRepo,
"com.h2database:h2:2.2.220", libRepo,
"org.mongodb:mongodb-driver-sync:4.10.2", libRepo,
"org.xerial:sqlite-jdbc:3.42.0.0", libRepo,
"dev.jorel:commandapi-bukkit-shade:9.1.0", "https://repo.maven.apache.org/maven2/"
);
}
private void disableNBTAPILogs() {
MinecraftVersion.disableBStats();
MinecraftVersion.disableUpdateCheck();
VersionChecker.hideOk = true;
try {
Field field = MinecraftVersion.class.getDeclaredField("version");
field.setAccessible(true);
MinecraftVersion minecraftVersion;
try {
minecraftVersion = MinecraftVersion.valueOf(getVersionManager().getServerVersion().replace("v", "MC"));
} catch (IllegalArgumentException ex) {
minecraftVersion = MinecraftVersion.UNKNOWN;
}
field.set(MinecraftVersion.class, minecraftVersion);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
boolean hasGsonSupport;
try {
Class.forName("com.google.gson.Gson");
hasGsonSupport = true;
} catch (Exception ex) {
hasGsonSupport = false;
}
try {
Field field= MinecraftVersion.class.getDeclaredField("hasGsonSupport");
field.setAccessible(true);
field.set(Boolean.class, hasGsonSupport);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public YamlConfiguration getConfig(String file) {
File config = new File(this.getDataFolder(), file);
if (!config.exists()) this.saveResource(file, false);
return YamlConfiguration.loadConfiguration(config);
}
@Override
public boolean isHookedPluginEnabled(String plugin) {
return Bukkit.getPluginManager().isPluginEnabled(plugin);
}
@NotNull
public static ProtocolManager getProtocolManager() {
return protocolManager;
}
public void debug(String message) {
if (!Config.debug) return;
LogUtils.info(message);
}
}

View File

@@ -0,0 +1,204 @@
package net.momirealms.customfishing.adventure;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.title.Title;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.AdventureManager;
import net.momirealms.customfishing.api.util.ReflectionUtils;
import net.momirealms.customfishing.setting.Locale;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
public class AdventureManagerImpl implements AdventureManager {
private final BukkitAudiences adventure;
private static AdventureManager instance;
public AdventureManagerImpl(CustomFishingPlugin plugin) {
this.adventure = BukkitAudiences.create(plugin);
instance = this;
}
public static AdventureManager getInstance() {
return instance;
}
public void close() {
if (adventure != null)
adventure.close();
}
@Override
public Component getComponentFromMiniMessage(String text) {
return MiniMessage.miniMessage().deserialize(text);
}
@Override
public void sendMessage(CommandSender sender, String s) {
if (s == null) return;
if (sender instanceof Player player) sendPlayerMessage(player, s);
else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(s);
}
@Override
public void sendMessageWithPrefix(CommandSender sender, String s) {
if (s == null) return;
if (sender instanceof Player player) sendPlayerMessage(player, Locale.MSG_Prefix + s);
else if (sender instanceof ConsoleCommandSender) sendConsoleMessage(Locale.MSG_Prefix + s);
}
@Override
public void sendConsoleMessage(String s) {
if (s == null) return;
Audience au = adventure.sender(Bukkit.getConsoleSender());
au.sendMessage(getComponentFromMiniMessage(s));
}
@Override
public void sendPlayerMessage(Player player, String s) {
if (s == null) return;
Audience au = adventure.player(player);
au.sendMessage(getComponentFromMiniMessage(s));
}
@Override
public void sendTitle(Player player, String title, String subtitle, int in, int duration, int out) {
Audience au = adventure.player(player);
Title.Times times = Title.Times.times(Duration.ofMillis(in), Duration.ofMillis(duration), Duration.ofMillis(out));
au.showTitle(Title.title(getComponentFromMiniMessage(title), getComponentFromMiniMessage(subtitle), times));
}
@Override
public void sendTitle(Player player, Component title, Component subtitle, int in, int duration, int out) {
Audience au = adventure.player(player);
Title.Times times = Title.Times.times(Duration.ofMillis(in), Duration.ofMillis(duration), Duration.ofMillis(out));
au.showTitle(Title.title(title, subtitle, times));
}
@Override
public void sendActionbar(Player player, String s) {
Audience au = adventure.player(player);
au.sendActionBar(getComponentFromMiniMessage(s));
}
@Override
public void sendSound(Player player, Sound.Source source, Key key, float volume, float pitch) {
Sound sound = Sound.sound(key, source, volume, pitch);
Audience au = adventure.player(player);
au.playSound(sound);
}
@Override
public void sendSound(Player player, Sound sound) {
Audience au = adventure.player(player);
au.playSound(sound);
}
@Override
public String legacyToMiniMessage(String legacy) {
StringBuilder stringBuilder = new StringBuilder();
char[] chars = legacy.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (!isColorCode(chars[i])) {
stringBuilder.append(chars[i]);
continue;
}
if (i + 1 >= chars.length) {
stringBuilder.append(chars[i]);
continue;
}
switch (chars[i+1]) {
case '0' -> stringBuilder.append("<black>");
case '1' -> stringBuilder.append("<dark_blue>");
case '2' -> stringBuilder.append("<dark_green>");
case '3' -> stringBuilder.append("<dark_aqua>");
case '4' -> stringBuilder.append("<dark_red>");
case '5' -> stringBuilder.append("<dark_purple>");
case '6' -> stringBuilder.append("<gold>");
case '7' -> stringBuilder.append("<gray>");
case '8' -> stringBuilder.append("<dark_gray>");
case '9' -> stringBuilder.append("<blue>");
case 'a' -> stringBuilder.append("<green>");
case 'b' -> stringBuilder.append("<aqua>");
case 'c' -> stringBuilder.append("<red>");
case 'd' -> stringBuilder.append("<light_purple>");
case 'e' -> stringBuilder.append("<yellow>");
case 'f' -> stringBuilder.append("<white>");
case 'r' -> stringBuilder.append("<r><!i>");
case 'l' -> stringBuilder.append("<b>");
case 'm' -> stringBuilder.append("<s>");
case 'o' -> stringBuilder.append("<i>");
case 'n' -> stringBuilder.append("<u>");
case 'k' -> stringBuilder.append("<o>");
case 'x' -> {
if (i + 13 >= chars.length
|| !isColorCode(chars[i+2])
|| !isColorCode(chars[i+4])
|| !isColorCode(chars[i+6])
|| !isColorCode(chars[i+8])
|| !isColorCode(chars[i+10])
|| !isColorCode(chars[i+12])) {
stringBuilder.append(chars[i]);
continue;
}
stringBuilder
.append("<#")
.append(chars[i+3])
.append(chars[i+5])
.append(chars[i+7])
.append(chars[i+9])
.append(chars[i+11])
.append(chars[i+13])
.append(">");
i += 13;
}
default -> {
stringBuilder.append(chars[i]);
continue;
}
}
i++;
}
return stringBuilder.toString();
}
@Override
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isColorCode(char c) {
return c == '§' || c == '&';
}
@Override
public String componentToLegacy(Component component) {
return LegacyComponentSerializer.legacySection().serialize(component);
}
@Override
public String componentToJson(Component component) {
return GsonComponentSerializer.gson().serialize(component);
}
@Override
public Object shadedComponentToPaperComponent(Component component) {
Object cp;
try {
cp = ReflectionUtils.gsonDeserializeMethod.invoke(ReflectionUtils.gsonInstance, GsonComponentSerializer.gson().serialize(component));
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
return cp;
}
}

View File

@@ -0,0 +1,51 @@
package net.momirealms.customfishing.command;
import dev.jorel.commandapi.CommandAPI;
import dev.jorel.commandapi.CommandAPIBukkitConfig;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.CommandPermission;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.CommandManager;
import net.momirealms.customfishing.command.sub.CompetitionCommand;
import net.momirealms.customfishing.command.sub.FishingBagCommand;
import net.momirealms.customfishing.command.sub.ItemCommand;
import net.momirealms.customfishing.setting.Locale;
public class CommandManagerImpl implements CommandManager {
private final CustomFishingPlugin plugin;
public CommandManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
CommandAPI.onLoad(new CommandAPIBukkitConfig(plugin).silentLogs(true));
}
@Override
public void loadCommands() {
new CommandAPICommand("customfishing")
.withAliases("cfishing")
.withPermission(CommandPermission.OP)
.withSubcommands(
getReloadCommand(),
CompetitionCommand.INSTANCE.getCompetitionCommand(),
ItemCommand.INSTANCE.getItemCommand()
)
.register();
if (CustomFishingPlugin.get().getBagManager().isBagEnabled()) {
FishingBagCommand.INSTANCE.getBagCommand().register();
}
}
private CommandAPICommand getReloadCommand() {
return new CommandAPICommand("reload")
.withPermission("customfishing.command.reload")
.executes((sender, args) -> {
long time = System.currentTimeMillis();
CustomFishingPlugin.get().reload();
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_Reload.replace("{time}", String.valueOf(System.currentTimeMillis()-time)));
});
}
}

View File

@@ -0,0 +1,90 @@
package net.momirealms.customfishing.command.sub;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
import dev.jorel.commandapi.arguments.StringArgument;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.competition.FishingCompetition;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.setting.Locale;
import net.momirealms.customfishing.storage.method.database.nosql.RedisManager;
import java.util.Set;
public class CompetitionCommand {
public static CompetitionCommand INSTANCE = new CompetitionCommand();
public CommandAPICommand getCompetitionCommand() {
return new CommandAPICommand("competition")
.withPermission("customfishing.command.competition")
.withSubcommands(
getCompetitionStartCommand(),
getCompetitionEndCommand(),
getCompetitionStopCommand()
);
}
private CommandAPICommand getCompetitionStartCommand() {
Set<String> allCompetitions = CustomFishingPlugin.get().getCompetitionManager().getAllCompetitions();
var command = new CommandAPICommand("start")
.withArguments(
new StringArgument("id")
.replaceSuggestions(
ArgumentSuggestions.strings(allCompetitions)
)
);
if (Config.redisRanking) command.withOptionalArguments(new StringArgument("-allservers"));
command.executes((sender, args) -> {
String id = (String) args.get(0);
if (!allCompetitions.contains(id)) {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_Competition_Not_Exist.replace("{id}", id));
return;
}
boolean allServer = args.getOrDefault(1, "").equals("-allservers");
CustomFishingPlugin.get().getCompetitionManager().startCompetition(id, true, allServer);
});
return command;
}
private CommandAPICommand getCompetitionEndCommand() {
var command = new CommandAPICommand("end");
if (Config.redisRanking) command.withOptionalArguments(new StringArgument("-allservers"));
command.executes((sender, args) -> {
boolean allServer = args.getOrDefault(1, "").equals("-allservers");
if (allServer) {
RedisManager.getInstance().sendRedisMessage("cf_competition", "end");
} else {
FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition();
if (competition != null) {
competition.end();
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_End_Competition);
} else {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_No_Competition_Ongoing);
}
}
});
return command;
}
private CommandAPICommand getCompetitionStopCommand() {
var command = new CommandAPICommand("stop");
if (Config.redisRanking) command.withOptionalArguments(new StringArgument("-allservers"));
command.executes((sender, args) -> {
boolean allServer = args.getOrDefault(1, "").equals("-allservers");
if (allServer) {
RedisManager.getInstance().sendRedisMessage("cf_competition", "stop");
} else {
FishingCompetition competition = CustomFishingPlugin.get().getCompetitionManager().getOnGoingCompetition();
if (competition != null) {
competition.stop();
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_Stop_Competition);
} else {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_No_Competition_Ongoing);
}
}
});
return command;
}
}

View File

@@ -0,0 +1,55 @@
package net.momirealms.customfishing.command.sub;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.OfflinePlayerArgument;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.user.OfflineUser;
import net.momirealms.customfishing.setting.Locale;
import net.momirealms.customfishing.storage.user.OfflineUserImpl;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.Inventory;
import java.util.UUID;
public class FishingBagCommand {
public static FishingBagCommand INSTANCE = new FishingBagCommand();
public CommandAPICommand getBagCommand() {
return new CommandAPICommand("fishingbag")
.withPermission("fishingbag.user")
.withSubcommand(getAdminCommand())
.executesPlayer(((player, args) -> {
var inv = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(player.getUniqueId());
if (inv != null) player.openInventory(inv);
}));
}
private CommandAPICommand getAdminCommand() {
return new CommandAPICommand("edit")
.withPermission("fishingbag.admin")
.withArguments(new OfflinePlayerArgument("player"))
.executesPlayer(((player, args) -> {
OfflinePlayer offlinePlayer = (OfflinePlayer) args.get("player");
UUID uuid = offlinePlayer.getUniqueId();
Inventory onlineInv = CustomFishingPlugin.get().getBagManager().getOnlineBagInventory(uuid);
if (onlineInv != null) {
player.openInventory(onlineInv);
} else {
CustomFishingPlugin.get().getStorageManager().getOfflineUser(uuid, false).thenAccept(optional -> {
if (optional.isEmpty()) {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, Locale.MSG_Unsafe_Modification);
} else {
OfflineUser offlineUser = optional.get();
if (offlineUser == OfflineUserImpl.NEVER_PLAYED_USER) {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, Locale.MSG_Never_Played);
} else {
}
}
});
}
}));
}
}

View File

@@ -0,0 +1,150 @@
package net.momirealms.customfishing.command.sub;
import dev.jorel.commandapi.CommandAPICommand;
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
import dev.jorel.commandapi.arguments.EntitySelectorArgument;
import dev.jorel.commandapi.arguments.IntegerArgument;
import dev.jorel.commandapi.arguments.TextArgument;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Key;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.setting.Locale;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import java.util.Collection;
public class ItemCommand {
public static ItemCommand INSTANCE = new ItemCommand();
public CommandAPICommand getItemCommand() {
return new CommandAPICommand("items")
.withPermission("customfishing.command.items")
.withSubcommands(
getSubCommand("loot"),
getSubCommand("util"),
getSubCommand("bait"),
getSubCommand("rod")
);
}
private CommandAPICommand getSubCommand(String namespace) {
Collection<String> items = CustomFishingPlugin.get()
.getItemManager()
.getAllItemsKey()
.stream()
.filter(it -> it.namespace().equals(namespace))
.map(Key::value)
.toList();
return new CommandAPICommand(namespace)
.withSubcommands(
getCommand(namespace, items),
giveCommand(namespace, items)
);
}
private CommandAPICommand getCommand(String namespace, Collection<String> items) {
return new CommandAPICommand("get")
.withArguments(new TextArgument("id").replaceSuggestions(ArgumentSuggestions.strings(items)))
.withOptionalArguments(new IntegerArgument("amount", 1))
.executesPlayer((player, args) -> {
String id = (String) args.get("id");
int amount = (int) args.getOrDefault("amount", 1);
ItemStack item = CustomFishingPlugin.get().getItemManager().build(player, namespace, id);
if (item != null) {
int actual = giveCertainAmountOfItem(player, item, amount);
AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, Locale.MSG_Get_Item.replace("{item}", id).replace("{amount}", String.valueOf(actual)));
} else {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, Locale.MSG_Item_Not_Exists);
}
});
}
private CommandAPICommand giveCommand(String namespace, Collection<String> items) {
return new CommandAPICommand("give")
.withArguments(new EntitySelectorArgument.ManyPlayers("player"))
.withArguments(new TextArgument("id").replaceSuggestions(ArgumentSuggestions.strings(items)))
.withOptionalArguments(new IntegerArgument("amount", 1))
.executes((sender, args) -> {
Collection<Player> players = (Collection<Player>) args.get("player");
String id = (String) args.get("id");
int amount = (int) args.getOrDefault("amount", 1);
ItemStack item = CustomFishingPlugin.get().getItemManager().build(players.stream().findAny().get(), namespace, id);
if (item != null) {
for (Player player : players) {
int actual = giveCertainAmountOfItem(player, item, amount);
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_Give_Item.replace("{item}", id).replace("{amount}", String.valueOf(actual)).replace("{player}", player.getName()));
}
} else {
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, Locale.MSG_Item_Not_Exists);
}
});
}
private int giveCertainAmountOfItem(Player player, ItemStack itemStack, int amount) {
PlayerInventory inventory = player.getInventory();
String metaStr = itemStack.getItemMeta().getAsString();
int maxStackSize = itemStack.getMaxStackSize();
if (amount > maxStackSize * 100) {
LogUtils.warn("Detected too many items spawning. Lowering the amount to " + (maxStackSize * 100));
amount = maxStackSize * 100;
}
int actualAmount = amount;
for (ItemStack other : inventory.getStorageContents()) {
if (other != null) {
if (other.getType() == itemStack.getType() && other.getItemMeta().getAsString().equals(metaStr)) {
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;
}
}

View File

@@ -0,0 +1,160 @@
package net.momirealms.customfishing.compatibility;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.integration.EnchantmentInterface;
import net.momirealms.customfishing.api.integration.LevelInterface;
import net.momirealms.customfishing.api.integration.SeasonInterface;
import net.momirealms.customfishing.api.manager.IntegrationManager;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.enchant.AdvancedEnchantmentsImpl;
import net.momirealms.customfishing.compatibility.enchant.VanillaEnchantmentsImpl;
import net.momirealms.customfishing.compatibility.item.*;
import net.momirealms.customfishing.compatibility.level.*;
import net.momirealms.customfishing.compatibility.season.CustomCropsSeasonImpl;
import net.momirealms.customfishing.compatibility.season.RealisticSeasonsImpl;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class IntegrationManagerImpl implements IntegrationManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, LevelInterface> levelPluginMap;
private final HashMap<String, EnchantmentInterface> enchantments;
private SeasonInterface seasonInterface;
public IntegrationManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.levelPluginMap = new HashMap<>();
this.enchantments = new HashMap<>();
this.init();
}
public void disable() {
this.enchantments.clear();
this.levelPluginMap.clear();
}
public void init() {
if (plugin.isHookedPluginEnabled("ItemsAdder")) {
plugin.getItemManager().registerItemLibrary(new ItemsAdderItemImpl());
hookMessage("ItemsAdder");
}
if (plugin.isHookedPluginEnabled("MMOItems")) {
plugin.getItemManager().registerItemLibrary(new MMOItemsItemImpl());
hookMessage("MMOItems");
}
if (plugin.isHookedPluginEnabled("Oraxen")) {
plugin.getItemManager().registerItemLibrary(new OraxenItemImpl());
hookMessage("Oraxen");
}
if (plugin.isHookedPluginEnabled("NeigeItems")) {
plugin.getItemManager().registerItemLibrary(new NeigeItemsItemImpl());
hookMessage("NeigeItems");
}
if (plugin.isHookedPluginEnabled("MythicMobs")) {
plugin.getItemManager().registerItemLibrary(new MythicMobsItemImpl());
hookMessage("MythicMobs");
}
if (plugin.isHookedPluginEnabled("EcoJobs")) {
registerLevelPlugin("EcoJobs", new EcoJobsImpl());
hookMessage("EcoJobs");
}
if (plugin.isHookedPluginEnabled("EcoSkills")) {
registerLevelPlugin("EcoSkills", new EcoSkillsImpl());
hookMessage("EcoSkills");
}
if (plugin.isHookedPluginEnabled("Jobs")) {
registerLevelPlugin("JobsReborn", new JobsRebornImpl());
hookMessage("Jobs");
}
if (plugin.isHookedPluginEnabled("MMOCore")) {
registerLevelPlugin("MMOCore", new MMOCoreImpl());
hookMessage("MMOCore");
}
if (plugin.isHookedPluginEnabled("mcMMO")) {
try {
plugin.getItemManager().registerCustomItem("loot", "mcmmo", new McMMOBuildableItem());
} catch (ClassNotFoundException | NoSuchMethodException e) {
LogUtils.warn("Failed to initialize mcMMO Treasure");
}
registerLevelPlugin("mcMMO", new McMMOImpl());
hookMessage("mcMMO");
}
if (plugin.isHookedPluginEnabled("AureliumSkills")) {
registerLevelPlugin("AureliumSkills", new AureliumSkillsImpl());
hookMessage("AureliumSkills");
}
if (plugin.isHookedPluginEnabled("EcoEnchants")) {
this.enchantments.put("EcoEnchants", new VanillaEnchantmentsImpl());
hookMessage("EcoEnchants");
} else {
this.enchantments.put("vanilla", new VanillaEnchantmentsImpl());
}
if (plugin.isHookedPluginEnabled("AdvancedEnchantments")) {
this.enchantments.put("AdvancedEnchantments", new AdvancedEnchantmentsImpl());
hookMessage("AdvancedEnchantments");
}
if (plugin.isHookedPluginEnabled("RealisticSeasons")) {
this.seasonInterface = new RealisticSeasonsImpl();
} else if (plugin.isHookedPluginEnabled("CustomCrops")) {
this.seasonInterface = new CustomCropsSeasonImpl();
}
}
@Override
public boolean registerLevelPlugin(String plugin, LevelInterface level) {
if (levelPluginMap.containsKey(plugin)) return false;
levelPluginMap.put(plugin, level);
return true;
}
@Override
public boolean unregisterLevelPlugin(String plugin) {
return levelPluginMap.remove(plugin) != null;
}
@Override
public boolean registerEnchantment(String plugin, EnchantmentInterface enchantment) {
if (enchantments.containsKey(plugin)) return false;
enchantments.put(plugin, enchantment);
return true;
}
@Override
public boolean unregisterEnchantment(String plugin) {
return enchantments.remove(plugin) != null;
}
private void hookMessage(String plugin) {
AdventureManagerImpl.getInstance().sendConsoleMessage("[CustomFishing] <green>" + plugin + "</green> hooked!");
}
@Override
public LevelInterface getLevelHook(String plugin) {
return levelPluginMap.get(plugin);
}
@Override
public List<String> getEnchantments(ItemStack itemStack) {
ArrayList<String> list = new ArrayList<>();
for (EnchantmentInterface enchantmentInterface : enchantments.values()) {
list.addAll(enchantmentInterface.getEnchants(itemStack));
}
return list;
}
@Nullable
public SeasonInterface getSeasonInterface() {
return seasonInterface;
}
@Override
public void setSeasonInterface(SeasonInterface season) {
this.seasonInterface = season;
}
}

View File

@@ -0,0 +1,27 @@
package net.momirealms.customfishing.compatibility.block;
import dev.lone.itemsadder.api.CustomBlock;
import net.momirealms.customfishing.api.mechanic.block.BlockDataModifier;
import net.momirealms.customfishing.api.mechanic.block.BlockLibrary;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.List;
public class ItemsAdderBlockImpl implements BlockLibrary {
@Override
public String identification() {
return "ItemsAdder";
}
@Override
public BlockData getBlockData(Player player, String id, List<BlockDataModifier> modifiers) {
BlockData blockData = CustomBlock.getBaseBlockData(id);
for (BlockDataModifier modifier : modifiers) {
modifier.apply(player, blockData);
}
return blockData;
}
}

View File

@@ -0,0 +1,29 @@
package net.momirealms.customfishing.compatibility.block;
import net.momirealms.customfishing.api.mechanic.block.BlockDataModifier;
import net.momirealms.customfishing.api.mechanic.block.BlockLibrary;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class VanillaBlockImpl implements BlockLibrary {
@Override
public String identification() {
return "vanilla";
}
@Override
public BlockData getBlockData(Player player, String id, List<BlockDataModifier> modifiers) {
BlockData blockData = Material.valueOf(id.toUpperCase(Locale.ENGLISH)).createBlockData();
for (BlockDataModifier modifier : modifiers) {
modifier.apply(player, blockData);
}
return blockData;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.compatibility.enchant;
import net.advancedplugins.ae.api.AEAPI;
import net.momirealms.customfishing.api.integration.EnchantmentInterface;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AdvancedEnchantmentsImpl implements EnchantmentInterface {
@Override
public List<String> getEnchants(ItemStack itemStack) {
List<String> enchants = new ArrayList<>();
for (Map.Entry<String, Integer> entry : AEAPI.getEnchantmentsOnItem(itemStack).entrySet()) {
enchants.add("AE:" + entry.getKey() + ":" + entry.getValue());
}
return enchants;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.compatibility.enchant;
import net.momirealms.customfishing.api.integration.EnchantmentInterface;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class VanillaEnchantmentsImpl implements EnchantmentInterface {
@Override
public List<String> getEnchants(ItemStack itemStack) {
Map<Enchantment, Integer> enchantments = itemStack.getEnchantments();
List<String> enchants = new ArrayList<>(enchantments.size());
for (Map.Entry<Enchantment, Integer> en : enchantments.entrySet()) {
String key = en.getKey().getKey() + ":" + en.getValue();
enchants.add(key);
}
return enchants;
}
}

View File

@@ -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.compatibility.item;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class CustomFishingItemImpl implements ItemLibrary {
@Override
public String identification() {
return "CustomFishing";
}
@Override
public ItemStack buildItem(Player player, String id) {
String[] split = id.split(":", 2);
return CustomFishingPlugin.get().getItemManager().build(player, split[0], split[1]);
}
@Override
public String getItemID(ItemStack itemStack) {
return CustomFishingPlugin.get().getItemManager().getItemID(itemStack);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.compatibility.item;
import dev.lone.itemsadder.api.CustomStack;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class ItemsAdderItemImpl implements ItemLibrary {
@Override
public String identification() {
return "ItemsAdder";
}
@Override
public ItemStack buildItem(Player player, String id) {
return CustomStack.getInstance(id).getItemStack();
}
@Override
public String getItemID(ItemStack itemStack) {
CustomStack customStack = CustomStack.byItemStack(itemStack);
if (customStack == null) return null;
return customStack.getNamespacedID();
}
}

View File

@@ -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.compatibility.item;
import de.tr7zw.changeme.nbtapi.NBTItem;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Locale;
public class MMOItemsItemImpl implements ItemLibrary {
@Override
public String identification() {
return "MMOItems";
}
@Override
public ItemStack buildItem(Player player, String id) {
String[] split = id.split(":");
MMOItem mmoItem = MMOItems.plugin.getMMOItem(Type.get(split[0]), split[1].toUpperCase(Locale.ENGLISH));
return mmoItem == null ? new ItemStack(Material.AIR) : mmoItem.newBuilder().build();
}
@Override
public String getItemID(ItemStack itemStack) {
NBTItem nbtItem = new NBTItem(itemStack);
if (!nbtItem.hasTag("MMOITEMS_ITEM_ID")) return null;
return nbtItem.getString("MMOITEMS_ITEM_ID");
}
}

View File

@@ -0,0 +1,51 @@
package net.momirealms.customfishing.compatibility.item;
import net.momirealms.customfishing.api.mechanic.item.BuildableItem;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
public class McMMOBuildableItem implements BuildableItem {
private final Method getMcMMOPlayerMethod;
private final Method getFishingManagerMethod;
private final Method getFishingTreasureMethod;
private final Method getItemStackMethod;
public McMMOBuildableItem() throws ClassNotFoundException, NoSuchMethodException {
Class<?> userClass = Class.forName("com.gmail.nossr50.util.player.UserManager");
getMcMMOPlayerMethod = userClass.getMethod("getPlayer", Player.class);
Class<?> mcMMOPlayerClass = Class.forName("com.gmail.nossr50.datatypes.player.McMMOPlayer");
getFishingManagerMethod = mcMMOPlayerClass.getMethod("getFishingManager");
Class<?> fishingManagerClass = Class.forName("com.gmail.nossr50.skills.fishing.FishingManager");
getFishingTreasureMethod = fishingManagerClass.getDeclaredMethod("getFishingTreasure");
Class<?> treasureClass = Class.forName("com.gmail.nossr50.datatypes.treasure.Treasure");
getItemStackMethod = treasureClass.getMethod("getDrop");
}
@Override
public ItemStack build(Player player, Map<String, String> placeholders) {
ItemStack itemStack = null;
while (itemStack == null) {
try {
Object mcMMOPlayer = getMcMMOPlayerMethod.invoke(null, player);
Object fishingManager = getFishingManagerMethod.invoke(mcMMOPlayer);
Object treasure = getFishingTreasureMethod.invoke(fishingManager);
if (treasure != null) {
itemStack = (ItemStack) getItemStackMethod.invoke(treasure);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
return itemStack;
}
@Override
public boolean persist() {
return true;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.compatibility.item;
import de.tr7zw.changeme.nbtapi.NBTItem;
import io.lumine.mythic.bukkit.MythicBukkit;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class MythicMobsItemImpl implements ItemLibrary {
private MythicBukkit mythicBukkit;
public MythicMobsItemImpl() {
this.mythicBukkit = MythicBukkit.inst();
}
@Override
public String identification() {
return "MythicMobs";
}
@Override
public ItemStack buildItem(Player player, String id) {
if (mythicBukkit == null || mythicBukkit.isClosed()) {
this.mythicBukkit = MythicBukkit.inst();
}
return mythicBukkit.getItemManager().getItemStack(id);
}
@Override
public String getItemID(ItemStack itemStack) {
NBTItem nbtItem = new NBTItem(itemStack);
return nbtItem.hasTag("MYTHIC_TYPE") ? nbtItem.getString("MYTHIC_TYPE") : null;
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.compatibility.item;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import pers.neige.neigeitems.item.ItemInfo;
import pers.neige.neigeitems.manager.ItemManager;
import pers.neige.neigeitems.utils.ItemUtils;
public class NeigeItemsItemImpl implements ItemLibrary {
@Override
public String identification() {
return "NeigeItems";
}
@Override
public ItemStack buildItem(Player player, String id) {
return ItemManager.INSTANCE.getItemStack(id, player);
}
@Override
public String getItemID(ItemStack itemStack) {
ItemInfo itemInfo = ItemUtils.isNiItem(itemStack);
if (itemInfo != null) {
return itemInfo.getId();
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.compatibility.item;
import io.th0rgal.oraxen.api.OraxenItems;
import io.th0rgal.oraxen.items.ItemBuilder;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class OraxenItemImpl implements ItemLibrary {
@Override
public String identification() {
return "Oraxen";
}
@Override
public ItemStack buildItem(Player player, String id) {
ItemBuilder itemBuilder = OraxenItems.getItemById(id);
return itemBuilder == null ? new ItemStack(Material.AIR) : itemBuilder.build();
}
@Override
public String getItemID(ItemStack itemStack) {
return OraxenItems.getIdByItem(itemStack);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.compatibility.item;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Locale;
public class VanillaItemImpl implements ItemLibrary {
@Override
public String identification() {
return "vanilla";
}
@Override
public ItemStack buildItem(Player player, String id) {
return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH)));
}
@Override
public String getItemID(ItemStack itemStack) {
return itemStack.getType().name();
}
}

View File

@@ -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.compatibility.level;
import com.archyx.aureliumskills.api.AureliumAPI;
import com.archyx.aureliumskills.leveler.Leveler;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
public class AureliumSkillsImpl implements LevelInterface {
private final Leveler leveler;
public AureliumSkillsImpl() {
leveler = AureliumAPI.getPlugin().getLeveler();
}
@Override
public void addXp(Player player, String target, double amount) {
leveler.addXp(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target), amount);
}
@Override
public int getLevel(Player player, String target) {
return AureliumAPI.getSkillLevel(player, AureliumAPI.getPlugin().getSkillRegistry().getSkill(target));
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.compatibility.level;
import com.willfp.ecojobs.api.EcoJobsAPI;
import com.willfp.ecojobs.jobs.Job;
import com.willfp.ecojobs.jobs.Jobs;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
public class EcoJobsImpl implements LevelInterface {
@Override
public void addXp(Player player, String target, double amount) {
for (Job job : EcoJobsAPI.getActiveJobs(player)) {
if (job.getId().equals(target)) {
EcoJobsAPI.giveJobExperience(player, job, amount);
}
}
}
@Override
public int getLevel(Player player, String target) {
Job job = Jobs.getByID(target);
if (job == null) return 0;
return EcoJobsAPI.getJobLevel(player, job);
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.compatibility.level;
import com.willfp.ecoskills.api.EcoSkillsAPI;
import com.willfp.ecoskills.skills.Skills;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
import java.util.Objects;
public class EcoSkillsImpl implements LevelInterface {
@Override
public void addXp(Player player, String target, double amount) {
EcoSkillsAPI.giveSkillXP(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target)), amount);
}
@Override
public int getLevel(Player player, String target) {
return EcoSkillsAPI.getSkillLevel(player, Objects.requireNonNull(Skills.INSTANCE.getByID(target)));
}
}

View File

@@ -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.compatibility.level;
import com.gamingmesh.jobs.Jobs;
import com.gamingmesh.jobs.container.Job;
import com.gamingmesh.jobs.container.JobProgression;
import com.gamingmesh.jobs.container.JobsPlayer;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
import java.util.List;
public class JobsRebornImpl implements LevelInterface {
@Override
public void addXp(Player player, String target, double amount) {
JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player);
if (jobsPlayer != null) {
List<JobProgression> jobs = jobsPlayer.getJobProgression();
Job job = Jobs.getJob(target);
for (JobProgression progression : jobs)
if (progression.getJob().equals(job))
progression.addExperience(amount);
}
}
@Override
public int getLevel(Player player, String target) {
JobsPlayer jobsPlayer = Jobs.getPlayerManager().getJobsPlayer(player);
if (jobsPlayer != null) {
List<JobProgression> jobs = jobsPlayer.getJobProgression();
Job job = Jobs.getJob(target);
for (JobProgression progression : jobs)
if (progression.getJob().equals(job))
return progression.getLevel();
}
return 0;
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.compatibility.level;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.EXPSource;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
public class MMOCoreImpl implements LevelInterface {
@Override
public void addXp(Player player, String target, double amount) {
MMOCore.plugin.professionManager.get(target).giveExperience(PlayerData.get(player), amount, null ,EXPSource.OTHER);
}
@Override
public int getLevel(Player player, String target) {
return PlayerData.get(player).getCollectionSkills().getLevel(MMOCore.plugin.professionManager.get(target));
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.compatibility.level;
import com.gmail.nossr50.api.ExperienceAPI;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import net.momirealms.customfishing.api.integration.LevelInterface;
import org.bukkit.entity.Player;
public class McMMOImpl implements LevelInterface {
@Override
public void addXp(Player player, String target, double amount) {
ExperienceAPI.addRawXP(player, target, (float) amount, "UNKNOWN");
}
@Override
public int getLevel(Player player, String target) {
return ExperienceAPI.getLevel(player, PrimarySkillType.valueOf(target));
}
}

View File

@@ -0,0 +1,43 @@
package net.momirealms.customfishing.compatibility.mob;
import io.lumine.mythic.api.adapters.AbstractLocation;
import io.lumine.mythic.api.mobs.MythicMob;
import io.lumine.mythic.bukkit.MythicBukkit;
import io.lumine.mythic.bukkit.utils.serialize.Position;
import io.lumine.mythic.core.mobs.ActiveMob;
import net.momirealms.customfishing.api.mechanic.mob.MobLibrary;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import java.util.Map;
import java.util.Optional;
public class MythicMobsLibraryImpl implements MobLibrary {
private MythicBukkit mythicBukkit;
public MythicMobsLibraryImpl() {
this.mythicBukkit = MythicBukkit.inst();
}
@Override
public String identification() {
return "MythicMobs";
}
@Override
public Entity spawn(Location location, String id, Map<String, Object> mobPropertyMap) {
if (this.mythicBukkit == null || mythicBukkit.isClosed()) {
this.mythicBukkit = MythicBukkit.inst();
}
Optional<MythicMob> mythicMob = mythicBukkit.getMobManager().getMythicMob(id);
if (mythicMob.isPresent()) {
MythicMob theMob = mythicMob.get();
Position position = Position.of(location);
AbstractLocation abstractLocation = new AbstractLocation(position);
ActiveMob activeMob = theMob.spawn(abstractLocation, (Double) mobPropertyMap.get("{level}"));
return activeMob.getEntity().getBukkitEntity();
}
throw new NullPointerException("MythicMobs: " + id + " doesn't exist.");
}
}

View File

@@ -0,0 +1,22 @@
package net.momirealms.customfishing.compatibility.mob;
import net.momirealms.customfishing.api.mechanic.mob.MobLibrary;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import java.util.Locale;
import java.util.Map;
public class VanillaMobImpl implements MobLibrary {
@Override
public String identification() {
return "vanilla";
}
@Override
public Entity spawn(Location location, String id, Map<String, Object> mobPropertyMap) {
return location.getWorld().spawnEntity(location, EntityType.valueOf(id.toUpperCase(Locale.ENGLISH)));
}
}

View File

@@ -0,0 +1,4 @@
package net.momirealms.customfishing.compatibility.papi;
public class MiniPlaceholdersHook {
}

View File

@@ -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.compatibility.papi;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public class ParseUtils {
public static String setPlaceholders(Player player, String text) {
return PlaceholderAPI.setPlaceholders(player, text);
}
public static String setPlaceholders(OfflinePlayer player, String text) {
return PlaceholderAPI.setPlaceholders(player, text);
}
}

View File

@@ -0,0 +1,34 @@
package net.momirealms.customfishing.compatibility.papi;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PlaceholderAPIHook extends PlaceholderExpansion {
@Override
public @NotNull String getIdentifier() {
return "customfishing";
}
@Override
public @NotNull String getAuthor() {
return "XiaoMoMi";
}
@Override
public @NotNull String getVersion() {
return "1.4";
}
@Override
public boolean persist() {
return true;
}
@Override
public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) {
return super.onRequest(player, params);
}
}

View File

@@ -0,0 +1,94 @@
package net.momirealms.customfishing.compatibility.papi;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.PlaceholderManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class PlaceholderManagerImpl implements PlaceholderManager {
private static PlaceholderManagerImpl instance;
private CustomFishingPlugin plugin;
private final boolean hasPapi;
private final Pattern pattern;
private final HashMap<String, String> customPlaceholderMap;
public PlaceholderManagerImpl(CustomFishingPlugin plugin) {
instance = this;
this.plugin = plugin;
this.hasPapi = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
this.pattern = Pattern.compile("\\{[^{}]+}");
this.customPlaceholderMap = new HashMap<>();
}
public void disable() {
this.customPlaceholderMap.clear();
}
@Override
public String setPlaceholders(Player player, String text) {
return hasPapi ? ParseUtils.setPlaceholders(player, text) : text;
}
@Override
public String setPlaceholders(OfflinePlayer player, String text) {
return hasPapi ? ParseUtils.setPlaceholders(player, text) : text;
}
@Override
public List<String> detectPlaceholders(String text) {
List<String> placeholders = new ArrayList<>();
Matcher matcher = pattern.matcher(text);
while (matcher.find()) placeholders.add(matcher.group());
return placeholders;
}
@Override
public String getSingleValue(@Nullable Player player, String placeholder, Map<String, String> placeholders) {
String result;
result = placeholders.get(placeholder);
if (result != null) return result;
String custom = customPlaceholderMap.get(placeholder);
if (custom == null) return placeholder;
return setPlaceholders(player, custom);
}
@Override
public String parse(@Nullable OfflinePlayer player, String text, Map<String, String> placeholders) {
var list = detectPlaceholders(text);
for (String papi : list) {
String replacer = placeholders.get(papi);
if (replacer == null) {
String custom = customPlaceholderMap.get(papi);
if (custom != null) {
replacer = setPlaceholders(player, custom);
}
}
if (replacer != null) {
text = text.replace(papi, replacer);
}
}
return text;
}
@Override
public List<String> parse(@Nullable OfflinePlayer player, List<String> list, Map<String, String> replacements) {
return list.stream()
.map(s -> parse(player, s, replacements))
.collect(Collectors.toList());
}
public static PlaceholderManagerImpl getInstance() {
return instance;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.compatibility.season;
import net.momirealms.customcrops.api.CustomCropsAPI;
import net.momirealms.customfishing.api.integration.SeasonInterface;
import org.bukkit.World;
import java.util.Objects;
public class CustomCropsSeasonImpl implements SeasonInterface {
private final CustomCropsAPI customCropsAPI;
public CustomCropsSeasonImpl() {
customCropsAPI = CustomCropsAPI.getInstance();
}
@Override
public String getSeason(World world) {
return Objects.requireNonNull(customCropsAPI.getSeason(world.getName())).getSeason();
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.compatibility.season;
import me.casperge.realisticseasons.api.SeasonsAPI;
import net.momirealms.customfishing.api.integration.SeasonInterface;
import org.bukkit.World;
public class RealisticSeasonsImpl implements SeasonInterface {
@Override
public String getSeason(World world) {
return switch (SeasonsAPI.getInstance().getSeason(world)) {
case WINTER -> "winter";
case SPRING -> "spring";
case SUMMER -> "summer";
case FALL -> "autumn";
case DISABLED -> "disabled";
case RESTORE -> "restore";
};
}
}

View File

@@ -0,0 +1,189 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Represents an element in a gui that will query all it's data when drawn.
*/
public class DynamicGuiElement extends GuiElement {
private Function<HumanEntity, GuiElement> query;
private Map<UUID, CacheEntry> cachedElements = new ConcurrentHashMap<>();
/**
* Represents an element in a gui that will query all it's data when drawn.
* @param slotChar The character to replace in the gui setup string
* @param query Query the element data, this should return an element with the information
*/
public DynamicGuiElement(char slotChar, Supplier<GuiElement> query) {
this(slotChar, (h) -> query.get());
}
/**
* Represents an element in a gui that will query all it's data when drawn.
* @param slotChar The character to replace in the gui setup string
* @param query Query the element data, this should return an element with the information and handle null players properly
*/
public DynamicGuiElement(char slotChar, Function<HumanEntity, GuiElement> query) {
super(slotChar);
this.query = query;
}
/**
* Query this element's state for every player who had it cached
*/
public void update() {
for (UUID playerId : new ArrayList<>(cachedElements.keySet())) {
Player p = gui.getPlugin().getServer().getPlayer(playerId);
if (p != null && p.isOnline()) {
update(p);
} else {
cachedElements.remove(playerId);
}
}
}
/**
* Query this element's state for a certain player
* @param player The player for whom to update the element
*/
public CacheEntry update(HumanEntity player) {
CacheEntry cacheEntry = new CacheEntry(queryElement(player));
if (cacheEntry.element instanceof DynamicGuiElement) {
((DynamicGuiElement) cacheEntry.element).update(player);
} else if (cacheEntry.element instanceof GuiElementGroup) {
InventoryGui.updateElements(player, ((GuiElementGroup) cacheEntry.element).getElements());
}
cachedElements.put(player.getUniqueId(), cacheEntry);
return cacheEntry;
}
@Override
public void setGui(InventoryGui gui) {
super.setGui(gui);
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
GuiElement element = getCachedElement(who);
return element != null ? element.getItem(who, slot) : null;
}
@Override
public Action getAction(HumanEntity who) {
GuiElement element = getCachedElement(who);
return element != null ? element.getAction(who) : null;
}
/**
* Get the supplier for this element's content
* @return The supplier query
*/
public Function<HumanEntity, GuiElement> getQuery() {
return query;
}
/**
* Set the supplier for this element's content
* @param query The supplier query to set
*/
public void setQuery(Function<HumanEntity, GuiElement> query) {
this.query = query;
}
/**
* Query the element for a player
* @param who The player
* @return The GuiElement or null
*/
public GuiElement queryElement(HumanEntity who) {
GuiElement element = getQuery().apply(who);
if (element != null) {
element.setGui(gui);
element.setSlots(slots);
}
return element;
}
/**
* Get the cached element, creates a new one if there is none for that player.
* Use {@link #getLastCached(HumanEntity)} to check if a player has something cached.
* @param who The player to get the element for
* @return The element that is currently cached
*/
public GuiElement getCachedElement(HumanEntity who) {
CacheEntry cached = cachedElements.get(who.getUniqueId());
if (cached == null) {
cached = update(who);
}
return cached.getElement();
}
/**
* Remove the cached element if the player has one.
* @param who The player to remove the cached element for
* @return The element that was cached or null if none was cached
*/
public GuiElement removeCachedElement(HumanEntity who) {
CacheEntry cached = cachedElements.remove(who.getUniqueId());
return cached != null ? cached.getElement() : null;
}
/**
* Get the time at which this element was last cached for a certain player
* @param who The player to get the last cache time for
* @return The timestamp from when it was last cached or -1 if it wasn't cached
*/
public long getLastCached(HumanEntity who) {
CacheEntry cached = cachedElements.get(who.getUniqueId());
return cached != null ? cached.getCreated() : -1;
}
public class CacheEntry {
private final GuiElement element;
private final long created = System.currentTimeMillis();
CacheEntry(GuiElement element) {
this.element = element;
}
public GuiElement getElement() {
return element;
}
public long getCreated() {
return created;
}
}
}

View File

@@ -0,0 +1,77 @@
package net.momirealms.customfishing.libraries.inventorygui;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
/**
* An element that will not appear if there is no previous history,
* but will go back one step if there is
*/
public class GuiBackElement extends StaticGuiElement {
private boolean close;
/**
* An element used to go back in history of the gui if there is something to go back to.
* Will not display when there is nothing to go back to.
*
* @param slotChar The character to replace in the gui setup string
* @param item The {@link ItemStack} representing this element
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public GuiBackElement(char slotChar, ItemStack item, String... text) {
this(slotChar, item, false, text);
}
/**
* An element used to go back in history of the gui
*
* @param slotChar The character to replace in the gui setup string
* @param item The {@link ItemStack} representing this element
* @param close Whether to close the GUI if there is nothing to go back to.
* Will not display item if set to false and nothing to go back to.
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public GuiBackElement(char slotChar, ItemStack item, boolean close, String... text) {
super(slotChar, item, text);
this.close = close;
setAction(click -> {
if (canGoBack(click.getWhoClicked())) {
InventoryGui.goBack(click.getWhoClicked());
} else if (close) {
click.getGui().close();
}
return true;
});
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
if (!canGoBack(who) && !close) {
return gui.getFiller() != null ? gui.getFiller().getItem(who, slot) : null;
}
return super.getItem(who, slot);
}
/**
* Whether this element can close the GUI when nothing to go back to
* @return Close the GUI when nothing to go back
*/
public boolean canClose() {
return close;
}
private boolean canGoBack(HumanEntity who) {
return InventoryGui.getHistory(who).size() > 1 || (InventoryGui.getHistory(who).size() == 1 && InventoryGui.getHistory(who).peekLast() != gui);
}
}

View File

@@ -0,0 +1,235 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryInteractEvent;
import org.bukkit.inventory.ItemStack;
/**
* Represents an element in a gui
*/
public abstract class GuiElement {
private final char slotChar;
private Action action;
protected int[] slots = new int[0];
protected InventoryGui gui;
/**
* Represents an element in a gui
* @param slotChar The character to replace in the gui setup string
* @param action The action to run when the player clicks on this element
*/
public GuiElement(char slotChar, Action action) {
this.slotChar = slotChar;
setAction(action);
}
/**
* Represents an element in a gui that doesn't have any action when clicked
* @param slotChar The character to replace in the gui setup string
*/
public GuiElement(char slotChar) {
this(slotChar, null);
}
/**
* Get the character in the gui setup that corresponds with this element
* @return The character
*/
public char getSlotChar() {
return slotChar;
}
/**
* Get the item that is displayed by this element on a certain page
* @param who The player who views the page
* @param slot The slot to get the item for
* @return The ItemStack that is displayed as this element
*/
public abstract ItemStack getItem(HumanEntity who, int slot);
/**
* Get the action that is executed when clicking on this element
* @param who The player who views the page
* @return The action to run
*/
public Action getAction(HumanEntity who) {
return action;
}
/**
* Set the action that is executed when clicking on this element
* @param action The action to run. The {@link Action#onClick} method should
* return whether or not the click event should be cancelled
*/
public void setAction(Action action) {
this.action = action;
}
/**
* Get the indexes of the lots that this element is displayed in
* @return An array of the lost indexes
*/
public int[] getSlots() {
return slots;
}
/**
* Set the ids of the slots where this element is assigned to
* @param slots An array of the slot ids where this element is displayed
*/
public void setSlots(int[] slots) {
this.slots = slots;
}
/**
* Get the index that this slot has in the list of slots that this element is displayed in
* @param slot The id of the slot
* @return The index in the list of slots that this id has or <code>-1</code> if it isn't in that list
*/
public int getSlotIndex(int slot) {
return getSlotIndex(slot, 0);
}
/**
* Get the index that this slot has in the list of slots that this element is displayed in
* @param slot The id of the slot
* @param pageNumber The number of the page that the gui is on
* @return The index in the list of slots that this id has or <code>-1</code> if it isn't in that list
*/
public int getSlotIndex(int slot, int pageNumber) {
for (int i = 0; i < slots.length; i++) {
if (slots[i] == slot) {
return i + slots.length * pageNumber;
}
}
return -1;
}
/**
* Set the gui this element belongs to
* @param gui The GUI that this element is in
*/
public void setGui(InventoryGui gui) {
this.gui = gui;
}
/**
* Get the gui this element belongs to
* @return The GUI that this element is in
*/
public InventoryGui getGui() {
return gui;
}
public static interface Action {
/**
* Executed when a player clicks on an element
* @param click The Click class containing information about the click
* @return Whether or not the click event should be cancelled
*/
boolean onClick(Click click);
}
public static class Click {
private final InventoryGui gui;
private final int slot;
private final ClickType clickType;
private ItemStack cursor;
private final GuiElement element;
private final InventoryInteractEvent event;
public Click(InventoryGui gui, int slot, ClickType clickType, ItemStack cursor, GuiElement element, InventoryInteractEvent event) {
this.gui = gui;
this.slot = slot;
this.clickType = clickType;
this.cursor = cursor;
this.element = element;
this.event = event;
}
/**
* Get the slot of the GUI that was clicked
* @return The clicked slot
*/
public int getSlot() {
return slot;
}
/**
* Get the element that was clicked
* @return The clicked GuiElement
*/
public GuiElement getElement() {
return element;
}
/**
* Get the type of the click
* @return The type of the click
*/
public ClickType getType() {
return clickType;
}
/**
* Get the item on the cursor
* @return The item on the cursor when this click occurred
*/
public ItemStack getCursor() {
return cursor;
}
/**
* Set the item on the cursor after the click
* @param cursor The new item on the cursor
*/
public void setCursor(ItemStack cursor) {
this.cursor = cursor;
}
/**
* Get who clicked the element
* @return The player that clicked
*/
public HumanEntity getWhoClicked() {
return event.getWhoClicked();
}
/**
* Get the event of the inventory interaction
* @return The InventoryInteractEvent associated with this Click
*/
public InventoryInteractEvent getRawEvent() {
return event;
}
public InventoryGui getGui() {
return gui;
}
}
}

View File

@@ -0,0 +1,276 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Represents a group of multiple elements. Will be left-aligned by default.
*/
public class GuiElementGroup extends GuiElement {
private List<GuiElement> elements = new ArrayList<>();
private GuiElement filler = null;
private Alignment alignment = Alignment.LEFT;
/**
* A group of elements
* @param slotChar The character to replace in the gui setup string
* @param elements The elements in this group
*/
public GuiElementGroup(char slotChar, GuiElement... elements) {
super(slotChar, null);
setAction(click -> {
GuiElement element = getElement(click.getSlot(), click.getGui().getPageNumber(click.getWhoClicked()));
if (element != null && element.getAction(click.getRawEvent().getWhoClicked()) != null) {
return element.getAction(click.getWhoClicked()).onClick(click);
}
return true;
});
Collections.addAll(this.elements, elements);
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
GuiElement element = getElement(slot, gui.getPageNumber(who));
if (element != null) {
return element.getItem(who, slot);
}
return null;
}
@Override
public void setGui(InventoryGui gui) {
super.setGui(gui);
for (GuiElement element : elements) {
if (element != null) {
element.setGui(gui);
}
}
if (filler != null) {
filler.setGui(gui);
}
}
@Override
public void setSlots(int[] slots) {
super.setSlots(slots);
for (GuiElement element : elements) {
if (element != null) {
element.setSlots(slots);
}
}
}
/**
* Add an element to this group
* @param element The element to add
*/
public void addElement(GuiElement element){
elements.add(element);
if (element != null) {
element.setGui(gui);
element.setSlots(slots);
}
}
/**
* Add elements to this group
* @param elements The elements to add
*/
public void addElements(GuiElement... elements){
for (GuiElement element : elements) {
addElement(element);
}
}
/**
* Add elements to this group
* @param elements The elements to add
*/
public void addElements(Collection<GuiElement> elements){
for (GuiElement element : elements) {
addElement(element);
}
}
/**
* Get the element in a certain slot
* @param slot The slot to get the element for
* @return The GuiElement in that slot or <code>null</code>
*/
public GuiElement getElement(int slot) {
return getElement(slot, 0);
}
/**
* Get the element in a certain slot on a certain page
* @param slot The slot to get the element for
* @param pageNumber The number of the page that the gui is on
* @return The GuiElement in that slot or <code>null</code>
*/
public GuiElement getElement(int slot, int pageNumber) {
if (elements.isEmpty()) {
return null;
}
int index = getSlotIndex(slot, slots.length < elements.size() ? pageNumber : 0);
if (index > -1) {
if (alignment == Alignment.LEFT) {
if (index < elements.size()) {
return elements.get(index);
}
} else {
int lineWidth = getLineWidth(slot);
int linePosition = getLinePosition(slot);
if (elements.size() - index > lineWidth - linePosition) {
return elements.get(index);
}
int rest = elements.size() - (index - linePosition);
int blankBefore = alignment == Alignment.CENTER ? (lineWidth - rest) / 2 : lineWidth - rest;
if (linePosition < blankBefore || index - blankBefore >= elements.size()) {
return filler;
}
return elements.get(index - blankBefore);
}
}
return filler;
}
/**
* Get the width of the line the slot is in
* @param slot The slot
* @return The width of the line in the GUI setup of this group
*/
private int getLineWidth(int slot) {
int width = gui.getWidth();
int row = slot / width;
int amount = 0;
for (int s : slots) {
if (s >= row * width && s < (row + 1) * width) {
amount++;
}
}
return amount;
}
/**
* Get the position of the slot in its line
* @param slot The slot ID
* @return The line position or -1 if not in its line. wat
*/
private int getLinePosition(int slot) {
int width = gui.getWidth();
int row = slot / width;
int position = -1;
for (int s : slots) {
if (s >= row * width && s < (row + 1) * width) {
position++;
if (s == slot) {
return position;
}
}
}
return position;
}
/**
* Get all elements of this group. This list is immutable, use {@link #addElement(GuiElement)}
* and {@link #clearElements()} to modify the elements in this group.
* @return An immutable list of all elements in this group
*/
public List<GuiElement> getElements() {
return Collections.unmodifiableList(elements);
}
/**
* Removes all elements in the group
*/
public void clearElements() {
elements.clear();
}
/**
* Set the filler element for empty slots
* @param item The item for the filler element
*/
public void setFiller(ItemStack item) {
filler = new StaticGuiElement(' ', item, " ");
filler.setGui(gui);
}
/**
* Set the filler element for empty slots
* @param filler The item for the filler element
*/
public void setFiller(GuiElement filler) {
this.filler = filler;
if (filler != null) {
filler.setGui(gui);
}
}
/**
* Get the filler element
* @return The filler element
*/
public GuiElement getFiller() {
return filler;
}
/**
* Get the size of this group
* @return The amount of elements that this group has
*/
public int size() {
return elements.size();
}
/**
* Set the alignment of the elements in this group
* @param alignment The alignment
*/
public void setAlignment(Alignment alignment) {
this.alignment = alignment;
}
/**
* Get the alignment of the elements in this group
* @return The alignment
*/
public Alignment getAlignment() {
return alignment;
}
public enum Alignment {
LEFT,
CENTER,
RIGHT;
}
}

View File

@@ -0,0 +1,124 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
/**
* This is an element that allows for controlling the pagination of the gui.
* <b>Untested und potentially unfinished.</b>
*/
public class GuiPageElement extends StaticGuiElement {
private PageAction pageAction;
private boolean silent = false;
/**
* An element that allows for controlling the pagination of the gui.
* @param slotChar The character to replace in the gui setup string
* @param item The {@link ItemStack} representing this element
* @param pageAction What kind of page action you want to happen when interacting with the element.
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public GuiPageElement(char slotChar, ItemStack item, PageAction pageAction, String... text) {
super(slotChar, item, text);
setAction(click -> {
switch (pageAction) {
case NEXT:
if (click.getGui().getPageNumber(click.getWhoClicked()) + 1 < click.getGui().getPageAmount(click.getWhoClicked())) {
if (!isSilent()) {
click.getGui().playClickSound();
}
click.getGui().setPageNumber(click.getWhoClicked(), click.getGui().getPageNumber(click.getWhoClicked()) + 1);
}
break;
case PREVIOUS:
if (click.getGui().getPageNumber(click.getWhoClicked()) > 0) {
if (!isSilent()) {
click.getGui().playClickSound();
}
click.getGui().setPageNumber(click.getWhoClicked(), click.getGui().getPageNumber(click.getWhoClicked()) - 1);
}
break;
case FIRST:
if (!isSilent()) {
click.getGui().playClickSound();
}
click.getGui().setPageNumber(click.getWhoClicked(), 0);
break;
case LAST:
if (!isSilent()) {
click.getGui().playClickSound();
}
click.getGui().setPageNumber(click.getWhoClicked(), click.getGui().getPageAmount(click.getWhoClicked()) - 1);
break;
}
return true;
});
this.pageAction = pageAction;
}
/**
* Get whether or not this element should make a sound when interacted with
* @return Whether or not to make a sound when interacted with
*/
public boolean isSilent() {
return silent;
}
/**
* Set whether or not this element should make a sound when interacted with
* @param silent Whether or not to make a sound when interacted with
*/
public void setSilent(boolean silent) {
this.silent = silent;
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
if (((pageAction == PageAction.FIRST || pageAction == PageAction.LAST) && gui.getPageAmount(who) < 3)
|| (pageAction == PageAction.NEXT && gui.getPageNumber(who) + 1 >= gui.getPageAmount(who))
|| (pageAction == PageAction.PREVIOUS && gui.getPageNumber(who) == 0)) {
return gui.getFiller() != null ? gui.getFiller().getItem(who, slot) : null;
}
if (pageAction == PageAction.PREVIOUS) {
setNumber(gui.getPageNumber(who));
} else if (pageAction == PageAction.NEXT) {
setNumber(gui.getPageNumber(who) + 2);
} else if (pageAction == PageAction.LAST) {
setNumber(gui.getPageAmount(who));
}
return super.getItem(who, slot).clone();
}
public enum PageAction {
NEXT,
PREVIOUS,
FIRST,
LAST;
}
}

View File

@@ -0,0 +1,280 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import java.util.function.Supplier;
/**
* An element that can switch between certain states. It automatically handles the switching
* of the item in the slot that corresponds to the state that the element is in.
*/
public class GuiStateElement extends GuiElement {
private Supplier<Integer> queryState = null;
private boolean silent = false;
private int currentState;
private final State[] states;
/**
* An element that can switch between certain states.
* @param slotChar The character to replace in the gui setup string.
* @param defaultState The index of the default state.
* @param states The list of different {@link State}s that this element can have.
*/
public GuiStateElement(char slotChar, int defaultState, State... states) {
super(slotChar, null);
if (states.length == 0) {
throw new IllegalArgumentException("You need to add at least one State!");
}
this.currentState = defaultState;
this.states = states;
setAction(click -> {
State next = nextState();
next.change.onChange(click);
if (!isSilent()) {
click.getGui().playClickSound();
}
gui.draw(click.getWhoClicked(), false);
return true;
});
}
/**
* An element that can switch between certain states.
* @param slotChar The character to replace in the gui setup string.
* @param defaultState The key of the default state.
* @param states The list of different {@link State}s that this element can have.
*/
public GuiStateElement(char slotChar, String defaultState, State... states) {
this(slotChar, getStateIndex(defaultState, states), states);
}
/**
* An element that can switch between certain states.
* @param slotChar The character to replace in the gui setup string.
* @param queryState Supplier for the current state.
* @param states The list of different {@link State}s that this element can have.
*/
public GuiStateElement(char slotChar, Supplier<String> queryState, State... states) {
this(slotChar, queryState.get(), states);
this.queryState = () -> getStateIndex(queryState.get(), states);
}
/**
* An element that can switch between certain states. The first state will be the default one.
* @param slotChar The character to replace in the gui setup string.
* @param states The list of different {@link State}s that this element can have.
*/
public GuiStateElement(char slotChar, State... states) {
this(slotChar, 0, states);
}
/**
* Loop through the states of this element
* @return The new state (next one to the old)
*/
public State nextState() {
queryCurrentState();
currentState = states.length > currentState + 1 ? currentState + 1 : 0;
return states[currentState];
}
/**
* Loop through the states of this element backwards
* @return The new state (previous one to the old)
*/
public State previousState() {
queryCurrentState();
currentState = currentState > 0 ? currentState - 1 : states.length - 1;
return states[currentState];
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
return getState().getItem(who);
}
@Override
public void setGui(InventoryGui gui) {
super.setGui(gui);
for (State state : states) {
state.setGui(gui);
}
}
/**
* Get the current state of this element
* @return The current state of this element
*/
public State getState() {
queryCurrentState();
return states[currentState];
}
/**
* Get whether or not this element should make a sound when interacted with
* @return Whether or not to make a sound when interacted with
*/
public boolean isSilent() {
return silent;
}
/**
* Set whether or not this element should make a sound when interacted with
* @param silent Whether or not to make a sound when interacted with
*/
public void setSilent(boolean silent) {
this.silent = silent;
}
/**
* Try to query the current state if there is a query
*/
private void queryCurrentState() {
if (queryState != null) {
currentState = queryState.get();
}
}
/**
* Set the current state with the state's key. Does not trigger the state's change.
* @param key The key to search for.
* @throws IllegalArgumentException Thrown if there is no state with the provided key.
*/
public void setState(String key) throws IllegalArgumentException {
currentState = getStateIndex(key, states);
}
/**
* Get the index of a state from a key
* @param key The key to search for.
* @param states The states to search in.
* @return The index of that key in the state array.
* @throws IllegalArgumentException Thrown if there is no state with the provided key.
*/
private static int getStateIndex(String key, State[] states) throws IllegalArgumentException {
for (int i = 0; i < states.length; i++) {
if (states[i].getKey().equals(key)) {
return i;
}
}
throw new IllegalArgumentException("This element does not have the state " + key);
}
/**
* A state that the {@link GuiStateElement} can have.
*/
public static class State {
private final Change change;
private final String key;
private final ItemStack item;
private String[] text;
private InventoryGui gui;
/**
* A state that the {@link GuiStateElement} can have.
* @param change What to do when the state changes
* @param key The state's string key
* @param item The {@link ItemStack} to represent this state
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public State(Change change, String key, ItemStack item, String... text) {
this.change = change;
this.key = key;
this.item = item;
this.text = text;
}
/**
* Set this element's display text. If this is an empty array the item's name will be displayed
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public void setText(String... text) {
this.text = text;
}
/**
* Get the {@link ItemStack} that represents this state.
* @return The {@link ItemStack} that represents this state
* @deprecated Use {@link #getItem(HumanEntity)}
*/
@Deprecated
public ItemStack getItem() {
return getItem(null);
}
/**
* Get the {@link ItemStack} that represents this state.
* @param who The player viewing the GUI
* @return The {@link ItemStack} that represents this state
*/
public ItemStack getItem(HumanEntity who) {
ItemStack clone = item.clone();
gui.setItemText(who, clone, getText());
return clone;
}
/**
* Get the string key of the state.
* @return The state's string key
*/
public String getKey() {
return key;
}
/**
* Get the text lines that describe this state.
* @return The text lines for this state
*/
public String[] getText() {
return text;
}
private void setGui(InventoryGui gui) {
this.gui = gui;
}
/**
* Define what should happen when the state of the element' state changes to this state
*/
public interface Change {
/**
* What should happen when the element's state changes to this state
* @param click The click that triggered this change
*/
void onChange(Click click);
}
}
}

View File

@@ -0,0 +1,368 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.function.Function;
/**
* This element is used to access an {@link Inventory}. The slots in the inventory are selected
* by searching through the whole gui the element is in and getting the number of the spot
* in the character group that this element is in. <br>
* E.g. if you have five characters called "s" in the gui setup and the second element is
* accessed by the player then it will translate to the second slot in the inventory.
*/
public class GuiStorageElement extends GuiElement {
private final Inventory storage;
private final int invSlot;
private Runnable applyStorage;
private Function<ValidatorInfo, Boolean> itemValidator;
/**
* An element used to access an {@link Inventory}.
* @param slotChar The character to replace in the gui setup string.
* @param storage The {@link Inventory} that this element is linked to.
*/
public GuiStorageElement(char slotChar, Inventory storage) {
this(slotChar, storage, -1);
}
/**
* An element used to access a specific slot in an {@link Inventory}.
* @param slotChar The character to replace in the gui setup string.
* @param storage The {@link Inventory} that this element is linked to.
* @param invSlot The index of the slot to access in the {@link Inventory}.
*/
public GuiStorageElement(char slotChar, Inventory storage, int invSlot) {
this(slotChar, storage, invSlot, null, null);
}
/**
* An element used to access a specific slot in an {@link Inventory}.
* @param slotChar The character to replace in the gui setup string.
* @param storage The {@link Inventory} that this element is linked to.
* @param invSlot The index of the slot to access in the {@link Inventory}.
* @param applyStorage Apply the storage that this element represents.
* @param itemValidator Should return <code>false</code> for items that should not work in that slot
* Can be null if the storage is directly linked.
*/
public GuiStorageElement(char slotChar, Inventory storage, int invSlot, Runnable applyStorage, Function<ValidatorInfo, Boolean> itemValidator) {
super(slotChar, null);
this.invSlot = invSlot;
this.applyStorage = applyStorage;
this.itemValidator = itemValidator;
setAction(click -> {
if (getStorageSlot(click.getWhoClicked(), click.getSlot()) < 0) {
return true;
}
ItemStack storageItem = getStorageItem(click.getWhoClicked(), click.getSlot());
ItemStack slotItem = click.getRawEvent().getView().getTopInventory().getItem(click.getSlot());
if (slotItem == null && storageItem != null && storageItem.getType() != Material.AIR
|| storageItem == null && slotItem != null && slotItem.getType() != Material.AIR
|| storageItem != null && !storageItem.equals(slotItem)) {
gui.draw(click.getWhoClicked(), false);
return true;
}
if (!(click.getRawEvent() instanceof InventoryClickEvent)) {
// Only the click event will be handled here, drag event is handled separately
return true;
}
InventoryClickEvent event = (InventoryClickEvent) click.getRawEvent();
ItemStack movedItem = null;
switch (event.getAction()) {
case NOTHING:
case CLONE_STACK:
return false;
case MOVE_TO_OTHER_INVENTORY:
if (event.getRawSlot() < click.getRawEvent().getView().getTopInventory().getSize()) {
// Moved from storage
// Check if there is actually space (more advanced checks can unfortunately not be supported right now)
if (click.getRawEvent().getView().getBottomInventory().firstEmpty() == -1) {
// No empty slot, cancel
return true;
}
movedItem = null;
} else {
// Moved to storage
// Check if there is actually space (more advanced checks can unfortunately not be supported right now)
if (click.getRawEvent().getView().getTopInventory().firstEmpty() == -1) {
// No empty slot, cancel
return true;
}
movedItem = event.getCurrentItem();
}
// Update GUI to avoid display glitches
gui.runTask(gui::draw);
break;
case HOTBAR_MOVE_AND_READD:
case HOTBAR_SWAP:
int button = event.getHotbarButton();
if (button < 0) {
return true;
}
ItemStack hotbarItem = click.getRawEvent().getView().getBottomInventory().getItem(button);
if (hotbarItem != null) {
movedItem = hotbarItem.clone();
}
break;
case PICKUP_ONE:
case DROP_ONE_SLOT:
if (event.getCurrentItem() != null) {
movedItem = event.getCurrentItem().clone();
movedItem.setAmount(movedItem.getAmount() - 1);
}
break;
case DROP_ALL_SLOT:
movedItem = null;
break;
case PICKUP_HALF:
if (event.getCurrentItem() != null) {
movedItem = event.getCurrentItem().clone();
movedItem.setAmount(movedItem.getAmount() / 2);
}
break;
case PLACE_SOME:
if (event.getCurrentItem() == null) {
if (event.getCursor() != null) {
movedItem = event.getCursor().clone();
}
} else {
movedItem = event.getCurrentItem().clone();
int newAmount = movedItem.getAmount() + (event.getCursor() != null ? event.getCursor().getAmount() : 0);
if (newAmount < movedItem.getMaxStackSize()) {
movedItem.setAmount(newAmount);
} else {
movedItem.setAmount(movedItem.getMaxStackSize());
}
}
break;
case PLACE_ONE:
if (event.getCursor() != null) {
if (event.getCurrentItem() == null) {
movedItem = event.getCursor().clone();
movedItem.setAmount(1);
} else {
movedItem = event.getCursor().clone();
movedItem.setAmount(event.getCurrentItem().getAmount() + 1);
}
}
break;
case PLACE_ALL:
if (event.getCursor() != null) {
movedItem = event.getCursor().clone();
if (event.getCurrentItem() != null && event.getCurrentItem().getAmount() > 0) {
movedItem.setAmount(event.getCurrentItem().getAmount() + movedItem.getAmount());
}
}
break;
case PICKUP_ALL:
case SWAP_WITH_CURSOR:
if (event.getCursor() != null) {
movedItem = event.getCursor().clone();
};
break;
case COLLECT_TO_CURSOR:
if (event.getCursor() == null
|| event.getCurrentItem() != null && event.getCurrentItem().getType() != Material.AIR) {
return true;
}
gui.simulateCollectToCursor(click);
return false;
default:
click.getRawEvent().getWhoClicked().sendMessage(ChatColor.RED + "The action " + event.getAction() + " is not supported! Sorry about that :(");
return true;
}
return !setStorageItem(click.getWhoClicked(), click.getSlot(), movedItem);
});
this.storage = storage;
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
int index = getStorageSlot(who, slot);
if (index > -1 && index < storage.getSize()) {
return storage.getItem(index);
}
return null;
}
/**
* Get the {@link Inventory} that this element is linked to.
* @return The {@link Inventory} that this element is linked to.
*/
public Inventory getStorage() {
return storage;
}
/**
* Get the storage slot index that corresponds to the InventoryGui slot
* @param player The player which is using the GUI view
* @param slot The slot in the GUI
* @return The index of the storage slot or <code>-1</code> if it's outside the storage
*/
private int getStorageSlot(HumanEntity player, int slot) {
int index = invSlot != -1 ? invSlot : getSlotIndex(slot, gui.getPageNumber(player));
if (index < 0 || index >= storage.getSize()) {
return -1;
}
return index;
}
/**
* Get the item in the storage that corresponds to the InventoryGui slot
* @param slot The slot in the GUI
* @return The {@link ItemStack} or <code>null</code> if the slot is outside of the item's size
* @deprecated Use {@link #getStorageItem(HumanEntity, int)}
*/
@Deprecated
public ItemStack getStorageItem(int slot) {
return getStorageItem(null, slot);
}
/**
* Get the item in the storage that corresponds to the InventoryGui slot
* @param player The player which is using the GUI view
* @param slot The slot in the GUI
* @return The {@link ItemStack} or <code>null</code> if the slot is outside of the item's size
*/
public ItemStack getStorageItem(HumanEntity player, int slot) {
int index = getStorageSlot(player, slot);
if (index == -1) {
return null;
}
return storage.getItem(index);
}
/**
* Set the item in the storage that corresponds to the InventoryGui slot.
* @param slot The slot in the GUI
* @param item The {@link ItemStack} to set
* @return <code>true</code> if the item was set; <code>false</code> if the slot was outside of this storage
* @deprecated Use {@link #setStorageItem(HumanEntity, int, ItemStack)}
*/
@Deprecated
public boolean setStorageItem(int slot, ItemStack item) {
return setStorageItem(null, slot, item);
}
/**
* Set the item in the storage that corresponds to the InventoryGui slot.
* @param player The player using the GUI view
* @param slot The slot in the GUI
* @param item The {@link ItemStack} to set
* @return <code>true</code> if the item was set; <code>false</code> if the slot was outside of this storage
*/
public boolean setStorageItem(HumanEntity player, int slot, ItemStack item) {
int index = getStorageSlot(player, slot);
if (index == -1) {
return false;
}
if (!validateItem(slot, item)) {
return false;
}
storage.setItem(index, item);
if (applyStorage != null) {
applyStorage.run();
}
return true;
}
/**
* Get the runnable that applies the storage
* @return The storage applying runnable; might be null
*/
public Runnable getApplyStorage() {
return applyStorage;
}
/**
* Set what should be done to apply the storage.
* Not necessary if the storage is directly backed by a real inventory.
* @param applyStorage How to apply the storage; can be null if nothing should be done
*/
public void setApplyStorage(Runnable applyStorage) {
this.applyStorage = applyStorage;
}
/**
* Get the item validator
* @return The item validator
*/
public Function<ValidatorInfo, Boolean> getItemValidator() {
return itemValidator;
}
/**
* Set a function that can validate whether or not an item can fit in the slot
* @param itemValidator The item validator that takes a {@link ValidatorInfo} and returns <code>true</code> for items that
* should and <code>false</code> for items that should not work in that slot
*/
public void setItemValidator(Function<ValidatorInfo, Boolean> itemValidator) {
this.itemValidator = itemValidator;
}
/**
* Validate whether or not an item can be put in a slot with the item validator set in {@link #setItemValidator(Function)}
* @param slot The slot the item should be tested for
* @param item The item to test
* @return <code>true</code> for items that should and <code>false</code> for items that should not work in that slot
*/
public boolean validateItem(int slot, ItemStack item) {
return itemValidator == null || itemValidator.apply(new ValidatorInfo(this, slot, item));
}
public static class ValidatorInfo {
private final GuiElement element;
private final int slot;
private final ItemStack item;
public ValidatorInfo(GuiElement element, int slot, ItemStack item) {
this.item = item;
this.slot = slot;
this.element = element;
}
public GuiElement getElement() {
return element;
}
public int getSlot() {
return slot;
}
public ItemStack getItem() {
return item;
}
}
}

View File

@@ -0,0 +1,159 @@
package net.momirealms.customfishing.libraries.inventorygui;
/*
* Copyright 2017 Max Lee (https://github.com/Phoenix616)
*
* 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.
*/
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
/**
* Represents a simple element in a gui to which an action can be assigned.
* If you want the item to change on click you have to do that yourself.
*/
public class StaticGuiElement extends GuiElement {
private ItemStack item;
private int number;
private String[] text;
/**
* Represents an element in a gui
* @param slotChar The character to replace in the gui setup string
* @param item The item this element displays
* @param number The number, 1 will not display the number
* @param action The action to run when the player clicks on this element
* @param text The text to display on this element, placeholders are automatically
* replaced, see for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
* @throws IllegalArgumentException If the number is below 1 or above the max stack count (currently 64)
*/
public StaticGuiElement(char slotChar, ItemStack item, int number, Action action, String... text) throws IllegalArgumentException {
super(slotChar, action);
this.item = item;
this.text = text;
setNumber(number);
}
/**
* Represents an element in a gui
* @param slotChar The character to replace in the gui setup string
* @param item The item this element displays
* @param action The action to run when the player clicks on this element
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public StaticGuiElement(char slotChar, ItemStack item, Action action, String... text) {
this(slotChar, item, item != null ? item.getAmount() : 1, action, text);
}
/**
* Represents an element in a gui that doesn't have any action when clicked
* @param slotChar The character to replace in the gui setup string
* @param item The item this element displays
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public StaticGuiElement(char slotChar, ItemStack item, String... text) {
this(slotChar, item, item != null ? item.getAmount() : 1, null, text);
}
/**
* Set the item that is displayed by this element
* @param item The item that should be displayed by this element
*/
public void setItem(ItemStack item) {
this.item = item;
}
/**
* Get the raw item displayed by this element which was passed to the constructor or set with {@link #setItem(ItemStack)}.
* This item will not have the amount or text applied! Use {@link #getItem(HumanEntity, int)} for that!
* @return The raw item
*/
public ItemStack getRawItem() {
return item;
}
@Override
public ItemStack getItem(HumanEntity who, int slot) {
if (item == null) {
return null;
}
ItemStack clone = item.clone();
gui.setItemText(who, clone, getText());
if (number > 0 && number <= 64) {
clone.setAmount(number);
}
return clone;
}
/**
* Set this element's display text. If this is an empty array the item's name will be displayed
* @param text The text to display on this element, placeholders are automatically
* replaced, see {@link InventoryGui#replaceVars} for a list of the
* placeholder variables. Empty text strings are also filter out, use
* a single space if you want to add an empty line!<br>
* If it's not set/empty the item's default name will be used
*/
public void setText(String... text) {
this.text = text;
}
/**
* Get the text that this element displays
* @return The text that is displayed on this element
*/
public String[] getText() {
return text;
}
/**
* Set the number that this element should display (via the Item's amount)
* @param number The number, 1 will not display the number
* @return <code>true</code> if the number was set; <code>false</code> if it was below 1 or above 64
*/
public boolean setNumber(int number) {
if (number < 1 || number > 64) {
this.number = 1;
return false;
}
this.number = number;
return true;
}
/**
* Get the number that this element should display
* @return The number (item amount) that this element currently has
*/
public int getNumber() {
return number;
}
}

View File

@@ -0,0 +1,181 @@
/*
* This file is part of helper, 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.libraries.libraryloader;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.util.LogUtils;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Resolves {@link MavenLibrary} annotations for a class, and loads the dependency
* into the classloader.
*/
public final class LibraryLoader {
@SuppressWarnings("Guava")
private static final Supplier<URLClassLoaderAccess> URL_INJECTOR = Suppliers.memoize(() -> URLClassLoaderAccess.create((URLClassLoader) CustomFishingPlugin.getInstance().getClass().getClassLoader()));
/**
* Resolves all {@link MavenLibrary} annotations on the given object.
*
* @param object the object to load libraries for.
*/
public static void loadAll(Object object) {
loadAll(object.getClass());
}
/**
* Resolves all {@link MavenLibrary} annotations on the given class.
*
* @param clazz the class to load libraries for.
*/
public static void loadAll(Class<?> clazz) {
MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class);
for (MavenLibrary lib : libs) {
load(lib.groupId(), lib.artifactId(), lib.version(), lib.repo().url());
}
}
public static void load(String groupId, String artifactId, String version, String repoUrl) {
load(new Dependency(groupId, artifactId, version, repoUrl));
}
public static void loadDependencies(String... libs) {
if (libs == null || libs.length % 2 != 0)
return;
for (int i = 0; i < libs.length; i+=2) {
String[] split = libs[i].split(":");
load(new Dependency(
split[0],
split[1],
split[2],
libs[i+1]
));
}
}
public static void load(Dependency d) {
LogUtils.info(String.format("Loading dependency %s:%s:%s from %s", d.groupId, d.artifactId, d.version, d.repoUrl));
String name = d.artifactId() + "-" + d.version();
File saveLocation = new File(getLibFolder(d), name + ".jar");
if (!saveLocation.exists()) {
try {
LogUtils.info("Dependency '" + name + "' is not already in the libraries folder. Attempting to download...");
URL url = d.getUrl();
try (InputStream is = url.openStream()) {
Files.copy(is, saveLocation.toPath());
LogUtils.info("Dependency '" + name + "' successfully downloaded.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (!saveLocation.exists())
throw new RuntimeException("Unable to download dependency: " + d);
try {
URL_INJECTOR.get().addURL(saveLocation.toURI().toURL());
} catch (Exception e) {
throw new RuntimeException("Unable to load dependency: " + saveLocation, e);
}
}
@SuppressWarnings("all")
private static File getLibFolder(Dependency dependency) {
File pluginDataFolder = CustomFishingPlugin.getInstance().getDataFolder();
File serverDir = pluginDataFolder.getParentFile().getParentFile();
File helperDir = new File(serverDir, "libraries");
String[] split = dependency.groupId().split("\\.");
File jarDir;
StringJoiner stringJoiner = new StringJoiner(File.separator);
for (String str : split) {
stringJoiner.add(str);
}
jarDir = new File(helperDir, stringJoiner + File.separator + dependency.artifactId + File.separator + dependency.version);
jarDir.mkdirs();
return jarDir;
}
public record Dependency(String groupId, String artifactId, String version, String repoUrl) {
public Dependency(String groupId, String artifactId, String version, String repoUrl) {
this.groupId = Objects.requireNonNull(groupId, "groupId");
this.artifactId = Objects.requireNonNull(artifactId, "artifactId");
this.version = Objects.requireNonNull(version, "version");
this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl");
}
public URL getUrl() throws MalformedURLException {
String repo = this.repoUrl;
if (!repo.endsWith("/")) {
repo += "/";
}
repo += "%s/%s/%s/%s-%s.jar";
String url = String.format(repo, this.groupId.replace(".", "/"), this.artifactId, this.version, this.artifactId, this.version);
return new URL(url);
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Dependency other)) return false;
return this.groupId().equals(other.groupId()) &&
this.artifactId().equals(other.artifactId()) &&
this.version().equals(other.version()) &&
this.repoUrl().equals(other.repoUrl());
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.groupId().hashCode();
result = result * PRIME + this.artifactId().hashCode();
result = result * PRIME + this.version().hashCode();
result = result * PRIME + this.repoUrl().hashCode();
return result;
}
@Override
public String toString() {
return "LibraryLoader.Dependency(" +
"groupId=" + this.groupId() + ", " +
"artifactId=" + this.artifactId() + ", " +
"version=" + this.version() + ", " +
"repoUrl=" + this.repoUrl() + ")";
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* This file is part of helper, 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.libraries.libraryloader;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.*;
/**
* Annotation to indicate the required libraries for a class.
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MavenLibraries {
@NotNull
MavenLibrary[] value() default {};
}

View File

@@ -0,0 +1,73 @@
/*
* This file is part of helper, 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.libraries.libraryloader;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.*;
/**
* Annotation to indicate a required library for a class.
*/
@Documented
@Repeatable(MavenLibraries.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MavenLibrary {
/**
* The group id of the library
*
* @return the group id of the library
*/
@NotNull
String groupId();
/**
* The artifact id of the library
*
* @return the artifact id of the library
*/
@NotNull
String artifactId();
/**
* The version of the library
*
* @return the version of the library
*/
@NotNull
String version();
/**
* The repo where the library can be obtained from
*
* @return the repo where the library can be obtained from
*/
@NotNull
Repository repo() default @Repository(url = "https://repo1.maven.org/maven2");
}

View File

@@ -0,0 +1,45 @@
/*
* This file is part of helper, 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.libraries.libraryloader;
import java.lang.annotation.*;
/**
* Represents a maven repository.
*/
@Documented
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
/**
* Gets the base url of the repository.
*
* @return the base url of the repository
*/
String url();
}

View File

@@ -0,0 +1,139 @@
/*
* This file is part of helper, 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.libraries.libraryloader;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
/**
* Provides access to {@link URLClassLoader}#addURL.
*/
public abstract class URLClassLoaderAccess {
/**
* Creates a {@link URLClassLoaderAccess} for the given class loader.
*
* @param classLoader the class loader
* @return the access object
*/
static URLClassLoaderAccess create(URLClassLoader classLoader) {
if (Unsafe.isSupported()) {
return new Unsafe(classLoader);
} else {
return Noop.INSTANCE;
}
}
private final URLClassLoader classLoader;
protected URLClassLoaderAccess(URLClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Adds the given URL to the class loader.
*
* @param url the URL to add
*/
public abstract void addURL(@NotNull URL url);
/**
* Accesses using sun.misc.Unsafe, supported on Java 9+.
*
* @author Vaishnav Anil (https://github.com/slimjar/slimjar)
*/
private static class Unsafe extends URLClassLoaderAccess {
private static final sun.misc.Unsafe UNSAFE;
static {
sun.misc.Unsafe unsafe;
try {
Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (sun.misc.Unsafe) unsafeField.get(null);
} catch (Throwable t) {
unsafe = null;
}
UNSAFE = unsafe;
}
private static boolean isSupported() {
return UNSAFE != null;
}
private final Collection<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "path");
} catch (Throwable e) {
unopenedURLs = null;
pathURLs = null;
}
this.unopenedURLs = unopenedURLs;
this.pathURLs = pathURLs;
}
private static Object fetchField(final Class<?> clazz, final Object object, final String name) throws NoSuchFieldException {
Field field = clazz.getDeclaredField(name);
long offset = UNSAFE.objectFieldOffset(field);
return UNSAFE.getObject(object, offset);
}
@Override
public void addURL(@NotNull URL url) {
this.unopenedURLs.add(url);
this.pathURLs.add(url);
}
}
private static class Noop extends URLClassLoaderAccess {
private static final Noop INSTANCE = new Noop();
private Noop() {
super(null);
}
@Override
public void addURL(@NotNull URL url) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,317 @@
package net.momirealms.customfishing.mechanic.action;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.ActionManager;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.action.ActionBuilder;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
import net.momirealms.customfishing.util.ConfigUtils;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.ExperienceOrb;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class ActionManagerImpl implements ActionManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, ActionBuilder> actionBuilderMap;
public ActionManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.actionBuilderMap = new HashMap<>();
this.registerInbuiltActions();
}
private void registerInbuiltActions() {
this.registerMessageAction();
this.registerCommandAction();
this.registerMendingAction();
this.registerExpAction();
this.registerChainAction();
this.registerPotionAction();
this.registerSoundAction();
this.registerPluginExpAction();
this.registerTitleAction();
this.registerActionBarAction();
}
@Override
public boolean registerAction(String type, ActionBuilder actionBuilder) {
if (this.actionBuilderMap.containsKey(type)) return false;
this.actionBuilderMap.put(type, actionBuilder);
return true;
}
@Override
public boolean unregisterAction(String type) {
return this.actionBuilderMap.remove(type) != null;
}
@Override
public Action getAction(ConfigurationSection section) {
return getActionBuilder(section.getString("type")).build(section.get("value"), section.getDouble("chance", 1d));
}
@Nullable
@Override
public Action[] getActions(ConfigurationSection section) {
if (section == null) return null;
ArrayList<Action> actionList = new ArrayList<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
actionList.add(getAction(innerSection));
}
}
return actionList.toArray(new Action[0]);
}
@Override
public ActionBuilder getActionBuilder(String type) {
return actionBuilderMap.get(type);
}
private void registerMessageAction() {
registerAction("message", (args, chance) -> {
ArrayList<String> msg = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
List<String> replaced = PlaceholderManagerImpl.getInstance().parse(
condition.getPlayer(),
msg,
condition.getArgs()
);
for (String text : replaced) {
AdventureManagerImpl.getInstance().sendPlayerMessage(condition.getPlayer(), text);
}
};
});
registerAction("broadcast", (args, chance) -> {
ArrayList<String> msg = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
List<String> replaced = PlaceholderManagerImpl.getInstance().parse(
condition.getPlayer(),
msg,
condition.getArgs()
);
for (Player player : Bukkit.getOnlinePlayers()) {
for (String text : replaced) {
AdventureManagerImpl.getInstance().sendPlayerMessage(player, text);
}
}
};
});
registerAction("random-message", (args, chance) -> {
ArrayList<String> msg = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
String random = msg.get(ThreadLocalRandom.current().nextInt(msg.size()));
random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs());
AdventureManagerImpl.getInstance().sendPlayerMessage(condition.getPlayer(), random);
};
});
}
private void registerCommandAction() {
registerAction("command", (args, chance) -> {
ArrayList<String> cmd = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
List<String> replaced = PlaceholderManagerImpl.getInstance().parse(
condition.getPlayer(),
cmd,
condition.getArgs()
);
plugin.getScheduler().runTaskSync(() -> {
for (String text : replaced) {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), text);
}
}, condition.getLocation());
};
});
registerAction("random-command", (args, chance) -> {
ArrayList<String> cmd = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
String random = cmd.get(ThreadLocalRandom.current().nextInt(cmd.size()));
random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs());
String finalRandom = random;
plugin.getScheduler().runTaskSync(() -> {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), finalRandom);
}, condition.getLocation());
};
});
}
private void registerActionBarAction() {
registerAction("actionbar", (args, chance) -> {
String text = (String) args;
return condition -> {
if (Math.random() > chance) return;
String parsed = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), text, condition.getArgs());
AdventureManagerImpl.getInstance().sendActionbar(condition.getPlayer(), parsed);
};
});
registerAction("random-actionbar", (args, chance) -> {
ArrayList<String> texts = ConfigUtils.stringListArgs(args);
return condition -> {
if (Math.random() > chance) return;
String random = texts.get(ThreadLocalRandom.current().nextInt(texts.size()));
random = PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), random, condition.getArgs());
AdventureManagerImpl.getInstance().sendActionbar(condition.getPlayer(), random);
};
});
}
private void registerMendingAction() {
registerAction("mending", (args, chance) -> {
int xp = (int) args;
return condition -> {
if (Math.random() > chance) return;
if (CustomFishingPlugin.get().getVersionManager().isSpigot()) {
condition.getPlayer().getLocation().getWorld().spawn(condition.getPlayer().getLocation(), ExperienceOrb.class, e -> e.setExperience(xp));
} else {
condition.getPlayer().giveExp(xp, true);
AdventureManagerImpl.getInstance().sendSound(condition.getPlayer(), Sound.Source.PLAYER, Key.key("minecraft:entity.experience_orb.pickup"), 1, 1);
}
};
});
}
private void registerExpAction() {
registerAction("exp", (args, chance) -> {
int xp = (int) args;
return condition -> {
if (Math.random() > chance) return;
condition.getPlayer().giveExp(xp);
};
});
}
private void registerChainAction() {
registerAction("chain", (args, chance) -> {
List<Action> actions = new ArrayList<>();
if (args instanceof ConfigurationSection section) {
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
actions.add(getAction(innerSection));
}
}
}
return condition -> {
if (Math.random() > chance) return;
for (Action action : actions) {
action.trigger(condition);
}
};
});
}
private void registerTitleAction() {
registerAction("title", (args, chance) -> {
if (args instanceof ConfigurationSection section) {
String title = section.getString("title");
String subtitle = section.getString("subtitle");
int fadeIn = section.getInt("fade-in", 20);
int stay = section.getInt("stay", 30);
int fadeOut = section.getInt("fade-out", 10);
return condition -> {
if (Math.random() > chance) return;
AdventureManagerImpl.getInstance().sendTitle(
condition.getPlayer(),
PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), title, condition.getArgs()),
PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), subtitle, condition.getArgs()),
fadeIn * 50,
stay * 50,
fadeOut * 50
);
};
}
return null;
});
registerAction("random-title", (args, chance) -> {
if (args instanceof ConfigurationSection section) {
List<String> titles = section.getStringList("titles");
List<String> subtitles = section.getStringList("subtitles");
int fadeIn = section.getInt("fade-in", 20);
int stay = section.getInt("stay", 30);
int fadeOut = section.getInt("fade-out", 10);
return condition -> {
if (Math.random() > chance) return;
AdventureManagerImpl.getInstance().sendTitle(
condition.getPlayer(),
PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), titles.get(ThreadLocalRandom.current().nextInt(titles.size())), condition.getArgs()),
PlaceholderManagerImpl.getInstance().parse(condition.getPlayer(), subtitles.get(ThreadLocalRandom.current().nextInt(subtitles.size())), condition.getArgs()),
fadeIn * 50,
stay * 50,
fadeOut * 50
);
};
}
return null;
});
}
private void registerPotionAction() {
registerAction("potion-effect", (args, chance) -> {
if (args instanceof ConfigurationSection 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 condition -> {
if (Math.random() > chance) return;
condition.getPlayer().addPotionEffect(potionEffect);
};
}
return null;
});
}
@SuppressWarnings("all")
private void registerSoundAction() {
registerAction("sound", (args, chance) -> {
if (args instanceof ConfigurationSection section) {
Sound sound = Sound.sound(
Key.key(section.getString("key")),
Sound.Source.valueOf(section.getString("source", "PLAYER").toUpperCase(Locale.ENGLISH)),
(float) section.getDouble("volume", 1),
(float) section.getDouble("pitch", 1)
);
return condition -> {
if (Math.random() > chance) return;
AdventureManagerImpl.getInstance().sendSound(condition.getPlayer(), sound);
};
}
return null;
});
}
private void registerPluginExpAction() {
registerAction("plugin-exp", (args, chance) -> {
if (args instanceof ConfigurationSection section) {
String pluginName = section.getString("plugin");
double exp = section.getDouble("exp", 1);
String target = section.getString("target");
return condition -> {
if (Math.random() > chance) return;
Optional.ofNullable(plugin.getIntegrationManager().getLevelHook(pluginName)).ifPresentOrElse(it -> {
it.addXp(condition.getPlayer(), target, exp);
}, () -> LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation."));
};
}
return null;
});
}
}

View File

@@ -0,0 +1,97 @@
package net.momirealms.customfishing.mechanic.bag;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.BagManager;
import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
import net.momirealms.customfishing.setting.Config;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.Inventory;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class BagManagerImpl implements BagManager {
private final CustomFishingPlugin plugin;
private final ConcurrentHashMap<UUID, FishingBagHolder> bagMap;
private final WindowPacketListener windowPacketListener;
public BagManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
this.bagMap = new ConcurrentHashMap<>();
this.windowPacketListener = new WindowPacketListener();
}
@Override
public boolean isBagEnabled() {
return Config.enableFishingBag;
}
public void load() {
CustomFishingPluginImpl.getProtocolManager().addPacketListener(windowPacketListener);
}
public void unload() {
CustomFishingPluginImpl.getProtocolManager().removePacketListener(windowPacketListener);
}
public void disable() {
unload();
}
@Override
public Inventory getOnlineBagInventory(UUID uuid) {
var onlinePlayer = plugin.getStorageManager().getOnlineUser(uuid);
if (onlinePlayer == null) {
LogUtils.warn("Player " + uuid + "'s bag data is not loaded.");
return null;
}
return onlinePlayer.getHolder().getInventory();
}
public static class WindowPacketListener extends PacketAdapter {
public WindowPacketListener() {
super(CustomFishingPlugin.getInstance(), PacketType.Play.Server.OPEN_WINDOW);
}
@Override
public void onPacketSending(PacketEvent event) {
final PacketContainer packet = event.getPacket();
StructureModifier<WrappedChatComponent> wrappedChatComponentStructureModifier = packet.getChatComponents();
WrappedChatComponent component = wrappedChatComponentStructureModifier.getValues().get(0);
String windowTitleJson = component.getJson();
Component titleComponent = GsonComponentSerializer.gson().deserialize(windowTitleJson);
if (titleComponent instanceof ScoreComponent scoreComponent && scoreComponent.name().equals("bag")) {
HashMap<String, String> placeholders = new HashMap<>();
String uuidStr = scoreComponent.objective();
UUID uuid = UUID.fromString(uuidStr);
placeholders.put("{uuid}", uuidStr);
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
placeholders.put("{player}", Optional.ofNullable(offlinePlayer.getName()).orElse(uuidStr));
wrappedChatComponentStructureModifier.write(0,
WrappedChatComponent.fromJson(
GsonComponentSerializer.gson().serialize(
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
PlaceholderManagerImpl.getInstance().parse(offlinePlayer, Config.bagTitle, placeholders)
))));
}
}
}
}

View File

@@ -0,0 +1,288 @@
package net.momirealms.customfishing.mechanic.block;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.common.Tuple;
import net.momirealms.customfishing.api.manager.BlockManager;
import net.momirealms.customfishing.api.mechanic.block.*;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.block.VanillaBlockImpl;
import net.momirealms.customfishing.util.ConfigUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.Barrel;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Chest;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
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.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class BlockManagerImpl implements BlockManager, Listener {
private final CustomFishingPlugin plugin;
private final HashMap<String, BlockLibrary> blockLibraryMap;
private final HashMap<String, BlockConfig> blockConfigMap;
private final HashMap<String, BlockDataModifierBuilder> dataBuilderMap;
private final HashMap<String, BlockStateModifierBuilder> stateBuilderMap;
public BlockManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
this.blockLibraryMap = new HashMap<>();
this.blockConfigMap = new HashMap<>();
this.dataBuilderMap = new HashMap<>();
this.stateBuilderMap = new HashMap<>();
this.registerBlockLibrary(new VanillaBlockImpl());
this.registerInbuiltProperties();
this.registerStorage();
}
@Override
public boolean registerBlockLibrary(BlockLibrary library) {
if (this.blockLibraryMap.containsKey(library.identification())) return false;
this.blockLibraryMap.put(library.identification(), library);
return true;
}
@Override
public boolean unregisterBlockLibrary(BlockLibrary library) {
return unregisterBlockLibrary(library.identification());
}
@Override
public boolean unregisterBlockLibrary(String library) {
return blockLibraryMap.remove(library) != null;
}
@Override
public boolean registerBlockDataModifierBuilder(String type, BlockDataModifierBuilder builder) {
if (dataBuilderMap.containsKey(type)) return false;
dataBuilderMap.put(type, builder);
return true;
}
@Override
public boolean registerBlockStateModifierBuilder(String type, BlockStateModifierBuilder builder) {
if (stateBuilderMap.containsKey(type)) return false;
stateBuilderMap.put(type, builder);
return true;
}
public void load() {
this.loadConfig();
Bukkit.getPluginManager().registerEvents(this, plugin);
}
private void registerInbuiltProperties() {
this.registerDirectional();
this.registerStorage();
}
public void unload() {
HandlerList.unregisterAll(this);
HashMap<String, BlockConfig> tempMap = new HashMap<>(this.blockConfigMap);
this.blockConfigMap.clear();
for (Map.Entry<String, BlockConfig> entry : tempMap.entrySet()) {
if (entry.getValue().isPersist()) {
tempMap.put(entry.getKey(), entry.getValue());
}
}
}
public void disable() {
this.blockLibraryMap.clear();
}
@SuppressWarnings("DuplicatedCode")
private void loadConfig() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("blocks")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
this.loadSingleFile(subFile);
}
}
}
}
}
private void loadSingleFile(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
String blockID = section.getString("block");
if (blockID == null) {
LogUtils.warn("Block can't be null. File:" + file.getAbsolutePath() + "; Section:" + section.getCurrentPath());
continue;
}
List<BlockDataModifier> dataModifiers = new ArrayList<>();
List<BlockStateModifier> stateModifiers = new ArrayList<>();
ConfigurationSection property = section.getConfigurationSection("properties");
if (property != null) {
for (Map.Entry<String, Object> innerEntry : property.getValues(false).entrySet()) {
BlockDataModifierBuilder dataBuilder = dataBuilderMap.get(innerEntry.getKey());
if (dataBuilder != null) {
dataModifiers.add(dataBuilder.build(innerEntry.getValue()));
continue;
}
BlockStateModifierBuilder stateBuilder = stateBuilderMap.get(innerEntry.getKey());
if (stateBuilder != null) {
stateModifiers.add(stateBuilder.build(innerEntry.getValue()));
}
}
}
BlockConfig blockConfig = new BlockConfig.Builder()
.blockID(blockID)
.persist(false)
.horizontalVector(section.getDouble("vector.horizontal", 1.1))
.verticalVector(section.getDouble("vector.vertical", 1.2))
.dataModifiers(dataModifiers)
.stateModifiers(stateModifiers)
.build();
blockConfigMap.put(entry.getKey(), blockConfig);
}
}
}
@Override
public void summonBlock(Player player, Location hookLocation, Location playerLocation, Loot loot) {
BlockConfig config = blockConfigMap.get(loot.getID());
if (config == null) {
LogUtils.warn("Block: " + loot.getID() + " doesn't exist.");
return;
}
String blockID = config.getBlockID();
BlockData blockData;
if (blockID.contains(":")) {
String[] split = blockID.split(":", 2);
String lib = split[0];
String id = split[1];
blockData = blockLibraryMap.get(lib).getBlockData(player, id, config.getDataModifier());
} else {
blockData = blockLibraryMap.get("vanilla").getBlockData(player, blockID, config.getDataModifier());
}
FallingBlock fallingBlock = hookLocation.getWorld().spawnFallingBlock(hookLocation, blockData);
fallingBlock.getPersistentDataContainer().set(
Objects.requireNonNull(NamespacedKey.fromString("block", CustomFishingPlugin.get())),
PersistentDataType.STRING,
loot.getID() + ";" + player.getName()
);
fallingBlock.setDropItem(false);
Vector vector = playerLocation.subtract(hookLocation).toVector().multiply((config.getHorizontalVector()) - 1);
vector = vector.setY((vector.getY() + 0.2) * config.getVerticalVector());
fallingBlock.setVelocity(vector);
}
private void registerDirectional() {
this.registerBlockDataModifierBuilder("directional", (args) -> {
boolean arg = (boolean) args;
return (player, blockData) -> {
if (arg && blockData instanceof Directional directional) {
directional.setFacing(BlockFace.values()[ThreadLocalRandom.current().nextInt(0,6)]);
}
};
});
}
private void registerStorage() {
this.registerBlockStateModifierBuilder("storage", (args) -> {
if (args instanceof ConfigurationSection section) {
ArrayList<Tuple<Double, String, Pair<Integer, Integer>>> tempChanceList = new ArrayList<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection inner) {
String item = inner.getString("item");
Pair<Integer, Integer> amountPair = ConfigUtils.splitStringIntegerArgs(inner.getString("amount","1~1"));
double chance = inner.getDouble("chance", 1);
tempChanceList.add(Tuple.of(chance, item, amountPair));
}
}
return (player, blockState) -> {
LinkedList<Integer> unused = new LinkedList<>();
for (int i = 0; i < 27; i++) {
unused.add(i);
}
Collections.shuffle(unused);
if (blockState instanceof Chest chest) {
for (Tuple<Double, String, Pair<Integer, Integer>> tuple : tempChanceList) {
ItemStack itemStack = plugin.getItemManager().buildAnyItemByID(player, tuple.getMid());
itemStack.setAmount(ThreadLocalRandom.current().nextInt(tuple.getRight().left(), tuple.getRight().right() + 1));
if (tuple.getLeft() > Math.random()) {
chest.getBlockInventory().setItem(unused.pop(), itemStack);
}
}
return;
}
if (blockState instanceof Barrel barrel) {
for (Tuple<Double, String, Pair<Integer, Integer>> tuple : tempChanceList) {
ItemStack itemStack = plugin.getItemManager().buildAnyItemByID(player, tuple.getMid());
itemStack.setAmount(ThreadLocalRandom.current().nextInt(tuple.getRight().left(), tuple.getRight().right() + 1));
if (tuple.getLeft() > Math.random()) {
barrel.getInventory().setItem(unused.pop(), itemStack);
}
}
}
};
} else {
LogUtils.warn("Invalid property format found at block storage.");
return null;
}
});
}
@EventHandler
public void onBlockLands(EntityChangeBlockEvent event) {
if (event.isCancelled()) return;
String temp = event.getEntity().getPersistentDataContainer().get(
Objects.requireNonNull(NamespacedKey.fromString("block", CustomFishingPlugin.get())),
PersistentDataType.STRING
);
if (temp == null) return;
String[] split = temp.split(";");
BlockConfig blockConfig = blockConfigMap.get(split[0]);
if (blockConfig == null) return;
Player player = Bukkit.getPlayer(split[1]);
if (player == null) {
event.getEntity().remove();
event.getBlock().setType(Material.AIR);
return;
}
Location location = event.getBlock().getLocation();
plugin.getScheduler().runTaskSyncLater(() -> {
BlockState state = location.getBlock().getState();
for (BlockStateModifier modifier : blockConfig.getStateModifierList()) {
modifier.apply(player, state);
}
}, location, 50, TimeUnit.MILLISECONDS);
}
}

View File

@@ -0,0 +1,265 @@
package net.momirealms.customfishing.mechanic.competition;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.mechanic.action.Action;
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.Ranking;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.mechanic.competition.actionbar.ActionBarManager;
import net.momirealms.customfishing.mechanic.competition.bossbar.BossBarManager;
import net.momirealms.customfishing.mechanic.competition.ranking.LocalRankingImpl;
import net.momirealms.customfishing.mechanic.competition.ranking.RedisRankingImpl;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.setting.Locale;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.time.Instant;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class Competition implements FishingCompetition {
private final CompetitionConfig config;
private CancellableTask competitionTimerTask;
private final CompetitionGoal goal;
private final ConcurrentHashMap<String, String> publicPlaceholders;
private final Ranking ranking;
private float progress;
private long remainingTime;
private long startTime;
private BossBarManager bossBarManager;
private ActionBarManager actionBarManager;
public Competition(CompetitionConfig config) {
this.config = config;
this.goal = config.getGoal() == CompetitionGoal.RANDOM ? CompetitionGoal.getRandom() : config.getGoal();
if (Config.redisRanking) this.ranking = new RedisRankingImpl();
else this.ranking = new LocalRankingImpl();
this.publicPlaceholders = new ConcurrentHashMap<>();
this.publicPlaceholders.put("{goal}", getCompetitionLocale(goal));
}
@Override
public void start() {
this.progress = 1;
this.remainingTime = config.getDuration();
this.startTime = Instant.now().getEpochSecond();
this.updatePublicPlaceholders();
this.arrangeTimerTask();
if (config.getBossBarConfig() != null) {
this.bossBarManager = new BossBarManager(config.getBossBarConfig(), this);
this.bossBarManager.load();
}
if (config.getActionBarConfig() != null) {
this.actionBarManager = new ActionBarManager(config.getActionBarConfig(), this);
this.actionBarManager.load();
}
Action[] actions = config.getStartActions();
if (actions != null) {
Condition condition = new Condition();
for (Action action : actions) {
action.trigger(condition);
}
}
}
private void arrangeTimerTask() {
this.competitionTimerTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> {
if (decreaseTime()) {
end();
return;
}
updatePublicPlaceholders();
}, 1, 1, TimeUnit.SECONDS);
}
private void updatePublicPlaceholders() {
for (int i = 1; i < Config.placeholderLimit + 1; i++) {
int finalI = i;
Optional.ofNullable(ranking.getPlayerAt(i)).ifPresentOrElse(player -> {
publicPlaceholders.put("{" + finalI + "_player}", player);
publicPlaceholders.put("{" + finalI + "_score}", String.format("%.2f", ranking.getScoreAt(finalI)));
}, () -> {
publicPlaceholders.put("{" + finalI + "_player}", Locale.MSG_No_Player);
publicPlaceholders.put("{" + finalI + "_score}", Locale.MSG_No_Score);
});
}
publicPlaceholders.put("{hour}", String.valueOf(remainingTime / 3600));
publicPlaceholders.put("{minute}", String.valueOf((remainingTime % 3600) / 60));
publicPlaceholders.put("{second}", String.valueOf(remainingTime % 60));
publicPlaceholders.put("{time}", String.valueOf(remainingTime));
}
@Override
public void stop() {
if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel();
if (this.bossBarManager != null) this.bossBarManager.unload();
if (this.actionBarManager != null) this.actionBarManager.unload();
this.ranking.clear();
this.remainingTime = 0;
}
@Override
public void end() {
// mark it as ended
this.remainingTime = 0;
// cancel some sub tasks
if (!competitionTimerTask.isCancelled()) this.competitionTimerTask.cancel();
if (this.bossBarManager != null) this.bossBarManager.unload();
if (this.actionBarManager != null) this.actionBarManager.unload();
// give prizes
HashMap<String, Action[]> rewardsMap = config.getRewards();
if (ranking.getSize() != 0 && rewardsMap != null) {
Iterator<Pair<String, Double>> iterator = ranking.getIterator();
int i = 1;
while (iterator.hasNext()) {
Pair<String, Double> competitionPlayer = iterator.next();
this.publicPlaceholders.put("{" + i + "_player}", competitionPlayer.left());
this.publicPlaceholders.put("{" + i + "_score}", String.format("%.2f", competitionPlayer.right()));
if (i < rewardsMap.size()) {
Player player = Bukkit.getPlayer(competitionPlayer.left());
if (player != null)
for (Action action : rewardsMap.get(String.valueOf(i)))
action.trigger(new Condition(player));
i++;
} else {
Action[] actions = rewardsMap.get("participation");
if (actions != null) {
iterator.forEachRemaining(playerName -> {
Player player = Bukkit.getPlayer(competitionPlayer.left());
if (player != null)
for (Action action : actions)
action.trigger(new Condition(player));
});
} else {
break;
}
}
}
}
// do end actions
Action[] actions = config.getEndActions();
if (actions != null) {
Condition condition = new Condition(new HashMap<>(publicPlaceholders));
for (Action action : actions) {
action.trigger(condition);
}
}
// 1.5 seconds delay for other servers to read the redis data
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(this.ranking::clear, 1500, TimeUnit.MILLISECONDS);
}
@Override
public boolean isOnGoing() {
return remainingTime > 0;
}
private boolean decreaseTime() {
long current = Instant.now().getEpochSecond();
int duration = config.getDuration();
remainingTime = duration - (current - startTime);
progress = (float) remainingTime / duration;
return remainingTime <= 0;
}
@Override
public void refreshData(Player player, double score, boolean doubleScore) {
// if player join for the first time, trigger join actions
if (!hasPlayerJoined(player)) {
Action[] actions = config.getJoinActions();
if (actions != null) {
Condition condition = new Condition(player);
for (Action action : actions) {
action.trigger(condition);
}
}
}
// show competition info
if (this.bossBarManager != null) this.bossBarManager.showBossBarTo(player);
if (this.actionBarManager != null) this.actionBarManager.showActionBarTo(player);
// refresh data
switch (this.goal) {
case CATCH_AMOUNT -> ranking.refreshData(player.getName(), doubleScore ? 2 : 1);
case TOTAL_SIZE, TOTAL_SCORE -> ranking.refreshData(player.getName(), doubleScore ? 2 * score : score);
case MAX_SIZE -> {
if (score > ranking.getPlayerScore(player.getName())) {
ranking.setData(player.getName(), score);
}
}
}
}
@Override
public boolean hasPlayerJoined(OfflinePlayer player) {
return ranking.getPlayerRank(player.getName()) != -1;
}
@Override
public float getProgress() {
return progress;
}
@Override
public long getRemainingTime() {
return remainingTime;
}
@Override
public long getStartTime() {
return startTime;
}
@Override
public CompetitionConfig getConfig() {
return config;
}
@Override
public CompetitionGoal getGoal() {
return goal;
}
@Override
public Ranking getRanking() {
return ranking;
}
public ConcurrentHashMap<String, String> getPublicPlaceholders() {
return publicPlaceholders;
}
private String getCompetitionLocale(CompetitionGoal goal) {
switch (goal) {
case MAX_SIZE -> {
return Locale.MSG_Max_Size;
}
case CATCH_AMOUNT -> {
return Locale.MSG_Catch_Amount;
}
case TOTAL_SCORE -> {
return Locale.MSG_Total_Score;
}
case TOTAL_SIZE -> {
return Locale.MSG_Total_Size;
}
}
return "";
}
}

View File

@@ -0,0 +1,259 @@
package net.momirealms.customfishing.mechanic.competition;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.manager.CompetitionManager;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.competition.*;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.storage.method.database.nosql.RedisManager;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CompetitionManagerImpl implements CompetitionManager {
private final CustomFishingPlugin plugin;
private final HashMap<CompetitionSchedule, CompetitionConfig> timeConfigMap;
private final HashMap<String, CompetitionConfig> commandConfigMap;
private Competition currentCompetition;
private CancellableTask timerCheckTask;
private int nextCompetitionSeconds;
public CompetitionManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.timeConfigMap = new HashMap<>();
this.commandConfigMap = new HashMap<>();
}
public void load() {
loadConfig();
this.timerCheckTask = plugin.getScheduler().runTaskAsyncTimer(
this::timerCheck,
1,
1,
TimeUnit.SECONDS
);
}
public void unload() {
if (this.timerCheckTask != null && !this.timerCheckTask.isCancelled())
this.timerCheckTask.cancel();
this.commandConfigMap.clear();
this.timeConfigMap.clear();
if (currentCompetition != null && currentCompetition.isOnGoing())
currentCompetition.end();
}
public void disable() {
if (this.timerCheckTask != null && !this.timerCheckTask.isCancelled())
this.timerCheckTask.cancel();
this.commandConfigMap.clear();
this.timeConfigMap.clear();
if (currentCompetition != null && currentCompetition.isOnGoing())
currentCompetition.stop();
}
@Override
public Set<String> getAllCompetitions() {
return commandConfigMap.keySet();
}
@SuppressWarnings("DuplicatedCode")
private void loadConfig() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("competitions")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
this.loadSingleFileCompetition(subFile);
}
}
}
}
}
private void loadSingleFileCompetition(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
CompetitionConfig.Builder builder = new CompetitionConfig.Builder(entry.getKey())
.goal(CompetitionGoal.valueOf(section.getString("goal", "TOTAL_SCORE").toUpperCase(Locale.ENGLISH)))
.minPlayers(section.getInt("min-players", 0))
.duration(section.getInt("duration", 300))
.rewards(getPrizeActions(section.getConfigurationSection("rewards")))
.startActions(plugin.getActionManager().getActions(section.getConfigurationSection("start-actions")))
.endActions(plugin.getActionManager().getActions(section.getConfigurationSection("end-actions")))
.skipActions(plugin.getActionManager().getActions(section.getConfigurationSection("skip-actions")));
if (section.getBoolean("bossbar.enable", false)) {
builder.bossbar(new BossBarConfig.Builder()
.color(BarColor.valueOf(section.getString("bossbar.color", "WHITE").toUpperCase(Locale.ENGLISH)))
.overlay(BossBarConfig.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.actionbar(new ActionBarConfig.Builder()
.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(this::getTimePair).toList();
List<Integer> weekdays = section.getIntegerList("start-weekday");
if (weekdays.size() == 0) {
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);
}
}
}
public HashMap<String, Action[]> getPrizeActions(ConfigurationSection section) {
HashMap<String, Action[]> map = new HashMap<>();
if (section == null) return map;
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
map.put(entry.getKey(), plugin.getActionManager().getActions(innerSection));
}
}
return map;
}
public Pair<Integer, Integer> getTimePair(String time) {
String[] split = time.split(":");
return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
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, false);
}
}
@Override
public void startCompetition(String competition, boolean force, boolean allServer) {
CompetitionConfig config = commandConfigMap.get(competition);
if (config == null) {
LogUtils.warn("Competition " + competition + " doesn't exist.");
return;
}
startCompetition(config, force, allServer);
}
@Override
@Nullable
public FishingCompetition getOnGoingCompetition() {
if (currentCompetition == null) return null;
return currentCompetition.isOnGoing() ? currentCompetition : null;
}
@Override
public void startCompetition(CompetitionConfig config, boolean force, boolean allServer) {
if (!force)
this.getPlayerCount().thenAccept(count -> {
if (count < config.getMinPlayers()) {
var actions = config.getSkipActions();
if (actions != null)
for (Player player : Bukkit.getOnlinePlayers()) {
for (Action action : actions) {
action.trigger(new Condition(player));
}
}
return;
}
start(config);
});
else if (!allServer) {
start(config);
} else {
RedisManager.getInstance().sendRedisMessage("cf_competition", "start;" + config.getKey());
}
}
private void start(CompetitionConfig config) {
if (getOnGoingCompetition() != null) {
currentCompetition.end();
plugin.getScheduler().runTaskAsyncLater(() -> {
this.currentCompetition = new Competition(config);
this.currentCompetition.start();
}, 1, TimeUnit.SECONDS);
} else {
this.currentCompetition = new Competition(config);
this.currentCompetition.start();
}
}
@Override
public int getNextCompetitionSeconds() {
return nextCompetitionSeconds;
}
@Override
public CompletableFuture<Integer> getPlayerCount() {
if (!Config.redisRanking) {
return CompletableFuture.completedFuture(Bukkit.getOnlinePlayers().size());
} else {
return plugin.getStorageManager().getRedisPlayerCount();
}
}
@Nullable
@Override
public CompetitionConfig getConfig(String key) {
return commandConfigMap.get(key);
}
}

View File

@@ -0,0 +1,77 @@
package net.momirealms.customfishing.mechanic.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;
}
public int getWeekday() {
return weekday;
}
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
public int getSecond() {
return second;
}
public int getTotalSeconds() {
return second +
minute * 60 +
hour * 60 * 60 +
weekday * 24 * 60 * 60;
}
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;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.mechanic.competition.actionbar;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.competition.ActionBarConfig;
import net.momirealms.customfishing.mechanic.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;
}
public void load() {
Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.getInstance());
if (actionBarConfig.isShowToAll()) {
for (Player player : Bukkit.getOnlinePlayers()) {
ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition);
if (!sender.isVisible()) {
sender.show();
}
senderMap.put(player.getUniqueId(), sender);
}
}
}
public void unload() {
HandlerList.unregisterAll(this);
for (ActionBarSender ActionBarSender : senderMap.values()) {
ActionBarSender.hide();
}
senderMap.clear();
}
@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();
}
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
CustomFishingPlugin.getInstance().getScheduler().runTaskAsyncLater(() -> {
boolean hasJoined = competition.hasPlayerJoined(player);
if ((hasJoined || actionBarConfig.isShowToAll())
&& !senderMap.containsKey(player.getUniqueId())) {
ActionBarSender sender = new ActionBarSender(player, actionBarConfig, competition);
if (!sender.isVisible()) {
sender.show();
}
senderMap.put(player.getUniqueId(), sender);
}
}, 200, TimeUnit.MILLISECONDS);
}
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();
}
}
}

View File

@@ -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.mechanic.competition.actionbar;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.competition.ActionBarConfig;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.mechanic.competition.Competition;
import net.momirealms.customfishing.setting.Locale;
import net.momirealms.customfishing.util.DynamicText;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
public class ActionBarSender {
private final Player player;
private int refreshTimer;
private int switchTimer;
private int counter;
private final DynamicText[] texts;
private CancellableTask senderTask;
private final ActionBarConfig config;
private boolean isShown;
private final Competition competition;
private final HashMap<String, String> privatePlaceholders;
public ActionBarSender(Player player, ActionBarConfig config, Competition competition) {
this.player = player;
this.config = config;
this.isShown = false;
this.competition = competition;
this.privatePlaceholders = new HashMap<>();
this.privatePlaceholders.put("{player}", player.getName());
this.updatePrivatePlaceholders();
String[] str = config.getTexts();
texts = new DynamicText[str.length];
for (int i = 0; i < str.length; i++) {
texts[i] = new DynamicText(player, str[i]);
texts[i].update(privatePlaceholders);
}
}
@SuppressWarnings("DuplicatedCode")
private void updatePrivatePlaceholders() {
this.privatePlaceholders.put("{score}", String.format("%.2f", competition.getRanking().getPlayerScore(player.getName())));
int rank = competition.getRanking().getPlayerRank(player.getName());
this.privatePlaceholders.put("{rank}", rank != -1 ? String.valueOf(rank) : Locale.MSG_No_Rank);
this.privatePlaceholders.putAll(competition.getPublicPlaceholders());
}
public void show() {
this.isShown = true;
senderTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> {
switchTimer++;
if (switchTimer > config.getSwitchInterval()) {
switchTimer = 0;
counter++;
}
if (refreshTimer < config.getRefreshRate()){
refreshTimer++;
} else {
refreshTimer = 0;
DynamicText text = texts[counter % (texts.length)];
updatePrivatePlaceholders();
text.update(privatePlaceholders);
AdventureManagerImpl.getInstance().sendActionbar(
player,
AdventureManagerImpl.getInstance().legacyToMiniMessage(text.getLatestValue())
);
}
}, 50, 50, TimeUnit.MILLISECONDS);
}
public void hide() {
if (senderTask != null && !senderTask.isCancelled())
senderTask.cancel();
this.isShown = false;
}
public boolean isVisible() {
return this.isShown;
}
public ActionBarConfig getConfig() {
return config;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.mechanic.competition.bossbar;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.competition.BossBarConfig;
import net.momirealms.customfishing.mechanic.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;
}
public void load() {
Bukkit.getPluginManager().registerEvents(this, CustomFishingPlugin.getInstance());
if (bossBarConfig.isShowToAll()) {
for (Player player : Bukkit.getOnlinePlayers()) {
BossBarSender sender = new BossBarSender(player, bossBarConfig, competition);
if (!sender.isVisible()) {
sender.show();
}
senderMap.put(player.getUniqueId(), sender);
}
}
}
public void unload() {
HandlerList.unregisterAll(this);
for (BossBarSender bossBarSender : senderMap.values()) {
bossBarSender.hide();
}
senderMap.clear();
}
@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();
}
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
CustomFishingPlugin.getInstance().getScheduler().runTaskAsyncLater(() -> {
boolean hasJoined = competition.hasPlayerJoined(player);
if ((hasJoined || bossBarConfig.isShowToAll())
&& !senderMap.containsKey(player.getUniqueId())) {
BossBarSender sender = new BossBarSender(player, bossBarConfig, competition);
if (!sender.isVisible()) {
sender.show();
}
senderMap.put(player.getUniqueId(), sender);
}
}, 200, TimeUnit.MILLISECONDS);
}
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();
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* 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.mechanic.competition.bossbar;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.InternalStructure;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.competition.BossBarConfig;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.api.util.ReflectionUtils;
import net.momirealms.customfishing.mechanic.competition.Competition;
import net.momirealms.customfishing.setting.Locale;
import net.momirealms.customfishing.util.DynamicText;
import org.bukkit.boss.BarColor;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BossBarSender {
private final Player player;
private int refreshTimer;
private int switchTimer;
private int counter;
private final DynamicText[] texts;
private CancellableTask senderTask;
private final UUID uuid;
private final BossBarConfig config;
private boolean isShown;
private final Competition competition;
private final HashMap<String, String> privatePlaceholders;
public BossBarSender(Player player, BossBarConfig config, Competition competition) {
this.player = player;
this.uuid = UUID.randomUUID();
this.config = config;
this.isShown = false;
this.competition = competition;
this.privatePlaceholders = new HashMap<>();
this.privatePlaceholders.put("{player}", player.getName());
this.updatePrivatePlaceholders();
String[] str = config.getTexts();
texts = new DynamicText[str.length];
for (int i = 0; i < str.length; i++) {
texts[i] = new DynamicText(player, str[i]);
texts[i].update(privatePlaceholders);
}
}
@SuppressWarnings("DuplicatedCode")
private void updatePrivatePlaceholders() {
this.privatePlaceholders.put("{score}", String.format("%.2f", competition.getRanking().getPlayerScore(player.getName())));
int rank = competition.getRanking().getPlayerRank(player.getName());
this.privatePlaceholders.put("{rank}", rank != -1 ? String.valueOf(rank) : Locale.MSG_No_Rank);
this.privatePlaceholders.putAll(competition.getPublicPlaceholders());
}
public void show() {
this.isShown = true;
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getCreatePacket());
senderTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(() -> {
switchTimer++;
if (switchTimer > config.getSwitchInterval()) {
switchTimer = 0;
counter++;
}
if (refreshTimer < config.getRefreshRate()){
refreshTimer++;
} else {
refreshTimer = 0;
DynamicText text = texts[counter % (texts.length)];
updatePrivatePlaceholders();
if (text.update(privatePlaceholders)) {
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getUpdatePacket(text));
}
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getProgressPacket());
}
}, 50, 50, TimeUnit.MILLISECONDS);
}
public boolean isVisible() {
return this.isShown;
}
public BossBarConfig getConfig() {
return config;
}
public void hide() {
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, getRemovePacket());
if (senderTask != null && !senderTask.isCancelled()) senderTask.cancel();
this.isShown = false;
}
private PacketContainer getUpdatePacket(DynamicText text) {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
packet.getModifier().write(0, uuid);
try {
Object chatComponent = ReflectionUtils.iChatComponentMethod.invoke(null,
GsonComponentSerializer.gson().serialize(
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
AdventureManagerImpl.getInstance().legacyToMiniMessage(text.getLatestValue())
)));
Object updatePacket = ReflectionUtils.updateConstructor.newInstance(chatComponent);
packet.getModifier().write(1, updatePacket);
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
return packet;
}
private PacketContainer getProgressPacket() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
packet.getModifier().write(0, uuid);
try {
Object updatePacket = ReflectionUtils.progressConstructor.newInstance(competition.getProgress());
packet.getModifier().write(1, updatePacket);
} catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
return packet;
}
private PacketContainer getCreatePacket() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
packet.getModifier().write(0, uuid);
InternalStructure internalStructure = packet.getStructures().read(1);
internalStructure.getChatComponents().write(0, WrappedChatComponent.fromJson(GsonComponentSerializer.gson().serialize(MiniMessage.miniMessage().deserialize(texts[0].getLatestValue()))));
internalStructure.getFloat().write(0, competition.getProgress());
internalStructure.getEnumModifier(BarColor.class, 2).write(0, config.getColor());
internalStructure.getEnumModifier(BossBarConfig.Overlay.class, 3).write(0, config.getOverlay());
internalStructure.getModifier().write(4, false);
internalStructure.getModifier().write(5, false);
internalStructure.getModifier().write(6, false);
return packet;
}
private PacketContainer getRemovePacket() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.BOSS);
packet.getModifier().write(0, uuid);
packet.getModifier().write(1, ReflectionUtils.removeBossBarPacket);
return packet;
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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.mechanic.competition.ranking;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer;
import net.momirealms.customfishing.api.mechanic.competition.Ranking;
import java.util.*;
public class LocalRankingImpl implements Ranking {
private final Set<CompetitionPlayer> competitionPlayers;
public LocalRankingImpl() {
competitionPlayers = Collections.synchronizedSet(new TreeSet<>());
}
public void addPlayer(CompetitionPlayer competitionPlayer) {
competitionPlayers.add(competitionPlayer);
}
public void removePlayer(CompetitionPlayer competitionPlayer) {
competitionPlayers.removeIf(e -> e.equals(competitionPlayer));
}
@Override
public void clear() {
competitionPlayers.clear();
}
@Override
public CompetitionPlayer getCompetitionPlayer(String player) {
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
if (competitionPlayer.getPlayer().equals(player)) {
return competitionPlayer;
}
}
return null;
}
@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();
}
@Override
public int getSize() {
return competitionPlayers.size();
}
@Override
public int getPlayerRank(String player) {
int index = 1;
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
if (competitionPlayer.getPlayer().equals(player)) {
return index;
}else {
index++;
}
}
return -1;
}
@Override
public double getPlayerScore(String player) {
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
if (competitionPlayer.getPlayer().equals(player)) {
return competitionPlayer.getScore();
}
}
return 0;
}
@Override
public String getPlayerAt(int i) {
int index = 1;
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
if (index == i) {
return competitionPlayer.getPlayer();
}
index++;
}
return null;
}
@Override
public double getScoreAt(int i) {
int index = 1;
for (CompetitionPlayer competitionPlayer : competitionPlayers) {
if (index == i) {
return competitionPlayer.getScore();
}
index++;
}
return 0f;
}
@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);
}
}
@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);
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.mechanic.competition.ranking;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.mechanic.competition.CompetitionPlayer;
import net.momirealms.customfishing.api.mechanic.competition.Ranking;
import net.momirealms.customfishing.storage.method.database.nosql.RedisManager;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.resps.Tuple;
import java.util.Iterator;
import java.util.List;
public class RedisRankingImpl implements Ranking {
@Override
public void clear() {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
jedis.zremrangeByRank("cf_competition",0,-1);
}
}
@Override
public CompetitionPlayer getCompetitionPlayer(String player) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
Double score = jedis.zscore("cf_competition", player);
if (score == null || score == 0) return null;
return new CompetitionPlayer(player, Float.parseFloat(score.toString()));
}
}
@Override
public Iterator<Pair<String, Double>> getIterator() {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
List<Tuple> players = jedis.zrevrangeWithScores("cf_competition", 0, -1);
return players.stream().map(it -> Pair.of(it.getElement(), it.getScore())).toList().iterator();
}
}
@Override
public int getSize() {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
long size = jedis.zcard("cf_competition");
return (int) size;
}
}
@Override
public int getPlayerRank(String player) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
Long rank = jedis.zrevrank("cf_competition", player);
if (rank == null)
return -1;
return (int) (rank + 1);
}
}
@Override
public double getPlayerScore(String player) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
Double rank = jedis.zscore("cf_competition", player);
if (rank == null)
return 0;
return rank.floatValue();
}
}
@Override
public void refreshData(String player, double score) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
jedis.zincrby("cf_competition", score, player);
}
}
@Override
public void setData(String player, double score) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
jedis.zadd("cf_competition", score, player);
}
}
@Override
public String getPlayerAt(int rank) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
List<String> player = jedis.zrevrange("cf_competition", rank - 1, rank -1);
if (player == null || player.size() == 0) return null;
return player.get(0);
}
}
@Override
public double getScoreAt(int rank) {
try (Jedis jedis = RedisManager.getInstance().getJedis()) {
List<Tuple> players = jedis.zrevrangeWithScores("cf_competition", rank - 1, rank -1);
if (players == null || players.size() == 0) return 0;
return players.get(0).getScore();
}
}
}

View File

@@ -0,0 +1,114 @@
package net.momirealms.customfishing.mechanic.effect;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Key;
import net.momirealms.customfishing.api.manager.EffectManager;
import net.momirealms.customfishing.api.mechanic.effect.Effect;
import net.momirealms.customfishing.api.mechanic.effect.FishingEffect;
import net.momirealms.customfishing.util.ConfigUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class EffectManagerImpl implements EffectManager {
private final CustomFishingPlugin plugin;
private final HashMap<Key, Effect> effectMap;
public EffectManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.effectMap = new HashMap<>();
}
@Override
public boolean registerEffect(Key key, Effect effect) {
if (effectMap.containsKey(key)) return false;
this.effectMap.put(key, effect);
return true;
}
@Override
public boolean unregisterEffect(Key key) {
return this.effectMap.remove(key) != null;
}
@Nullable
@Override
public Effect getEffect(String namespace, String id) {
return effectMap.get(Key.of(namespace, id));
}
@SuppressWarnings("DuplicatedCode")
public void load() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("rods", "baits", "enchants", "utils", "totems")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
this.loadSingleFile(subFile, StringUtils.chop(type));
}
}
}
}
}
private void loadSingleFile(File file, String namespace) {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
String value = entry.getKey();
if (entry.getValue() instanceof ConfigurationSection section) {
effectMap.put(Key.of(namespace, value), getFishingEffectFromSection(section));
}
}
}
private Effect getFishingEffectFromSection(ConfigurationSection section) {
if (section == null) return getInitialEffect();
return new FishingEffect.Builder()
.lootWeightModifier(ConfigUtils.getModifiers(section.getStringList("weight")))
.timeModifier(section.getDouble("hook-time", 1))
.difficultyModifier(section.getDouble("difficulty", 0))
.multipleLootChance(section.getDouble("multiple-loot"))
.lavaFishing(section.getBoolean("lava-fishing", false))
.scoreMultiplier(section.getDouble("score-bonus", 1))
.sizeMultiplier(section.getDouble("size-bonus", 1))
.gameTimeModifier(section.getDouble("game-time", 0))
.requirements(plugin.getRequirementManager().getRequirements(section.getConfigurationSection("requirements"), true))
.build();
}
public void unload() {
HashMap<Key, Effect> temp = new HashMap<>(effectMap);
effectMap.clear();
for (Map.Entry<Key, Effect> entry : temp.entrySet()) {
if (entry.getValue().persist()) {
effectMap.put(entry.getKey(), entry.getValue());
}
}
}
@Override
public Effect getInitialEffect() {
return new FishingEffect.Builder().build();
}
public void disable() {
this.effectMap.clear();
}
}

View File

@@ -0,0 +1,49 @@
package net.momirealms.customfishing.mechanic.fishing;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.util.FakeItemUtils;
import org.bukkit.entity.FishHook;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class BaitAnimationTask implements Runnable {
private final CancellableTask cancellableTask;
private final int entityID;
private final Player player;
private final FishHook fishHook;
public BaitAnimationTask(CustomFishingPlugin plugin, Player player, FishHook fishHook, ItemStack baitItem) {
this.player = player;
this.fishHook = fishHook;
entityID = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getSpawnPacket(entityID, fishHook.getLocation()));
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getMetaPacket(entityID, baitItem));
this.cancellableTask = plugin.getScheduler().runTaskAsyncTimer(this, 50, 50, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
if ( fishHook == null
|| fishHook.isOnGround()
|| fishHook.isInLava()
|| fishHook.isInWater()
|| !fishHook.isValid()
) {
cancelAnimation();
} else {
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getVelocity(entityID, fishHook.getVelocity()));
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getTpPacket(entityID, fishHook.getLocation()));
}
}
private void cancelAnimation() {
cancellableTask.cancel();
CustomFishingPluginImpl.getProtocolManager().sendServerPacket(player, FakeItemUtils.getDestroyPacket(entityID));
}
}

View File

@@ -0,0 +1,570 @@
package net.momirealms.customfishing.mechanic.fishing;
import com.destroystokyo.paper.event.player.PlayerJumpEvent;
import de.tr7zw.changeme.nbtapi.NBTItem;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.event.LavaFishingEvent;
import net.momirealms.customfishing.api.event.RodCastEvent;
import net.momirealms.customfishing.api.manager.FishingManager;
import net.momirealms.customfishing.api.manager.RequirementManager;
import net.momirealms.customfishing.api.mechanic.TempFishingState;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation;
import net.momirealms.customfishing.api.mechanic.effect.Effect;
import net.momirealms.customfishing.api.mechanic.game.Game;
import net.momirealms.customfishing.api.mechanic.game.GameConfig;
import net.momirealms.customfishing.api.mechanic.game.GameSettings;
import net.momirealms.customfishing.api.mechanic.game.GamingPlayer;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.mechanic.loot.Modifier;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.api.util.WeightUtils;
import net.momirealms.customfishing.mechanic.loot.LootManagerImpl;
import net.momirealms.customfishing.mechanic.requirement.RequirementManagerImpl;
import net.momirealms.customfishing.setting.Config;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.Statistic;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryPickupItemEvent;
import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class FishingManagerImpl implements Listener, FishingManager {
private final CustomFishingPluginImpl plugin;
private final ConcurrentHashMap<UUID, FishHook> hookCacheMap;
private final ConcurrentHashMap<UUID, HookCheckTimerTask> hookCheckMap;
private final ConcurrentHashMap<UUID, TempFishingState> tempFishingStateMap;
private final ConcurrentHashMap<UUID, GamingPlayer> gamingPlayerMap;
private final ConcurrentHashMap<UUID, ItemStack> vanillaLootMap;
public FishingManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
this.hookCacheMap = new ConcurrentHashMap<>();
this.tempFishingStateMap = new ConcurrentHashMap<>();
this.gamingPlayerMap = new ConcurrentHashMap<>();
this.hookCheckMap = new ConcurrentHashMap<>();
this.vanillaLootMap = new ConcurrentHashMap<>();
}
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
public void unload() {
HandlerList.unregisterAll(this);
for (FishHook hook : hookCacheMap.values()) {
hook.remove();
}
for (HookCheckTimerTask task : hookCheckMap.values()) {
task.destroy();
}
for (GamingPlayer gamingPlayer : gamingPlayerMap.values()) {
gamingPlayer.cancel();
}
this.hookCacheMap.clear();
this.tempFishingStateMap.clear();
this.gamingPlayerMap.clear();
this.hookCheckMap.clear();
}
public void disable() {
unload();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onFishMONITOR(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.MONITOR) return;
this.selectState(event);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onFishHIGHEST(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.HIGHEST) return;
this.selectState(event);
}
@EventHandler(priority = EventPriority.HIGH)
public void onFishHIGH(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.HIGH) return;
this.selectState(event);
}
@EventHandler(priority = EventPriority.NORMAL)
public void onFishNORMAL(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.NORMAL) return;
this.selectState(event);
}
@EventHandler(priority = EventPriority.LOW)
public void onFishLOW(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.LOW) return;
this.selectState(event);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onFishLOWEST(PlayerFishEvent event) {
if (Config.eventPriority != EventPriority.LOWEST) return;
this.selectState(event);
}
@EventHandler
public void onPickUp(PlayerAttemptPickupItemEvent event) {
if (event.isCancelled()) return;
ItemStack itemStack = event.getItem().getItemStack();
NBTItem nbtItem = new NBTItem(itemStack);
if (!nbtItem.hasTag("owner")) return;
if (!Objects.equals(nbtItem.getString("owner"), event.getPlayer().getName())) {
event.setCancelled(true);
} else {
nbtItem.removeKey("owner");
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
}
}
@EventHandler
public void onMove(InventoryPickupItemEvent event) {
if (event.isCancelled()) return;
ItemStack itemStack = event.getItem().getItemStack();
NBTItem nbtItem = new NBTItem(itemStack);
if (!nbtItem.hasTag("owner")) return;
nbtItem.removeKey("owner");
itemStack.setItemMeta(nbtItem.getItem().getItemMeta());
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
this.removeHook(event.getPlayer().getUniqueId());
}
@EventHandler
public void onSwapHand(PlayerSwapHandItemsEvent event) {
if (event.isCancelled()) return;
GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId());
if (gamingPlayer != null) {
if (gamingPlayer.onSwapHand())
event.setCancelled(true);
}
}
@EventHandler
public void onJump(PlayerJumpEvent event) {
if (event.isCancelled()) return;
GamingPlayer gamingPlayer = gamingPlayerMap.get(event.getPlayer().getUniqueId());
if (gamingPlayer != null) {
if (gamingPlayer.onJump())
event.setCancelled(true);
}
}
@Override
public boolean removeHook(UUID uuid) {
FishHook hook = hookCacheMap.remove(uuid);
if (hook != null && hook.isValid()) {
plugin.getScheduler().runTaskSync(hook::remove, hook.getLocation());
return true;
} else {
return false;
}
}
public void selectState(PlayerFishEvent event) {
if (event.isCancelled()) return;
switch (event.getState()) {
case FISHING -> onCastRod(event);
case REEL_IN -> onReelIn(event);
case CAUGHT_ENTITY -> onCaughtEntity(event);
case CAUGHT_FISH -> onCaughtFish(event);
case BITE -> onBite(event);
}
}
public void onCastRod(PlayerFishEvent event) {
var player = event.getPlayer();
var fishingPreparation = new FishingPreparation(player, plugin);
if (!fishingPreparation.canFish()) {
event.setCancelled(true);
return;
}
// Check mechanic requirements
if (!RequirementManager.isRequirementsMet(
RequirementManagerImpl.mechanicRequirements,
fishingPreparation
)) {
return;
}
// Merge rod/bait/util effects
Effect initialEffect = plugin.getEffectManager().getInitialEffect();
initialEffect
.merge(fishingPreparation.getRodEffect())
.merge(fishingPreparation.getBaitEffect());
for (Effect utilEffect : fishingPreparation.getUtilEffects()) {
initialEffect.merge(utilEffect);
}
// Apply enchants
for (String enchant : plugin.getIntegrationManager().getEnchantments(fishingPreparation.getRodItemStack())) {
Effect enchantEffect = plugin.getEffectManager().getEffect("enchant", enchant);
if (enchantEffect != null && enchantEffect.canMerge(fishingPreparation)) {
initialEffect.merge(enchantEffect);
}
}
//TODO Apply totem effects
// Call custom event
RodCastEvent rodCastEvent = new RodCastEvent(event, initialEffect);
Bukkit.getPluginManager().callEvent(rodCastEvent);
if (rodCastEvent.isCancelled()) {
return;
}
// Store fishhook entity and apply the effects
final FishHook fishHook = event.getHook();
this.hookCacheMap.put(player.getUniqueId(), fishHook);
fishHook.setMaxWaitTime((int) (fishHook.getMaxWaitTime() * initialEffect.getTimeModifier()));
fishHook.setMinWaitTime((int) (fishHook.getMinWaitTime() * initialEffect.getTimeModifier()));
// Reduce amount & Send animation
var baitItem = fishingPreparation.getBaitItemStack();
if (baitItem != null) {
if (Config.enableBaitAnimation) {
ItemStack cloned = baitItem.clone();
cloned.setAmount(1);
new BaitAnimationTask(plugin, player, fishHook, cloned);
}
baitItem.setAmount(baitItem.getAmount() - 1);
}
// Arrange hook check task
this.hookCheckMap.put(player.getUniqueId(), new HookCheckTimerTask(this, fishHook, fishingPreparation, initialEffect));
}
private void onCaughtEntity(PlayerFishEvent event) {
final Player player = event.getPlayer();
final UUID uuid = player.getUniqueId();
Entity entity = event.getCaught();
if ((entity instanceof ArmorStand armorStand)
&& armorStand.getPersistentDataContainer().get(
Objects.requireNonNull(NamespacedKey.fromString("lavafishing", plugin)),
PersistentDataType.BOOLEAN
) != null) {
// The hook is hooked into the temp entity
// This might be called both not in game and in game
LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(player, LavaFishingEvent.State.REEL_IN, event.getHook());
Bukkit.getPluginManager().callEvent(lavaFishingEvent);
if (lavaFishingEvent.isCancelled()) {
event.setCancelled(true);
return;
}
GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid);
if (gamingPlayer != null) {
// in game
if (gamingPlayer.onRightClick())
event.setCancelled(true);
} else {
// not in game
HookCheckTimerTask task = hookCheckMap.get(uuid);
if (task != null)
task.destroy();
else
// should not reach this but in case
entity.remove();
}
}
// TODO It's unsure if the hook would hook into other entities when playing a game
// TODO But it should not affect the game result
}
private void onCaughtFish(PlayerFishEvent event) {
final Player player = event.getPlayer();
final UUID uuid = player.getUniqueId();
if (!(event.getCaught() instanceof Item item)) return;
// If player is playing the game
GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid);
if (gamingPlayer != null) {
if (gamingPlayer.onRightClick()) {
event.setCancelled(true);
}
return;
}
// If player is not playing the game
var temp = this.tempFishingStateMap.get(uuid);
if (temp != null ) {
var loot = temp.getLoot();
if (loot.getID().equals("vanilla")) {
// put vanilla loot in map
this.vanillaLootMap.put(uuid, item.getItemStack());
}
if (!loot.disableGame()) {
// start the game if the loot has a game
event.setCancelled(true);
startFishingGame(player, temp.getLoot(), temp.getEffect());
} else {
// If the game is disabled, then do success actions
success(temp, event.getHook());
// Cancel the event because loots can be multiple and unique
event.setCancelled(true);
event.getHook().remove();
}
return;
}
if (!Config.vanillaMechanicIfNoLoot) {
event.setCancelled(true);
event.getHook().remove();
}
}
private void onBite(PlayerFishEvent event) {
final Player player = event.getPlayer();
final UUID uuid = player.getUniqueId();
// If player is already in game
// then ignore the event
GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid);
if (gamingPlayer != null) {
return;
}
// If the loot's game is instant
TempFishingState temp = tempFishingStateMap.get(uuid);
if (temp != null) {
var loot = temp.getLoot();
Action[] actions = loot.getActions(ActionTrigger.HOOK);
if (actions != null)
for (Action action : actions)
action.trigger(temp.getPreparation());
if (loot.instanceGame() && !loot.disableGame()) {
startFishingGame(player, loot, temp.getEffect());
}
}
}
private void onReelIn(PlayerFishEvent event) {
final Player player = event.getPlayer();
final UUID uuid = player.getUniqueId();
// If player is in game
GamingPlayer gamingPlayer = gamingPlayerMap.get(uuid);
if (gamingPlayer != null) {
if (gamingPlayer.onRightClick())
event.setCancelled(true);
return;
}
// If player is lava fishing
HookCheckTimerTask hookTask = hookCheckMap.get(uuid);
if (hookTask != null && hookTask.isFishHooked()) {
LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(player, LavaFishingEvent.State.CAUGHT_FISH, event.getHook());
Bukkit.getPluginManager().callEvent(lavaFishingEvent);
if (lavaFishingEvent.isCancelled()) {
event.setCancelled(true);
return;
}
var temp = this.tempFishingStateMap.get(uuid);
if (temp != null ) {
if (!temp.getLoot().disableGame()) {
event.setCancelled(true);
startFishingGame(player, temp.getLoot(), temp.getEffect());
} else {
success(temp, event.getHook());
}
}
}
}
@Override
public void removeTempFishingState(Player player) {
this.tempFishingStateMap.remove(player.getUniqueId());
}
@Override
public void processGameResult(GamingPlayer gamingPlayer) {
final Player player = gamingPlayer.getPlayer();
final UUID uuid = player.getUniqueId();
FishHook fishHook = hookCacheMap.remove(uuid);
if (fishHook == null) {
LogUtils.warn("Unexpected situation: Can't get player's fish hook when processing game results.");
return;
}
TempFishingState tempFishingState = tempFishingStateMap.remove(uuid);
if (tempFishingState == null) {
LogUtils.warn("Unexpected situation: Can't get player's fishing state when processing game results.");
return;
}
Effect bonus = gamingPlayer.getEffectReward();
if (bonus != null)
tempFishingState.getEffect().merge(bonus);
if (gamingPlayer.isSucceeded())
success(tempFishingState, fishHook);
else
fail(tempFishingState);
// remove hook because some games don't depend on right clicks
fishHook.remove();
gamingPlayer.cancel();
gamingPlayerMap.remove(uuid);
}
public void fail(TempFishingState state) {
var loot = state.getLoot();
var fishingPreparation = state.getPreparation();
if (loot.getID().equals("vanilla")) {
ItemStack itemStack = this.vanillaLootMap.remove(fishingPreparation.getPlayer().getUniqueId());
if (itemStack != null) {
fishingPreparation.insertArg("loot", "<lang:item.minecraft." + itemStack.getType().toString().toLowerCase() + ">");
}
}
Action[] globalActions = LootManagerImpl.globalLootProperties.getActions(ActionTrigger.FAILURE);
if (globalActions != null)
for (Action action : globalActions)
action.trigger(fishingPreparation);
Action[] actions = loot.getActions(ActionTrigger.FAILURE);
if (actions != null)
for (Action action : actions)
action.trigger(fishingPreparation);
}
public void success(TempFishingState state, FishHook hook) {
var loot = state.getLoot();
var effect = state.getEffect();
var fishingPreparation = state.getPreparation();
var player = fishingPreparation.getPlayer();
int amount = (int) effect.getMultipleLootChance();
amount += Math.random() < (effect.getMultipleLootChance() - amount) ? 2 : 1;
fishingPreparation.insertArg("{amount}", String.valueOf(amount));
fishingPreparation.insertArg("{score}", String.format("%.2f", loot.getScore() * effect.getScoreMultiplier()));
fishingPreparation.insertArg("{size-multiplier}", String.format("%.2f", effect.getSizeMultiplier()));
fishingPreparation.insertArg("{x}", String.valueOf(hook.getLocation().getBlockX()));
fishingPreparation.insertArg("{y}", String.valueOf(hook.getLocation().getBlockY()));
fishingPreparation.insertArg("{z}", String.valueOf(hook.getLocation().getBlockZ()));
fishingPreparation.insertArg("{loot}", loot.getID());
fishingPreparation.insertArg("{nick}", loot.getNick());
fishingPreparation.insertArg("{score}", String.format("%.2f", loot.getScore()));
switch (loot.getType()) {
case LOOT -> {
// build the items for multiple times instead of using setAmount() to make sure that each item is unique
if (loot.getID().equals("vanilla")) {
ItemStack itemStack = vanillaLootMap.remove(player.getUniqueId());
if (itemStack != null) {
fishingPreparation.insertArg("{loot}", "<lang:item.minecraft." + itemStack.getType().toString().toLowerCase() + ">");
for (int i = 0; i < amount; i++) {
plugin.getItemManager().dropItem(hook.getLocation(), player.getLocation(), itemStack.clone());
doActions(loot, fishingPreparation, player);
}
}
} else {
for (int i = 0; i < amount; i++) {
plugin.getItemManager().dropItem(player, hook.getLocation(), player.getLocation(), loot, fishingPreparation.getArgs());
doActions(loot, fishingPreparation, player);
}
}
return;
}
case MOB -> plugin.getMobManager().summonMob(hook.getLocation(), player.getLocation(), loot);
case BLOCK -> plugin.getBlockManager().summonBlock(player, hook.getLocation(), player.getLocation(), loot);
}
doActions(loot, fishingPreparation, player);
}
private void doActions(Loot loot, FishingPreparation fishingPreparation, Player player) {
Action[] globalActions = LootManagerImpl.globalLootProperties.getActions(ActionTrigger.SUCCESS);
if (globalActions != null)
for (Action action : globalActions)
action.trigger(fishingPreparation);
Action[] actions = loot.getActions(ActionTrigger.SUCCESS);
if (actions != null)
for (Action action : actions)
action.trigger(fishingPreparation);
player.setStatistic(
Statistic.FISH_CAUGHT,
player.getStatistic(Statistic.FISH_CAUGHT) + 1
);
}
@Nullable
public Loot getNextLoot(Effect initialEffect, FishingPreparation fishingPreparation) {
HashMap<String, Double> lootWithWeight = plugin.getRequirementManager().getLootWithWeight(fishingPreparation);
if (lootWithWeight.size() == 0) {
LogUtils.warn(String.format("No Loot found at %s for Player %s!", fishingPreparation.getPlayer().getLocation(), fishingPreparation.getPlayer().getName()));
return null;
}
for (Pair<String, Modifier> pair : initialEffect.getLootWeightModifier()) {
double previous = lootWithWeight.getOrDefault(pair.left(), 0d);
lootWithWeight.put(pair.left(), pair.right().modify(previous));
}
String key = WeightUtils.getRandom(lootWithWeight);
Loot loot = plugin.getLootManager().getLoot(key);
if (loot == null) {
LogUtils.warn(String.format("Loot %s doesn't exist!", key));
return null;
}
return loot;
}
@Override
public void startFishingGame(Player player, Loot loot, Effect effect) {
GameConfig gameConfig = loot.getGameConfig();
if (gameConfig == null) {
gameConfig = plugin.getGameManager().getRandomGameConfig();
}
var gamePair = gameConfig.getRandomGame(effect);
if (gamePair == null) {
return;
}
startFishingGame(player, gamePair.right(), gamePair.left());
}
@Override
public void startFishingGame(Player player, GameSettings settings, Game game) {
this.gamingPlayerMap.put(player.getUniqueId(), game.start(player, settings, this));
}
@Override
public void setTempFishingState(Player player, TempFishingState tempFishingState) {
tempFishingStateMap.put(player.getUniqueId(), tempFishingState);
}
@Override
public void removeHookCheckTask(Player player) {
hookCheckMap.remove(player.getUniqueId());
}
}

View File

@@ -0,0 +1,225 @@
package net.momirealms.customfishing.mechanic.fishing;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.event.LavaFishingEvent;
import net.momirealms.customfishing.api.mechanic.TempFishingState;
import net.momirealms.customfishing.api.mechanic.condition.FishingPreparation;
import net.momirealms.customfishing.api.mechanic.effect.Effect;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.util.ArmorStandUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.FishHook;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.Vector;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class HookCheckTimerTask implements Runnable {
private final FishingManagerImpl manager;
private final CancellableTask hookMovementTask;
private LavaEffectTask lavaFishingTask;
private final FishHook fishHook;
private final FishingPreparation fishingPreparation;
private final Effect initialEffect;
private final int lureLevel;
private boolean firstTime;
private boolean fishHooked;
private boolean reserve;
private int jumpTimer;
private Entity hookedEntity;
public HookCheckTimerTask(
FishingManagerImpl manager,
FishHook fishHook,
FishingPreparation fishingPreparation,
Effect initialEffect
) {
this.manager = manager;
this.fishHook = fishHook;
this.initialEffect = initialEffect;
this.fishingPreparation = fishingPreparation;
this.hookMovementTask = CustomFishingPlugin.get().getScheduler().runTaskSyncTimer(this, fishHook.getLocation(), 1, 1);
this.lureLevel = fishingPreparation.getRodItemStack().getEnchantmentLevel(Enchantment.LURE);
this.firstTime = true;
}
@Override
public void run() {
if (
!fishHook.isValid()
|| fishHook.isOnGround()
|| (fishHook.getHookedEntity() != null && fishHook.getHookedEntity().getType() != EntityType.ARMOR_STAND)
) {
// This task would be cancelled when hook is not at a proper place
// or player reels in before it goes into water or lava
this.destroy();
return;
}
if (fishHook.getLocation().getBlock().getType() == Material.LAVA) {
// if player can fish in lava
if (!initialEffect.canLavaFishing()) {
this.destroy();
return;
}
if (firstTime) {
this.fishingPreparation.insertArg("in-lava", "true");
if (Config.enableSplashAnimation)
ArmorStandUtils.sendAnimationToPlayer(
fishingPreparation.getPlayer(),
fishHook.getLocation(),
CustomFishingPlugin.get().getItemManager().build(null, "util", Config.lavaSplashItem),
Config.splashAnimationTime
);
firstTime = false;
this.setTempState();
}
// simulate fishing mechanic
if (fishHooked) {
jumpTimer++;
if (jumpTimer < 4)
return;
jumpTimer = 0;
fishHook.setVelocity(new Vector(0,0.24,0));
return;
}
if (!reserve) {
if (jumpTimer < 5) {
jumpTimer++;
fishHook.setVelocity(new Vector(0,0.2 - jumpTimer * 0.02,0));
return;
}
reserve = true;
this.startLavaFishingMechanic();
this.makeHookStatic(fishHook.getLocation());
}
return;
}
if (fishHook.isInWater()) {
// if the hook is in water
// then cancel the task
this.fishingPreparation.insertArg("in-lava", "false");
if (Config.enableSplashAnimation)
ArmorStandUtils.sendAnimationToPlayer(
fishingPreparation.getPlayer(),
fishHook.getLocation(),
CustomFishingPlugin.get().getItemManager().build(null, "util", Config.waterSplashItem),
Config.splashAnimationTime
);
this.destroy();
this.setTempState();
return;
}
}
public void destroy() {
this.cancelSubTask();
this.removeTempEntity();
this.hookMovementTask.cancel();
this.manager.removeHookCheckTask(fishingPreparation.getPlayer());
}
public void cancelSubTask() {
if (lavaFishingTask != null && !lavaFishingTask.isCancelled()) {
lavaFishingTask.cancel();
lavaFishingTask = null;
}
}
private void setTempState() {
Loot nextLoot = manager.getNextLoot(initialEffect, fishingPreparation);
if (nextLoot == null)
return;
fishingPreparation.insertArg("loot", nextLoot.getNick());
fishingPreparation.insertArg("id", nextLoot.getID());
CustomFishingPlugin.get().getScheduler().runTaskAsync(() -> manager.setTempFishingState(fishingPreparation.getPlayer(), new TempFishingState(
initialEffect,
fishingPreparation,
nextLoot
)));
}
public void removeTempEntity() {
if (hookedEntity != null && !hookedEntity.isDead())
hookedEntity.remove();
}
private void startLavaFishingMechanic() {
// get random time
int random = ThreadLocalRandom.current().nextInt(Config.lavaMinTime, Config.lavaMaxTime);
random -= lureLevel * 100;
random *= initialEffect.getTimeModifier();
random = Math.max(Config.lavaMinTime, random);
// lava effect task (Three seconds in advance)
this.lavaFishingTask = new LavaEffectTask(
this,
fishHook.getLocation(),
random - 3 * 20
);
}
public void getHooked() {
LavaFishingEvent lavaFishingEvent = new LavaFishingEvent(fishingPreparation.getPlayer(), LavaFishingEvent.State.BITE, fishHook);
Bukkit.getPluginManager().callEvent(lavaFishingEvent);
if (lavaFishingEvent.isCancelled()) {
this.startLavaFishingMechanic();
return;
}
this.fishHooked = true;
this.removeTempEntity();
AdventureManagerImpl.getInstance().sendSound(
fishingPreparation.getPlayer(),
Sound.Source.NEUTRAL,
Key.key("minecraft:block.pointed_dripstone.drip_lava_into_cauldron"),
1,
1
);
CustomFishingPlugin.get().getScheduler().runTaskAsyncLater(() -> {
fishHooked = false;
reserve = false;
}, (2 * 20) * 50L, TimeUnit.MILLISECONDS);
}
private void makeHookStatic(Location armorLoc) {
armorLoc.setY(armorLoc.getBlockY() + 0.2);
if (hookedEntity != null && !hookedEntity.isDead())
hookedEntity.remove();
hookedEntity = armorLoc.getWorld().spawn(armorLoc, ArmorStand.class, a -> {
a.setInvisible(true);
a.setCollidable(false);
a.setInvulnerable(true);
a.setVisible(false);
a.setCustomNameVisible(false);
a.setSmall(true);
a.setGravity(false);
a.getPersistentDataContainer().set(
Objects.requireNonNull(NamespacedKey.fromString("lavafishing", CustomFishingPlugin.get())),
PersistentDataType.BOOLEAN,
true
);
});
fishHook.setHookedEntity(hookedEntity);
}
public boolean isFishHooked() {
return fishHooked;
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.mechanic.fishing;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import org.bukkit.Location;
import org.bukkit.Particle;
import java.util.concurrent.TimeUnit;
public class LavaEffectTask implements Runnable {
private final Location startLoc;
private final Location endLoc;
private final Location controlLoc;
private int timer;
private final CancellableTask lavaTask;
private final HookCheckTimerTask hookCheckTimerTask;
public LavaEffectTask(HookCheckTimerTask hookCheckTimerTask, Location loc, int delay) {
this.hookCheckTimerTask = hookCheckTimerTask;
this.startLoc = loc.clone().add(0,0.3,0);
this.endLoc = this.startLoc.clone().add((Math.random() * 16 - 8), startLoc.getY(), (Math.random() * 16 - 8));
this.controlLoc = new Location(
startLoc.getWorld(),
(startLoc.getX() + endLoc.getX())/2 + Math.random() * 12 - 6,
startLoc.getY(),
(startLoc.getZ() + endLoc.getZ())/2 + Math.random() * 12 - 6
);
this.lavaTask = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(this, delay * 50L, 50, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
timer++;
if (timer > 60) {
lavaTask.cancel();
CustomFishingPlugin.get().getScheduler().runTaskSync(hookCheckTimerTask::getHooked, startLoc);
} else {
double t = (double) timer / 60;
Location particleLoc = endLoc.clone().multiply(Math.pow((1 - t), 2)).add(controlLoc.clone().multiply(2 * t * (1 - t))).add(startLoc.clone().multiply(Math.pow(t, 2)));
particleLoc.setY(startLoc.getY());
startLoc.getWorld().spawnParticle(Particle.FLAME, particleLoc,1,0,0,0,0);
}
}
public void cancel() {
if (lavaTask != null && !lavaTask.isCancelled())
lavaTask.cancel();
}
public boolean isCancelled() {
return lavaTask.isCancelled();
}
}

View File

@@ -0,0 +1,213 @@
package net.momirealms.customfishing.mechanic.game;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.GameManager;
import net.momirealms.customfishing.api.mechanic.game.*;
import net.momirealms.customfishing.api.util.FontUtils;
import net.momirealms.customfishing.api.util.OffsetUtils;
import net.momirealms.customfishing.util.ConfigUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class GameManagerImpl implements GameManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, GameCreator> gameCreatorMap;
private final HashMap<String, Game> gameMap;
private final HashMap<String, GameConfig> gameConfigMap;
public GameManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.gameCreatorMap = new HashMap<>();
this.gameMap = new HashMap<>();
this.gameConfigMap = new HashMap<>();
this.registerInbuiltGames();
}
private void registerInbuiltGames() {
this.registerAccurateClickGame();
}
public void load() {
this.loadGamesFromPluginFolder();
this.loadGameConfigs();
}
public void unload() {
this.gameMap.clear();
this.gameConfigMap.clear();
}
public void disable() {
unload();
this.gameCreatorMap.clear();
}
@Override
public boolean registerGameType(String type, GameCreator gameCreator) {
if (gameCreatorMap.containsKey(type))
return false;
else
gameCreatorMap.put(type, gameCreator);
return true;
}
@Override
public boolean unregisterGameType(String type) {
return gameCreatorMap.remove(type) != null;
}
@Override
@Nullable
public GameCreator getGameCreator(String type) {
return gameCreatorMap.get(type);
}
@Override
@Nullable
public Game getGame(String key) {
return gameMap.get(key);
}
@Override
@Nullable
public GameConfig getGameConfig(String key) {
return gameConfigMap.get(key);
}
@Override
public Game getRandomGame() {
Collection<Game> collection = gameMap.values();
return (Game) collection.toArray()[ThreadLocalRandom.current().nextInt(collection.size())];
}
@Override
public GameConfig getRandomGameConfig() {
Collection<GameConfig> collection = gameConfigMap.values();
return (GameConfig) collection.toArray()[ThreadLocalRandom.current().nextInt(collection.size())];
}
public void loadGameConfigs() {
YamlConfiguration config = plugin.getConfig("game-groups.yml");
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
if (section.contains("groups")) {
gameConfigMap.put(entry.getKey(), new GameGroups(ConfigUtils.getWeights(section.getStringList("groups"))));
} else if (section.contains("games")) {
var pair1 = ConfigUtils.splitStringIntegerArgs(section.getString("difficulty", "1~100"));
var pair2 = ConfigUtils.splitStringIntegerArgs(section.getString("time", "10~20"));
gameConfigMap.put(entry.getKey(),
new GameGroup(ConfigUtils.getWeights(section.getStringList("games")))
.difficulty(pair1.left(), pair1.right())
.time(pair2.left(), pair2.right())
);
}
}
}
}
public void loadGamesFromPluginFolder() {
Deque<File> fileDeque = new ArrayDeque<>();
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + "minigames");
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.saveResource("contents" + File.separator + "minigames" + 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()) {
loadSingleFile(subFile);
}
}
}
}
private void loadSingleFile(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
GameCreator creator = this.getGameCreator(section.getString("game-type"));
if (creator != null) {
gameMap.put(entry.getKey(), creator.setArgs(section));
}
}
}
}
private void registerAccurateClickGame() {
this.registerGameType("accurate_click", (section -> {
Set<String> chances = Objects.requireNonNull(section.getConfigurationSection("success-rate-sections")).getKeys(false);
var widthPerSection = section.getInt("arguments.width-per-section", 16);
var successRate = new double[chances.size()];
for(int i = 0; i < chances.size(); i++)
successRate[i] = section.getDouble("success-rate-sections." + (i + 1));
var totalWidth = chances.size() * widthPerSection - 1;
var pointerOffset = section.getInt("arguments.pointer-offset");
var pointerWidth = section.getInt("arguments.pointer-width");
var title = section.getString("title");
var font = section.getString("subtitle.font");
var barImage = section.getString("subtitle.bar");
var pointerImage = section.getString("subtitle.pointer");
return (player, settings, manager) -> new AbstractGamingPlayer(player, settings, manager) {
private int progress;
private boolean face;
@Override
public void arrangeTask() {
var period = ((double) 10*(200-settings.getDifficulty()))/((double) (1+4*settings.getDifficulty()));
this.task = CustomFishingPlugin.get().getScheduler().runTaskAsyncTimer(
this,
50,
(long) period,
TimeUnit.MILLISECONDS
);
}
@Override
public void run() {
super.run();
if (face) progress++;
else progress--;
if (progress > totalWidth) {
face = !face;
progress = 2 * totalWidth - progress;
} else if (progress < 0) {
face = !face;
progress = -progress;
}
showUI();
}
public void showUI() {
String bar = FontUtils.surroundWithFont(barImage, font)
+ OffsetUtils.getOffsetChars(pointerOffset + progress)
+ FontUtils.surroundWithFont(pointerImage, font)
+ OffsetUtils.getOffsetChars(totalWidth - progress - pointerWidth);
AdventureManagerImpl.getInstance().sendTitle(player, title, bar,0,500,0);
}
@Override
public boolean isSucceeded() {
int last = progress / widthPerSection;
return (Math.random() < successRate[last]);
}
};
}));
}
}

View File

@@ -0,0 +1,554 @@
package net.momirealms.customfishing.mechanic.item;
import de.tr7zw.changeme.nbtapi.*;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.common.Key;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.manager.ItemManager;
import net.momirealms.customfishing.api.mechanic.item.BuildableItem;
import net.momirealms.customfishing.api.mechanic.item.ItemBuilder;
import net.momirealms.customfishing.api.mechanic.item.ItemLibrary;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.item.CustomFishingItemImpl;
import net.momirealms.customfishing.compatibility.item.VanillaItemImpl;
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.util.NBTUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public class ItemManagerImpl implements ItemManager {
private static ItemManager instance;
private final CustomFishingPlugin plugin;
private final HashMap<Key, BuildableItem> buildableItemMap;
private final HashMap<String, ItemLibrary> itemLibraryMap;
public ItemManagerImpl(CustomFishingPlugin plugin) {
instance = this;
this.plugin = plugin;
this.itemLibraryMap = new LinkedHashMap<>();
this.buildableItemMap = new HashMap<>();
this.registerItemLibrary(new CustomFishingItemImpl());
this.registerItemLibrary(new VanillaItemImpl());
}
public void load() {
this.loadItemsFromPluginFolder();
AdventureManagerImpl.getInstance().sendMessageWithPrefix(Bukkit.getConsoleSender(), "<white>Loaded <green>" + buildableItemMap.size() + " <white>items.");
}
public void unload() {
HashMap<Key, BuildableItem> tempMap = new HashMap<>(this.buildableItemMap);
this.buildableItemMap.clear();
for (Map.Entry<Key, BuildableItem> entry : tempMap.entrySet()) {
if (entry.getValue().persist()) {
tempMap.put(entry.getKey(), entry.getValue());
}
}
}
@Override
public Set<Key> getAllItemsKey() {
return buildableItemMap.keySet();
}
public void disable() {
this.buildableItemMap.clear();
this.itemLibraryMap.clear();
}
@SuppressWarnings("DuplicatedCode")
public void loadItemsFromPluginFolder() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("loots", "baits", "rods", "utils")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
this.loadSingleFile(subFile, StringUtils.chop(type));
}
}
}
}
}
private void loadSingleFile(File file, String namespace) {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
String value = entry.getKey();
if (entry.getValue() instanceof ConfigurationSection section) {
Key key = Key.of(namespace, value);
if (buildableItemMap.containsKey(key)) {
LogUtils.severe("Duplicated item key found: " + key + ".");
} else {
buildableItemMap.put(key, getItemBuilder(section, namespace, value));
}
}
}
}
@Override
public boolean registerCustomItem(String namespace, String value, BuildableItem buildableItem) {
Key key = Key.of(namespace, value);
if (buildableItemMap.containsKey(key)) return false;
buildableItemMap.put(key, buildableItem);
return true;
}
@Override
public boolean unregisterCustomItem(String namespace, String value) {
return buildableItemMap.remove(Key.of(namespace, value)) != null;
}
@Override
public ItemStack build(Player player, String namespace, String value) {
return build(player, namespace, value, new HashMap<>());
}
@Override
public ItemStack build(Player player, String namespace, String value, Map<String, String> placeholders) {
BuildableItem buildableItem = buildableItemMap.get(Key.of(namespace, value));
if (buildableItem == null) return null;
return buildableItem.build(player, placeholders);
}
@NotNull
@Override
public ItemStack build(Player player, ItemBuilder builder) {
return build(player, builder, new HashMap<>());
}
@Override
@Nullable
public BuildableItem getBuildableItem(String namespace, String value) {
return buildableItemMap.get(Key.of(namespace, value));
}
@Override
public String getAnyItemID(ItemStack itemStack) {
for (String plugin : Config.itemDetectOrder) {
ItemLibrary itemLibrary = itemLibraryMap.get(plugin);
if (itemLibrary != null) {
String id = itemLibrary.getItemID(itemStack);
if (id != null) {
return id;
}
}
}
// should not reach this because vanilla library would always work
return null;
}
@Override
public ItemStack buildAnyItemByID(Player player, String id) {
if (id.contains(":")) {
String[] split = id.split(":", 2);
return itemLibraryMap.get(split[0]).buildItem(player, split[1]);
} else {
return new ItemStack(Material.valueOf(id.toUpperCase(Locale.ENGLISH)));
}
}
@Nullable
@Override
public String getItemID(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR) return null;
NBTItem nbtItem = new NBTItem(itemStack);
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
if (cfCompound == null) return null;
return cfCompound.getString("id");
}
@Nullable
@Override
public CFBuilder getItemBuilder(ConfigurationSection section, String type, String id) {
if (section == null) return null;
String material = section.getString("material", "PAPER");
CFBuilder itemCFBuilder;
if (material.contains(":")) {
String[] split = material.split(":", 2);
itemCFBuilder = CFBuilder.of(split[0], split[1]);
} else {
itemCFBuilder = CFBuilder.of("vanilla", material.toUpperCase(Locale.ENGLISH));
}
itemCFBuilder
.amount(section.getInt("amount", 1))
.stackable(section.getBoolean("stackable", true))
.size(getSizePair(section.getString("size")))
.price((float) section.getDouble("price.base"), (float) section.getDouble("price.bonus"))
.customModelData(section.getInt("custom-model-data"))
.nbt(section.getConfigurationSection("nbt"))
.maxDurability(section.getInt("max-durability"))
.itemFlag(section.getStringList("item-flags").stream().map(flag -> ItemFlag.valueOf(flag.toUpperCase())).toList())
.enchantment(getEnchantmentPair(section.getConfigurationSection("enchantments")), false)
.enchantment(getEnchantmentPair(section.getConfigurationSection("stored-enchantments")), true)
.tag(section.getBoolean("tag", true), type, id)
.randomDamage(section.getBoolean("random-durability", false))
.unbreakable(section.getBoolean("unbreakable", false))
.preventGrabbing(section.getBoolean("prevent-grabbing", false))
.head(section.getString("head64"))
.name(section.getString("display.name"))
.lore(section.getStringList("display.lore"));
return itemCFBuilder;
}
@Override
public ItemStack build(Player player, ItemBuilder builder, Map<String, String> placeholders) {
ItemStack temp = itemLibraryMap.get(builder.getLibrary()).buildItem(player, builder.getId());
temp.setAmount(builder.getAmount());
NBTItem nbtItem = new NBTItem(temp);
for (ItemBuilder.ItemPropertyEditor editor : builder.getEditors()) {
editor.edit(player, nbtItem, placeholders);
}
return nbtItem.getItem();
}
@Override
public boolean registerItemLibrary(ItemLibrary itemLibrary) {
if (itemLibraryMap.containsKey(itemLibrary.identification())) return false;
itemLibraryMap.put(itemLibrary.identification(), itemLibrary);
return true;
}
@Override
public boolean unRegisterItemLibrary(ItemLibrary itemLibrary) {
return itemLibraryMap.remove(itemLibrary.identification(), itemLibrary);
}
@Override
public boolean unRegisterItemLibrary(String itemLibrary) {
return itemLibraryMap.remove(itemLibrary) != null;
}
@Override
public void dropItem(Player player, Location hookLocation, Location playerLocation, Loot loot, Map<String, String> args) {
ItemStack item = build(player, "loot", loot.getID(), args);
if (item == null) {
LogUtils.warn(String.format("Item %s not exists", loot.getID()));
return;
}
if (item.getType() == Material.AIR) {
return;
}
Entity itemEntity = hookLocation.getWorld().dropItem(hookLocation, item);
Vector vector = playerLocation.subtract(hookLocation).toVector().multiply(0.105);
vector = vector.setY((vector.getY() + 0.2) * 1.18);
itemEntity.setVelocity(vector);
}
@Override
public void dropItem(Location hookLocation, Location playerLocation, ItemStack itemStack) {
Entity itemEntity = hookLocation.getWorld().dropItem(hookLocation, itemStack);
Vector vector = playerLocation.subtract(hookLocation).toVector().multiply(0.105);
vector = vector.setY((vector.getY() + 0.2) * 1.18);
itemEntity.setVelocity(vector);
}
@NotNull
private List<Pair<String, Short>> getEnchantmentPair(ConfigurationSection section) {
List<Pair<String, Short>> list = new ArrayList<>();
if (section == null) return list;
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
list.add(Pair.of(entry.getKey(), (short) entry.getValue()));
}
return list;
}
@Nullable
private Pair<Float, Float> getSizePair(String size) {
if (size == null) return null;
String[] split = size.split("~", 2);
return Pair.of(Float.parseFloat(split[0]), Float.parseFloat(split[1]));
}
public static class CFBuilder implements ItemBuilder, BuildableItem {
private final String library;
private final String id;
private int amount;
private final LinkedHashMap<String, ItemPropertyEditor> editors;
public CFBuilder(String library, String id) {
this.id = id;
this.library = library;
this.editors = new LinkedHashMap<>();
this.amount = 1;
}
public static CFBuilder of(String library, String id) {
return new CFBuilder(library, id);
}
@Override
public ItemStack build(Player player, Map<String, String> placeholders) {
return ItemManagerImpl.instance.build(player, this, placeholders);
}
@Override
public boolean persist() {
return false;
}
@Override
public ItemBuilder customModelData(int value) {
if (value == 0) return this;
editors.put("custom-model-data", (player, nbtItem, placeholders) -> nbtItem.setInteger("CustomModelData", value));
return this;
}
@Override
public ItemBuilder name(String name) {
if (name == null) return this;
String replacedName = AdventureManagerImpl.getInstance().legacyToMiniMessage(name);
editors.put("name", (player, nbtItem, placeholders) -> {
NBTCompound displayCompound = nbtItem.getOrCreateCompound("display");
displayCompound.setString("Name", AdventureManagerImpl.getInstance().componentToJson(
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
"<!i>" + PlaceholderManagerImpl.getInstance().parse(player, replacedName, placeholders)
)
));
});
return this;
}
@Override
public ItemBuilder amount(int amount) {
this.amount = amount;
return this;
}
@Override
public ItemBuilder tag(boolean tag, String type, String id) {
editors.put("tag", (player, nbtItem, placeholders) -> {
if (!tag) return;
NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing");
cfCompound.setString("type", type);
cfCompound.setString("id", id);
});
return this;
}
@Override
public ItemBuilder unbreakable(boolean unbreakable) {
editors.put("unbreakable", (player, nbtItem, placeholders) -> {
if (!unbreakable) return;
nbtItem.setByte("Unbreakable", (byte) 1);
});
return this;
}
@Override
public ItemBuilder lore(List<String> lore) {
if (lore.size() == 0) return this;
List<String> replacedList = lore.stream().map(s -> AdventureManagerImpl.getInstance().legacyToMiniMessage(s)).toList();
editors.put("lore", (player, nbtItem, placeholders) -> {
NBTCompound displayCompound = nbtItem.getOrCreateCompound("display");
NBTList<String> list = displayCompound.getStringList("Lore");
list.clear();
list.addAll(replacedList.stream().map(s -> AdventureManagerImpl.getInstance().componentToJson(
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
"<!i>" + PlaceholderManagerImpl.getInstance().parse(player, s, placeholders)
)
)).toList());
});
return this;
}
@Override
public ItemBuilder nbt(Map<String, Object> nbt) {
if (nbt.size() == 0) return this;
editors.put("nbt", (player, nbtItem, placeholders) -> NBTUtils.setTagsFromBukkitYAML(nbtItem, nbt));
return this;
}
@Override
public ItemBuilder nbt(ConfigurationSection section) {
if (section == null) return this;
editors.put("nbt", (player, nbtItem, placeholders) -> NBTUtils.setTagsFromBukkitYAML(nbtItem, section.getValues(false)));
return this;
}
@Override
public ItemBuilder itemFlag(List<ItemFlag> itemFlags) {
if (itemFlags.size() == 0) return this;
editors.put("item-flag", (player, nbtItem, placeholders) -> {
int flag = 0;
for (ItemFlag itemFlag : itemFlags) {
flag = flag | 1 << itemFlag.ordinal();
}
nbtItem.setInteger("HideFlags", flag);
});
return this;
}
@Override
public ItemBuilder enchantment(List<Pair<String, Short>> enchantments, boolean store) {
if (enchantments.size() == 0) return this;
editors.put("enchantment", (player, nbtItem, placeholders) -> {
NBTCompoundList list = nbtItem.getCompoundList(store ? "StoredEnchantments" : "Enchantments");
for (Pair<String, Short> pair : enchantments) {
NBTCompound nbtCompound = list.addCompound();
nbtCompound.setString("id", pair.left());
nbtCompound.setShort("lvl", pair.right());
}
});
return this;
}
@Override
public ItemBuilder maxDurability(int max) {
if (max == 0) return this;
editors.put("durability", (player, nbtItem, placeholders) -> {
NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing");
cfCompound.setInteger("max_dur", max);
cfCompound.setInteger("cur_dur", max);
});
return this;
}
@Override
public ItemBuilder price(float base, float bonus) {
editors.put("price", (player, nbtItem, placeholders) -> {
if (base != 0) {
placeholders.put("{base}", String.format("%.2f", base));
}
if (bonus != 0) {
placeholders.put("{bonus}", String.format("%.2f", bonus));
}
float size = Float.parseFloat(placeholders.getOrDefault("{size}", "0"));
double price = CustomFishingPlugin.get().getMarketManager().getPrice(
base,
bonus,
size
);
nbtItem.setDouble("Price", price);
placeholders.put("{price}", String.format("%.2f", price));
});
return this;
}
@Override
public ItemBuilder size(Pair<Float, Float> size) {
if (size == null) return this;
editors.put("size", (player, nbtItem, placeholders) -> {
NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing");
float random = size.left() + ThreadLocalRandom.current().nextFloat(size.right() - size.left());
float bonus = Float.parseFloat(placeholders.getOrDefault("size-multiplier", "1.0"));
random *= bonus;
cfCompound.setFloat("size", random);
placeholders.put("{size}", String.format("%.2f", random));
});
return this;
}
@Override
public ItemBuilder stackable(boolean stackable) {
if (stackable) return this;
editors.put("stackable", (player, nbtItem, placeholders) -> {
NBTCompound cfCompound = nbtItem.getOrCreateCompound("CustomFishing");
cfCompound.setUUID("uuid", UUID.randomUUID());
});
return this;
}
@Override
public ItemBuilder preventGrabbing(boolean prevent) {
if (!prevent) return this;
editors.put("grabbing", (player, nbtItem, placeholders) -> {
nbtItem.setString("owner", placeholders.get("player"));
});
return this;
}
@Override
public ItemBuilder head(String base64) {
if (base64 == null) return this;
editors.put("head", (player, nbtItem, placeholders) -> {
NBTCompound nbtCompound = nbtItem.addCompound("SkullOwner");
nbtCompound.setUUID("Id", UUID.nameUUIDFromBytes(base64.substring(0,8).getBytes()));
NBTListCompound texture = nbtCompound.addCompound("Properties").getCompoundList("textures").addCompound();
texture.setString("Value", base64);
});
return this;
}
@Override
public ItemBuilder randomDamage(boolean damage) {
if (!damage) return this;
editors.put("damage", (player, nbtItem, placeholders) -> {
NBTCompound cfCompound = nbtItem.getCompound("CustomFishing");
if (cfCompound != null) {
int i = cfCompound.getInteger("max_dur");
if (i != 0) {
int dur = ThreadLocalRandom.current().nextInt(i);
cfCompound.setInteger("cur_dur", dur);
nbtItem.setInteger("Damage", (int) (nbtItem.getItem().getType().getMaxDurability() * ((double) dur / i)));
}
} else {
nbtItem.setInteger("Damage", ThreadLocalRandom.current().nextInt(nbtItem.getItem().getType().getMaxDurability()));
}
});
return this;
}
@Override
public @NotNull String getId() {
return id;
}
@Override
public @NotNull String getLibrary() {
return library;
}
@Override
public int getAmount() {
return amount;
}
@Override
public Collection<ItemPropertyEditor> getEditors() {
return editors.values();
}
@Override
public ItemBuilder removeEditor(String type) {
editors.remove(type);
return this;
}
@Override
public ItemBuilder registerCustomEditor(String type, ItemPropertyEditor editor) {
editors.put(type, editor);
return this;
}
}
}

View File

@@ -0,0 +1,304 @@
package net.momirealms.customfishing.mechanic.loot;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.LootManager;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.action.ActionTrigger;
import net.momirealms.customfishing.api.mechanic.game.GameConfig;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.mechanic.loot.LootType;
import net.momirealms.customfishing.api.util.LogUtils;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class LootManagerImpl implements LootManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, Loot> lootMap;
public static CFLoot globalLootProperties;
private boolean disableStats;
private boolean disableGames;
private boolean instantGame;
private boolean showInFinder;
private String gameGroup;
public LootManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.lootMap = new HashMap<>();
}
public void load() {
this.loadGlobalLootProperties();
this.loadLootsFromPluginFolder();
}
public void unload() {
this.lootMap.clear();
}
public void disable() {
unload();
}
@SuppressWarnings("DuplicatedCode")
public void loadLootsFromPluginFolder() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("loots", "mobs", "blocks")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
loadSingleFile(subFile, StringUtils.chop(type));
}
}
}
}
}
@Nullable
@Override
public Loot getLoot(String key) {
return lootMap.get(key);
}
private void loadGlobalLootProperties() {
YamlConfiguration config = plugin.getConfig("config.yml");
globalLootProperties = getSingleSectionItem(
Objects.requireNonNull(config.getConfigurationSection("mechanics.global-loot-properties")),
"GLOBAL",
"global"
);
disableStats = globalLootProperties.disableStats();
disableGames = globalLootProperties.disableGame();
instantGame = globalLootProperties.instanceGame();
showInFinder = globalLootProperties.showInFinder();
gameGroup = globalLootProperties.gameConfig;
}
private void loadSingleFile(File file, String namespace) {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
var loot = getSingleSectionItem(
section,
namespace,
entry.getKey()
);
if (lootMap.containsKey(entry.getKey())) {
LogUtils.severe("Duplicated loot found: " + entry.getKey() + ".");
} else {
lootMap.put(entry.getKey(), loot);
}
}
}
}
private CFLoot getSingleSectionItem(ConfigurationSection section, String namespace, String key) {
return new CFLoot.Builder(key, LootType.valueOf(namespace.toUpperCase(Locale.ENGLISH)))
.disableStats(section.getBoolean("disable-stat", disableStats))
.disableGames(section.getBoolean("disable-game", disableGames))
.instantGame(section.getBoolean("instant-game", instantGame))
.showInFinder(section.getBoolean("show-in-fishfinder", showInFinder))
.gameConfig(section.getString("game-group", gameGroup))
.nick(section.getString("nick", section.getString("display.name", key)))
.addActions(getActionMap(section.getConfigurationSection("action")))
.addTimesActions(getTimesActionMap(section.getConfigurationSection("action.success-times")))
.build();
}
private HashMap<ActionTrigger, Action[]> getActionMap(ConfigurationSection section) {
HashMap<ActionTrigger, Action[]> actionMap = new HashMap<>();
if (section == null) return actionMap;
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
actionMap.put(
ActionTrigger.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)),
plugin.getActionManager().getActions(innerSection)
);
}
}
return actionMap;
}
private HashMap<Integer, Action[]> getTimesActionMap(ConfigurationSection section) {
HashMap<Integer, Action[]> actionMap = new HashMap<>();
if (section == null) return actionMap;
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
actionMap.put(Integer.parseInt(entry.getKey()), plugin.getActionManager().getActions(innerSection));
}
}
return actionMap;
}
public static class CFLoot implements Loot {
private final String id;
private final LootType type;
private String gameConfig;
private final HashMap<ActionTrigger, Action[]> actionMap;
private final HashMap<Integer, Action[]> successTimesActionMap;
private String nick;
private boolean showInFinder;
private boolean disableGame;
private boolean disableStats;
private boolean instanceGame;
private double score;
public CFLoot(String id, LootType type) {
this.id = id;
this.type = type;
this.actionMap = new HashMap<>();
this.successTimesActionMap = new HashMap<>();
}
public static CFLoot of(String id, LootType type) {
return new CFLoot(id, type);
}
public static class Builder {
private final CFLoot loot;
public Builder(String id, LootType type) {
this.loot = new CFLoot(id, type);
}
public Builder nick(String nick) {
this.loot.nick = nick;
return this;
}
public Builder showInFinder(boolean show) {
this.loot.showInFinder = show;
return this;
}
public Builder instantGame(boolean instant) {
this.loot.instanceGame = instant;
return this;
}
public Builder gameConfig(String gameConfig) {
this.loot.gameConfig = gameConfig;
return this;
}
public Builder disableGames(boolean disable) {
this.loot.disableGame = disable;
return this;
}
public Builder disableStats(boolean disable) {
this.loot.disableStats = disable;
return this;
}
public Builder score(double score) {
this.loot.score = score;
return this;
}
public Builder addActions(ActionTrigger trigger, Action[] actions) {
this.loot.actionMap.put(trigger, actions);
return this;
}
public Builder addActions(HashMap<ActionTrigger, Action[]> actionMap) {
this.loot.actionMap.putAll(actionMap);
return this;
}
public Builder addTimesActions(int times, Action[] actions) {
this.loot.successTimesActionMap.put(times, actions);
return this;
}
public Builder addTimesActions(HashMap<Integer, Action[]> actionMap) {
this.loot.successTimesActionMap.putAll(actionMap);
return this;
}
public CFLoot build() {
return loot;
}
}
@Override
public boolean instanceGame() {
return this.instanceGame;
}
@Override
public String getID() {
return this.id;
}
@Override
public LootType getType() {
return this.type;
}
@Override
public @NotNull String getNick() {
return this.nick;
}
@Override
public boolean showInFinder() {
return this.showInFinder;
}
@Override
public double getScore() {
return this.score;
}
@Override
public boolean disableGame() {
return this.disableGame;
}
@Override
public boolean disableStats() {
return this.disableStats;
}
@Override
public GameConfig getGameConfig() {
return CustomFishingPlugin.get().getGameManager().getGameConfig(this.gameConfig);
}
@Override
public Action[] getActions(ActionTrigger actionTrigger) {
return actionMap.get(actionTrigger);
}
@Override
public Action[] getSuccessTimesActions(int times) {
return successTimesActionMap.get(times);
}
@Override
public HashMap<Integer, Action[]> getSuccessTimesActionMap() {
return successTimesActionMap;
}
}
}

View File

@@ -0,0 +1,136 @@
package net.momirealms.customfishing.mechanic.market;
import de.tr7zw.changeme.nbtapi.NBTItem;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.MarketManager;
import net.momirealms.customfishing.api.mechanic.item.ItemBuilder;
import net.momirealms.customfishing.api.mechanic.market.MarketGUI;
import net.momirealms.customfishing.libraries.inventorygui.InventoryGui;
import net.momirealms.customfishing.libraries.inventorygui.StaticGuiElement;
import net.momirealms.customfishing.mechanic.item.ItemManagerImpl;
import net.momirealms.customfishing.util.ConfigUtils;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MarketManagerImpl implements MarketManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, Double> priceMap;
private String[] layout;
private String title;
private String formula;
private final HashMap<Character, ItemBuilder> decorativeIcons;
private char itemSlot;
public MarketManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.priceMap = new HashMap<>();
this.decorativeIcons = new HashMap<>();
}
public void load() {
YamlConfiguration config = plugin.getConfig("market.yml");
this.layout = config.getStringList("layout").toArray(new String[0]);
this.title = config.getString("title", "market.title");
this.formula = config.getString("price-formula", "{base} + {bonus} * {size}");
ConfigurationSection priceSection = config.getConfigurationSection("item-price");
if (priceSection != null) {
for (Map.Entry<String, Object> entry : priceSection.getValues(false).entrySet()) {
this.priceMap.put(entry.getKey(), ConfigUtils.getDoubleValue(entry.getValue()));
}
}
ConfigurationSection decorativeSection = config.getConfigurationSection("decorative-icons");
if (decorativeSection != null) {
for (Map.Entry<String, Object> entry : decorativeSection.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
char symbol = Objects.requireNonNull(innerSection.getString("symbol")).charAt(0);
var builder = plugin.getItemManager().getItemBuilder(innerSection, "gui", entry.getKey());
decorativeIcons.put(symbol, builder);
}
}
}
}
public void unload() {
this.priceMap.clear();
this.decorativeIcons.clear();
}
public void disable() {
unload();
}
public void openMarketGUI(Player player) {
player.closeInventory();
InventoryGui gui = new InventoryGui(
plugin,
new MarketGUI(),
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(title),
layout
);
gui.setCloseAction(close -> {
var elements = gui.getElement(itemSlot);
return false;
});
for (Map.Entry<Character, ItemBuilder> entry : decorativeIcons.entrySet()) {
gui.addElement(new StaticGuiElement(
entry.getKey(),
((ItemManagerImpl.CFBuilder) entry.getValue()).build()
));
}
}
@Override
public int getDate() {
Calendar calendar = Calendar.getInstance();
return (calendar.get(Calendar.MONTH) +1) * 100 + calendar.get(Calendar.DATE);
}
@Override
public double getItemPrice(ItemStack itemStack) {
if (itemStack == null || itemStack.getType() == Material.AIR)
return 0;
NBTItem nbtItem = new NBTItem(itemStack);
Double price = nbtItem.getDouble("Price");
if (price != null && price != 0) {
return price;
}
String itemID = itemStack.getType().name();
if (nbtItem.hasTag("CustomModelData")) {
itemID = itemID + ":" + nbtItem.getInteger("CustomModelData");
}
return priceMap.getOrDefault(itemID, 0d);
}
@Override
public String getFormula() {
return formula;
}
@Override
public double getPrice(float base, float bonus, float size) {
Expression expression = new ExpressionBuilder(getFormula())
.variables("base", "bonus", "size")
.build()
.setVariable("base", base)
.setVariable("bonus", bonus)
.setVariable("size", size);
return expression.evaluate();
}
}

View File

@@ -0,0 +1,142 @@
package net.momirealms.customfishing.mechanic.mob;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.manager.MobManager;
import net.momirealms.customfishing.api.mechanic.loot.Loot;
import net.momirealms.customfishing.api.mechanic.mob.MobConfig;
import net.momirealms.customfishing.api.mechanic.mob.MobLibrary;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.compatibility.mob.VanillaMobImpl;
import org.bukkit.Location;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.*;
public class MobManagerImpl implements MobManager {
private final CustomFishingPlugin plugin;
private final HashMap<String, MobLibrary> mobLibraryMap;
private final HashMap<String, MobConfig> mobConfigMap;
public MobManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.mobLibraryMap = new HashMap<>();
this.mobConfigMap = new HashMap<>();
this.registerMobLibrary(new VanillaMobImpl());
}
public void load() {
this.loadConfig();
}
public void unload() {
HashMap<String, MobConfig> tempMap = new HashMap<>(this.mobConfigMap);
this.mobConfigMap.clear();
for (Map.Entry<String, MobConfig> entry : tempMap.entrySet()) {
if (entry.getValue().isPersist()) {
tempMap.put(entry.getKey(), entry.getValue());
}
}
}
@Override
public boolean registerMobLibrary(MobLibrary mobLibrary) {
if (mobLibraryMap.containsKey(mobLibrary.identification())) return false;
else mobLibraryMap.put(mobLibrary.identification(), mobLibrary);
return true;
}
@Override
public boolean unregisterMobLibrary(String lib) {
return mobLibraryMap.remove(lib) != null;
}
@Override
public boolean unregisterMobLibrary(MobLibrary mobLibrary) {
return unregisterMobLibrary(mobLibrary.identification());
}
@SuppressWarnings("DuplicatedCode")
private void loadConfig() {
Deque<File> fileDeque = new ArrayDeque<>();
for (String type : List.of("mobs")) {
File typeFolder = new File(plugin.getDataFolder() + File.separator + "contents" + File.separator + type);
if (!typeFolder.exists()) {
if (!typeFolder.mkdirs()) return;
plugin.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()) {
this.loadSingleFile(subFile);
}
}
}
}
}
private void loadSingleFile(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
String mobID = section.getString("mob");
if (mobID == null) {
LogUtils.warn("Mob can't be null. File:" + file.getAbsolutePath() + "; Section:" + section.getCurrentPath());
continue;
}
HashMap<String, Object> propertyMap = new HashMap<>();
ConfigurationSection property = section.getConfigurationSection("properties");
if (property != null) {
propertyMap.putAll(property.getValues(false));
}
MobConfig mobConfig = new MobConfig.Builder()
.mobID(mobID)
.persist(false)
.horizontalVector(section.getDouble("vector.horizontal", 1.1))
.verticalVector(section.getDouble("vector.vertical", 1.2))
.propertyMap(propertyMap)
.build();
mobConfigMap.put(entry.getKey(), mobConfig);
}
}
}
public void disable() {
unload();
this.mobConfigMap.clear();
this.mobLibraryMap.clear();
}
@Override
public void summonMob(Location hookLocation, Location playerLocation, Loot loot) {
MobConfig config = mobConfigMap.get(loot.getID());
if (config == null) {
LogUtils.warn("Mob: " + loot.getID() + " doesn't exist.");
return;
}
String mobID = config.getMobID();
Entity entity;
if (mobID.contains(":")) {
String[] split = mobID.split(":", 2);
String identification = split[0];
String id = split[1];
MobLibrary library = mobLibraryMap.get(identification);
entity = library.spawn(hookLocation, id, config.getPropertyMap());
} else {
entity = mobLibraryMap.get("vanilla").spawn(hookLocation, mobID, config.getPropertyMap());
}
Vector vector = playerLocation.subtract(hookLocation).toVector().multiply((config.getHorizontalVector()) - 1);
vector = vector.setY((vector.getY() + 0.2) * config.getVerticalVector());
entity.setVelocity(vector);
}
}

View File

@@ -0,0 +1,24 @@
package net.momirealms.customfishing.mechanic.requirement;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
import java.util.List;
public abstract class AbstractRequirement implements Requirement {
private final List<Action> actions;
public AbstractRequirement(List<Action> actions) {
this.actions = actions;
}
protected void triggerActions(Condition condition) {
if (actions != null) {
for (Action action : actions) {
action.trigger(condition);
}
}
}
}

View File

@@ -0,0 +1,46 @@
package net.momirealms.customfishing.mechanic.requirement;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.mechanic.loot.Modifier;
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
import java.util.HashMap;
import java.util.List;
public class ConditionalLoots {
private final List<Pair<String, Modifier>> modifierList;
private final HashMap<String, ConditionalLoots> subLoots;
private final Requirement[] requirements;
public ConditionalLoots(
Requirement[] requirements,
List<Pair<String, Modifier>> modifierList,
HashMap<String, ConditionalLoots> subLoots
) {
this.modifierList = modifierList;
this.requirements = requirements;
this.subLoots = subLoots;
}
synchronized public void combine(HashMap<String, Double> weightMap) {
for (Pair<String, Modifier> modifierPair : this.modifierList) {
double previous = weightMap.getOrDefault(modifierPair.left(), 0d);
weightMap.put(modifierPair.left(), modifierPair.right().modify(previous));
}
}
public boolean isConditionsMet(Condition condition) {
for (Requirement requirement : requirements) {
if (!requirement.isConditionMet(condition)) {
return false;
}
}
return true;
}
public HashMap<String, ConditionalLoots> getSubLoots() {
return subLoots;
}
}

View File

@@ -0,0 +1,475 @@
package net.momirealms.customfishing.mechanic.requirement;
import net.momirealms.biomeapi.BiomeAPI;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.api.common.Pair;
import net.momirealms.customfishing.api.integration.LevelInterface;
import net.momirealms.customfishing.api.integration.SeasonInterface;
import net.momirealms.customfishing.api.manager.RequirementManager;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
import net.momirealms.customfishing.api.mechanic.requirement.RequirementBuilder;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.mechanic.requirement.inbuilt.LogicRequirement;
import net.momirealms.customfishing.util.ConfigUtils;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemorySection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RequirementManagerImpl implements RequirementManager {
public static Requirement[] mechanicRequirements;
private final CustomFishingPluginImpl plugin;
private final HashMap<String, RequirementBuilder> requirementBuilderMap;
private final LinkedHashMap<String, ConditionalLoots> conditionalLootsMap;
public RequirementManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
this.requirementBuilderMap = new HashMap<>();
this.conditionalLootsMap = new LinkedHashMap<>();
this.registerInbuiltRequirements();
}
public void load() {
this.loadRequirementGroupFileConfig();
}
public void unload() {
this.conditionalLootsMap.clear();
}
public void disable() {
this.requirementBuilderMap.clear();
this.conditionalLootsMap.clear();
}
public void loadRequirementGroupFileConfig() {
YamlConfiguration main = plugin.getConfig("config.yml");
mechanicRequirements = getRequirements(main.getConfigurationSection("mechanics.mechanic-requirements"), true);
YamlConfiguration config = plugin.getConfig("loot-conditions.yml");
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection section) {
conditionalLootsMap.put(entry.getKey(), getConditionalLoots(section));
}
}
}
@Override
public boolean registerRequirement(String type, RequirementBuilder requirementBuilder) {
if (this.requirementBuilderMap.containsKey(type)) return false;
this.requirementBuilderMap.put(type, requirementBuilder);
return true;
}
@Override
public boolean unregisterRequirement(String type) {
return this.requirementBuilderMap.remove(type) != null;
}
private void registerInbuiltRequirements() {
this.registerTimeRequirement();
this.registerYRequirement();
this.registerLogicRequirement();
this.registerCompare();
this.registerBiomeRequirement();
this.registerDateRequirement();
this.registerPluginLevelRequirement();
this.registerPermissionRequirement();
this.registerWorldRequirement();
this.registerWeatherRequirement();
this.registerSeasonRequirement();
this.registerInLavaRequirement();
this.registerRodRequirement();
this.registerBaitRequirement();
}
public ConditionalLoots getConditionalLoots(ConfigurationSection section) {
var sub = section.getConfigurationSection("sub-groups");
if (sub == null) {
return new ConditionalLoots(
getRequirements(section.getConfigurationSection("conditions"), false),
ConfigUtils.getModifiers(section.getStringList("list")),
null
);
} else {
HashMap<String, ConditionalLoots> subLoots = new HashMap<>();
for (Map.Entry<String, Object> entry : sub.getValues(false).entrySet()) {
if (entry.getValue() instanceof ConfigurationSection innerSection) {
subLoots.put(entry.getKey(), getConditionalLoots(innerSection));
}
}
return new ConditionalLoots(
getRequirements(section.getConfigurationSection("conditions"), false),
ConfigUtils.getModifiers(section.getStringList("list")),
subLoots
);
}
}
@Override
public HashMap<String, Double> getLootWithWeight(Condition condition) {
HashMap<String, Double> lootWeightMap = new HashMap<>();
Queue<HashMap<String, ConditionalLoots>> lootQueue = new LinkedList<>();
lootQueue.add(conditionalLootsMap);
while (!lootQueue.isEmpty()) {
HashMap<String, ConditionalLoots> currentLootMap = lootQueue.poll();
for (ConditionalLoots loots : currentLootMap.values()) {
if (loots.isConditionsMet(condition)) {
loots.combine(lootWeightMap);
if (loots.getSubLoots() != null) {
lootQueue.add(loots.getSubLoots());
}
}
}
}
return lootWeightMap;
}
@Nullable
@Override
public Requirement[] getRequirements(ConfigurationSection section, boolean advanced) {
if (section == null) return null;
List<Requirement> requirements = new ArrayList<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
String typeOrName = entry.getKey();
if (hasRequirement(typeOrName)) {
requirements.add(getRequirementBuilder(typeOrName).build(entry.getValue(), null, advanced));
} else {
requirements.add(getRequirement(section.getConfigurationSection(typeOrName), advanced));
}
}
return requirements.toArray(new Requirement[0]);
}
public boolean hasRequirement(String type) {
return requirementBuilderMap.containsKey(type);
}
@NotNull
@Override
public Requirement getRequirement(ConfigurationSection section, boolean advanced) {
List<Action> actionList = null;
if (advanced) {
actionList = new ArrayList<>();
if (section.contains("actions")) {
for (Map.Entry<String, Object> entry : Objects.requireNonNull(section.getConfigurationSection("actions")).getValues(false).entrySet()) {
if (entry.getValue() instanceof MemorySection inner) {
actionList.add(plugin.getActionManager().getAction(inner));
}
}
}
if (actionList.size() == 0)
actionList = null;
}
String type = section.getString("type");
if (type == null) {
throw new NullPointerException(section.getCurrentPath() + ".type" + " doesn't exist");
}
var builder = getRequirementBuilder(type);
if (builder == null) {
throw new NullPointerException("Requirement type: " + type + " doesn't exist");
}
return builder.build(section.get("value"), actionList, advanced);
}
@Override
public Requirement getRequirement(String key, Object value) {
return getRequirementBuilder(key).build(value);
}
private Pair<Integer, Integer> getIntegerPair(String range) {
String[] split = range.split("~");
return Pair.of(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
@Override
public RequirementBuilder getRequirementBuilder(String type) {
return requirementBuilderMap.get(type);
}
private void registerLogicRequirement() {
registerRequirement("logic", (args, actions, advanced) ->
new LogicRequirement(this, args, actions, advanced)
);
}
private void registerTimeRequirement() {
registerRequirement("time", (args, actions, advanced) -> {
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(this::getIntegerPair).toList();
return condition -> {
long time = condition.getLocation().getWorld().getTime();
for (Pair<Integer, Integer> pair : timePairs)
if (time >= pair.left() && time <= pair.right())
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerYRequirement() {
registerRequirement("ypos", (args, actions, advanced) -> {
List<Pair<Integer, Integer>> timePairs = ConfigUtils.stringListArgs(args).stream().map(this::getIntegerPair).toList();
return condition -> {
int y = condition.getLocation().getBlockY();
for (Pair<Integer, Integer> pair : timePairs)
if (y >= pair.left() && y <= pair.right())
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerInLavaRequirement() {
registerRequirement("in-lava", (args, actions, advanced) -> {
boolean inLava = (boolean) args;
return condition -> {
String current = condition.getArgs().get("in-lava");
if (current.equals(String.valueOf(inLava)))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerBiomeRequirement() {
registerRequirement("biome", (args, actions, advanced) -> {
HashSet<String> biomes = new HashSet<>(ConfigUtils.stringListArgs(args));
return condition -> {
String currentBiome = BiomeAPI.getBiome(condition.getLocation());
if (biomes.contains(currentBiome))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
registerRequirement("!biome", (args, actions, advanced) -> {
HashSet<String> biomes = new HashSet<>(ConfigUtils.stringListArgs(args));
return condition -> {
String currentBiome = BiomeAPI.getBiome(condition.getLocation());
if (!biomes.contains(currentBiome))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerWorldRequirement() {
registerRequirement("world", (args, actions, advanced) -> {
HashSet<String> worlds = new HashSet<>(ConfigUtils.stringListArgs(args));
return condition -> {
if (worlds.contains(condition.getLocation().getWorld().getName()))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
registerRequirement("!world", (args, actions, advanced) -> {
HashSet<String> worlds = new HashSet<>(ConfigUtils.stringListArgs(args));
return condition -> {
if (!worlds.contains(condition.getLocation().getWorld().getName()))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerWeatherRequirement() {
registerRequirement("weather", (args, actions, advanced) -> {
List<String> weathers = ConfigUtils.stringListArgs(args);
return condition -> {
String currentWeather;
World world = condition.getLocation().getWorld();
if (world.isThundering()) currentWeather = "thunder";
else if (world.isClearWeather()) currentWeather = "clear";
else currentWeather = "rain";
for (String weather : weathers)
if (weather.equalsIgnoreCase(currentWeather))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerDateRequirement() {
registerRequirement("date", (args, actions, advanced) -> {
HashSet<String> dates = new HashSet<>(ConfigUtils.stringListArgs(args));
return condition -> {
Calendar calendar = Calendar.getInstance();
String current = (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DATE);
if (dates.contains(current))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerPermissionRequirement() {
registerRequirement("permission", (args, actions, advanced) -> {
List<String> perms = ConfigUtils.stringListArgs(args);
return condition -> {
for (String perm : perms)
if (condition.getPlayer().hasPermission(perm))
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
registerRequirement("!permission", (args, actions, advanced) -> {
List<String> perms = ConfigUtils.stringListArgs(args);
return condition -> {
for (String perm : perms)
if (condition.getPlayer().hasPermission(perm)) {
if (advanced) triggerActions(actions, condition);
return false;
}
return true;
};
});
}
private void registerSeasonRequirement() {
registerRequirement("season", (args, actions, advanced) -> {
List<String> seasons = ConfigUtils.stringListArgs(args);
return condition -> {
SeasonInterface seasonInterface = plugin.getIntegrationManager().getSeasonInterface();
if (seasonInterface == null) return true;
String season = seasonInterface.getSeason(condition.getLocation().getWorld());
if (seasons.contains(season)) return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerRodRequirement() {
registerRequirement("rod", (args, actions, advanced) -> {
List<String> rods = ConfigUtils.stringListArgs(args);
return condition -> {
String id = condition.getArg("rod");
if (rods.contains(id)) return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
registerRequirement("!rod", (args, actions, advanced) -> {
List<String> rods = ConfigUtils.stringListArgs(args);
return condition -> {
String id = condition.getArg("rod");
if (!rods.contains(id)) return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerBaitRequirement() {
registerRequirement("bait", (args, actions, advanced) -> {
List<String> baits = ConfigUtils.stringListArgs(args);
return condition -> {
String id = condition.getArg("bait");
if (baits.contains(id)) return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
registerRequirement("!bait", (args, actions, advanced) -> {
List<String> baits = ConfigUtils.stringListArgs(args);
return condition -> {
String id = condition.getArg("bait");
if (!baits.contains(id)) return true;
if (advanced) triggerActions(actions, condition);
return false;
};
});
}
private void registerPluginLevelRequirement() {
registerRequirement("plugin-level", (args, actions, advanced) -> {
if (args instanceof ConfigurationSection section) {
String pluginName = section.getString("plugin");
int level = section.getInt("level");
String target = section.getString("target");
return condition -> {
LevelInterface levelInterface = plugin.getIntegrationManager().getLevelHook(pluginName);
if (levelInterface == null) {
LogUtils.warn("Plugin (" + pluginName + "'s) level is not compatible. Please double check if it's a problem caused by pronunciation.");
return true;
}
if (levelInterface.getLevel(condition.getPlayer(), target) >= level)
return true;
if (advanced) triggerActions(actions, condition);
return false;
};
}
return null;
});
}
private void registerCompare() {
registerRequirement("compare", (args, actions, advanced) -> condition -> {
if (evaluateExpression((String) args, condition.getPlayer()))
return true;
if (advanced) triggerActions(actions, condition);
return false;
});
}
private void triggerActions(List<Action> actions, Condition condition) {
if (actions != null)
for (Action action : actions)
action.trigger(condition);
}
private double doubleArg(String s, Player player) {
double arg = 0;
try {
arg = Double.parseDouble(s);
} catch (NumberFormatException e1) {
try {
arg = Double.parseDouble(plugin.getPlaceholderManager().setPlaceholders(player, s));
} catch (NumberFormatException e2) {
LogUtils.severe(String.format("Invalid placeholder %s", s), e2);
}
}
return arg;
}
public boolean evaluateExpression(String input, Player player) {
input = input.replace("\\s", "");
Pattern pattern = Pattern.compile("(-?\\d+\\.?\\d*)(==|!=|<=?|>=?)(-?\\d+\\.?\\d*)");
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
double num1 = doubleArg(matcher.group(1), player);
String operator = matcher.group(2);
double num2 = doubleArg(matcher.group(3), player);
return switch (operator) {
case ">" -> num1 > num2;
case "<" -> num1 < num2;
case ">=" -> num1 >= num2;
case "<=" -> num1 <= num2;
case "==" -> num1 == num2;
case "!=" -> num1 != num2;
default -> throw new IllegalArgumentException("Unsupported operator: " + operator);
};
} else {
throw new IllegalArgumentException("Invalid input format: " + input);
}
}
}

View File

@@ -0,0 +1,117 @@
package net.momirealms.customfishing.mechanic.requirement.inbuilt;
import net.momirealms.customfishing.api.manager.RequirementManager;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.mechanic.condition.Condition;
import net.momirealms.customfishing.api.mechanic.requirement.Requirement;
import net.momirealms.customfishing.mechanic.requirement.AbstractRequirement;
import org.bukkit.configuration.MemorySection;
import java.util.*;
public class LogicRequirement extends AbstractRequirement {
private List<Requirement> requirementList;
private boolean checkAction;
public LogicRequirement(RequirementManager requirementManager, Object args, List<Action> actions, boolean checkAction) {
super(actions);
if (!(args instanceof MemorySection section1))
return;
this.requirementList = new ArrayList<>();
this.checkAction = checkAction;
Deque<StackElement> stack = new ArrayDeque<>();
stack.push(new StackElement(section1.getValues(false), requirementList));
while (!stack.isEmpty()) {
StackElement stackElement = stack.pop();
Map<String, Object> currentMap = stackElement.getMap();
List<Requirement> currentResult = stackElement.getRequirements();
for (Map.Entry<String, Object> entry : currentMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof MemorySection section2) {
Map<String, Object> sectionMap = section2.getValues(false);
if (key.startsWith("&&")) {
List<Requirement> andReqList = new ArrayList<>();
currentResult.add(new AndRequirement(andReqList));
stack.push(new StackElement(sectionMap, andReqList));
} else if (key.startsWith("||")) {
List<Requirement> orReqList = new ArrayList<>();
currentResult.add(new OrRequirement(orReqList));
stack.push(new StackElement(sectionMap, orReqList));
} else {
currentResult.add(requirementManager.getRequirement(section2, checkAction));
}
}
}
}
}
public static class StackElement {
private final Map<String, Object> map;
private final List<Requirement> requirements;
public StackElement(Map<String, Object> map, List<Requirement> requirements) {
this.map = map;
this.requirements = requirements;
}
public Map<String, Object> getMap() {
return map;
}
public List<Requirement> getRequirements() {
return requirements;
}
}
@Override
public boolean isConditionMet(Condition condition) {
for (Requirement requirement : requirementList) {
if (!requirement.isConditionMet(condition)) {
if (checkAction) super.triggerActions(condition);
return false;
}
}
return true;
}
public static class AndRequirement implements Requirement {
private final List<Requirement> requirementList;
public AndRequirement(List<Requirement> requirementList) {
this.requirementList = requirementList;
}
@Override
public boolean isConditionMet(Condition condition) {
for (Requirement requirement : requirementList) {
if (!requirement.isConditionMet(condition)) {
return false;
}
}
return true;
}
}
public static class OrRequirement implements Requirement {
private final List<Requirement> requirementList;
public OrRequirement(List<Requirement> requirementList) {
this.requirementList = requirementList;
}
@Override
public boolean isConditionMet(Condition condition) {
for (Requirement requirement : requirementList) {
if (requirement.isConditionMet(condition)) {
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,4 @@
package net.momirealms.customfishing.mechanic.totem;
public class TotemManagerImpl {
}

View File

@@ -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.scheduler;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.scheduler.BukkitTask;
public class BukkitSchedulerImpl implements SyncScheduler {
private final CustomFishingPlugin plugin;
public BukkitSchedulerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
}
@Override
public void runSyncTask(Runnable runnable, Location location) {
if (Bukkit.isPrimaryThread())
runnable.run();
else
Bukkit.getScheduler().runTask(plugin, runnable);
}
@Override
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
return new BukkitCancellableTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
}
@Override
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
return new BukkitCancellableTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
}
public static class BukkitCancellableTask implements CancellableTask {
private final BukkitTask bukkitTask;
public BukkitCancellableTask(BukkitTask bukkitTask) {
this.bukkitTask = bukkitTask;
}
@Override
public void cancel() {
this.bukkitTask.cancel();
}
@Override
public boolean isCancelled() {
return this.bukkitTask.isCancelled();
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.scheduler;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
public class FoliaSchedulerImpl implements SyncScheduler {
private final CustomFishingPlugin plugin;
public FoliaSchedulerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
}
@Override
public void runSyncTask(Runnable runnable, Location location) {
Bukkit.getRegionScheduler().execute(plugin, location, runnable);
}
@Override
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
return new FoliaCancellableTask(Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, (scheduledTask -> runnable.run()), delay, period));
}
@Override
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
return new FoliaCancellableTask(Bukkit.getRegionScheduler().runDelayed(plugin, location, (scheduledTask -> runnable.run()), delay));
}
public static class FoliaCancellableTask implements CancellableTask {
private final ScheduledTask scheduledTask;
public FoliaCancellableTask(ScheduledTask scheduledTask) {
this.scheduledTask = scheduledTask;
}
@Override
public void cancel() {
this.scheduledTask.cancel();
}
@Override
public boolean isCancelled() {
return this.scheduledTask.isCancelled();
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.scheduler;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.api.scheduler.Scheduler;
import net.momirealms.customfishing.setting.Config;
import org.bukkit.Location;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SchedulerImpl implements Scheduler {
private final SyncScheduler syncScheduler;
private final ScheduledThreadPoolExecutor schedule;
private final CustomFishingPlugin plugin;
public SchedulerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.syncScheduler = plugin.getVersionManager().isFolia() ?
new FoliaSchedulerImpl(plugin) : new BukkitSchedulerImpl(plugin);
this.schedule = new ScheduledThreadPoolExecutor(4);
this.schedule.setMaximumPoolSize(4);
this.schedule.setKeepAliveTime(10, TimeUnit.SECONDS);
this.schedule.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
}
public void reload() {
this.schedule.setCorePoolSize(Config.corePoolSize);
this.schedule.setKeepAliveTime(Config.keepAliveTime, TimeUnit.SECONDS);
this.schedule.setMaximumPoolSize(Config.maximumPoolSize);
}
public void shutdown() {
if (this.schedule != null && !this.schedule.isShutdown())
this.schedule.shutdown();
}
@Override
public void runTaskSync(Runnable runnable, Location location) {
this.syncScheduler.runSyncTask(runnable, location);
}
@Override
public void runTaskAsync(Runnable runnable) {
this.schedule.execute(runnable);
}
@Override
public CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period) {
return this.syncScheduler.runTaskSyncTimer(runnable, location, delay, period);
}
@Override
public CancellableTask runTaskAsyncLater(Runnable runnable, long delay, TimeUnit timeUnit) {
return new ScheduledTask(schedule.schedule(runnable, delay, timeUnit));
}
@Override
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay, TimeUnit timeUnit) {
return new ScheduledTask(schedule.schedule(() -> {
runTaskSync(runnable, location);
}, delay, timeUnit));
}
@Override
public CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay) {
return this.syncScheduler.runTaskSyncLater(runnable, location, delay);
}
@Override
public CancellableTask runTaskAsyncTimer(Runnable runnable, long delay, long period, TimeUnit timeUnit) {
return new ScheduledTask(schedule.scheduleAtFixedRate(runnable, delay, period, timeUnit));
}
public static class ScheduledTask implements CancellableTask {
private final ScheduledFuture<?> scheduledFuture;
public ScheduledTask(ScheduledFuture<?> scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
@Override
public void cancel() {
this.scheduledFuture.cancel(false);
}
@Override
public boolean isCancelled() {
return this.scheduledFuture.isCancelled();
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.scheduler;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import org.bukkit.Location;
public interface SyncScheduler {
void runSyncTask(Runnable runnable, Location location);
CancellableTask runTaskSyncTimer(Runnable runnable, Location location, long delay, long period);
CancellableTask runTaskSyncLater(Runnable runnable, Location location, long delay);
}

View File

@@ -0,0 +1,139 @@
package net.momirealms.customfishing.setting;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.mechanic.action.Action;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.api.util.OffsetUtils;
import org.bukkit.Material;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.EventPriority;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public class Config {
// config version
public static String configVersion = "26";
// language
public static String language;
// update checker
public static boolean updateChecker;
// BStats
public static boolean metrics;
// fishing event priority
public static EventPriority eventPriority;
// thread pool settings
public static int corePoolSize;
public static int maximumPoolSize;
public static int keepAliveTime;
// detection order for item id
public static List<String> itemDetectOrder;
// fishing bag
public static boolean enableFishingBag;
public static boolean bagStoreLoots;
public static String bagTitle;
public static List<Material> bagWhiteListItems;
// Animation
public static boolean enableBaitAnimation;
public static boolean enableSplashAnimation;
public static int splashAnimationTime;
public static String lavaSplashItem;
public static String waterSplashItem;
// Lava fishing
public static int lavaMinTime;
public static int lavaMaxTime;
// Exception
public static boolean vanillaMechanicIfNoLoot;
public static Action[] noLootActions;
public static boolean debug;
// Competition
public static boolean redisRanking;
public static int placeholderLimit;
//
public static int dataSaveInterval;
public static void load() {
try {
YamlDocument.create(
new File(CustomFishingPlugin.getInstance().getDataFolder(), "config.yml"),
Objects.requireNonNull(CustomFishingPlugin.getInstance().getResource("config.yml")),
GeneralSettings.DEFAULT,
LoaderSettings
.builder()
.setAutoUpdate(true)
.build(),
DumperSettings.DEFAULT,
UpdaterSettings
.builder()
.setVersioning(new BasicVersioning("config-version"))
.addIgnoredRoute(configVersion, "mechanics.mechanic-requirements", '.')
.addIgnoredRoute(configVersion, "mechanics.global-loot-properties", '.')
.build()
);
loadSettings(CustomFishingPlugin.getInstance().getConfig("config.yml"));
} catch (IOException e) {
LogUtils.warn(e.getMessage());
}
}
private static void loadSettings(YamlConfiguration config) {
debug = config.getBoolean("debug", false);
language = config.getString("lang", "english");
updateChecker = config.getBoolean("update-checker");
metrics = config.getBoolean("metrics");
eventPriority = EventPriority.valueOf(config.getString("other-settings.event-priority", "NORMAL").toUpperCase(Locale.ENGLISH));
corePoolSize = config.getInt("other-settings.thread-pool-settings.corePoolSize", 4);
maximumPoolSize = config.getInt("other-settings.thread-pool-settings.maximumPoolSize", 8);
keepAliveTime = config.getInt("other-settings.thread-pool-settings.keepAliveTime", 10);
itemDetectOrder = config.getStringList("other-settings.item-detection-order");
enableFishingBag = config.getBoolean("mechanics.fishing-bag.enable", true);
bagTitle = config.getString("mechanics.fishing-bag.bag-title");
bagStoreLoots = config.getBoolean("mechanics.fishing-bag.can-store-loot", false);
bagWhiteListItems = config.getStringList("mechanics.fishing-bag.whitelist-items").stream().map(it -> Material.valueOf(it.toUpperCase(Locale.ENGLISH))).toList();
lavaMinTime = config.getInt("mechanics.lava-fishing.min-wait-time", 100);
lavaMaxTime = config.getInt("mechanics.lava-fishing.max-wait-time", 600);
enableSplashAnimation = config.getBoolean("mechanics.animation.splash.enable", true);
enableBaitAnimation = config.getBoolean("mechanics.animation.bait.enable", true);
waterSplashItem = config.getString("mechanics.animation.splash.water");
lavaSplashItem = config.getString("mechanics.animation.splash.lava");
splashAnimationTime = config.getInt("mechanics.animation.splash.duration");
vanillaMechanicIfNoLoot = config.getBoolean("mechanics.vanilla-mechanic-if-no-loot.enable", false);
noLootActions = CustomFishingPlugin.get().getActionManager().getActions(config.getConfigurationSection("mechanics.vanilla-mechanic-if-no-loot.actions"));
redisRanking = config.getBoolean("mechanics.competition.redis-ranking", false);
placeholderLimit = config.getInt("mechanics.competition.placeholder-limit", 3);
dataSaveInterval = config.getInt("other-settings.data-saving-interval", 600);
OffsetUtils.loadConfig(config.getConfigurationSection("other-settings.offset-characters"));
}
}

View File

@@ -0,0 +1,83 @@
package net.momirealms.customfishing.setting;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.util.LogUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
public class Locale {
public static String MSG_Total_Size;
public static String MSG_Catch_Amount;
public static String MSG_Total_Score;
public static String MSG_Max_Size;
public static String MSG_No_Player;
public static String MSG_No_Score;
public static String MSG_Prefix;
public static String MSG_Reload;
public static String MSG_Competition_Not_Exist;
public static String MSG_No_Competition_Ongoing;
public static String MSG_End_Competition;
public static String MSG_Stop_Competition;
public static String MSG_No_Rank;
public static String MSG_Item_Not_Exists;
public static String MSG_Get_Item;
public static String MSG_Give_Item;
public static String MSG_Never_Played;
public static String MSG_Unsafe_Modification;
public static void load() {
try {
YamlDocument.create(
new File(CustomFishingPlugin.getInstance().getDataFolder(), "messages/" + Config.language + ".yml"),
Objects.requireNonNull(CustomFishingPlugin.getInstance().getResource("messages/" + Config.language + ".yml")),
GeneralSettings.DEFAULT,
LoaderSettings
.builder()
.setAutoUpdate(true)
.build(),
DumperSettings.DEFAULT,
UpdaterSettings
.builder()
.setVersioning(new BasicVersioning("config-version"))
.build()
);
} catch (IOException e) {
LogUtils.warn(e.getMessage());
}
loadSettings(CustomFishingPlugin.get().getConfig("messages/" + Config.language + ".yml"));
}
private static void loadSettings(YamlConfiguration locale) {
ConfigurationSection msgSection = locale.getConfigurationSection("messages");
if (msgSection != null) {
MSG_Prefix = msgSection.getString("prefix");
MSG_Reload = msgSection.getString("reload");
MSG_Competition_Not_Exist = msgSection.getString("competition-not-exist");
MSG_No_Competition_Ongoing = msgSection.getString("no-competition-ongoing");
MSG_Stop_Competition = msgSection.getString("stop-competition");
MSG_End_Competition = msgSection.getString("end-competition");
MSG_No_Player = msgSection.getString("no-player");
MSG_No_Score = msgSection.getString("no-score");
MSG_No_Rank = msgSection.getString("no-rank");
MSG_Catch_Amount = msgSection.getString("goal-catch-amount");
MSG_Max_Size = msgSection.getString("goal-max-size");
MSG_Total_Score = msgSection.getString("goal-total-score");
MSG_Total_Size = msgSection.getString("goal-total-size");
MSG_Item_Not_Exists = msgSection.getString("item-not-exist");
MSG_Get_Item = msgSection.getString("get-item");
MSG_Give_Item = msgSection.getString("give-item");
MSG_Never_Played = msgSection.getString("never-played");
MSG_Unsafe_Modification = msgSection.getString("unsafe-modification");
}
}
}

View File

@@ -0,0 +1,278 @@
package net.momirealms.customfishing.storage;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import net.momirealms.customfishing.CustomFishingPluginImpl;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.DataStorageInterface;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.api.data.user.OfflineUser;
import net.momirealms.customfishing.api.manager.StorageManager;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.storage.method.database.nosql.MongoDBImpl;
import net.momirealms.customfishing.storage.method.database.nosql.RedisManager;
import net.momirealms.customfishing.storage.method.database.sql.H2Impl;
import net.momirealms.customfishing.storage.method.database.sql.MariaDBImpl;
import net.momirealms.customfishing.storage.method.database.sql.MySQLImpl;
import net.momirealms.customfishing.storage.method.database.sql.SQLiteImpl;
import net.momirealms.customfishing.storage.method.file.JsonImpl;
import net.momirealms.customfishing.storage.method.file.YAMLImpl;
import net.momirealms.customfishing.storage.user.OfflineUserImpl;
import net.momirealms.customfishing.storage.user.OnlineUserImpl;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
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.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 StorageManagerImpl implements StorageManager, Listener {
private final CustomFishingPlugin plugin;
private DataStorageInterface dataSource;
private StorageType previousType;
private final ConcurrentHashMap<UUID, OnlineUserImpl> onlineUserMap;
private final HashSet<UUID> locked;
private boolean hasRedis;
private RedisManager redisManager;
private String uniqueID;
public StorageManagerImpl(CustomFishingPluginImpl plugin) {
this.plugin = plugin;
this.locked = new HashSet<>();
this.onlineUserMap = new ConcurrentHashMap<>();
Bukkit.getPluginManager().registerEvents(this, plugin);
}
public void reload() {
YamlConfiguration config = plugin.getConfig("database.yml");
uniqueID = config.getString("unique-server-id", "default");
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 H2Impl(plugin);
case JSON -> this.dataSource = new JsonImpl(plugin);
case YAML -> this.dataSource = new YAMLImpl(plugin);
case SQLite -> this.dataSource = new SQLiteImpl(plugin);
case MySQL -> this.dataSource = new MySQLImpl(plugin);
case MariaDB -> this.dataSource = new MariaDBImpl(plugin);
case MongoDB -> this.dataSource = new MongoDBImpl(plugin);
}
if (dataSource != null) this.dataSource.initialize();
else LogUtils.severe("No storage type is set.");
}
if (!hasRedis && config.getBoolean("Redis.enable", false)) {
hasRedis = true;
this.redisManager = new RedisManager(plugin);
this.redisManager.initialize();
}
if (hasRedis && !config.getBoolean("Redis.enable", false) && redisManager != null) {
redisManager.disable();
redisManager = null;
}
}
public void disable() {
HandlerList.unregisterAll(this);
if (this.dataSource != null)
dataSource.disable();
if (this.redisManager != null)
redisManager.disable();
}
@Override
public String getUniqueID() {
return uniqueID;
}
@Override
public OnlineUserImpl getOnlineUser(UUID uuid) {
return onlineUserMap.get(uuid);
}
@Override
public CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean force) {
var optionalDataFuture = dataSource.getPlayerData(uuid, force);
return optionalDataFuture.thenCompose(optionalUser -> {
if (optionalUser.isEmpty()) {
// locked
return CompletableFuture.completedFuture(Optional.empty());
}
PlayerData data = optionalUser.get();
if (data == PlayerData.NEVER_PLAYED) {
return CompletableFuture.completedFuture(Optional.of(OfflineUserImpl.NEVER_PLAYED_USER));
} else {
OfflineUserImpl offlineUser = new OfflineUserImpl(uuid, data.getName(), data);
return CompletableFuture.completedFuture(Optional.of(offlineUser));
}
});
}
@Override
public CompletableFuture<Integer> getRedisPlayerCount() {
return redisManager.getPlayerCount();
}
@Override
public DataStorageInterface getDataSource() {
return dataSource;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
locked.add(uuid);
if (!hasRedis) {
waitForDataLockRelease(uuid, 1);
} else {
redisReadingData(uuid);
}
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
UUID uuid = player.getUniqueId();
if (locked.contains(uuid))
return;
OnlineUserImpl onlineUser = onlineUserMap.remove(uuid);
if (onlineUser == null) return;
PlayerData data = onlineUser.getPlayerData();
if (hasRedis) {
redisManager.setChangeServer(uuid).thenRun(
() -> redisManager.setPlayData(uuid, data, true).thenRun(
() -> dataSource.setPlayData(uuid, data, true).thenAccept(
result -> {
if (result) locked.remove(uuid);
})));
} else {
dataSource.setPlayData(uuid, data, true).thenAccept(
result -> {
if (result) locked.remove(uuid);
});
}
}
public void redisReadingData(UUID uuid) {
// delay 0.5s for another server to insert the key
plugin.getScheduler().runTaskAsyncLater(() -> redisManager.getChangeServer(uuid).thenAccept(result -> {
if (!result) {
waitForDataLockRelease(uuid, 3);
} else {
new RedisGetDataTask(uuid);
}
}), 500, TimeUnit.MILLISECONDS);
}
public class RedisGetDataTask implements Runnable {
private final UUID uuid;
private int triedTimes;
private final CancellableTask task;
public RedisGetDataTask(UUID uuid) {
this.uuid = uuid;
this.task = plugin.getScheduler().runTaskAsyncTimer(this, 200, 200, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
triedTimes++;
Player player = Bukkit.getPlayer(uuid);
if (player == null || !player.isOnline()) {
// offline
task.cancel();
return;
}
if (triedTimes >= 10) {
waitForDataLockRelease(uuid, 3);
return;
}
redisManager.getPlayerData(uuid, false).thenAccept(optionalData -> {
if (optionalData.isPresent()) {
putDataInCache(player, optionalData.get());
task.cancel();
}
});
}
}
// wait 1 second for the lock to release
// try three times at most
public void waitForDataLockRelease(UUID uuid, int times) {
plugin.getScheduler().runTaskAsyncLater(() -> {
var player = Bukkit.getPlayer(uuid);
if (player == null || !player.isOnline() || times > 3)
return;
this.dataSource.getPlayerData(uuid, false).thenAccept(optionalData -> {
if (optionalData.isEmpty()) {
waitForDataLockRelease(uuid, times + 1);
} else {
putDataInCache(player, optionalData.get());
}
});
}, 1, TimeUnit.SECONDS);
}
public void putDataInCache(Player player, PlayerData playerData) {
locked.remove(player.getUniqueId());
OnlineUserImpl bukkitUser = new OnlineUserImpl(player, playerData);
onlineUserMap.put(player.getUniqueId(), bukkitUser);
}
@Override
public boolean isRedisEnabled() {
return hasRedis;
}
@Nullable
public RedisManager getRedisManager() {
return redisManager;
}
@Override
public byte[] toBytes(@NotNull PlayerData data) {
return toJson(data).getBytes(StandardCharsets.UTF_8);
}
@Override
@NotNull
public String toJson(@NotNull PlayerData data) {
return new GsonBuilder().create().toJson(data);
}
@Override
@NotNull
public PlayerData fromBytes(byte[] data) {
try {
return new GsonBuilder().create().fromJson(new String(data, StandardCharsets.UTF_8), PlayerData.class);
} catch (JsonSyntaxException e) {
throw new DataSerializationException("Failed to get PlayerData from bytes", e);
}
}
public static class DataSerializationException extends RuntimeException {
protected DataSerializationException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -0,0 +1,29 @@
package net.momirealms.customfishing.storage.method;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.DataStorageInterface;
import java.time.Instant;
public abstract class AbstractStorage implements DataStorageInterface {
protected CustomFishingPlugin plugin;
public AbstractStorage(CustomFishingPlugin plugin) {
this.plugin = plugin;
}
@Override
public void initialize() {
}
@Override
public void disable() {
}
public int getCurrentSeconds() {
return (int) Instant.now().getEpochSecond();
}
}

View File

@@ -0,0 +1,134 @@
package net.momirealms.customfishing.storage.method.database.nosql;
import com.mongodb.*;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.InsertOneResult;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.storage.method.AbstractStorage;
import org.bson.Document;
import org.bson.UuidRepresentation;
import org.bson.types.Binary;
import org.bson.types.ObjectId;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class MongoDBImpl extends AbstractStorage {
private MongoClient mongoClient;
private MongoDatabase database;
private String collectionPrefix;
public MongoDBImpl(CustomFishingPlugin plugin) {
super(plugin);
}
@Override
public void initialize() {
YamlConfiguration config = plugin.getConfig("database.yml");
ConfigurationSection section = config.getConfigurationSection("MongoDB");
if (section == null) {
LogUtils.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();
}
}
public String getCollectionName(String sub) {
return getCollectionPrefix() + "_" + sub;
}
public String getCollectionPrefix() {
return collectionPrefix;
}
@Override
public StorageType getStorageType() {
return StorageType.MongoDB;
}
@Override
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
var future = new CompletableFuture<Optional<PlayerData>>();
plugin.getScheduler().runTaskAsync(() -> {
MongoCollection<Document> collection = database.getCollection("movies");
Document doc = collection.find(Filters.eq("uuid", uuid)).first();
if (doc == null) {
if (Bukkit.getPlayer(uuid) != null) {
future.complete(Optional.of(PlayerData.empty()));
} else {
future.complete(Optional.of(PlayerData.NEVER_PLAYED));
}
} else {
if (!force && doc.getInteger("lock") != 0) {
future.complete(Optional.empty());
return;
}
Binary binary = (Binary) doc.get("data");
future.complete(Optional.of(plugin.getStorageManager().fromBytes(binary.getData())));
}
});
return future;
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean unlock) {
var future = new CompletableFuture<Boolean>();
plugin.getScheduler().runTaskAsync(() -> {
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
try {
InsertOneResult result = collection.insertOne(new Document()
.append("_id", new ObjectId())
.append("uuid", uuid)
.append("lock", unlock ? 0 : getCurrentSeconds())
.append("data", new Binary(plugin.getStorageManager().toBytes(playerData))));
future.complete(result.wasAcknowledged());
} catch (MongoException e) {
future.completeExceptionally(e);
}
});
return future;
}
}

View File

@@ -0,0 +1,259 @@
package net.momirealms.customfishing.storage.method.database.nosql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.api.scheduler.CancellableTask;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.storage.method.AbstractStorage;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.resps.Tuple;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class RedisManager extends AbstractStorage {
private static RedisManager instance;
private JedisPool jedisPool;
private String password;
private int port;
private String host;
private JedisPoolConfig jedisPoolConfig;
private boolean useSSL;
private Jedis subscriber;
private JedisPubSub pubSub;
private CancellableTask checkConnectionTask;
public RedisManager(CustomFishingPlugin plugin) {
super(plugin);
instance = this;
}
public static RedisManager getInstance() {
return instance;
}
public Jedis getJedis() {
return jedisPool.getResource();
}
@Override
public void initialize() {
YamlConfiguration config = plugin.getConfig("database.yml");
ConfigurationSection section = config.getConfigurationSection("Redis");
if (section == null) {
LogUtils.warn("Failed to load database config. It seems that your config is broken. Please regenerate a new one.");
return;
}
jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setTestWhileIdle(true);
jedisPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000));
jedisPoolConfig.setNumTestsPerEvictionRun(-1);
jedisPoolConfig.setMinEvictableIdleTime(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);
}
try (Jedis jedis = jedisPool.getResource()) {
jedis.ping();
} catch (JedisException e) {
LogUtils.warn("Failed to connect redis.", e);
}
this.checkConnectionTask = plugin.getScheduler().runTaskAsyncTimer(() -> {
try {
pubSub.ping();
} catch (JedisConnectionException e) {
subscribe();
}
}, 30, 30, TimeUnit.SECONDS);
}
@Override
public void disable() {
this.removeServerPlayers(plugin.getStorageManager().getUniqueID());
if (checkConnectionTask != null && !checkConnectionTask.isCancelled())
checkConnectionTask.cancel();
if (jedisPool != null && !jedisPool.isClosed())
jedisPool.close();
if (pubSub != null && !pubSub.isSubscribed())
pubSub.unsubscribe();
if (subscriber != null)
subscriber.close();
}
public void sendRedisMessage(@NotNull String channel, @NotNull String message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.publish(channel, message);
}
}
private void subscribe() {
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())
) {
subscriber = jedis;
subscriber.connect();
pubSub = new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
if (!channel.equals("cf_competition")) {
return;
}
String[] split = message.split(";");
String action = split[0];
switch (action) {
case "start" -> {
// start competition for all the servers that connected to redis
plugin.getCompetitionManager().startCompetition(split[1], true, false);
}
case "end" -> {
if (plugin.getCompetitionManager().getOnGoingCompetition() != null)
plugin.getCompetitionManager().getOnGoingCompetition().end();
}
case "stop" -> {
if (plugin.getCompetitionManager().getOnGoingCompetition() != null)
plugin.getCompetitionManager().getOnGoingCompetition().stop();
}
}
}
};
subscriber.subscribe(pubSub, "cf_competition");
}
}).start();
}
@Override
public StorageType getStorageType() {
return StorageType.Redis;
}
public CompletableFuture<Integer> getPlayerCount() {
var future = new CompletableFuture<Integer>();
plugin.getScheduler().runTaskAsync(() -> {
int players = 0;
try (Jedis jedis = jedisPool.getResource()) {
var list = jedis.zrangeWithScores("cf_players",0, -1);
for (Tuple tuple : list) {
players += (int) tuple.getScore();
}
}
future.complete(players);
});
return future;
}
public CompletableFuture<Void> setServerPlayers(int amount, String unique) {
var future = new CompletableFuture<Void>();
plugin.getScheduler().runTaskAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.zadd("cf_players", amount, unique);
}
future.complete(null);
});
return future;
}
public void removeServerPlayers(String unique) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.zrem("cf_players", unique);
}
}
public CompletableFuture<Void> setChangeServer(UUID uuid) {
var future = new CompletableFuture<Void>();
plugin.getScheduler().runTaskAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.setex(
getRedisKey("cf_server", uuid),
10,
new byte[0]
);
}
future.complete(null);
});
return future;
}
public CompletableFuture<Boolean> getChangeServer(UUID uuid) {
var future = new CompletableFuture<Boolean>();
plugin.getScheduler().runTaskAsync(() -> {
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;
}
@Override
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean ignore) {
var future = new CompletableFuture<Optional<PlayerData>>();
plugin.getScheduler().runTaskAsync(() -> {
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());
}
}
});
return future;
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean ignore) {
var future = new CompletableFuture<Boolean>();
plugin.getScheduler().runTaskAsync(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.setex(
getRedisKey("cf_data", uuid),
10,
plugin.getStorageManager().toBytes(playerData)
);
}
});
return future;
}
private byte[] getRedisKey(String key, @NotNull UUID uuid) {
return (key + ":" + uuid).getBytes(StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,103 @@
package net.momirealms.customfishing.storage.method.database.sql;
import com.zaxxer.hikari.HikariDataSource;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.api.util.LogUtils;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
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(CustomFishingPlugin 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) {
LogUtils.warn("No MariaDB driver is found");
} else if (getStorageType() == StorageType.MySQL) {
try {
Class.forName("com.mysql.jdbc.Driver");
LogUtils.warn("It seems that you are not using MySQL 8.0+. It's recommended to update.");
} catch (ClassNotFoundException e2) {
LogUtils.warn("No MySQL driver is found");
}
}
}
}
@Override
public void initialize() {
YamlConfiguration config = plugin.getConfig("database.yml");
ConfigurationSection section = config.getConfigurationSection(sqlBrand);
if (section == null) {
LogUtils.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");
dataSource = new HikariDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.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")
));
dataSource.setUsername(section.getString("user", "root"));
dataSource.setPassword(section.getString("password", "pa55w0rd"));
dataSource.setMaximumPoolSize(section.getInt("Pool-Settings.max-pool-size", 10));
dataSource.setMinimumIdle(section.getInt("Pool-Settings.min-idle", 10));
dataSource.setMaxLifetime(section.getLong("Pool-Settings.max-lifetime", 180000L));
dataSource.setKeepaliveTime(section.getLong("Pool-Settings.keep-alive-time", 60000L));
dataSource.setConnectionTimeout(section.getLong("Pool-Settings.time-out", 20000L));
dataSource.setPoolName("CustomFishingHikariPool");
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")
);
dataSource.setDataSourceProperties(properties);
super.createTableIfNotExist();
}
@Override
public void disable() {
if (dataSource != null && !dataSource.isClosed())
dataSource.close();
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}

View File

@@ -0,0 +1,143 @@
package net.momirealms.customfishing.storage.method.database.sql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.setting.Config;
import net.momirealms.customfishing.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.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractSQLDatabase extends AbstractStorage {
protected String tablePrefix;
public AbstractSQLDatabase(CustomFishingPlugin plugin) {
super(plugin);
}
public abstract Connection getConnection() throws SQLException;
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) {
LogUtils.warn("Failed to create tables", e);
}
} catch (SQLException e) {
LogUtils.warn("Failed to get sql connection", e);
} catch (IOException e) {
LogUtils.warn("Failed to get schema resource", e);
}
}
private String[] getSchema(@NotNull String fileName) throws IOException {
return replaceSchemaPlaceholder(new String(Objects.requireNonNull(plugin.getResource("schema/" + fileName + ".sql"))
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
}
private String replaceSchemaPlaceholder(@NotNull String sql) {
return sql.replace("{prefix}", tablePrefix);
}
public String getTableName(String sub) {
return getTablePrefix() + "_" + sub;
}
public String getTablePrefix() {
return tablePrefix;
}
@Override
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
var future = new CompletableFuture<Optional<PlayerData>>();
plugin.getScheduler().runTaskAsync(() -> {
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()) {
int lock = rs.getInt(2);
if (!force && (lock != 0 && getCurrentSeconds() - Config.dataSaveInterval <= lock)) {
statement.close();
rs.close();
connection.close();
future.complete(Optional.empty());
return;
}
final Blob blob = rs.getBlob("data");
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
blob.free();
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
} else if (Bukkit.getPlayer(uuid) != null) {
var data = PlayerData.empty();
insertPlayerData(uuid, data);
future.complete(Optional.of(data));
} else {
future.complete(Optional.of(PlayerData.NEVER_PLAYED));
}
} catch (SQLException e) {
LogUtils.warn("Failed to get " + uuid + "'s data.", e);
future.completeExceptionally(e);
}
});
return future;
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean unlock) {
var future = new CompletableFuture<Boolean>();
plugin.getScheduler().runTaskAsync(() -> {
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) {
LogUtils.warn("Failed to update " + uuid + "'s data.", e);
future.completeExceptionally(e);
}
});
return future;
}
public void insertPlayerData(UUID uuid, PlayerData playerData) {
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, getCurrentSeconds());
statement.setBlob(3, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
statement.execute();
} catch (SQLException e) {
LogUtils.warn("Failed to insert " + uuid + "'s data.", e);
}
}
public static class SqlConstants {
public static final String SQL_SELECT_BY_UUID = "SELECT * FROM `%s` WHERE `uuid` = ?";
public static final String SQL_UPDATE_BY_UUID = "UPDATE `%s` SET `lock` = ?, `data` = ? WHERE `uuid` = ?";
public static final String SQL_INSERT_DATA_BY_UUID = "INSERT INTO `%s`(`uuid`, `lock`, `data`) VALUES(?, ?, ?)";
}
}

View File

@@ -0,0 +1,47 @@
package net.momirealms.customfishing.storage.method.database.sql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.StorageType;
import org.bukkit.configuration.file.YamlConfiguration;
import org.h2.jdbcx.JdbcConnectionPool;
import java.io.File;
import java.sql.Connection;
import java.sql.SQLException;
public class H2Impl extends AbstractSQLDatabase {
private JdbcConnectionPool connectionPool;
public H2Impl(CustomFishingPlugin plugin) {
super(plugin);
}
@Override
public void initialize() {
YamlConfiguration config = plugin.getConfig("database.yml");
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());
this.connectionPool = JdbcConnectionPool.create(url, "sa", "");
super.createTableIfNotExist();
}
@Override
public void disable() {
if (connectionPool != null) {
connectionPool.dispose();
}
}
@Override
public StorageType getStorageType() {
return StorageType.H2;
}
@Override
public Connection getConnection() throws SQLException {
return connectionPool.getConnection();
}
}

View File

@@ -0,0 +1,16 @@
package net.momirealms.customfishing.storage.method.database.sql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.StorageType;
public class MariaDBImpl extends AbstractHikariDatabase {
public MariaDBImpl(CustomFishingPlugin plugin) {
super(plugin);
}
@Override
public StorageType getStorageType() {
return StorageType.MariaDB;
}
}

View File

@@ -0,0 +1,16 @@
package net.momirealms.customfishing.storage.method.database.sql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.StorageType;
public class MySQLImpl extends AbstractHikariDatabase {
public MySQLImpl(CustomFishingPlugin plugin) {
super(plugin);
}
@Override
public StorageType getStorageType() {
return StorageType.MySQL;
}
}

View File

@@ -0,0 +1,151 @@
package net.momirealms.customfishing.storage.method.database.sql;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.setting.Config;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.sqlite.SQLiteConfig;
import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class SQLiteImpl extends AbstractSQLDatabase {
private Connection connection;
private File databaseFile;
public SQLiteImpl(CustomFishingPlugin plugin) {
super(plugin);
}
@Override
public void initialize() {
YamlConfiguration config = plugin.getConfig("database.yml");
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;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null || connection.isClosed()) {
setConnection();
}
return connection;
}
@Override
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
var future = new CompletableFuture<Optional<PlayerData>>();
plugin.getScheduler().runTaskAsync(() -> {
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()) {
int lock = rs.getInt(2);
if (!force && (lock != 0 && getCurrentSeconds() - Config.dataSaveInterval <= lock)) {
statement.close();
rs.close();
connection.close();
future.complete(Optional.empty());
return;
}
final byte[] dataByteArray = rs.getBytes("data");
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
} else if (Bukkit.getPlayer(uuid) != null) {
var data = PlayerData.empty();
insertPlayerData(uuid, data);
future.complete(Optional.of(data));
} else {
future.complete(Optional.of(PlayerData.NEVER_PLAYED));
}
} catch (SQLException e) {
LogUtils.warn("Failed to get " + uuid + "'s data.", e);
future.completeExceptionally(e);
}
});
return future;
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean unlock) {
var future = new CompletableFuture<Boolean>();
plugin.getScheduler().runTaskAsync(() -> {
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) {
LogUtils.warn("Failed to update " + uuid + "'s data.", e);
future.completeExceptionally(e);
}
});
return future;
}
@Override
public void insertPlayerData(UUID uuid, PlayerData playerData) {
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, getCurrentSeconds());
statement.setBytes(3, plugin.getStorageManager().toBytes(playerData));
statement.execute();
} catch (SQLException e) {
LogUtils.warn("Failed to insert " + uuid + "'s data.", e);
}
}
private void setConnection() {
try {
if (!databaseFile.exists()) databaseFile.createNewFile();
Class.forName("org.sqlite.JDBC");
SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
config.setEncoding(SQLiteConfig.Encoding.UTF8);
config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
connection = DriverManager.getConnection(
String.format("jdbc:sqlite:%s", databaseFile.getAbsolutePath()),
config.toProperties()
);
} catch (IOException e) {
LogUtils.warn("Failed to create the SQLite database.", e);
} catch (SQLException e) {
LogUtils.warn("Failed to initialize SQLite database.", e);
} catch (ClassNotFoundException e) {
LogUtils.warn("Failed to find SQLite driver.", e);
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.storage.method.file;
import com.google.gson.Gson;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StorageType;
import net.momirealms.customfishing.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.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class JsonImpl extends AbstractStorage {
public JsonImpl(CustomFishingPlugin 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 ignore) {
File file = getPlayerDataFile(uuid);
PlayerData playerData;
if (file.exists()) {
playerData = readFromJsonFile(file, PlayerData.class);
} else if (Bukkit.getPlayer(uuid) != null) {
playerData = PlayerData.empty();
} else {
playerData = PlayerData.NEVER_PLAYED;
}
return CompletableFuture.completedFuture(Optional.of(playerData));
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean ignore) {
this.saveToJsonFile(playerData, getPlayerDataFile(uuid));
return CompletableFuture.completedFuture(true);
}
public File getPlayerDataFile(UUID uuid) {
return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".json");
}
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();
}
}
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);
}
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;
}
}

View File

@@ -0,0 +1,88 @@
package net.momirealms.customfishing.storage.method.file;
import net.momirealms.customfishing.api.CustomFishingPlugin;
import net.momirealms.customfishing.api.data.*;
import net.momirealms.customfishing.api.util.LogUtils;
import net.momirealms.customfishing.storage.method.AbstractStorage;
import net.momirealms.customfishing.util.ConfigUtils;
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.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class YAMLImpl extends AbstractStorage {
public YAMLImpl(CustomFishingPlugin plugin) {
super(plugin);
File folder = new File(plugin.getDataFolder(), "data");
if (!folder.exists()) folder.mkdirs();
}
@Override
public StorageType getStorageType() {
return StorageType.YAML;
}
public File getPlayerDataFile(UUID uuid) {
return new File(plugin.getDataFolder(), "data" + File.separator + uuid + ".yml");
}
@Override
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean ignore) {
File dataFile = getPlayerDataFile(uuid);
if (!dataFile.exists()) {
if (Bukkit.getPlayer(uuid) != null) {
return CompletableFuture.completedFuture(Optional.of(PlayerData.empty()));
} else {
return CompletableFuture.completedFuture(Optional.of(PlayerData.NEVER_PLAYED));
}
}
YamlConfiguration data = ConfigUtils.readData(dataFile);
PlayerData playerData = new PlayerData.Builder()
.setBagData(new InventoryData(data.getString("bag", ""), data.getInt("size", 9)))
.setEarningData(new EarningData(data.getDouble("earnings"), data.getInt("date")))
.setStats(getStatistics(data.getConfigurationSection("stats")))
.setName(data.getString("name"))
.build();
return CompletableFuture.completedFuture(Optional.of(playerData));
}
@Override
public CompletableFuture<Boolean> setPlayData(UUID uuid, PlayerData playerData, boolean ignore) {
YamlConfiguration data = new YamlConfiguration();
data.set("name", playerData.getName());
data.set("bag", playerData.getBagData().serialized);
data.set("size", playerData.getBagData().size);
data.set("date", playerData.getEarningData().date);
data.set("earnings", playerData.getEarningData().earnings);
ConfigurationSection section = data.createSection("stats");
for (Map.Entry<String, Integer> entry : playerData.getStatistics().statisticMap.entrySet()) {
section.set(entry.getKey(), entry.getValue());
}
try {
data.save(getPlayerDataFile(uuid));
} catch (IOException e) {
LogUtils.warn("Failed to save player data", e);
}
return CompletableFuture.completedFuture(true);
}
public StatisticData getStatistics(ConfigurationSection section) {
if (section == null)
return StatisticData.empty();
else {
HashMap<String, Integer> map = new HashMap<>();
for (Map.Entry<String, Object> entry : section.getValues(false).entrySet()) {
map.put(entry.getKey(), (Integer) entry.getValue());
}
return new StatisticData(map);
}
}
}

View File

@@ -0,0 +1,87 @@
package net.momirealms.customfishing.storage.user;
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
import net.momirealms.customfishing.api.data.EarningData;
import net.momirealms.customfishing.api.data.InventoryData;
import net.momirealms.customfishing.api.data.PlayerData;
import net.momirealms.customfishing.api.data.StatisticData;
import net.momirealms.customfishing.api.data.user.OfflineUser;
import net.momirealms.customfishing.api.mechanic.bag.FishingBagHolder;
import net.momirealms.customfishing.api.mechanic.statistic.Statistics;
import net.momirealms.customfishing.api.util.InventoryUtils;
import net.momirealms.customfishing.compatibility.papi.PlaceholderManagerImpl;
import net.momirealms.customfishing.setting.Config;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class OfflineUserImpl implements OfflineUser {
private final UUID uuid;
private final String name;
private final FishingBagHolder holder;
private final EarningData earningData;
private final Statistics statistics;
public static OfflineUserImpl NEVER_PLAYED_USER = new OfflineUserImpl(UUID.randomUUID(), "", PlayerData.empty());
public OfflineUserImpl(UUID uuid, String name, PlayerData playerData) {
this.name = name;
this.uuid = uuid;
this.holder = new FishingBagHolder(uuid);
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
this.holder.setInventory(InventoryUtils.createInventory(this.holder, playerData.getBagData().size,
AdventureManagerImpl.getInstance().getComponentFromMiniMessage(
PlaceholderManagerImpl.getInstance().parse(
offlinePlayer, Config.bagTitle, Map.of("{player}", Optional.ofNullable(offlinePlayer.getName()).orElse(String.valueOf(uuid)))
)
)));
this.holder.setItems(InventoryUtils.getInventoryItems(playerData.getBagData().serialized));
this.earningData = playerData.getEarningData();
this.statistics = new Statistics(playerData.getStatistics());
}
@Override
public String getName() {
return name;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public FishingBagHolder getHolder() {
return holder;
}
@Override
public EarningData getEarningData() {
return earningData;
}
@Override
public Statistics getStatistics() {
return statistics;
}
@Override
public boolean isOnline() {
Player player = Bukkit.getPlayer(uuid);
return player != null && player.isOnline();
}
@Override
public PlayerData getPlayerData() {
return new PlayerData.Builder()
.setBagData(new InventoryData(InventoryUtils.stacksToBase64(holder.getInventory().getStorageContents()), holder.getInventory().getSize()))
.setEarningData(earningData)
.setStats(new StatisticData(statistics.getStatisticMap()))
.setName(name)
.build();
}
}

Some files were not shown because too many files have changed in this diff Show More