9
0
mirror of https://github.com/Xiao-MoMi/Custom-Fishing.git synced 2025-12-30 20:39:18 +00:00

I don't want to waste time on resolving conflicts

This commit is contained in:
XiaoMoMi
2024-07-10 03:24:56 +08:00
parent 0ed5d5745a
commit 80594731e6
551 changed files with 29876 additions and 31172 deletions

38
common/build.gradle.kts Normal file
View File

@@ -0,0 +1,38 @@
repositories {
maven("https://jitpack.io/") // rtag
}
dependencies {
compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") {
exclude(module = "adventure-bom")
exclude(module = "checker-qual")
exclude(module = "annotations")
}
compileOnly("org.incendo:cloud-core:${rootProject.properties["cloud_core_version"]}")
compileOnly("org.incendo:cloud-minecraft-extras:${rootProject.properties["cloud_minecraft_extras_version"]}")
compileOnly("dev.dejvokep:boosted-yaml:${rootProject.properties["boosted_yaml_version"]}")
compileOnly("org.jetbrains:annotations:${rootProject.properties["jetbrains_annotations_version"]}")
compileOnly("org.slf4j:slf4j-api:${rootProject.properties["slf4j_version"]}")
compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.properties["log4j_version"]}")
compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}")
compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}")
compileOnly("com.google.code.gson:gson:${rootProject.properties["gson_version"]}")
compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}")
compileOnly("com.saicone.rtag:rtag:${rootProject.properties["rtag_version"]}")
compileOnly("net.objecthunter:exp4j:${rootProject.properties["exp4j_version"]}")
compileOnly("com.google.guava:guava:${rootProject.properties["guava_version"]}")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.release.set(17)
dependsOn(tasks.clean)
}

View File

@@ -0,0 +1,85 @@
/*
* 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.common.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.momirealms.customfishing.common.sender.SenderFactory;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
public abstract class AbstractCommandFeature<C> implements CommandFeature<C> {
protected final CustomFishingCommandManager<C> commandManager;
protected CommandConfig<C> commandConfig;
public AbstractCommandFeature(CustomFishingCommandManager<C> commandManager) {
this.commandManager = commandManager;
}
protected abstract SenderFactory<?, C> getSenderFactory();
public abstract Command.Builder<? extends C> assembleCommand(CommandManager<C> manager, Command.Builder<C> builder);
@Override
@SuppressWarnings("unchecked")
public Command<C> registerCommand(CommandManager<C> manager, Command.Builder<C> builder) {
Command<C> command = (Command<C>) assembleCommand(manager, builder).build();
manager.command(command);
return command;
}
@Override
public void registerRelatedFunctions() {
// empty
}
@Override
public void unregisterRelatedFunctions() {
// empty
}
@Override
@SuppressWarnings("unchecked")
public void handleFeedback(CommandContext<?> context, TranslatableComponent.Builder key, Component... args) {
if (context.flags().hasFlag("silent")) {
return;
}
commandManager.handleCommandFeedback((C) context.sender(), key, args);
}
@Override
public void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args) {
commandManager.handleCommandFeedback(sender, key, args);
}
@Override
public CustomFishingCommandManager<C> getCustomFishingCommandManager() {
return commandManager;
}
@Override
public CommandConfig<C> getCommandConfig() {
return commandConfig;
}
public void setCommandConfig(CommandConfig<C> commandConfig) {
this.commandConfig = commandConfig;
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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.common.command;
import dev.dejvokep.boostedyaml.YamlDocument;
import dev.dejvokep.boostedyaml.block.implementation.Section;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TranslatableComponent;
import net.momirealms.customfishing.common.locale.CustomFishingCaptionFormatter;
import net.momirealms.customfishing.common.locale.CustomFishingCaptionProvider;
import net.momirealms.customfishing.common.locale.TranslationManager;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.sender.Sender;
import net.momirealms.customfishing.common.util.ArrayUtils;
import net.momirealms.customfishing.common.util.TriConsumer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.caption.Caption;
import org.incendo.cloud.caption.StandardCaptionKeys;
import org.incendo.cloud.component.CommandComponent;
import org.incendo.cloud.exception.*;
import org.incendo.cloud.exception.handling.ExceptionContext;
import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public abstract class AbstractCommandManager<C> implements CustomFishingCommandManager<C> {
protected final HashSet<CommandComponent<C>> registeredRootCommandComponents = new HashSet<>();
protected final HashSet<CommandFeature<C>> registeredFeatures = new HashSet<>();
protected final CommandManager<C> commandManager;
protected final CustomFishingPlugin plugin;
private final CustomFishingCaptionFormatter<C> captionFormatter = new CustomFishingCaptionFormatter<C>();
private final MinecraftExceptionHandler.Decorator<C> decorator = (formatter, ctx, msg) -> msg;
private TriConsumer<C, String, Component> feedbackConsumer;
public AbstractCommandManager(CustomFishingPlugin plugin, CommandManager<C> commandManager) {
this.commandManager = commandManager;
this.plugin = plugin;
this.inject();
this.feedbackConsumer = defaultFeedbackConsumer();
}
@Override
public void setFeedbackConsumer(@NotNull TriConsumer<C, String, Component> feedbackConsumer) {
this.feedbackConsumer = feedbackConsumer;
}
@Override
public TriConsumer<C, String, Component> defaultFeedbackConsumer() {
return ((sender, node, component) -> {
wrapSender(sender).sendMessage(
component, true
);
});
}
protected abstract Sender wrapSender(C c);
private void inject() {
getCommandManager().captionRegistry().registerProvider(new CustomFishingCaptionProvider<>());
injectExceptionHandler(InvalidSyntaxException.class, MinecraftExceptionHandler.createDefaultInvalidSyntaxHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SYNTAX);
injectExceptionHandler(InvalidCommandSenderException.class, MinecraftExceptionHandler.createDefaultInvalidSenderHandler(), StandardCaptionKeys.EXCEPTION_INVALID_SENDER);
injectExceptionHandler(NoPermissionException.class, MinecraftExceptionHandler.createDefaultNoPermissionHandler(), StandardCaptionKeys.EXCEPTION_NO_PERMISSION);
injectExceptionHandler(ArgumentParseException.class, MinecraftExceptionHandler.createDefaultArgumentParsingHandler(), StandardCaptionKeys.EXCEPTION_INVALID_ARGUMENT);
injectExceptionHandler(CommandExecutionException.class, MinecraftExceptionHandler.createDefaultCommandExecutionHandler(), StandardCaptionKeys.EXCEPTION_UNEXPECTED);
}
private void injectExceptionHandler(Class<? extends Throwable> type, MinecraftExceptionHandler.MessageFactory<C, ?> factory, Caption key) {
getCommandManager().exceptionController().registerHandler(type, ctx -> {
final @Nullable ComponentLike message = factory.message(captionFormatter, (ExceptionContext) ctx);
if (message != null) {
handleCommandFeedback(ctx.context().sender(), key.key(), decorator.decorate(captionFormatter, ctx, message.asComponent()).asComponent());
}
});
}
@Override
public CommandConfig<C> getCommandConfig(YamlDocument document, String featureID) {
Section section = document.getSection(featureID);
if (section == null) return null;
return new CommandConfig.Builder<C>()
.permission(section.getString("permission"))
.usages(section.getStringList("usage"))
.enable(section.getBoolean("enable", false))
.build();
}
@Override
public Collection<Command.Builder<C>> buildCommandBuilders(CommandConfig<C> config) {
ArrayList<Command.Builder<C>> list = new ArrayList<>();
for (String usage : config.getUsages()) {
if (!usage.startsWith("/")) continue;
String command = usage.substring(1).trim();
String[] split = command.split(" ");
Command.Builder<C> builder = new CommandBuilder.BasicCommandBuilder<>(getCommandManager(), split[0])
.setCommandNode(ArrayUtils.subArray(split, 1))
.setPermission(config.getPermission())
.getBuiltCommandBuilder();
list.add(builder);
}
return list;
}
@Override
public void registerFeature(CommandFeature<C> feature, CommandConfig<C> config) {
if (!config.isEnable()) throw new RuntimeException("Registering a disabled command feature is not allowed");
for (Command.Builder<C> builder : buildCommandBuilders(config)) {
Command<C> command = feature.registerCommand(commandManager, builder);
this.registeredRootCommandComponents.add(command.rootComponent());
}
feature.registerRelatedFunctions();
this.registeredFeatures.add(feature);
((AbstractCommandFeature<C>) feature).setCommandConfig(config);
}
@Override
public void registerDefaultFeatures() {
YamlDocument document = plugin.getConfigManager().loadConfig(commandsFile);
this.getFeatures().values().forEach(feature -> {
CommandConfig<C> config = getCommandConfig(document, feature.getFeatureID());
if (config.isEnable()) {
registerFeature(feature, config);
}
});
}
@Override
public void unregisterFeatures() {
this.registeredRootCommandComponents.forEach(component -> this.commandManager.commandRegistrationHandler().unregisterRootCommand(component));
this.registeredRootCommandComponents.clear();
this.registeredFeatures.forEach(CommandFeature::unregisterRelatedFunctions);
this.registeredFeatures.clear();
}
@Override
public CommandManager<C> getCommandManager() {
return commandManager;
}
@Override
public void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args) {
TranslatableComponent component = key.arguments(args).build();
this.feedbackConsumer.accept(sender, component.key(), TranslationManager.render(component));
}
@Override
public void handleCommandFeedback(C sender, String node, Component component) {
this.feedbackConsumer.accept(sender, node, component);
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.command;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
public interface CommandBuilder<C> {
CommandBuilder<C> setPermission(String permission);
CommandBuilder<C> setCommandNode(String... subNodes);
Command.Builder<C> getBuiltCommandBuilder();
class BasicCommandBuilder<C> implements CommandBuilder<C> {
private Command.Builder<C> commandBuilder;
public BasicCommandBuilder(CommandManager<C> commandManager, String rootNode) {
this.commandBuilder = commandManager.commandBuilder(rootNode);
}
@Override
public CommandBuilder<C> setPermission(String permission) {
this.commandBuilder = this.commandBuilder.permission(permission);
return this;
}
@Override
public CommandBuilder<C> setCommandNode(String... subNodes) {
for (String sub : subNodes) {
this.commandBuilder = this.commandBuilder.literal(sub);
}
return this;
}
@Override
public Command.Builder<C> getBuiltCommandBuilder() {
return commandBuilder;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.command;
import java.util.ArrayList;
import java.util.List;
public class CommandConfig<C> {
private boolean enable = false;
private List<String> usages = new ArrayList<>();
private String permission = null;
private CommandConfig() {
}
public CommandConfig(boolean enable, List<String> usages, String permission) {
this.enable = enable;
this.usages = usages;
this.permission = permission;
}
public boolean isEnable() {
return enable;
}
public List<String> getUsages() {
return usages;
}
public String getPermission() {
return permission;
}
public static class Builder<C> {
private final CommandConfig<C> config;
public Builder() {
this.config = new CommandConfig<>();
}
public Builder<C> usages(List<String> usages) {
config.usages = usages;
return this;
}
public Builder<C> permission(String permission) {
config.permission = permission;
return this;
}
public Builder<C> enable(boolean enable) {
config.enable = enable;
return this;
}
public CommandConfig<C> build() {
return config;
}
}
}

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.common.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
public interface CommandFeature<C> {
Command<C> registerCommand(CommandManager<C> cloudCommandManager, Command.Builder<C> builder);
String getFeatureID();
void registerRelatedFunctions();
void unregisterRelatedFunctions();
void handleFeedback(CommandContext<?> context, TranslatableComponent.Builder key, Component... args);
void handleFeedback(C sender, TranslatableComponent.Builder key, Component... args);
CustomFishingCommandManager<C> getCustomFishingCommandManager();
CommandConfig<C> getCommandConfig();
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.command;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.util.Index;
import net.momirealms.customfishing.common.util.TriConsumer;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public interface CustomFishingCommandManager<C> {
String commandsFile = "commands.yml";
void unregisterFeatures();
void registerFeature(CommandFeature<C> feature, CommandConfig<C> config);
void registerDefaultFeatures();
Index<String, CommandFeature<C>> getFeatures();
void setFeedbackConsumer(@NotNull TriConsumer<C, String, Component> feedbackConsumer);
TriConsumer<C, String, Component> defaultFeedbackConsumer();
CommandConfig<C> getCommandConfig(YamlDocument document, String featureID);
Collection<Command.Builder<C>> buildCommandBuilders(CommandConfig<C> config);
CommandManager<C> getCommandManager();
void handleCommandFeedback(C sender, TranslatableComponent.Builder key, Component... args);
void handleCommandFeedback(C sender, String node, Component component);
}

View File

@@ -0,0 +1,35 @@
/*
* 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.common.config;
import dev.dejvokep.boostedyaml.YamlDocument;
import java.io.File;
public interface ConfigLoader {
YamlDocument loadConfig(String filePath);
YamlDocument loadConfig(String filePath, char routeSeparator);
YamlDocument loadData(File file);
YamlDocument loadData(File file, char routeSeparator);
void saveResource(String filePath);
}

View File

@@ -0,0 +1,57 @@
/*
* 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.common.config.node;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
public class Node<T> {
private final T value;
private final HashMap<String, Node<T>> childTree = new HashMap<>();
public Node(T value) {
this.value = value;
}
public Node() {
this(null);
}
@Nullable
public T nodeValue() {
return value;
}
@NotNull
public HashMap<String, Node<T>> getChildTree() {
return childTree;
}
@Nullable
public Node<T> getChild(String node) {
return childTree.get(node);
}
@Nullable
public Node<T> removeChild(String node) {
return childTree.remove(node);
}
}

View File

@@ -0,0 +1,357 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
import net.momirealms.customfishing.common.dependency.relocation.Relocation;
import org.jetbrains.annotations.Nullable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/**
* The dependencies used by CustomFishing.
*/
public enum Dependency {
ASM(
"org.ow2.asm",
"asm",
"maven",
"asm"
),
ASM_COMMONS(
"org.ow2.asm",
"asm-commons",
"maven",
"asm-commons"
),
JAR_RELOCATOR(
"me.lucko",
"jar-relocator",
"maven",
"jar-relocator"
),
H2_DRIVER(
"com.h2database",
"h2",
"maven",
"h2-driver"
),
SQLITE_DRIVER(
"org.xerial",
"sqlite-jdbc",
"maven",
"sqlite-driver"
),
CLOUD_CORE(
"org{}incendo",
"cloud-core",
"maven",
"cloud-core",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
CLOUD_BRIGADIER(
"org{}incendo",
"cloud-brigadier",
"maven",
"cloud-brigadier",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
CLOUD_SERVICES(
"org{}incendo",
"cloud-services",
"maven",
"cloud-services",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
CLOUD_BUKKIT(
"org{}incendo",
"cloud-bukkit",
"maven",
"cloud-bukkit",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
CLOUD_PAPER(
"org{}incendo",
"cloud-paper",
"maven",
"cloud-paper",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
CLOUD_MINECRAFT_EXTRAS(
"org{}incendo",
"cloud-minecraft-extras",
"maven",
"cloud-minecraft-extras",
Relocation.of("cloud", "org{}incendo{}cloud"),
Relocation.of("adventure", "net{}kyori{}adventure"),
Relocation.of("option", "net{}kyori{}option"),
Relocation.of("examination", "net{}kyori{}examination"),
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
GEANTY_REF(
"io{}leangen{}geantyref",
"geantyref",
"maven",
"geantyref",
Relocation.of("geantyref", "io{}leangen{}geantyref")
),
BOOSTED_YAML(
"dev{}dejvokep",
"boosted-yaml",
"maven",
"boosted-yaml",
Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml")
),
BYTEBUDDY(
"net{}bytebuddy",
"byte-buddy",
"maven",
"byte-buddy",
Relocation.of("bytebuddy", "net{}bytebuddy")
),
MARIADB_DRIVER(
"org{}mariadb{}jdbc",
"mariadb-java-client",
"maven",
"mariadb-java-client",
Relocation.of("mariadb", "org{}mariadb")
),
MYSQL_DRIVER(
"com{}mysql",
"mysql-connector-j",
"maven",
"mysql-connector-j",
Relocation.of("mysql", "com{}mysql")
),
HIKARI_CP(
"com{}zaxxer",
"HikariCP",
"maven",
"hikari-cp",
Relocation.of("hikari", "com{}zaxxer{}hikari")
),
MONGODB_DRIVER_CORE(
"org{}mongodb",
"mongodb-driver-core",
"maven",
"mongodb-driver-core",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
),
MONGODB_DRIVER_SYNC(
"org{}mongodb",
"mongodb-driver-sync",
"maven",
"mongodb-driver-sync",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
) {
@Override
public String getVersion() {
return Dependency.MONGODB_DRIVER_CORE.getVersion();
}
},
MONGODB_DRIVER_BSON(
"org{}mongodb",
"bson",
"maven",
"mongodb-bson",
Relocation.of("mongodb", "com{}mongodb"),
Relocation.of("bson", "org{}bson")
) {
@Override
public String getVersion() {
return Dependency.MONGODB_DRIVER_CORE.getVersion();
}
},
COMMONS_POOL_2(
"org{}apache{}commons",
"commons-pool2",
"maven",
"commons-pool",
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
BSTATS_BASE(
"org{}bstats",
"bstats-base",
"maven",
"bstats-base",
Relocation.of("bstats", "org{}bstats")
),
BSTATS_BUKKIT(
"org{}bstats",
"bstats-bukkit",
"maven",
"bstats-bukkit",
Relocation.of("bstats", "org{}bstats")
) {
@Override
public String getVersion() {
return Dependency.BSTATS_BASE.getVersion();
}
},
GSON(
"com.google.code.gson",
"gson",
"maven",
"gson"
),
CAFFEINE(
"com{}github{}ben-manes{}caffeine",
"caffeine",
"maven",
"caffeine",
Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")
),
JEDIS(
"redis{}clients",
"jedis",
"maven",
"jedis",
Relocation.of("jedis", "redis{}clients{}jedis"),
Relocation.of("commonspool2", "org{}apache{}commons{}pool2")
),
EXP4J(
"net{}objecthunter",
"exp4j",
"maven",
"exp4j",
Relocation.of("exp4j", "net{}objecthunter{}exp4j")
),
SLF4J_SIMPLE(
"org.slf4j",
"slf4j-simple",
"maven",
"slf4j_simple"
) {
@Override
public String getVersion() {
return Dependency.SLF4J_API.getVersion();
}
},
SLF4J_API(
"org.slf4j",
"slf4j-api",
"maven",
"slf4j"
),
LZ4(
"org{}lz4",
"lz4-java",
"maven",
"lz4-java",
Relocation.of("jpountz", "net{}jpountz")
);
private final List<Relocation> relocations;
private final String repo;
private final String groupId;
private String rawArtifactId;
private String customArtifactID;
private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar";
Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID) {
this(groupId, rawArtifactId, repo, customArtifactID, new Relocation[0]);
}
Dependency(String groupId, String rawArtifactId, String repo, String customArtifactID, Relocation... relocations) {
this.rawArtifactId = rawArtifactId;
this.groupId = groupId;
this.relocations = new ArrayList<>(Arrays.stream(relocations).toList());
this.repo = repo;
this.customArtifactID = customArtifactID;
}
public Dependency setCustomArtifactID(String customArtifactID) {
this.customArtifactID = customArtifactID;
return this;
}
public Dependency setRawArtifactID(String artifactId) {
this.rawArtifactId = artifactId;
return this;
}
public String getVersion() {
return DependencyProperties.getDependencyVersion(customArtifactID);
}
private static String rewriteEscaping(String s) {
return s.replace("{}", ".");
}
public String getFileName(String classifier) {
String name = customArtifactID.toLowerCase(Locale.ROOT).replace('_', '-');
String extra = classifier == null || classifier.isEmpty()
? ""
: "-" + classifier;
return name + "-" + this.getVersion() + extra + ".jar";
}
String getMavenRepoPath() {
return String.format(MAVEN_FORMAT,
rewriteEscaping(groupId).replace(".", "/"),
rewriteEscaping(rawArtifactId),
getVersion(),
rewriteEscaping(rawArtifactId),
getVersion()
);
}
public List<Relocation> getRelocations() {
return this.relocations;
}
/**
* Creates a {@link MessageDigest} suitable for computing the checksums
* of dependencies.
*
* @return the digest
*/
public static MessageDigest createDigest() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Nullable
public String getRepo() {
return repo;
}
}

View File

@@ -0,0 +1,48 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
/**
* Exception thrown if a dependency cannot be downloaded.
*/
public class DependencyDownloadException extends Exception {
public DependencyDownloadException() {
}
public DependencyDownloadException(String message) {
super(message);
}
public DependencyDownloadException(String message, Throwable cause) {
super(message, cause);
}
public DependencyDownloadException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,53 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
import java.util.Collection;
import java.util.Set;
/**
* Loads and manages runtime dependencies for the plugin.
*/
public interface DependencyManager extends AutoCloseable {
/**
* Loads dependencies.
*
* @param dependencies the dependencies to load
*/
void loadDependencies(Collection<Dependency> dependencies);
/**
* Obtains an isolated classloader containing the given dependencies.
*
* @param dependencies the dependencies
* @return the classloader
*/
ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies);
@Override
void close();
}

View File

@@ -0,0 +1,227 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
import net.momirealms.customfishing.common.dependency.classloader.IsolatedClassLoader;
import net.momirealms.customfishing.common.dependency.relocation.Relocation;
import net.momirealms.customfishing.common.dependency.relocation.RelocationHandler;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender;
import net.momirealms.customfishing.common.util.FileUtils;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
* Loads and manages runtime dependencies for the plugin.
*/
public class DependencyManagerImpl implements DependencyManager {
/** A registry containing plugin specific behaviour for dependencies. */
private final DependencyRegistry registry;
/** The path where library jars are cached. */
private final Path cacheDirectory;
/** The classpath appender to preload dependencies into */
private final ClassPathAppender classPathAppender;
/** A map of dependencies which have already been loaded. */
private final EnumMap<Dependency, Path> loaded = new EnumMap<>(Dependency.class);
/** A map of isolated classloaders which have been created. */
private final Map<Set<Dependency>, IsolatedClassLoader> loaders = new HashMap<>();
/** Cached relocation handler instance. */
private final RelocationHandler relocationHandler;
private final Executor loadingExecutor;
private final CustomFishingPlugin plugin;
public DependencyManagerImpl(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.registry = new DependencyRegistry();
this.cacheDirectory = setupCacheDirectory(plugin);
this.classPathAppender = plugin.getClassPathAppender();
this.loadingExecutor = plugin.getScheduler().async();
this.relocationHandler = new RelocationHandler(this);
}
@Override
public ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies) {
Set<Dependency> set = new HashSet<>(dependencies);
for (Dependency dependency : dependencies) {
if (!this.loaded.containsKey(dependency)) {
throw new IllegalStateException("Dependency " + dependency + " is not loaded.");
}
}
synchronized (this.loaders) {
IsolatedClassLoader classLoader = this.loaders.get(set);
if (classLoader != null) {
return classLoader;
}
URL[] urls = set.stream()
.map(this.loaded::get)
.map(file -> {
try {
return file.toUri().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
})
.toArray(URL[]::new);
classLoader = new IsolatedClassLoader(urls);
this.loaders.put(set, classLoader);
return classLoader;
}
}
@Override
public void loadDependencies(Collection<Dependency> dependencies) {
CountDownLatch latch = new CountDownLatch(dependencies.size());
for (Dependency dependency : dependencies) {
if (this.loaded.containsKey(dependency)) {
latch.countDown();
continue;
}
this.loadingExecutor.execute(() -> {
try {
loadDependency(dependency);
} catch (Throwable e) {
this.plugin.getPluginLogger().warn("Unable to load dependency " + dependency.name(), e);
} finally {
latch.countDown();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void loadDependency(Dependency dependency) throws Exception {
if (this.loaded.containsKey(dependency)) {
return;
}
Path file = remapDependency(dependency, downloadDependency(dependency));
this.loaded.put(dependency, file);
if (this.classPathAppender != null && this.registry.shouldAutoLoad(dependency)) {
this.classPathAppender.addJarToClasspath(file);
}
}
private Path downloadDependency(Dependency dependency) throws DependencyDownloadException {
String fileName = dependency.getFileName(null);
Path file = this.cacheDirectory.resolve(fileName);
// if the file already exists, don't attempt to re-download it.
if (Files.exists(file)) {
return file;
}
DependencyDownloadException lastError = null;
String forceRepo = dependency.getRepo();
List<DependencyRepository> repository = DependencyRepository.getByID(forceRepo);
if (!repository.isEmpty()) {
int i = 0;
while (i < repository.size()) {
try {
plugin.getPluginLogger().info("Downloading dependency(" + fileName + ") from " + repository.get(i).getUrl() + dependency.getMavenRepoPath());
repository.get(i).download(dependency, file);
plugin.getPluginLogger().info("Successfully downloaded " + fileName);
return file;
} catch (DependencyDownloadException e) {
lastError = e;
i++;
}
}
}
throw Objects.requireNonNull(lastError);
}
private Path remapDependency(Dependency dependency, Path normalFile) throws Exception {
List<Relocation> rules = new ArrayList<>(dependency.getRelocations());
if (rules.isEmpty()) {
return normalFile;
}
Path remappedFile = this.cacheDirectory.resolve(dependency.getFileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped"));
// if the remapped source exists already, just use that.
if (Files.exists(remappedFile)) {
return remappedFile;
}
plugin.getPluginLogger().info("Remapping " + dependency.getFileName(null));
relocationHandler.remap(normalFile, remappedFile, rules);
plugin.getPluginLogger().info("Successfully remapped " + dependency.getFileName(null));
return remappedFile;
}
private static Path setupCacheDirectory(CustomFishingPlugin plugin) {
Path cacheDirectory = plugin.getDataDirectory().resolve("libs");
try {
FileUtils.createDirectoriesIfNotExists(cacheDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to create libs directory", e);
}
return cacheDirectory;
}
@Override
public void close() {
IOException firstEx = null;
for (IsolatedClassLoader loader : this.loaders.values()) {
try {
loader.close();
} catch (IOException ex) {
if (firstEx == null) {
firstEx = ex;
} else {
firstEx.addSuppressed(ex);
}
}
}
if (firstEx != null) {
plugin.getPluginLogger().severe(firstEx.getMessage(), firstEx);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.dependency;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class DependencyProperties {
private final HashMap<String, String> versionMap;
private DependencyProperties(HashMap<String, String> versionMap) {
this.versionMap = versionMap;
}
public static String getDependencyVersion(String dependencyID) {
if (!SingletonHolder.INSTANCE.versionMap.containsKey(dependencyID)) {
throw new RuntimeException("Unknown dependency: " + dependencyID);
}
return SingletonHolder.INSTANCE.versionMap.get(dependencyID);
}
private static class SingletonHolder {
private static final DependencyProperties INSTANCE = getInstance();
private static DependencyProperties getInstance() {
try (InputStream inputStream = DependencyProperties.class.getClassLoader().getResourceAsStream("library-version.properties")) {
HashMap<String, String> versionMap = new HashMap<>();
Properties properties = new Properties();
properties.load(inputStream);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
if (entry.getKey() instanceof String key && entry.getValue() instanceof String value) {
versionMap.put(key, value);
}
}
return new DependencyProperties(versionMap);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
import com.google.gson.JsonElement;
/**
* Applies CustomFishing specific behaviour for {@link Dependency}s.
*/
public class DependencyRegistry {
public boolean shouldAutoLoad(Dependency dependency) {
return switch (dependency) {
// all used within 'isolated' classloaders, and are therefore not
// relocated.
case ASM, ASM_COMMONS, JAR_RELOCATOR, H2_DRIVER, SQLITE_DRIVER -> false;
default -> true;
};
}
@SuppressWarnings("ConstantConditions")
public static boolean isGsonRelocated() {
return JsonElement.class.getName().startsWith("net.momirealms");
}
private static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private static boolean slf4jPresent() {
return classExists("org.slf4j.Logger") && classExists("org.slf4j.LoggerFactory");
}
}

View File

@@ -0,0 +1,143 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a repository which contains {@link Dependency}s.
*/
public enum DependencyRepository {
/**
* Maven Central
*/
MAVEN_CENTRAL("maven", "https://repo1.maven.org/maven2/") {
@Override
protected URLConnection openConnection(Dependency dependency) throws IOException {
URLConnection connection = super.openConnection(dependency);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
return connection;
}
},
/**
* Maven Central Mirror
*/
MAVEN_CENTRAL_MIRROR("maven", "https://maven.aliyun.com/repository/public/");
private final String url;
private final String id;
DependencyRepository(String id, String url) {
this.url = url;
this.id = id;
}
public String getUrl() {
return url;
}
public static List<DependencyRepository> getByID(String id) {
ArrayList<DependencyRepository> repositories = new ArrayList<>();
for (DependencyRepository repository : values()) {
if (id.equals(repository.id)) {
repositories.add(repository);
}
}
return repositories;
}
/**
* Opens a connection to the given {@code dependency}.
*
* @param dependency the dependency to download
* @return the connection
* @throws IOException if unable to open a connection
*/
protected URLConnection openConnection(Dependency dependency) throws IOException {
URL dependencyUrl = new URL(this.url + dependency.getMavenRepoPath());
return dependencyUrl.openConnection();
}
/**
* Downloads the raw bytes of the {@code dependency}.
*
* @param dependency the dependency to download
* @return the downloaded bytes
* @throws DependencyDownloadException if unable to download
*/
public byte[] downloadRaw(Dependency dependency) throws DependencyDownloadException {
try {
URLConnection connection = openConnection(dependency);
try (InputStream in = connection.getInputStream()) {
byte[] bytes = in.readAllBytes();
if (bytes.length == 0) {
throw new DependencyDownloadException("Empty stream");
}
return bytes;
}
} catch (Exception e) {
throw new DependencyDownloadException(e);
}
}
/**
* @param dependency the dependency to download
* @return the downloaded bytes
* @throws DependencyDownloadException if unable to download
*/
public byte[] download(Dependency dependency) throws DependencyDownloadException {
return downloadRaw(dependency);
}
/**
* Downloads the the {@code dependency} to the {@code file}, ensuring the
* downloaded bytes match the checksum.
*
* @param dependency the dependency to download
* @param file the file to write to
* @throws DependencyDownloadException if unable to download
*/
public void download(Dependency dependency, Path file) throws DependencyDownloadException {
try {
Files.write(file, download(dependency));
} catch (IOException e) {
throw new DependencyDownloadException(e);
}
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency.classloader;
import java.net.URL;
import java.net.URLClassLoader;
/**
* A classloader "isolated" from the rest of the Minecraft server.
*
* <p>Used to load specific CustomFishing dependencies without causing conflicts
* with other plugins, or libraries provided by the server implementation.</p>
*/
public class IsolatedClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public IsolatedClassLoader(URL[] urls) {
/*
* ClassLoader#getSystemClassLoader returns the AppClassLoader
*
* Calling #getParent on this returns the ExtClassLoader (Java 8) or
* the PlatformClassLoader (Java 9). Since we want this classloader to
* be isolated from the Minecraft server (the app), we set the parent
* to be the platform class loader.
*/
super(urls, ClassLoader.getSystemClassLoader().getParent());
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency.relocation;
import java.util.Objects;
public final class Relocation {
private static final String RELOCATION_PREFIX = "net.momirealms.customfishing.libraries.";
public static Relocation of(String id, String pattern) {
return new Relocation(pattern.replace("{}", "."), RELOCATION_PREFIX + id);
}
private final String pattern;
private final String relocatedPattern;
private Relocation(String pattern, String relocatedPattern) {
this.pattern = pattern;
this.relocatedPattern = relocatedPattern;
}
public String getPattern() {
return this.pattern;
}
public String getRelocatedPattern() {
return this.relocatedPattern;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Relocation that = (Relocation) o;
return Objects.equals(this.pattern, that.pattern) &&
Objects.equals(this.relocatedPattern, that.relocatedPattern);
}
@Override
public int hashCode() {
return Objects.hash(this.pattern, this.relocatedPattern);
}
}

View File

@@ -0,0 +1,91 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency.relocation;
import net.momirealms.customfishing.common.dependency.Dependency;
import net.momirealms.customfishing.common.dependency.DependencyManager;
import net.momirealms.customfishing.common.dependency.classloader.IsolatedClassLoader;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.*;
/**
* Handles class runtime relocation of packages in downloaded dependencies
*/
public class RelocationHandler {
public static final Set<Dependency> DEPENDENCIES = EnumSet.of(Dependency.ASM, Dependency.ASM_COMMONS, Dependency.JAR_RELOCATOR);
private static final String JAR_RELOCATOR_CLASS = "me.lucko.jarrelocator.JarRelocator";
private static final String JAR_RELOCATOR_RUN_METHOD = "run";
private final Constructor<?> jarRelocatorConstructor;
private final Method jarRelocatorRunMethod;
public RelocationHandler(DependencyManager dependencyManager) {
ClassLoader classLoader = null;
try {
// download the required dependencies for remapping
dependencyManager.loadDependencies(DEPENDENCIES);
// get a classloader containing the required dependencies as sources
classLoader = dependencyManager.obtainClassLoaderWith(DEPENDENCIES);
// load the relocator class
Class<?> jarRelocatorClass = classLoader.loadClass(JAR_RELOCATOR_CLASS);
// prepare the the reflected constructor & method instances
this.jarRelocatorConstructor = jarRelocatorClass.getDeclaredConstructor(File.class, File.class, Map.class);
this.jarRelocatorConstructor.setAccessible(true);
this.jarRelocatorRunMethod = jarRelocatorClass.getDeclaredMethod(JAR_RELOCATOR_RUN_METHOD);
this.jarRelocatorRunMethod.setAccessible(true);
} catch (Exception e) {
try {
if (classLoader instanceof IsolatedClassLoader isolatedClassLoader) {
isolatedClassLoader.close();
}
} catch (IOException ex) {
e.addSuppressed(ex);
}
throw new RuntimeException(e);
}
}
public void remap(Path input, Path output, List<Relocation> relocations) throws Exception {
Map<String, String> mappings = new HashMap<>();
for (Relocation relocation : relocations) {
mappings.put(relocation.getPattern(), relocation.getRelocatedPattern());
}
// create and invoke a new relocator
Object relocator = this.jarRelocatorConstructor.newInstance(input.toFile(), output.toFile(), mappings);
this.jarRelocatorRunMethod.invoke(relocator);
}
}

View File

@@ -0,0 +1,36 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.dependency.relocation;
public final class RelocationHelper {
// screw maven shade
public static final String OKIO_STRING = String.valueOf(new char[]{'o', 'k', 'i', 'o'});
public static final String OKHTTP3_STRING = String.valueOf(new char[]{'o', 'k', 'h', 't', 't', 'p', '3'});
private RelocationHelper() {
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.helper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.title.Title;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class AdventureHelper {
private final MiniMessage miniMessage;
private final MiniMessage miniMessageStrict;
private final GsonComponentSerializer gsonComponentSerializer;
private final Cache<String, String> miniMessageToJsonCache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build();
public static boolean legacySupport = false;
private AdventureHelper() {
this.miniMessage = MiniMessage.builder().build();
this.miniMessageStrict = MiniMessage.builder().strict(true).build();
this.gsonComponentSerializer = GsonComponentSerializer.builder().build();
}
private static class SingletonHolder {
private static final AdventureHelper INSTANCE = new AdventureHelper();
}
public static AdventureHelper getInstance() {
return SingletonHolder.INSTANCE;
}
public static Component miniMessage(String text) {
if (legacySupport) {
return getMiniMessage().deserialize(legacyToMiniMessage(text));
} else {
return getMiniMessage().deserialize(text);
}
}
public static MiniMessage getMiniMessage() {
return getInstance().miniMessage;
}
public static GsonComponentSerializer getGson() {
return getInstance().gsonComponentSerializer;
}
public static String miniMessageToJson(String miniMessage) {
AdventureHelper instance = getInstance();
return instance.miniMessageToJsonCache.get(miniMessage, (text) -> instance.gsonComponentSerializer.serialize(miniMessage(text)));
}
public static void sendTitle(Audience audience, Component title, Component subtitle, int fadeIn, int stay, int fadeOut) {
audience.showTitle(Title.title(title, subtitle, Title.Times.times(Duration.ofMillis(fadeIn * 50L), Duration.ofMillis(stay * 50L), Duration.ofMillis(fadeOut * 50L))));
}
public static void sendActionBar(Audience audience, Component actionBar) {
audience.sendActionBar(actionBar);
}
public static void sendMessage(Audience audience, Component message) {
audience.sendMessage(message);
}
public static void playSound(Audience audience, Sound sound) {
audience.playSound(sound);
}
public static String surroundWithMiniMessageFont(String text, Key font) {
return "<font:" + font.asString() + ">" + text + "</font>";
}
public static String surroundWithMiniMessageFont(String text, String font) {
return "<font:" + font + ">" + text + "</font>";
}
public static String jsonToMiniMessage(String json) {
return getInstance().miniMessageStrict.serialize(getInstance().gsonComponentSerializer.deserialize(json));
}
public static Component jsonToComponent(String json) {
return getInstance().gsonComponentSerializer.deserialize(json);
}
public static 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("<st>");
case 'o' -> stringBuilder.append("<i>");
case 'n' -> stringBuilder.append("<u>");
case 'k' -> stringBuilder.append("<obf>");
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 += 12;
}
default -> {
stringBuilder.append(chars[i]);
continue;
}
}
i++;
}
return stringBuilder.toString();
}
public static String componentToJson(Component component) {
return getGson().serialize(component);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isColorCode(char c) {
return c == '§' || c == '&';
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.helper;
import net.objecthunter.exp4j.ExpressionBuilder;
public class ExpressionHelper {
public static double evaluate(String expression) {
return new ExpressionBuilder(expression).build().evaluate();
}
}

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.common.helper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonHelper {
private final Gson gson;
public GsonHelper() {
this.gson = new GsonBuilder()
.create();
}
public Gson getGson() {
return gson;
}
public static Gson get() {
return SingletonHolder.INSTANCE.getGson();
}
private static class SingletonHolder {
private static final GsonHelper INSTANCE = new GsonHelper();
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.common.helper;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* This class implements the VersionManager interface and is responsible for managing version-related information.
*/
public class VersionHelper {
// Method to asynchronously check for plugin updates
public static final Function<CustomFishingPlugin, CompletableFuture<Boolean>> UPDATE_CHECKER = (plugin) -> {
CompletableFuture<Boolean> updateFuture = new CompletableFuture<>();
plugin.getScheduler().async().execute(() -> {
try {
URL url = new URL("https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=2723&key=version");
URLConnection conn = url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(60000);
InputStream inputStream = conn.getInputStream();
String newest = new BufferedReader(new InputStreamReader(inputStream)).readLine();
String current = plugin.getPluginVersion();
inputStream.close();
if (!compareVer(newest, current)) {
updateFuture.complete(false);
return;
}
updateFuture.complete(true);
} catch (Exception exception) {
plugin.getPluginLogger().warn("Error occurred when checking update.", exception);
updateFuture.complete(false);
}
});
return updateFuture;
};
private static float version;
private static boolean mojmap;
private static boolean folia;
public static void init(String serverVersion) {
String[] split = serverVersion.split("\\.");
version = Float.parseFloat(split[1] + "." + (split.length == 3 ? split[2] : "0"));
checkMojMap();
checkFolia();
}
private static void checkMojMap() {
// Check if the server is Mojmap
try {
Class.forName("net.minecraft.network.protocol.game.ClientboundBossEventPacket");
mojmap = true;
} catch (ClassNotFoundException ignored) {
}
}
private static void checkFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
folia = true;
} catch (ClassNotFoundException ignored) {
}
}
public static boolean isVersionNewerThan1_19() {
return version >= 19;
}
public static boolean isVersionNewerThan1_19_4() {
return version >= 19.4;
}
public static boolean isVersionNewerThan1_19_3() {
return version >= 19.3;
}
public static boolean isVersionNewerThan1_20() {
return version >= 20.0;
}
public static boolean isVersionNewerThan1_20_5() {
return version >= 20.5;
}
public boolean isNewerThan1_20_5() {
return version >= 20.5;
}
public static boolean isFolia() {
return folia;
}
public static boolean isMojmap() {
return mojmap;
}
// Method to compare two version strings
private static boolean compareVer(String newV, String currentV) {
if (newV == null || currentV == null || newV.isEmpty() || currentV.isEmpty()) {
return false;
}
String[] newVS = newV.split("\\.");
String[] currentVS = currentV.split("\\.");
int maxL = Math.min(newVS.length, currentVS.length);
for (int i = 0; i < maxL; i++) {
try {
String[] newPart = newVS[i].split("-");
String[] currentPart = currentVS[i].split("-");
int newNum = Integer.parseInt(newPart[0]);
int currentNum = Integer.parseInt(currentPart[0]);
if (newNum > currentNum) {
return true;
} else if (newNum < currentNum) {
return false;
} else if (newPart.length > 1 && currentPart.length > 1) {
String[] newHotfix = newPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
String[] currentHotfix = currentPart[1].split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
if (newHotfix.length == 2 && currentHotfix.length == 1) return true;
else if (newHotfix.length > 1 && currentHotfix.length > 1) {
int newHotfixNum = Integer.parseInt(newHotfix[1]);
int currentHotfixNum = Integer.parseInt(currentHotfix[1]);
if (newHotfixNum > currentHotfixNum) {
return true;
} else if (newHotfixNum < currentHotfixNum) {
return false;
} else {
return newHotfix[0].compareTo(currentHotfix[0]) > 0;
}
}
} else if (newPart.length > 1) {
return true;
} else if (currentPart.length > 1) {
return false;
}
}
catch (NumberFormatException ignored) {
return false;
}
}
return newVS.length > currentVS.length;
}
}

View File

@@ -0,0 +1,185 @@
/*
* 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.common.item;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Key;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class AbstractItem<R, I> implements Item<I> {
private final CustomFishingPlugin plugin;
private final ItemFactory<?, R, I> factory;
private final R item;
AbstractItem(CustomFishingPlugin plugin, ItemFactory<?, R, I> factory, R item) {
this.plugin = plugin;
this.factory = factory;
this.item = item;
}
@Override
public Item<I> damage(Integer data) {
factory.damage(item, data);
return this;
}
@Override
public Optional<Integer> damage() {
return factory.damage(item);
}
@Override
public Item<I> maxDamage(Integer data) {
factory.maxDamage(item, data);
return this;
}
@Override
public Optional<Integer> maxDamage() {
return factory.maxDamage(item);
}
@Override
public Item<I> customModelData(Integer data) {
factory.customModelData(item, data);
return this;
}
@Override
public Optional<Integer> customModelData() {
return factory.customModelData(item);
}
@Override
public Optional<String> displayName() {
return factory.displayName(item);
}
@Override
public Item<I> lore(List<String> lore) {
factory.lore(item, lore);
return this;
}
@Override
public Optional<List<String>> lore() {
return factory.lore(item);
}
@Override
public Item<I> unbreakable(boolean unbreakable) {
factory.unbreakable(item, unbreakable);
return this;
}
@Override
public boolean unbreakable() {
return factory.unbreakable(item);
}
@Override
public Item<I> displayName(String displayName) {
factory.displayName(item, displayName);
return this;
}
@Override
public Item<I> skull(String data) {
factory.skull(item, data);
return this;
}
@Override
public Item<I> enchantments(Map<Key, Short> enchantments) {
factory.enchantments(item, enchantments);
return this;
}
@Override
public Item<I> addEnchantment(Key enchantment, int level) {
factory.addEnchantment(item, enchantment, level);
return this;
}
@Override
public Item<I> storedEnchantments(Map<Key, Short> enchantments) {
factory.storedEnchantments(item, enchantments);
return this;
}
@Override
public Item<I> addStoredEnchantment(Key enchantment, int level) {
factory.addStoredEnchantment(item, enchantment, level);
return this;
}
@Override
public Item<I> itemFlags(List<String> flags) {
factory.itemFlags(item, flags);
return this;
}
@Override
public Optional<Object> getTag(Object... path) {
return factory.getTag(item, path);
}
@Override
public Item<I> setTag(Object value, Object... path) {
factory.setTag(item, value, path);
return this;
}
@Override
public boolean hasTag(Object... path) {
return factory.hasTag(item, path);
}
@Override
public boolean removeTag(Object... path) {
return factory.removeTag(item, path);
}
@Override
public I getItem() {
return factory.getItem(item);
}
@Override
public I load() {
return factory.load(item);
}
@Override
public I loadCopy() {
return factory.loadCopy(item);
}
@Override
public void update() {
factory.update(item);
}
public R getRTagItem() {
return item;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.common.item;
import net.kyori.adventure.key.Key;
public class ComponentKeys {
public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString();
public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString();
public static final String LORE = Key.key("minecraft", "lore").asString();
public static final String DAMAGE = Key.key("minecraft", "damage").asString();
public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString();
public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString();
public static final String ENCHANTMENTS = Key.key("minecraft", "enchantments").asString();
public static final String STORED_ENCHANTMENTS = Key.key("minecraft", "stored_enchantments").asString();
}

View File

@@ -0,0 +1,79 @@
/*
* 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.common.item;
import net.momirealms.customfishing.common.util.Key;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public interface Item<I> {
Item<I> customModelData(Integer data);
Optional<Integer> customModelData();
Item<I> damage(Integer data);
Optional<Integer> damage();
Item<I> maxDamage(Integer data);
Optional<Integer> maxDamage();
Item<I> displayName(String displayName);
Optional<String> displayName();
Item<I> lore(List<String> lore);
Optional<List<String>> lore();
Item<I> unbreakable(boolean unbreakable);
boolean unbreakable();
Item<I> skull(String data);
Item<I> enchantments(Map<Key, Short> enchantments);
Item<I> addEnchantment(Key enchantment, int level);
Item<I> storedEnchantments(Map<Key, Short> enchantments);
Item<I> addStoredEnchantment(Key enchantment, int level);
Item<I> itemFlags(List<String> flags);
Optional<Object> getTag(Object... path);
Item<I> setTag(Object value, Object... path);
boolean hasTag(Object... path);
boolean removeTag(Object... path);
I getItem();
I load();
I loadCopy();
void update();
}

View File

@@ -0,0 +1,96 @@
/*
* 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.common.item;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Key;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public abstract class ItemFactory<P extends CustomFishingPlugin, R, I> {
protected final P plugin;
protected ItemFactory(P plugin) {
this.plugin = plugin;
}
public Item<I> wrap(R item) {
Objects.requireNonNull(item, "item");
return new AbstractItem<>(this.plugin, this, item);
}
protected abstract Optional<Object> getTag(R item, Object... path);
protected abstract void setTag(R item, Object value, Object... path);
protected abstract boolean hasTag(R item, Object... path);
protected abstract boolean removeTag(R item, Object... path);
protected abstract void update(R item);
protected abstract I load(R item);
protected abstract I getItem(R item);
protected abstract I loadCopy(R item);
protected abstract void customModelData(R item, Integer data);
protected abstract Optional<Integer> customModelData(R item);
protected abstract void displayName(R item, String json);
protected abstract Optional<String> displayName(R item);
protected abstract void skull(R item, String skullData);
protected abstract Optional<List<String>> lore(R item);
protected abstract void lore(R item, List<String> lore);
protected abstract boolean unbreakable(R item);
protected abstract void unbreakable(R item, boolean unbreakable);
protected abstract Optional<Boolean> glint(R item);
protected abstract void glint(R item, Boolean glint);
protected abstract Optional<Integer> damage(R item);
protected abstract void damage(R item, Integer damage);
protected abstract Optional<Integer> maxDamage(R item);
protected abstract void maxDamage(R item, Integer damage);
protected abstract void enchantments(R item, Map<Key, Short> enchantments);
protected abstract void storedEnchantments(R item, Map<Key, Short> enchantments);
protected abstract void addEnchantment(R item, Key enchantment, int level);
protected abstract void addStoredEnchantment(R item, Key enchantment, int level);
protected abstract void itemFlags(R item, List<String> flags);
}

View File

@@ -0,0 +1,35 @@
/*
* 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.common.locale;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.caption.Caption;
import org.incendo.cloud.caption.CaptionVariable;
import org.incendo.cloud.minecraft.extras.caption.ComponentCaptionFormatter;
import java.util.List;
public class CustomFishingCaptionFormatter<C> implements ComponentCaptionFormatter<C> {
@Override
public @NonNull Component formatCaption(@NonNull Caption captionKey, @NonNull C recipient, @NonNull String caption, @NonNull List<@NonNull CaptionVariable> variables) {
Component component = ComponentCaptionFormatter.translatable().formatCaption(captionKey, recipient, caption, variables);
return TranslationManager.render(component);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.locale;
import org.incendo.cloud.caption.Caption;
public final class CustomFishingCaptionKeys {
public static final Caption ARGUMENT_PARSE_FAILURE_TIME = Caption.of("argument.parse.failure.time");
public static final Caption ARGUMENT_PARSE_FAILURE_URL = Caption.of("argument.parse.failure.url");
public static final Caption ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR = Caption.of("argument.parse.failure.namedtextcolor");
}

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.common.locale;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.caption.CaptionProvider;
import org.incendo.cloud.caption.DelegatingCaptionProvider;
public final class CustomFishingCaptionProvider<C> extends DelegatingCaptionProvider<C> {
private static final CaptionProvider<?> PROVIDER = CaptionProvider.constantProvider()
.putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_URL, "")
.putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_TIME, "")
.putCaption(CustomFishingCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMEDTEXTCOLOR, "")
.build();
@SuppressWarnings("unchecked")
@Override
public @NonNull CaptionProvider<C> delegate() {
return (CaptionProvider<C>) PROVIDER;
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.common.locale;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
public interface MessageConstants {
TranslatableComponent.Builder COMPETITION_NO_PLAYER = Component.translatable().key("competition.no_player");;
TranslatableComponent.Builder COMPETITION_NO_SCORE = Component.translatable().key("competition.no_score");
TranslatableComponent.Builder COMPETITION_NO_RANK = Component.translatable().key("competition.no_rank");
TranslatableComponent.Builder GOAL_TOTAL_SIZE = Component.translatable().key("competition.goal.total_size");
TranslatableComponent.Builder GOAL_CATCH_AMOUNT = Component.translatable().key("competition.goal.catch_amount");
TranslatableComponent.Builder GOAL_TOTAL_SCORE = Component.translatable().key("competition.goal.total_score");
TranslatableComponent.Builder GOAL_MAX_SIZE = Component.translatable().key("competition.goal.max_size");
TranslatableComponent.Builder GOAL_MIN_SIZE = Component.translatable().key("competition.goal.min_size");
TranslatableComponent.Builder FORMAT_SECOND = Component.translatable().key("format.second");
TranslatableComponent.Builder FORMAT_MINUTE = Component.translatable().key("format.minute");
TranslatableComponent.Builder FORMAT_HOUR = Component.translatable().key("format.hour");
TranslatableComponent.Builder FORMAT_DAY = Component.translatable().key("format.day");
TranslatableComponent.Builder COMMAND_RELOAD_SUCCESS = Component.translatable().key("command.reload.success");
TranslatableComponent.Builder COMMAND_ITEM_FAILURE_NOT_EXIST = Component.translatable().key("command.item.failure.not_exist");
TranslatableComponent.Builder COMMAND_ITEM_GIVE_SUCCESS = Component.translatable().key("command.item.give.success");
TranslatableComponent.Builder COMMAND_ITEM_GET_SUCCESS = Component.translatable().key("command.item.get.success");
TranslatableComponent.Builder COMMAND_ITEM_IMPORT_FAILURE_NO_ITEM = Component.translatable().key("command.item.import.failure.no_item");
TranslatableComponent.Builder COMMAND_ITEM_IMPORT_SUCCESS = Component.translatable().key("command.item.import.success");
TranslatableComponent.Builder COMMAND_FISH_FINDER_POSSIBLE_LOOTS = Component.translatable().key("command.fish_finder.possible_loots");
TranslatableComponent.Builder COMMAND_FISH_FINDER_NO_LOOT = Component.translatable().key("command.fish_finder.no_loot");
TranslatableComponent.Builder COMMAND_FISH_FINDER_SPLIT_CHAR = Component.translatable().key("command.fish_finder.split_char");
TranslatableComponent.Builder COMMAND_COMPETITION_FAILURE_NOT_EXIST = Component.translatable().key("command.competition.failure.not_exist");
TranslatableComponent.Builder COMMAND_COMPETITION_FAILURE_NO_COMPETITION = Component.translatable().key("command.competition.failure.no_competition");
TranslatableComponent.Builder COMMAND_COMPETITION_START_SUCCESS = Component.translatable().key("command.competition.start.success");
TranslatableComponent.Builder COMMAND_COMPETITION_STOP_SUCCESS = Component.translatable().key("command.competition.stop.success");
TranslatableComponent.Builder COMMAND_COMPETITION_END_SUCCESS = Component.translatable().key("command.competition.end.success");
TranslatableComponent.Builder COMMAND_BAG_EDIT_FAILURE_UNSAFE = Component.translatable().key("command.bag.edit.failure.unsafe");
TranslatableComponent.Builder COMMAND_BAG_EDIT_FAILURE_NEVER_PLAYED = Component.translatable().key("command.bag.edit.failure.never_played");
TranslatableComponent.Builder COMMAND_BAG_OPEN_SUCCESS = Component.translatable().key("command.bag.open.success");
TranslatableComponent.Builder COMMAND_BAG_OPEN_FAILURE_NOT_LOADED = Component.translatable().key("command.bag.open.failure.not_loaded");
TranslatableComponent.Builder COMMAND_DATA_FAILURE_NOT_LOADED = Component.translatable().key("command.data.failure.not_loaded");
TranslatableComponent.Builder COMMAND_MARKET_OPEN_SUCCESS = Component.translatable().key("command.market.open.success");
TranslatableComponent.Builder COMMAND_MARKET_OPEN_FAILURE_NOT_LOADED = Component.translatable().key("command.market.open.failure.not_loaded");
TranslatableComponent.Builder COMMAND_DATA_UNLOCK_SUCCESS = Component.translatable().key("command.data.unlock.success");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_NOT_EXISTS = Component.translatable().key("command.data.import.failure.not_exists");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_PLAYER_ONLINE = Component.translatable().key("command.data.import.failure.player_online");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_FAILURE_INVALID_FILE = Component.translatable().key("command.data.import.failure.invalid_file");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_START = Component.translatable().key("command.data.import.start");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_PROGRESS = Component.translatable().key("command.data.import.progress");
TranslatableComponent.Builder COMMAND_DATA_IMPORT_SUCCESS = Component.translatable().key("command.data.import.success");
TranslatableComponent.Builder COMMAND_DATA_EXPORT_FAILURE_PLAYER_ONLINE = Component.translatable().key("command.data.export.failure.player_online");
TranslatableComponent.Builder COMMAND_DATA_EXPORT_START = Component.translatable().key("command.data.export.start");
TranslatableComponent.Builder COMMAND_DATA_EXPORT_PROGRESS = Component.translatable().key("command.data.export.progress");
TranslatableComponent.Builder COMMAND_DATA_EXPORT_SUCCESS = Component.translatable().key("command.data.export.success");
TranslatableComponent.Builder COMMAND_STATISTICS_FAILURE_NOT_LOADED = Component.translatable().key("command.statistics.failure.not_loaded");
TranslatableComponent.Builder COMMAND_STATISTICS_FAILURE_UNSUPPORTED = Component.translatable().key("command.statistics.failure.unsupported");
TranslatableComponent.Builder COMMAND_STATISTICS_MODIFY_SUCCESS = Component.translatable().key("command.statistics.modify.success");
TranslatableComponent.Builder COMMAND_STATISTICS_RESET_SUCCESS = Component.translatable().key("command.statistics.reset.success");
TranslatableComponent.Builder COMMAND_STATISTICS_QUERY_AMOUNT = Component.translatable().key("command.statistics.query.amount");
TranslatableComponent.Builder COMMAND_STATISTICS_QUERY_SIZE = Component.translatable().key("command.statistics.query.size");
// TranslatableComponent.Builder GUI_SELECT_FILE = Component.translatable().key("gui.select_file");
// TranslatableComponent.Builder GUI_SELECT_ITEM = Component.translatable().key("gui.select_item");
// TranslatableComponent.Builder GUI_INVALID_KEY = Component.translatable().key("gui.invalid_key");
// TranslatableComponent.Builder GUI_NEW_VALUE = Component.translatable().key("gui.new_value");
// TranslatableComponent.Builder GUI_TEMP_NEW_KEY = Component.translatable().key("gui.temp_new_key");
// TranslatableComponent.Builder GUI_SET_NEW_KEY = Component.translatable().key("gui.set_new_key");
// TranslatableComponent.Builder GUI_EDIT_KEY = Component.translatable().key("gui.edit_key");
// TranslatableComponent.Builder GUI_DELETE_PROPERTY = Component.translatable().key("gui.delete_property");
// TranslatableComponent.Builder GUI_CLICK_CONFIRM = Component.translatable().key("gui.click_confirm");
// TranslatableComponent.Builder GUI_INVALID_NUMBER = Component.translatable().key("gui.invalid_number");
// TranslatableComponent.Builder GUI_ILLEGAL_FORMAT = Component.translatable().key("gui.illegal_format");
// TranslatableComponent.Builder GUI_SCROLL_UP = Component.translatable().key("gui.scroll_up");
// TranslatableComponent.Builder GUI_SCROLL_DOWN = Component.translatable().key("gui.scroll_down");
// TranslatableComponent.Builder GUI_CANNOT_SCROLL_UP = Component.translatable().key("gui.cannot_scroll_up");
// TranslatableComponent.Builder GUI_CANNOT_SCROLL_DOWN = Component.translatable().key("gui.cannot_scroll_down");
// TranslatableComponent.Builder GUI_NEXT_PAGE = Component.translatable().key("gui.next_page");
// TranslatableComponent.Builder GUI_GOTO_NEXT_PAGE = Component.translatable().key("gui.goto_next_page");
// TranslatableComponent.Builder GUI_CANNOT_GOTO_NEXT_PAGE = Component.translatable().key("gui.cannot_goto_next_page");
// TranslatableComponent.Builder GUI_PREVIOUS_PAGE = Component.translatable().key("gui.previous_page");
// TranslatableComponent.Builder GUI_GOTO_PREVIOUS_PAGE = Component.translatable().key("gui.goto_previous_page");
// TranslatableComponent.Builder GUI_CANNOT_GOTO_PREVIOUS_PAGE = Component.translatable().key("gui.cannot_goto_previous_page");
// TranslatableComponent.Builder GUI_BACK_TO_PARENT_PAGE = Component.translatable().key("gui.back_to_parent_page");
// TranslatableComponent.Builder GUI_BACK_TO_PARENT_FOLDER = Component.translatable().key("gui.back_to_parent_folder");
// TranslatableComponent.Builder GUI_CURRENT_VALUE = Component.translatable().key("gui.current_value");
// TranslatableComponent.Builder GUI_CLICK_TO_TOGGLE = Component.translatable().key("gui.click_to_toggle");
// TranslatableComponent.Builder GUI_LEFT_CLICK_EDIT = Component.translatable().key("gui.left_click_edit");
// TranslatableComponent.Builder GUI_RIGHT_CLICK_RESET = Component.translatable().key("gui.right_click_reset");
// TranslatableComponent.Builder GUI_RIGHT_CLICK_DELETE = Component.translatable().key("gui.right_click_delete");
// TranslatableComponent.Builder GUI_RIGHT_CLICK_CANCEL = Component.translatable().key("gui.right_click_cancel");
// TranslatableComponent.Builder GUI_LOOT_SHOW_IN_FINDER = Component.translatable().key("gui.loot_show_in_finder");
// TranslatableComponent.Builder GUI_LOOT_SCORE = Component.translatable().key("gui.loot_score");
// TranslatableComponent.Builder GUI_LOOT_NICK = Component.translatable().key("gui.loot_nick");
// TranslatableComponent.Builder GUI_LOOT_INSTANT_GAME = Component.translatable().key("gui.loot_instant_game");
// TranslatableComponent.Builder GUI_LOOT_DISABLE_STATISTICS = Component.translatable().key("gui.loot_disable_statistics");
// TranslatableComponent.Builder GUI_LOOT_DISABLE_GAME = Component.translatable().key("gui.loot_disable_game");
// TranslatableComponent.Builder GUI_ITEM_AMOUNT = Component.translatable().key("gui.item_amount");
// TranslatableComponent.Builder GUI_ITEM_CUSTOM_MODEL_DATA = Component.translatable().key("gui.item_custom_model_data");
// TranslatableComponent.Builder GUI_ITEM_DISPLAY_NAME = Component.translatable().key("gui.item_display_name");
// TranslatableComponent.Builder GUI_ITEM_CUSTOM_DURABILITY = Component.translatable().key("gui.item_custom_durability");
// TranslatableComponent.Builder GUI_ITEM_ENCHANTMENT = Component.translatable().key("gui.item_enchantment");
// TranslatableComponent.Builder GUI_ITEM_HEAD64 = Component.translatable().key("gui.item_head64");
// TranslatableComponent.Builder GUI_ITEM_FLAG = Component.translatable().key("gui.item_item_flag");
// TranslatableComponent.Builder GUI_ITEM_LORE = Component.translatable().key("gui.item_lore");
// TranslatableComponent.Builder GUI_ITEM_MATERIAL = Component.translatable().key("gui.item_material");
// TranslatableComponent.Builder GUI_ITEM_NBT = Component.translatable().key("gui.item_nbt");
// TranslatableComponent.Builder GUI_ITEM_PREVENT_GRAB = Component.translatable().key("gui.item_prevent_grab");
// TranslatableComponent.Builder GUI_ITEM_PRICE = Component.translatable().key("gui.item_price");
// TranslatableComponent.Builder GUI_ITEM_PRICE_BASE = Component.translatable().key("gui.item_price_base");
// TranslatableComponent.Builder GUI_ITEM_PRICE_BONUS = Component.translatable().key("gui.item_price_bonus");
// TranslatableComponent.Builder GUI_ITEM_RANDOM_DURABILITY = Component.translatable().key("gui.item_random_durability");
// TranslatableComponent.Builder GUI_ITEM_SIZE = Component.translatable().key("gui.item_size");
// TranslatableComponent.Builder GUI_ITEM_STACKABLE = Component.translatable().key("gui.item_stackable");
// TranslatableComponent.Builder GUI_ITEM_STORED_ENCHANTMENT = Component.translatable().key("gui.item_stored_enchantment");
// TranslatableComponent.Builder GUI_ITEM_TAG = Component.translatable().key("gui.item_tag");
// TranslatableComponent.Builder GUI_ITEM_UNBREAKABLE = Component.translatable().key("gui.item_unbreakable");
// TranslatableComponent.Builder GUI_PAGE_AMOUNT_TITLE = Component.translatable().key("gui.page_amount_title");
// TranslatableComponent.Builder GUI_PAGE_MODEL_DATA_TITLE = Component.translatable().key("gui.page_model_data_title");
// TranslatableComponent.Builder GUI_PAGE_DISPLAY_NAME_TITLE = Component.translatable().key("gui.page_display_name_title");
// TranslatableComponent.Builder GUI_PAGE_NEW_DISPLAY_NAME = Component.translatable().key("gui.page_new_display_name");
// TranslatableComponent.Builder GUI_PAGE_CUSTOM_DURABILITY_TITLE = Component.translatable().key("gui.page_custom_durability_title");
// TranslatableComponent.Builder GUI_PAGE_STORED_ENCHANTMENT_TITLE = Component.translatable().key("gui.page_stored_enchantment_title");
// TranslatableComponent.Builder GUI_PAGE_ENCHANTMENT_TITLE = Component.translatable().key("gui.page_enchantment_title");
// TranslatableComponent.Builder GUI_PAGE_SELECT_ONE_ENCHANTMENT = Component.translatable().key("gui.page_select_one_enchantment");
// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_ENCHANTMENT = Component.translatable().key("gui.page_add_new_enchantment");
// TranslatableComponent.Builder GUI_PAGE_ITEM_FLAG_TITLE = Component.translatable().key("gui.page_item_flag_title");
// TranslatableComponent.Builder GUI_PAGE_LORE_TITLE = Component.translatable().key("gui.page_lore_title");
// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_LORE = Component.translatable().key("gui.page_add_new_lore");
// TranslatableComponent.Builder GUI_PAGE_SELECT_ONE_LORE = Component.translatable().key("gui.page_select_one_lore");
// TranslatableComponent.Builder GUI_PAGE_MATERIAL_TITLE = Component.translatable().key("gui.page_material_title");
// TranslatableComponent.Builder GUI_PAGE_NBT_COMPOUND_KEY_TITLE = Component.translatable().key("gui.page_nbt_compound_key_title");
// TranslatableComponent.Builder GUI_PAGE_NBT_LIST_KEY_TITLE = Component.translatable().key("gui.page_nbt_list_key_title");
// TranslatableComponent.Builder GUI_PAGE_NBT_KEY_TITLE = Component.translatable().key("gui.page_nbt_key_title");
// TranslatableComponent.Builder GUI_PAGE_NBT_INVALID_KEY = Component.translatable().key("gui.page_nbt_invalid_key");
// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_COMPOUND= Component.translatable().key("gui.page_nbt_add_new_compound");
// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_LIST = Component.translatable().key("gui.page_nbt_add_new_list");
// TranslatableComponent.Builder GUI_PAGE_NBT_ADD_NEW_VALUE = Component.translatable().key("gui.page_nbt_add_new_value");
// TranslatableComponent.Builder GUI_PAGE_ADD_NEW_KEY = Component.translatable().key("gui.page_add_new_key");
// TranslatableComponent.Builder GUI_PAGE_NBT_PREVIEW = Component.translatable().key("gui.page_nbt_preview");
// TranslatableComponent.Builder GUI_PAGE_NBT_BACK_TO_COMPOUND = Component.translatable().key("gui.page_nbt_back_to_compound");
// TranslatableComponent.Builder GUI_PAGE_NBT_SET_VALUE_TITLE = Component.translatable().key("gui.page_nbt_set_value_title");
// TranslatableComponent.Builder GUI_PAGE_NBT_EDIT_TITLE = Component.translatable().key("gui.page_nbt_edit_title");
// TranslatableComponent.Builder GUI_PAGE_NICK_TITLE = Component.translatable().key("gui.page_nick_title");
// TranslatableComponent.Builder GUI_PAGE_NEW_NICK = Component.translatable().key("gui.page_new_nick");
// TranslatableComponent.Builder GUI_PAGE_PRICE_TITLE = Component.translatable().key("gui.page_price_title");
// TranslatableComponent.Builder GUI_PAGE_BASE_PRICE = Component.translatable().key("gui.page_base_price");
// TranslatableComponent.Builder GUI_PAGE_BASE_BONUS = Component.translatable().key("gui.page_base_bonus");
// TranslatableComponent.Builder GUI_PAGE_SCORE_TITLE = Component.translatable().key("gui.page_score_title");
// TranslatableComponent.Builder GUI_PAGE_SIZE_TITLE = Component.translatable().key("gui.page_size_title");
// TranslatableComponent.Builder GUI_PAGE_SIZE_MIN = Component.translatable().key("gui.page_size_min");
// TranslatableComponent.Builder GUI_PAGE_SIZE_MAX = Component.translatable().key("gui.page_size_max");
// TranslatableComponent.Builder GUI_PAGE_SIZE_MAX_NO_LESS_MIN = Component.translatable().key("gui.page_size_max_no_less_min");
}

View File

@@ -0,0 +1,73 @@
/*
* 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.common.locale;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.translation.Translator;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
public interface MiniMessageTranslationRegistry extends Translator {
static @NotNull MiniMessageTranslationRegistry create(final Key name, final MiniMessage miniMessage) {
return new MiniMessageTranslationRegistryImpl(requireNonNull(name, "name"), requireNonNull(miniMessage, "MiniMessage"));
}
void register(@NotNull String key, @NotNull Locale locale, @NotNull String format);
void unregister(@NotNull String key);
boolean contains(@NotNull String key);
String miniMessageTranslation(@NotNull String key, @NotNull Locale locale);
void defaultLocale(@NotNull Locale defaultLocale);
default void registerAll(final @NotNull Locale locale, final @NotNull Map<String, String> bundle) {
this.registerAll(locale, bundle.keySet(), bundle::get);
}
default void registerAll(final @NotNull Locale locale, final @NotNull Set<String> keys, final Function<String, String> function) {
IllegalArgumentException firstError = null;
int errorCount = 0;
for (final String key : keys) {
try {
this.register(key, locale, function.apply(key));
} catch (final IllegalArgumentException e) {
if (firstError == null) {
firstError = e;
}
errorCount++;
}
}
if (firstError != null) {
if (errorCount == 1) {
throw firstError;
} else if (errorCount > 1) {
throw new IllegalArgumentException(String.format("Invalid key (and %d more)", errorCount - 1), firstError);
}
}
}
}

View File

@@ -0,0 +1,235 @@
/*
* 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.common.locale;
import net.kyori.adventure.internal.Internals;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.util.TriState;
import net.kyori.examination.Examinable;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
public class MiniMessageTranslationRegistryImpl implements Examinable, MiniMessageTranslationRegistry {
private final Key name;
private final Map<String, Translation> translations = new ConcurrentHashMap<>();
private Locale defaultLocale = Locale.US;
private final MiniMessage miniMessage;
MiniMessageTranslationRegistryImpl(final Key name, final MiniMessage miniMessage) {
this.name = name;
this.miniMessage = miniMessage;
}
@Override
public void register(final @NotNull String key, final @NotNull Locale locale, final @NotNull String format) {
this.translations.computeIfAbsent(key, Translation::new).register(locale, format);
}
@Override
public void unregister(final @NotNull String key) {
this.translations.remove(key);
}
@Override
public boolean contains(final @NotNull String key) {
return this.translations.containsKey(key);
}
@Override
public @NotNull Key name() {
return name;
}
@Override
public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) {
// No need to implement this method
return null;
}
@Override
public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) {
Translation translation = translations.get(component.key());
if (translation == null) {
return null;
}
String miniMessageString = translation.translate(locale);
if (miniMessageString == null) {
return null;
}
if (miniMessageString.isEmpty()) {
return Component.empty();
}
final Component resultingComponent;
if (component.arguments().isEmpty()) {
resultingComponent = this.miniMessage.deserialize(miniMessageString);
} else {
resultingComponent = this.miniMessage.deserialize(miniMessageString, new ArgumentTag(component.arguments()));
}
if (component.children().isEmpty()) {
return resultingComponent;
} else {
return resultingComponent.children(component.children());
}
}
@Override
public String miniMessageTranslation(@NotNull String key, @NotNull Locale locale) {
Translation translation = translations.get(key);
if (translation == null) {
return null;
}
return translation.translate(locale);
}
@Override
public @NotNull TriState hasAnyTranslations() {
if (!this.translations.isEmpty()) {
return TriState.TRUE;
}
return TriState.FALSE;
}
@Override
public void defaultLocale(final @NotNull Locale defaultLocale) {
this.defaultLocale = requireNonNull(defaultLocale, "defaultLocale");
}
@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(ExaminableProperty.of("translations", this.translations));
}
@Override
public boolean equals(final Object other) {
if (this == other) return true;
if (!(other instanceof MiniMessageTranslationRegistryImpl that)) return false;
return this.name.equals(that.name)
&& this.translations.equals(that.translations)
&& this.defaultLocale.equals(that.defaultLocale);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.translations, this.defaultLocale);
}
@Override
public String toString() {
return Internals.toString(this);
}
public static class ArgumentTag implements TagResolver {
private static final String NAME = "argument";
private static final String NAME_1 = "arg";
private final List<? extends ComponentLike> argumentComponents;
public ArgumentTag(final @NotNull List<? extends ComponentLike> argumentComponents) {
this.argumentComponents = Objects.requireNonNull(argumentComponents, "argumentComponents");
}
@Override
public @Nullable Tag resolve(final @NotNull String name, final @NotNull ArgumentQueue arguments, final @NotNull Context ctx) throws ParsingException {
final int index = arguments.popOr("No argument number provided").asInt().orElseThrow(() -> ctx.newException("Invalid argument number", arguments));
if (index < 0 || index >= argumentComponents.size()) {
throw ctx.newException("Invalid argument number", arguments);
}
return Tag.inserting(argumentComponents.get(index));
}
@Override
public boolean has(final @NotNull String name) {
return name.equals(NAME) || name.equals(NAME_1);
}
}
final class Translation implements Examinable {
private final String key;
private final Map<Locale, String> formats;
Translation(final @NotNull String key) {
this.key = requireNonNull(key, "translation key");
this.formats = new ConcurrentHashMap<>();
}
void register(final @NotNull Locale locale, final @NotNull String format) {
if (this.formats.putIfAbsent(requireNonNull(locale, "locale"), requireNonNull(format, "message format")) != null) {
throw new IllegalArgumentException(String.format("Translation already exists: %s for %s", this.key, locale));
}
}
@Nullable String translate(final @NotNull Locale locale) {
String format = this.formats.get(requireNonNull(locale, "locale"));
if (format == null) {
format = this.formats.get(new Locale(locale.getLanguage())); // try without country
if (format == null) {
format = this.formats.get(MiniMessageTranslationRegistryImpl.this.defaultLocale); // try local default locale
}
}
return format;
}
@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(
ExaminableProperty.of("key", this.key),
ExaminableProperty.of("formats", this.formats)
);
}
@Override
public boolean equals(final Object other) {
if (this == other) return true;
if (!(other instanceof Translation that)) return false;
return this.key.equals(that.key) &&
this.formats.equals(that.formats);
}
@Override
public int hashCode() {
return Objects.hash(this.key, this.formats);
}
@Override
public String toString() {
return Internals.toString(this);
}
}
}

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.common.locale;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.translation.Translator;
import net.kyori.examination.Examinable;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
public interface MiniMessageTranslator extends Translator, Examinable {
static @NotNull MiniMessageTranslator translator() {
return MiniMessageTranslatorImpl.INSTANCE;
}
static @NotNull TranslatableComponentRenderer<Locale> renderer() {
return MiniMessageTranslatorImpl.INSTANCE.renderer;
}
static @NotNull Component render(final @NotNull Component component, final @NotNull Locale locale) {
return renderer().render(component, locale);
}
@NotNull Iterable<? extends Translator> sources();
boolean addSource(final @NotNull Translator source);
boolean removeSource(final @NotNull Translator source);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.locale;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.translation.Translator;
import net.kyori.adventure.util.TriState;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
public class MiniMessageTranslatorImpl implements MiniMessageTranslator {
private static final Key NAME = Key.key("customfishing", "main");
static final MiniMessageTranslatorImpl INSTANCE = new MiniMessageTranslatorImpl();
final TranslatableComponentRenderer<Locale> renderer = TranslatableComponentRenderer.usingTranslationSource(this);
private final Set<Translator> sources = Collections.newSetFromMap(new ConcurrentHashMap<>());
@Override
public @NotNull Key name() {
return NAME;
}
@Override
public @NotNull TriState hasAnyTranslations() {
if (!this.sources.isEmpty()) {
return TriState.TRUE;
}
return TriState.FALSE;
}
@Override
public @Nullable MessageFormat translate(@NotNull String key, @NotNull Locale locale) {
// No need to implement this method
return null;
}
@Override
public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) {
for (final Translator source : this.sources) {
final Component translation = source.translate(component, locale);
if (translation != null) return translation;
}
return null;
}
@Override
public @NotNull Iterable<? extends Translator> sources() {
return Collections.unmodifiableSet(this.sources);
}
@Override
public boolean addSource(final @NotNull Translator source) {
if (source == this) throw new IllegalArgumentException("MiniMessageTranslationSource");
return this.sources.add(source);
}
@Override
public boolean removeSource(final @NotNull Translator source) {
return this.sources.remove(source);
}
@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(ExaminableProperty.of("sources", this.sources));
}
}

View File

@@ -0,0 +1,185 @@
/*
* 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.common.locale;
import dev.dejvokep.boostedyaml.YamlDocument;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.Translator;
import net.momirealms.customfishing.common.helper.AdventureHelper;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Pair;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TranslationManager {
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
private static final List<String> locales = List.of("en");
private static TranslationManager instance;
private final CustomFishingPlugin plugin;
private final Set<Locale> installed = ConcurrentHashMap.newKeySet();
private MiniMessageTranslationRegistry registry;
private final Path translationsDirectory;
public TranslationManager(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.translationsDirectory = this.plugin.getConfigDirectory().resolve("translations");
instance = this;
}
public void reload() {
// remove any previous registry
if (this.registry != null) {
MiniMessageTranslator.translator().removeSource(this.registry);
this.installed.clear();
}
for (String lang : locales) {
this.plugin.getConfigManager().saveResource("translations/" + lang + ".yml");
}
this.registry = MiniMessageTranslationRegistry.create(Key.key("customfishing", "main"), AdventureHelper.getMiniMessage());
this.registry.defaultLocale(DEFAULT_LOCALE);
this.loadFromFileSystem(this.translationsDirectory, false);
MiniMessageTranslator.translator().addSource(this.registry);
}
public static String miniMessageTranslation(String key) {
return miniMessageTranslation(key, null);
}
public static String miniMessageTranslation(String key, @Nullable Locale locale) {
if (locale == null) {
locale = Locale.getDefault();
if (locale == null) {
locale = DEFAULT_LOCALE;
}
}
return instance.registry.miniMessageTranslation(key, locale);
}
public static Component render(Component component) {
return render(component, null);
}
public static Component render(Component component, @Nullable Locale locale) {
if (locale == null) {
locale = Locale.getDefault();
if (locale == null) {
locale = DEFAULT_LOCALE;
}
}
return MiniMessageTranslator.render(component, locale);
}
public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) {
List<Path> translationFiles;
try (Stream<Path> stream = Files.list(directory)) {
translationFiles = stream.filter(TranslationManager::isTranslationFile).collect(Collectors.toList());
} catch (IOException e) {
translationFiles = Collections.emptyList();
}
if (translationFiles.isEmpty()) {
return;
}
Map<Locale, Map<String, String>> loaded = new HashMap<>();
for (Path translationFile : translationFiles) {
try {
Pair<Locale, Map<String, String>> result = loadTranslationFile(translationFile);
loaded.put(result.left(), result.right());
} catch (Exception e) {
if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) {
this.plugin.getPluginLogger().warn("Error loading locale file: " + translationFile.getFileName(), e);
}
}
}
// try registering the locale without a country code - if we don't already have a registration for that
loaded.forEach((locale, bundle) -> {
Locale localeWithoutCountry = new Locale(locale.getLanguage());
if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) {
try {
this.registry.registerAll(localeWithoutCountry, bundle);
} catch (IllegalArgumentException e) {
// ignore
}
}
});
Locale localLocale = Locale.getDefault();
if (!this.installed.contains(localLocale)) {
plugin.getPluginLogger().warn(localLocale.toString().toLowerCase(Locale.ENGLISH) + ".yml not exists, using en.yml as default locale.");
}
}
public static boolean isTranslationFile(Path path) {
return path.getFileName().toString().endsWith(".yml");
}
private static boolean isAdventureDuplicatesException(Exception e) {
return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists"));
}
@SuppressWarnings("unchecked")
private Pair<Locale, Map<String, String>> loadTranslationFile(Path translationFile) {
String fileName = translationFile.getFileName().toString();
String localeString = fileName.substring(0, fileName.length() - ".yml".length());
Locale locale = parseLocale(localeString);
if (locale == null) {
throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register.");
}
Map<String, String> bundle = new HashMap<>();
YamlDocument document = plugin.getConfigManager().loadConfig("translations" + "\\" + translationFile.getFileName(), '@');
Map<String, Object> map = document.getStringRouteMappedValues(false);
map.remove("config-version");
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof List<?> list) {
List<String> strList = (List<String>) list;
StringJoiner stringJoiner = new StringJoiner("<reset><newline>");
for (String str : strList) {
stringJoiner.add(str);
}
bundle.put(entry.getKey(), stringJoiner.toString());
} else if (entry.getValue() instanceof String str) {
bundle.put(entry.getKey(), str);
}
}
this.registry.registerAll(locale, bundle);
this.installed.add(locale);
return Pair.of(locale, bundle);
}
public static @Nullable Locale parseLocale(@Nullable String locale) {
return locale == null ? null : Translator.parseLocale(locale);
}
}

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.common.plugin;
import net.momirealms.customfishing.common.config.ConfigLoader;
import net.momirealms.customfishing.common.dependency.DependencyManager;
import net.momirealms.customfishing.common.locale.TranslationManager;
import net.momirealms.customfishing.common.plugin.classpath.ClassPathAppender;
import net.momirealms.customfishing.common.plugin.logging.PluginLogger;
import net.momirealms.customfishing.common.plugin.scheduler.SchedulerAdapter;
import java.io.InputStream;
import java.nio.file.Path;
public interface CustomFishingPlugin {
InputStream getResourceStream(String filePath);
PluginLogger getPluginLogger();
ClassPathAppender getClassPathAppender();
SchedulerAdapter<?> getScheduler();
Path getDataDirectory();
default Path getConfigDirectory() {
return getDataDirectory();
}
DependencyManager getDependencyManager();
TranslationManager getTranslationManager();
ConfigLoader getConfigManager();
String getServerVersion();
String getPluginVersion();
}

View File

@@ -0,0 +1,41 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.classpath;
import java.nio.file.Path;
/**
* Interface which allows access to add URLs to the plugin classpath at runtime.
*/
public interface ClassPathAppender extends AutoCloseable {
void addJarToClasspath(Path file);
@Override
default void close() {
}
}

View File

@@ -0,0 +1,58 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.classpath;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.nio.file.Path;
public class ReflectionClassPathAppender implements ClassPathAppender {
private final URLClassLoaderAccess classLoaderAccess;
public ReflectionClassPathAppender(ClassLoader classLoader) throws IllegalStateException {
if (classLoader instanceof URLClassLoader) {
this.classLoaderAccess = URLClassLoaderAccess.create((URLClassLoader) classLoader);
} else {
throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
}
}
public ReflectionClassPathAppender(CustomFishingPlugin plugin) throws IllegalStateException {
this(plugin.getClass().getClassLoader());
}
@Override
public void addJarToClasspath(Path file) {
try {
this.classLoaderAccess.addURL(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.classpath;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
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
*/
public static URLClassLoaderAccess create(URLClassLoader classLoader) {
if (Reflection.isSupported()) {
return new Reflection(classLoader);
} else 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);
private static void throwError(Throwable cause) throws UnsupportedOperationException {
throw new UnsupportedOperationException("CustomFishing is unable to inject into the plugin URLClassLoader.\n" +
"You may be able to fix this problem by adding the following command-line argument " +
"directly after the 'java' command in your start script: \n'--add-opens java.base/java.lang=ALL-UNNAMED'", cause);
}
/**
* Accesses using reflection, not supported on Java 9+.
*/
private static class Reflection extends URLClassLoaderAccess {
private static final Method ADD_URL_METHOD;
static {
Method addUrlMethod;
try {
addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addUrlMethod.setAccessible(true);
} catch (Exception e) {
addUrlMethod = null;
}
ADD_URL_METHOD = addUrlMethod;
}
private static boolean isSupported() {
return ADD_URL_METHOD != null;
}
Reflection(URLClassLoader classLoader) {
super(classLoader);
}
@Override
public void addURL(@NotNull URL url) {
try {
ADD_URL_METHOD.invoke(super.classLoader, url);
} catch (ReflectiveOperationException e) {
URLClassLoaderAccess.throwError(e);
}
}
}
/**
* 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) {
if (this.unopenedURLs == null || this.pathURLs == null) {
URLClassLoaderAccess.throwError(new NullPointerException("unopenedURLs or pathURLs"));
}
synchronized (this.unopenedURLs) {
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) {
URLClassLoaderAccess.throwError(null);
}
}
}

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.common.plugin.feature;
public interface Reloadable {
default void reload() {
unload();
load();
}
default void unload() {
}
default void load() {
}
default void disable() {
unload();
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.logging;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JavaPluginLogger implements PluginLogger {
private final Logger logger;
public JavaPluginLogger(Logger logger) {
this.logger = logger;
}
@Override
public void info(String s) {
this.logger.info(s);
}
@Override
public void warn(String s) {
this.logger.warning(s);
}
@Override
public void warn(String s, Throwable t) {
this.logger.log(Level.WARNING, s, t);
}
@Override
public void severe(String s) {
this.logger.severe(s);
}
@Override
public void severe(String s, Throwable t) {
this.logger.log(Level.SEVERE, s, t);
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.logging;
import org.apache.logging.log4j.Logger;
public class Log4jPluginLogger implements PluginLogger {
private final Logger logger;
public Log4jPluginLogger(Logger logger) {
this.logger = logger;
}
@Override
public void info(String s) {
this.logger.info(s);
}
@Override
public void warn(String s) {
this.logger.warn(s);
}
@Override
public void warn(String s, Throwable t) {
this.logger.warn(s, t);
}
@Override
public void severe(String s) {
this.logger.error(s);
}
@Override
public void severe(String s, Throwable t) {
this.logger.error(s, t);
}
}

View File

@@ -0,0 +1,46 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.logging;
/**
* Represents the logger instance being used by CustomFishing on the platform.
*
* <p>Messages sent using the logger are sent prefixed with the CustomFishing tag,
* and on some implementations will be colored depending on the message type.</p>
*/
public interface PluginLogger {
void info(String s);
void warn(String s);
void warn(String s, Throwable t);
void severe(String s);
void severe(String s, Throwable t);
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.logging;
import org.slf4j.Logger;
public class Slf4jPluginLogger implements PluginLogger {
private final Logger logger;
public Slf4jPluginLogger(Logger logger) {
this.logger = logger;
}
@Override
public void info(String s) {
this.logger.info(s);
}
@Override
public void warn(String s) {
this.logger.warn(s);
}
@Override
public void warn(String s, Throwable t) {
this.logger.warn(s, t);
}
@Override
public void severe(String s) {
this.logger.error(s);
}
@Override
public void severe(String s, Throwable t) {
this.logger.error(s, t);
}
}

View File

@@ -0,0 +1,132 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.scheduler;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Abstract implementation of {@link SchedulerAdapter} using a {@link ScheduledExecutorService}.
*/
public abstract class AbstractJavaScheduler<T> implements SchedulerAdapter<T> {
private static final int PARALLELISM = 16;
private final CustomFishingPlugin plugin;
private final ScheduledThreadPoolExecutor scheduler;
private final ForkJoinPool worker;
public AbstractJavaScheduler(CustomFishingPlugin plugin) {
this.plugin = plugin;
this.scheduler = new ScheduledThreadPoolExecutor(4, r -> {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setName("customfishing-scheduler");
return thread;
});
this.scheduler.setRemoveOnCancelPolicy(true);
this.scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.worker = new ForkJoinPool(PARALLELISM, new WorkerThreadFactory(), new ExceptionHandler(), false);
}
@Override
public Executor async() {
return this.worker;
}
@Override
public SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit) {
ScheduledFuture<?> future = this.scheduler.schedule(() -> this.worker.execute(task), delay, unit);
return () -> future.cancel(false);
}
@Override
public SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit) {
ScheduledFuture<?> future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(task), delay, interval, unit);
return () -> future.cancel(false);
}
@Override
public void shutdownScheduler() {
this.scheduler.shutdown();
try {
if (!this.scheduler.awaitTermination(1, TimeUnit.MINUTES)) {
this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing scheduler to terminate");
reportRunningTasks(thread -> thread.getName().equals("customfishing-scheduler"));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void shutdownExecutor() {
this.worker.shutdown();
try {
if (!this.worker.awaitTermination(1, TimeUnit.MINUTES)) {
this.plugin.getPluginLogger().severe("Timed out waiting for the CustomFishing worker thread pool to terminate");
reportRunningTasks(thread -> thread.getName().startsWith("customfishing-worker-"));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void reportRunningTasks(Predicate<Thread> predicate) {
Thread.getAllStackTraces().forEach((thread, stack) -> {
if (predicate.test(thread)) {
this.plugin.getPluginLogger().warn("Thread " + thread.getName() + " is blocked, and may be the reason for the slow shutdown!\n" +
Arrays.stream(stack).map(el -> " " + el).collect(Collectors.joining("\n"))
);
}
});
}
private static final class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
private static final AtomicInteger COUNT = new AtomicInteger(0);
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
thread.setDaemon(true);
thread.setName("customfishing-worker-" + COUNT.getAndIncrement());
return thread;
}
}
private final class ExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
AbstractJavaScheduler.this.plugin.getPluginLogger().warn("Thread " + t.getName() + " threw an uncaught exception", e);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.plugin.scheduler;
public interface RegionExecutor<T> {
void run(Runnable r, T l);
SchedulerTask runLater(Runnable r, long delayTicks, T l);
SchedulerTask runRepeating(Runnable r, long delayTicks, long period, T l);
}

View File

@@ -0,0 +1,106 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.scheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* A scheduler for running tasks using the systems provided by the platform
*/
public interface SchedulerAdapter<T> {
/**
* Gets an async executor instance
*
* @return an async executor instance
*/
Executor async();
/**
* Gets a sync executor instance
*
* @return a sync executor instance
*/
RegionExecutor<T> sync();
/**
* Executes a task async
*
* @param task the task
*/
default void executeAsync(Runnable task) {
async().execute(task);
}
/**
* Executes a task sync
*
* @param task the task
*/
default void executeSync(Runnable task, T location) {
sync().run(task, location);
}
default void executeSync(Runnable task) {
sync().run(task, null);
}
/**
* Executes the given task with a delay.
*
* @param task the task
* @param delay the delay
* @param unit the unit of delay
* @return the resultant task instance
*/
SchedulerTask asyncLater(Runnable task, long delay, TimeUnit unit);
/**
* Executes the given task repeatedly at a given interval.
*
* @param task the task
* @param interval the interval
* @param unit the unit of interval
* @return the resultant task instance
*/
SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit);
/**
* Shuts down the scheduler instance.
*
* <p>{@link #asyncLater(Runnable, long, TimeUnit)} and {@link #asyncRepeating(Runnable, long, long, TimeUnit)}.</p>
*/
void shutdownScheduler();
/**
* Shuts down the executor instance.
*
* <p>{@link #async()} and {@link #executeAsync(Runnable)}.</p>
*/
void shutdownExecutor();
}

View File

@@ -0,0 +1,38 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.plugin.scheduler;
/**
* Represents a scheduled task
*/
public interface SchedulerTask {
/**
* Cancels the task.
*/
void cancel();
}

View File

@@ -0,0 +1,116 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.sender;
import net.kyori.adventure.text.Component;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Tristate;
import java.util.UUID;
/**
* Simple implementation of {@link Sender} using a {@link SenderFactory}
*
* @param <T> the command sender type
*/
public final class AbstractSender<T> implements Sender {
private final CustomFishingPlugin plugin;
private final SenderFactory<?, T> factory;
private final T sender;
private final UUID uniqueId;
private final String name;
private final boolean isConsole;
AbstractSender(CustomFishingPlugin plugin, SenderFactory<?, T> factory, T sender) {
this.plugin = plugin;
this.factory = factory;
this.sender = sender;
this.uniqueId = factory.getUniqueId(this.sender);
this.name = factory.getName(this.sender);
this.isConsole = this.factory.isConsole(this.sender);
}
@Override
public CustomFishingPlugin getPlugin() {
return this.plugin;
}
@Override
public UUID getUniqueId() {
return this.uniqueId;
}
@Override
public String getName() {
return this.name;
}
@Override
public void sendMessage(Component message) {
this.factory.sendMessage(this.sender, message);
}
@Override
public void sendMessage(Component message, boolean ignoreEmpty) {
if (ignoreEmpty && message.equals(Component.empty())) {
return;
}
sendMessage(message);
}
@Override
public Tristate getPermissionValue(String permission) {
return (isConsole() && this.factory.consoleHasAllPermissions()) ? Tristate.TRUE : this.factory.getPermissionValue(this.sender, permission);
}
@Override
public boolean hasPermission(String permission) {
return (isConsole() && this.factory.consoleHasAllPermissions()) || this.factory.hasPermission(this.sender, permission);
}
@Override
public void performCommand(String commandLine) {
this.factory.performCommand(this.sender, commandLine);
}
@Override
public boolean isConsole() {
return this.isConsole;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof AbstractSender<?> that)) return false;
return this.getUniqueId().equals(that.getUniqueId());
}
@Override
public int hashCode() {
return this.uniqueId.hashCode();
}
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.sender;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import java.util.UUID;
public abstract class DummyConsoleSender implements Sender {
private final CustomFishingPlugin platform;
public DummyConsoleSender(CustomFishingPlugin plugin) {
this.platform = plugin;
}
@Override
public void performCommand(String commandLine) {
}
@Override
public boolean isConsole() {
return true;
}
@Override
public CustomFishingPlugin getPlugin() {
return this.platform;
}
@Override
public UUID getUniqueId() {
return Sender.CONSOLE_UUID;
}
@Override
public String getName() {
return Sender.CONSOLE_NAME;
}
}

View File

@@ -0,0 +1,121 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.sender;
import net.kyori.adventure.text.Component;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Tristate;
import java.util.UUID;
/**
* Wrapper interface to represent a CommandSender/CommandSource within the common command implementations.
*/
public interface Sender {
/** The uuid used by the console sender. */
UUID CONSOLE_UUID = new UUID(0, 0); // 00000000-0000-0000-0000-000000000000
/** The name used by the console sender. */
String CONSOLE_NAME = "Console";
/**
* Gets the plugin instance the sender is from.
*
* @return the plugin
*/
CustomFishingPlugin getPlugin();
/**
* Gets the sender's username
*
* @return a friendly username for the sender
*/
String getName();
/**
* Gets the sender's unique id.
*
* <p>See {@link #CONSOLE_UUID} for the console's UUID representation.</p>
*
* @return the sender's uuid
*/
UUID getUniqueId();
/**
* Send a json message to the Sender.
*
* @param message the message to send.
*/
void sendMessage(Component message);
/**
* Send a json message to the Sender.
*
* @param message the message to send.
* @param ignoreEmpty whether to ignore empty component
*/
void sendMessage(Component message, boolean ignoreEmpty);
/**
* Gets the tristate a permission is set to.
*
* @param permission the permission to check for
* @return a tristate
*/
Tristate getPermissionValue(String permission);
/**
* Check if the Sender has a permission.
*
* @param permission the permission to check for
* @return true if the sender has the permission
*/
boolean hasPermission(String permission);
/**
* Makes the sender perform a command.
*
* @param commandLine the command
*/
void performCommand(String commandLine);
/**
* Gets whether this sender is the console
*
* @return if the sender is the console
*/
boolean isConsole();
/**
* Gets whether this sender is still valid & receiving messages.
*
* @return if this sender is valid
*/
default boolean isValid() {
return true;
}
}

View File

@@ -0,0 +1,82 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.sender;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.momirealms.customfishing.common.plugin.CustomFishingPlugin;
import net.momirealms.customfishing.common.util.Tristate;
import java.util.Objects;
import java.util.UUID;
/**
* Factory class to make a thread-safe sender instance
*
* @param <P> the plugin type
* @param <T> the command sender type
*/
public abstract class SenderFactory<P extends CustomFishingPlugin, T> implements AutoCloseable {
private final P plugin;
public SenderFactory(P plugin) {
this.plugin = plugin;
}
protected P getPlugin() {
return this.plugin;
}
protected abstract UUID getUniqueId(T sender);
protected abstract String getName(T sender);
public abstract Audience getAudience(T sender);
protected abstract void sendMessage(T sender, Component message);
protected abstract Tristate getPermissionValue(T sender, String node);
protected abstract boolean hasPermission(T sender, String node);
protected abstract void performCommand(T sender, String command);
protected abstract boolean isConsole(T sender);
protected boolean consoleHasAllPermissions() {
return true;
}
public final Sender wrap(T sender) {
Objects.requireNonNull(sender, "sender");
return new AbstractSender<>(this.plugin, this, sender);
}
@Override
public void close() {
}
}

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.common.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArrayUtils {
private ArrayUtils() {}
public static <T> T[] subArray(T[] array, int index) {
if (index < 0) {
throw new IllegalArgumentException("Index should be a value no lower than 0");
}
if (array.length <= index) {
@SuppressWarnings("unchecked")
T[] emptyArray = (T[]) Array.newInstance(array.getClass().getComponentType(), 0);
return emptyArray;
}
@SuppressWarnings("unchecked")
T[] subArray = (T[]) Array.newInstance(array.getClass().getComponentType(), array.length - index);
System.arraycopy(array, index, subArray, 0, array.length - index);
return subArray;
}
public static <T> List<T[]> splitArray(T[] array, int chunkSize) {
List<T[]> result = new ArrayList<>();
for (int i = 0; i < array.length; i += chunkSize) {
int end = Math.min(array.length, i + chunkSize);
@SuppressWarnings("unchecked")
T[] chunk = (T[]) Array.newInstance(array.getClass().getComponentType(), end - i);
System.arraycopy(array, i, chunk, 0, end - i);
result.add(chunk);
}
return result;
}
public static <T> T[] appendElementToArray(T[] array, T element) {
T[] newArray = Arrays.copyOf(array, array.length + 1);
newArray[array.length] = element;
return newArray;
}
public static String[] splitValue(String value) {
return value.substring(value.indexOf('[') + 1, value.lastIndexOf(']'))
.replaceAll("\\s", "")
.split(",");
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.common.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
public class ClassUtils {
private ClassUtils() {}
/**
* Attempts to find a class within a JAR file that extends or implements a given class or interface.
*
* @param file The JAR file in which to search for the class.
* @param clazz The base class or interface to match against.
* @param <T> The type of the base class or interface.
* @return A Class object representing the found class, or null if not found.
* @throws IOException If there is an issue reading the JAR file.
* @throws ClassNotFoundException If the specified class cannot be found.
*/
@Nullable
public static <T> Class<? extends T> findClass(
@NotNull File file,
@NotNull Class<T> clazz
) throws IOException, ClassNotFoundException {
if (!file.exists()) {
return null;
}
URL jar = file.toURI().toURL();
URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader());
List<String> matches = new ArrayList<>();
List<Class<? extends T>> classes = new ArrayList<>();
try (JarInputStream stream = new JarInputStream(jar.openStream())) {
JarEntry entry;
while ((entry = stream.getNextJarEntry()) != null) {
final String name = entry.getName();
if (!name.endsWith(".class")) {
continue;
}
matches.add(name.substring(0, name.lastIndexOf('.')).replace('/', '.'));
}
for (String match : matches) {
try {
Class<?> loaded = loader.loadClass(match);
if (clazz.isAssignableFrom(loaded)) {
classes.add(loaded.asSubclass(clazz));
}
} catch (NoClassDefFoundError ignored) {
}
}
}
if (classes.isEmpty()) {
loader.close();
return null;
}
return classes.get(0);
}
}

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.common.util;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class CompletableFutures {
private CompletableFutures() {}
/**
* A collector for collecting a stream of CompletableFuture instances into a single CompletableFuture that completes
* when all of the input CompletableFutures complete.
*
* @param <T> The type of CompletableFuture.
* @return A collector for CompletableFuture instances.
*/
public static <T extends CompletableFuture<?>> Collector<T, ImmutableList.Builder<T>, CompletableFuture<Void>> collector() {
return Collector.of(
ImmutableList.Builder::new,
ImmutableList.Builder::add,
(l, r) -> l.addAll(r.build()),
builder -> allOf(builder.build())
);
}
/**
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
* CompletableFutures complete.
*
* @param futures A stream of CompletableFuture instances.
* @return A CompletableFuture that completes when all input CompletableFutures complete.
*/
public static CompletableFuture<Void> allOf(Stream<? extends CompletableFuture<?>> futures) {
CompletableFuture<?>[] arr = futures.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(arr);
}
/**
* Combines multiple CompletableFuture instances into a single CompletableFuture that completes when all of the input
* CompletableFutures complete.
*
* @param futures A collection of CompletableFuture instances.
* @return A CompletableFuture that completes when all input CompletableFutures complete.
*/
public static CompletableFuture<Void> allOf(Collection<? extends CompletableFuture<?>> futures) {
CompletableFuture<?>[] arr = futures.toArray(new CompletableFuture[0]);
return CompletableFuture.allOf(arr);
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.common.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
public interface Either<U, V> {
static <U, V> @NotNull Either<U, V> ofPrimary(final @NotNull U value) {
return EitherImpl.of(requireNonNull(value, "value"), null);
}
static <U, V> @NotNull Either<U, V> ofFallback(final @NotNull V value) {
return EitherImpl.of(null, requireNonNull(value, "value"));
}
@NotNull
Optional<U> primary();
@NotNull
Optional<V> fallback();
default @Nullable U primaryOrMapFallback(final @NotNull Function<V, U> mapFallback) {
return this.primary().orElseGet(() -> mapFallback.apply(this.fallback().get()));
}
default @Nullable V fallbackOrMapPrimary(final @NotNull Function<U, V> mapPrimary) {
return this.fallback().orElseGet(() -> mapPrimary.apply(this.primary().get()));
}
default @NotNull <R> R mapEither(
final @NotNull Function<U, R> mapPrimary,
final @NotNull Function<V, R> mapFallback
) {
return this.primary()
.map(mapPrimary)
.orElseGet(() -> this.fallback().map(mapFallback).get());
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.common.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
final class EitherImpl<U, V> implements Either<U, V> {
private final @Nullable U primary;
private final @Nullable V fallback;
private EitherImpl(Optional<? extends U> primary, Optional<? extends V> fallback) {
this.primary = primary.orElse(null);
this.fallback = fallback.orElse(null);
}
private EitherImpl(@Nullable U primary, @Nullable V fallback) {
this.primary = primary;
this.fallback = fallback;
}
private EitherImpl(
EitherImpl<U, V> original,
@Nullable U primary,
@Nullable V fallback
) {
this.primary = primary;
this.fallback = fallback;
}
@Override
public @NotNull Optional<U> primary() {
return Optional.ofNullable(primary);
}
@Override
public @NotNull Optional<V> fallback() {
return Optional.ofNullable(fallback);
}
public final EitherImpl<U, V> withPrimary(@Nullable U value) {
@Nullable U newValue = value;
if (this.primary == newValue) return this;
return new EitherImpl<>(this, newValue, this.fallback);
}
public EitherImpl<U, V> withPrimary(Optional<? extends U> optional) {
@Nullable U value = optional.orElse(null);
if (this.primary == value) return this;
return new EitherImpl<>(this, value, this.fallback);
}
public EitherImpl<U, V> withFallback(@Nullable V value) {
@Nullable V newValue = value;
if (this.fallback == newValue) return this;
return new EitherImpl<>(this, this.primary, newValue);
}
public EitherImpl<U, V> withFallback(Optional<? extends V> optional) {
@Nullable V value = optional.orElse(null);
if (this.fallback == value) return this;
return new EitherImpl<>(this, this.primary, value);
}
@Override
public boolean equals(@Nullable Object another) {
if (this == another) return true;
return another instanceof EitherImpl<?, ?>
&& equalTo((EitherImpl<?, ?>) another);
}
private boolean equalTo(EitherImpl<?, ?> another) {
return Objects.equals(primary, another.primary)
&& Objects.equals(fallback, another.fallback);
}
@Override
public int hashCode() {
int h = 5381;
h += (h << 5) + Objects.hashCode(primary);
h += (h << 5) + Objects.hashCode(fallback);
return h;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Either{");
if (primary != null) {
builder.append("primary=").append(primary);
}
if (fallback != null) {
if (builder.length() > 7) builder.append(", ");
builder.append("fallback=").append(fallback);
}
return builder.append("}").toString();
}
public static <U, V> EitherImpl<U, V> of(Optional<? extends U> primary, Optional<? extends V> fallback) {
return new EitherImpl<>(primary, fallback);
}
public static <U, V> EitherImpl<U, V> of(@Nullable U primary, @Nullable V fallback) {
return new EitherImpl<>(primary, fallback);
}
public static <U, V> EitherImpl<U, V> copyOf(Either<U, V> instance) {
if (instance instanceof EitherImpl<?, ?>) {
return (EitherImpl<U, V>) instance;
}
return EitherImpl.of(instance.primary(), instance.fallback());
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.common.util;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileUtils {
private FileUtils() {}
public static Path createFileIfNotExists(Path path) throws IOException {
if (!Files.exists(path)) {
Files.createFile(path);
}
return path;
}
public static Path createDirectoryIfNotExists(Path path) throws IOException {
if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) {
return path;
}
try {
Files.createDirectory(path);
} catch (FileAlreadyExistsException e) {
// ignore
}
return path;
}
public static Path createDirectoriesIfNotExists(Path path) throws IOException {
if (Files.exists(path) && (Files.isDirectory(path) || Files.isSymbolicLink(path))) {
return path;
}
try {
Files.createDirectories(path);
} catch (FileAlreadyExistsException e) {
// ignore
}
return path;
}
public static void deleteDirectory(Path path) throws IOException {
if (!Files.exists(path) || !Files.isDirectory(path)) {
return;
}
try (DirectoryStream<Path> contents = Files.newDirectoryStream(path)) {
for (Path file : contents) {
if (Files.isDirectory(file)) {
deleteDirectory(file);
} else {
Files.delete(file);
}
}
}
Files.delete(path);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.common.util;
public record Key(String namespace, String value) {
public static Key of(String namespace, String value) {
return new Key(namespace, value);
}
public static Key fromString(String key) {
String[] split = key.split(":", 2);
return of(split[0], split[1]);
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Key key)) return false;
return this.namespace.equals(key.namespace()) && this.value.equals(key.value());
}
@Override
public String toString() {
return namespace + ":" + value;
}
}

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.common.util;
import java.util.List;
public class ListUtils {
private ListUtils() {
}
@SuppressWarnings("unchecked")
public static List<String> toList(final Object obj) {
if (obj instanceof String s) {
return List.of(s);
} else if (obj instanceof List<?> list) {
return (List<String>) list;
}
throw new IllegalArgumentException("Cannot convert " + obj + " to a list");
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.common.util;
public record Pair<L, R>(L left, R right) {
public static <L, R> Pair<L, R> of(final L left, final R right) {
return new Pair<>(left, right);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) <2022> <XiaoMoMi>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.momirealms.customfishing.common.util;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
public class RandomUtils {
private final Random random;
private RandomUtils() {
random = ThreadLocalRandom.current();
}
private static class SingletonHolder {
private static final RandomUtils INSTANCE = new RandomUtils();
}
private static RandomUtils getInstance() {
return SingletonHolder.INSTANCE;
}
public static int generateRandomInt(int min, int max) {
return getInstance().random.nextInt(max - min + 1) + min;
}
public static double generateRandomDouble(double min, double max) {
return min + (max - min) * getInstance().random.nextDouble();
}
public static float generateRandomFloat(float min, float max) {
return min + (max - min) * getInstance().random.nextFloat();
}
public static boolean generateRandomBoolean() {
return getInstance().random.nextBoolean();
}
public static <T> T getRandomElementFromArray(T[] array) {
int index = getInstance().random.nextInt(array.length);
return array[index];
}
public static double triangle(double mode, double deviation) {
return mode + deviation * (generateRandomDouble(0,1) - generateRandomDouble(0,1));
}
public static <T> T[] getRandomElementsFromArray(T[] array, int count) {
if (count > array.length) {
throw new IllegalArgumentException("Count cannot be greater than array length");
}
@SuppressWarnings("unchecked")
T[] result = (T[]) new Object[count];
for (int i = 0; i < count; i++) {
int index = getInstance().random.nextInt(array.length - i);
result[i] = array[index];
array[index] = array[array.length - i - 1];
}
return result;
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.common.util;
public interface TriConsumer<K, V, S> {
void accept(K k, V v, S s);
}

View File

@@ -0,0 +1,32 @@
/*
* 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.common.util;
import java.util.Objects;
import java.util.function.Function;
public interface TriFunction<T, U, V, R> {
R apply(T var1, U var2, V var3);
default <W> TriFunction<T, U, V, W> andThen(Function<? super R, ? extends W> after) {
Objects.requireNonNull(after);
return (t, u, v) -> {
return after.apply(this.apply(t, u, v));
};
}
}

View File

@@ -0,0 +1,98 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.momirealms.customfishing.common.util;
import org.jetbrains.annotations.NotNull;
/**
* Represents three different states of a setting.
*
* <p>Possible values:</p>
* <p></p>
* <ul>
* <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li>
* <li>{@link #UNDEFINED} - a non-existent setting</li>
* </ul>
*/
public enum Tristate {
/**
* A value indicating a positive setting
*/
TRUE(true),
/**
* A value indicating a negative (negated) setting
*/
FALSE(false),
/**
* A value indicating a non-existent setting
*/
UNDEFINED(false);
/**
* Returns a {@link Tristate} from a boolean
*
* @param val the boolean value
* @return {@link #TRUE} or {@link #FALSE}, if the value is <code>true</code> or <code>false</code>, respectively.
*/
public static @NotNull Tristate of(boolean val) {
return val ? TRUE : FALSE;
}
/**
* Returns a {@link Tristate} from a nullable boolean.
*
* <p>Unlike {@link #of(boolean)}, this method returns {@link #UNDEFINED}
* if the value is null.</p>
*
* @param val the boolean value
* @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value
* is <code>null</code>, <code>true</code> or <code>false</code>, respectively.
*/
public static @NotNull Tristate of(Boolean val) {
return val == null ? UNDEFINED : val ? TRUE : FALSE;
}
private final boolean booleanValue;
Tristate(boolean booleanValue) {
this.booleanValue = booleanValue;
}
/**
* Returns the value of the Tristate as a boolean.
*
* <p>A value of {@link #UNDEFINED} converts to false.</p>
*
* @return a boolean representation of the Tristate.
*/
public boolean asBoolean() {
return this.booleanValue;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.common.util;
public record Tuple<L, M, R>(L left, M mid, R right) {
public static <L, M, R> Tuple<L, M, R> of(final L left, final M mid, final R right) {
return new Tuple<>(left, mid, right);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.common.util;
import java.math.BigInteger;
import java.util.UUID;
public class UUIDUtils {
public static UUID fromUnDashedUUID(String id) {
return id == null ? null : new UUID(
new BigInteger(id.substring(0, 16), 16).longValue(),
new BigInteger(id.substring(16, 32), 16).longValue()
);
}
public static String toUnDashedUUID(UUID uuid) {
return uuid.toString().replace("-", "");
}
public static UUID uuidFromIntArray(int[] array) {
return new UUID((long)array[0] << 32 | (long)array[1] & 4294967295L, (long)array[2] << 32 | (long)array[3] & 4294967295L);
}
public static int[] uuidToIntArray(UUID uuid) {
long l = uuid.getMostSignificantBits();
long m = uuid.getLeastSignificantBits();
return leastMostToIntArray(l, m);
}
private static int[] leastMostToIntArray(long uuidMost, long uuidLeast) {
return new int[]{(int)(uuidMost >> 32), (int)uuidMost, (int)(uuidLeast >> 32), (int)uuidLeast};
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.common.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Utility class for selecting random items based on weights.
*/
@SuppressWarnings("DuplicatedCode")
public class WeightUtils {
private WeightUtils() {}
/**
* Get a random item from a list of pairs, each associated with a weight.
*
* @param pairs A list of pairs where the left element is the item and the right element is its weight.
* @param <T> The type of items in the list.
* @return A randomly selected item from the list, or null if no item was selected.
*/
public static <T> T getRandom(List<Pair<T, Double>> pairs) {
List<T> available = new ArrayList<>();
double[] weights = new double[pairs.size()];
int index = 0;
for (Pair<T, Double> pair : pairs){
double weight = pair.right();
T key = pair.left();
if (weight <= 0) continue;
available.add(key);
weights[index++] = weight;
}
return getRandom(weights, available, index);
}
/**
* Get a random item from a map where each entry is associated with a weight.
*
* @param map A map where each entry's key is an item, and the value is its weight.
* @param <T> The type of items in the map.
* @return A randomly selected item from the map, or null if no item was selected.
*/
public static <T> T getRandom(Map<T, Double> map) {
List<T> available = new ArrayList<>();
double[] weights = new double[map.size()];
int index = 0;
for (Map.Entry<T, Double> entry : map.entrySet()){
double weight = entry.getValue();
T key = entry.getKey();
if (weight <= 0) continue;
available.add(key);
weights[index++] = weight;
}
return getRandom(weights, available, index);
}
/**
* Get a random item from a list of items with associated weights.
*
* @param weights An array of weights corresponding to the available items.
* @param available A list of available items.
* @param effectiveSize The effective size of the array and list after filtering out items with non-positive weights.
* @param <T> The type of items.
* @return A randomly selected item from the list, or null if no item was selected.
*/
private static <T> T getRandom(double[] weights, List<T> available, int effectiveSize) {
if (available.isEmpty()) return null;
double total = Arrays.stream(weights).sum();
double[] weightRatios = new double[effectiveSize];
for (int i = 0; i < effectiveSize; i++){
weightRatios[i] = weights[i]/total;
}
double[] weightRange = new double[effectiveSize];
double startPos = 0;
for (int i = 0; i < effectiveSize; i++) {
weightRange[i] = startPos + weightRatios[i];
startPos += weightRatios[i];
}
double random = Math.random();
int pos = Arrays.binarySearch(weightRange, random);
if (pos < 0) {
pos = -pos - 1;
}
if (pos < weightRange.length && random < weightRange[pos]) {
return available.get(pos);
}
return null;
}
}

View File

@@ -0,0 +1,27 @@
config=${config_version}
asm=${asm_version}
asm-commons=${asm_commons_version}
jar-relocator=${jar_relocator_version}
h2-driver=${h2_driver_version}
sqlite-driver=${sqlite_driver_version}
cloud-core=${cloud_core_version}
cloud-brigadier=${cloud_brigadier_version}
cloud-services=${cloud_services_version}
cloud-bukkit=${cloud_bukkit_version}
cloud-paper=${cloud_paper_version}
cloud-minecraft-extras=${cloud_minecraft_extras_version}
boosted-yaml=${boosted_yaml_version}
byte-buddy=${byte_buddy_version}
mongodb-driver-core=${mongodb_driver_version}
mariadb-java-client=${mariadb_driver_version}
mysql-connector-j=${mysql_driver_version}
hikari-cp=${hikari_version}
commons-pool=${commons_pool_version}
bstats-base=${bstats_version}
geantyref=${geantyref_version}
gson=${gson_version}
caffeine=${caffeine_version}
jedis=${jedis_version}
exp4j=${exp4j_version}
slf4j=${slf4j_version}
lz4-java=${lz4_version}