build: 更改项目结构

This commit is contained in:
2023-06-16 19:29:00 +08:00
parent 57357c6ae2
commit 20517e7207
61 changed files with 19 additions and 38 deletions

View File

@@ -0,0 +1,28 @@
package cn.hamster3.mc.plugin.core.common.api;
import net.kyori.adventure.platform.AudienceProvider;
import org.jetbrains.annotations.NotNull;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SuppressWarnings("unused")
public abstract class CoreAPI {
protected static CoreAPI instance;
public static CoreAPI getInstance() {
return instance;
}
@NotNull
public abstract AudienceProvider getAudienceProvider();
@NotNull
public abstract DataSource getDataSource();
@NotNull
public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
}

View File

@@ -0,0 +1,183 @@
package cn.hamster3.mc.plugin.core.common.data;
import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import com.google.gson.JsonObject;
import lombok.Data;
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.TextReplacementConfig;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.util.Ticks;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings({"unused", "UnusedReturnValue", "PatternValidation"})
@Data
public class DisplayMessage {
private Component message;
private Component actionbar;
private Title title;
/**
* <a href="https://minecraft.fandom.com/zh/wiki/Sounds.json">点击查看 minecraft 所有声音资源</a>
*/
private Sound sound;
public DisplayMessage() {
}
@NotNull
public static DisplayMessage fromJson(@NotNull JsonObject object) {
DisplayMessage displayMessage = new DisplayMessage();
if (object.has("message")) {
displayMessage.message = GsonComponentSerializer.gson().deserializeFromTree(object.get("message")).compact();
}
if (object.has("actionbar")) {
displayMessage.actionbar = GsonComponentSerializer.gson().deserializeFromTree(object.get("actionbar")).compact();
}
if (object.has("title")) {
displayMessage.title = CoreUtils.deserializeTitle(object.getAsJsonObject("title"));
}
if (object.has("sound")) {
displayMessage.sound = CoreUtils.deserializeSound(object.getAsJsonObject("sound"));
}
return displayMessage;
}
public void show(@NotNull Audience audience, @NotNull TextReplacementConfig... replacements) {
Component replacedMessage = message;
Component replacedActionbar = actionbar;
Title replacedTitle = title;
for (TextReplacementConfig replacement : replacements) {
if (replacedMessage != null) {
replacedMessage = replacedMessage.replaceText(replacement);
}
if (replacedActionbar != null) {
replacedActionbar = replacedActionbar.replaceText(replacement);
}
if (replacedTitle != null) {
replacedTitle = Title.title(
replacedTitle.title().replaceText(replacement).compact(),
replacedTitle.subtitle().replaceText(replacement).compact(),
replacedTitle.times()
);
}
}
if (replacedMessage != null) {
audience.sendMessage(replacedMessage);
}
if (replacedActionbar != null) {
audience.sendActionBar(replacedActionbar);
}
if (replacedTitle != null) {
audience.showTitle(replacedTitle);
}
if (sound != null) {
audience.playSound(sound);
}
}
@NotNull
public DisplayMessage setMessage(@NotNull String message) {
this.message = Component.text(message);
return this;
}
@NotNull
public DisplayMessage setActionbar(@NotNull String actionbar) {
this.actionbar = Component.text(actionbar);
return this;
}
@NotNull
public DisplayMessage setTitle(@Nullable String title, @Nullable String subtitle, int fadeIn, int stay, int fadeOut) {
this.title = Title.title(
title == null ? Component.empty() : Component.text(title),
subtitle == null ? Component.empty() : Component.text(subtitle),
Title.Times.times(
Ticks.duration(fadeIn),
Ticks.duration(stay),
Ticks.duration(fadeOut)
)
);
return this;
}
@NotNull
public DisplayMessage setSound(@NotNull String sound, float volume, float pitch) {
this.sound = Sound.sound(Key.key(sound), Sound.Source.MASTER, volume, pitch);
return this;
}
@NotNull
public DisplayMessage setSound(@NotNull String namespace, @NotNull String value, float volume, float pitch) {
this.sound = Sound.sound(Key.key(namespace, value), Sound.Source.MASTER, volume, pitch);
return this;
}
@NotNull
public DisplayMessage setSound(@NotNull String namespace, @NotNull String value, @NotNull Sound.Source source, float volume, float pitch) {
this.sound = Sound.sound(Key.key(namespace, value), source, volume, pitch);
return this;
}
@NotNull
public DisplayMessage replace(@NotNull TextReplacementConfig... replacements) {
DisplayMessage copy = copy();
if (copy.message != null) {
for (TextReplacementConfig replacement : replacements) {
copy.message = copy.message.replaceText(replacement).compact();
}
}
if (copy.actionbar != null) {
for (TextReplacementConfig replacement : replacements) {
copy.actionbar = copy.actionbar.replaceText(replacement).compact();
}
}
if (copy.title != null) {
for (TextReplacementConfig replacement : replacements) {
copy.title = Title.title(
title.title().replaceText(replacement).compact(),
title.subtitle().replaceText(replacement).compact(),
title.times()
);
}
}
return copy;
}
@NotNull
public JsonObject toJson() {
JsonObject object = new JsonObject();
if (message != null) {
object.add("message", GsonComponentSerializer.gson().serializeToTree(message));
}
if (actionbar != null) {
object.add("actionbar", GsonComponentSerializer.gson().serializeToTree(actionbar));
}
if (title != null) {
object.add("title", CoreUtils.serializeTitle(title));
}
if (sound != null) {
object.add("sound", CoreUtils.serializeSound(sound));
}
return object;
}
@NotNull
public DisplayMessage copy() {
DisplayMessage copy = new DisplayMessage();
copy.message = message;
copy.actionbar = actionbar;
copy.title = title;
copy.sound = sound;
return copy;
}
@Override
public String toString() {
return toJson().toString();
}
}

View File

@@ -0,0 +1,84 @@
package cn.hamster3.mc.plugin.core.common.thread;
import cn.hamster3.mc.plugin.core.common.util.CoreUtils;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unused")
public abstract class CountdownThread implements Runnable {
private final long interval;
private final long totalTicks;
private int nowTicks;
private ScheduledFuture<?> future;
public CountdownThread(long interval, long totalTicks) {
this.interval = interval;
this.totalTicks = totalTicks;
nowTicks = 0;
}
public void start() {
start(interval);
}
public void start(long initialDelay) {
future = CoreUtils.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(this, initialDelay, interval, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
if (nowTicks == totalTicks) {
try {
onFinish();
} catch (Exception e) {
e.printStackTrace();
}
cancel();
return;
}
try {
onTick(nowTicks);
} catch (Exception e) {
e.printStackTrace();
}
nowTicks++;
}
/**
* 计时器运行一个 tick
*
* @param tick 时间刻度,以 0 开始,到 interval-1 结束
*/
protected abstract void onTick(int tick);
protected abstract void onFinish();
public void cancel() {
future.cancel(false);
}
public long getInterval() {
return interval;
}
public long getTotalTicks() {
return totalTicks;
}
public int getNowTicks() {
return nowTicks;
}
public void setNowTicks(int nowTicks) {
this.nowTicks = nowTicks;
}
public ScheduledFuture<?> getFuture() {
return future;
}
public void setFuture(ScheduledFuture<?> future) {
this.future = future;
}
}

View File

@@ -0,0 +1,21 @@
package cn.hamster3.mc.plugin.core.common.thread;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
public class NamedThreadFactory implements ThreadFactory {
private final String name;
private int threadID;
public NamedThreadFactory(String name) {
this.name = name;
threadID = 0;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
threadID++;
return new Thread(runnable, name + "#" + threadID);
}
}

View File

@@ -0,0 +1,204 @@
package cn.hamster3.mc.plugin.core.common.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Stack;
/**
* 算数表达式求值工具
* <p>
* 传入算数表达式,将返回一个浮点值结果
* <p>
* 如果计算过程错误将返回一个NaN
*/
@SuppressWarnings("unused")
public final class Calculator {
public static final Calculator INSTANCE = new Calculator();
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 16;
private final Stack<String> postfixStack = new Stack<>();// 后缀式栈
private final Stack<Character> opStack = new Stack<>();// 运算符栈
private final int[] operaPriority = new int[]{0, 3, 2, 1, -1, 1, 0, 2};// 运用运算符ASCII码-40做索引的运算符优先级
private Calculator() {
}
/**
* 按照给定的表达式计算
*
* @param expression 要计算的表达式例如:5+12*(3+5)/7
* @return 计算结果
*/
public double calculate(String expression) {
Stack<String> resultStack = new Stack<>();
prepare(expression);
Collections.reverse(postfixStack);// 将后缀式栈反转
String firstValue, secondValue, currentValue;// 参与计算的第一个值,第二个值和算术运算符
while (!postfixStack.isEmpty()) {
currentValue = postfixStack.pop();
if (!isOperator(currentValue.charAt(0))) {// 如果不是运算符则存入操作数栈中
currentValue = currentValue.replace("~", "-");
resultStack.push(currentValue);
} else {// 如果是运算符则从操作数栈中取两个值和该数值一起参与运算
secondValue = resultStack.pop();
firstValue = resultStack.pop();
// 将负数标记符改为负号
firstValue = firstValue.replace("~", "-");
secondValue = secondValue.replace("~", "-");
String tempResult = calculate(firstValue, secondValue, currentValue.charAt(0));
resultStack.push(tempResult);
}
}
return Double.parseDouble(resultStack.pop());
}
/**
* 数据准备阶段将表达式转换成为后缀式栈
*
* @param expression 表达式
*/
private void prepare(String expression) {
opStack.push(',');// 运算符放入栈底元素逗号,此符号优先级最低
char[] arr = expression.toCharArray();
int currentIndex = 0;// 当前字符的位置
int count = 0;// 上次算术运算符到本次算术运算符的字符的长度便于或者之间的数值
char currentOp, peekOp;// 当前操作符和栈顶操作符
for (int i = 0; i < arr.length; i++) {
currentOp = arr[i];
if (isOperator(currentOp)) {// 如果当前字符是运算符
if (count > 0) {
postfixStack.push(new String(arr, currentIndex, count));// 取两个运算符之间的数字
}
peekOp = opStack.peek();
if (currentOp == ')') {// 遇到反括号则将运算符栈中的元素移除到后缀式栈中直到遇到左括号
while (opStack.peek() != '(') {
postfixStack.push(String.valueOf(opStack.pop()));
}
opStack.pop();
} else {
while (currentOp != '(' && peekOp != ',' && compare(currentOp, peekOp)) {
postfixStack.push(String.valueOf(opStack.pop()));
peekOp = opStack.peek();
}
opStack.push(currentOp);
}
count = 0;
currentIndex = i + 1;
} else {
count++;
}
}
if (count > 1 || (count == 1 && !isOperator(arr[currentIndex]))) {// 最后一个字符不是括号或者其他运算符的则加入后缀式栈中
postfixStack.push(new String(arr, currentIndex, count));
}
while (opStack.peek() != ',') {
postfixStack.push(String.valueOf(opStack.pop()));// 将操作符栈中的剩余的元素添加到后缀式栈中
}
}
/**
* 判断是否为算术符号
*
* @param c 要判断的字符
* @return 判断结果
*/
private boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')';
}
/**
* 利用ASCII码-40做下标去算术符号优先级
*
* @param cur 字符1
* @param peek 字符2
* @return 如果是peek优先级高于cur返回true默认都是peek优先级要低
*/
private boolean compare(char cur, char peek) {
return operaPriority[(peek) - 40] >= operaPriority[(cur) - 40];
}
/**
* 按照给定的算术运算符做计算
*
* @param firstValue 第一个值
* @param secondValue 第二个值
* @param currentOp 算数运算符
* @return 运算结果
*/
private String calculate(String firstValue, String secondValue, char currentOp) {
String result = "";
switch (currentOp) {
case '+':
result = String.valueOf(add(firstValue, secondValue));
break;
case '-':
result = String.valueOf(sub(firstValue, secondValue));
break;
case '*':
result = String.valueOf(mul(firstValue, secondValue));
break;
case '/':
result = String.valueOf(div(firstValue, secondValue));
break;
}
return result;
}
/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
private double add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
private double sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
private double mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 小数点以后10位以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
private double div(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.divide(b2, DEF_DIV_SCALE, RoundingMode.HALF_UP).doubleValue();
}
}

View File

@@ -0,0 +1,165 @@
package cn.hamster3.mc.plugin.core.common.util;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import cn.hamster3.mc.plugin.core.common.util.serializer.ComponentTypeAdapter;
import cn.hamster3.mc.plugin.core.common.util.serializer.MessageTypeAdapter;
import cn.hamster3.mc.plugin.core.common.thread.NamedThreadFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.title.Title;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("unused")
public final class CoreUtils {
/**
* 异步线程
*/
public static final ExecutorService WORKER_EXECUTOR = Executors.newCachedThreadPool(new NamedThreadFactory("HamsterCore - Executor"));
/**
* 调度器线程
*/
public static final ScheduledExecutorService SCHEDULED_EXECUTOR = Executors
.newScheduledThreadPool(1, new NamedThreadFactory("HamsterCore - Scheduler"));
/**
* GSON 工具
*/
public static Gson GSON = new GsonBuilder()
.registerTypeAdapter(Component.class, ComponentTypeAdapter.INSTANCE)
.registerTypeAdapter(DisplayMessage.class, MessageTypeAdapter.INSTANCE)
.create();
/**
* GSON 工具会使用格式化输出、且解析中包含null参数
*/
public static Gson GSON_HUMAN = new GsonBuilder()
.registerTypeAdapter(Component.class, ComponentTypeAdapter.INSTANCE)
.registerTypeAdapter(DisplayMessage.class, MessageTypeAdapter.INSTANCE)
.serializeNulls()
.setPrettyPrinting()
.create();
private CoreUtils() {
}
public static void zipCompressionFolder(@NotNull File folder, @NotNull File zipFile) throws IOException {
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
putFileToZipStream(stream, "", folder);
}
}
public static void putFileToZipStream(@NotNull ZipOutputStream stream, @NotNull String path, @NotNull File file) throws IOException {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null) {
throw new IOException();
}
for (File subFile : files) {
putFileToZipStream(stream, path + file.getName() + "/", subFile);
}
return;
}
ZipEntry entry = new ZipEntry(path + file.getName());
stream.putNextEntry(entry);
stream.write(Files.readAllBytes(file.toPath()));
stream.closeEntry();
}
/**
* 将 adventure 中的 title 对象序列化为 json
*
* @param title -
* @return -
*/
@NotNull
public static JsonObject serializeTitle(@NotNull Title title) {
JsonObject object = new JsonObject();
object.add("title", GsonComponentSerializer.gson().serializeToTree(title.title()));
object.add("subtitle", GsonComponentSerializer.gson().serializeToTree(title.subtitle()));
Title.Times times = title.times();
if (times != null) {
object.add("times", serializeTitleTimes(times));
}
return object;
}
/**
* 将 json 反序列化为 adventure 中的 title
*
* @param object -
* @return -
*/
@NotNull
public static Title deserializeTitle(@NotNull JsonObject object) {
if (object.has("times")) {
return Title.title(
GsonComponentSerializer.gson().deserializeFromTree(object.get("title")),
GsonComponentSerializer.gson().deserializeFromTree(object.get("subtitle")),
deserializeTitleTimes(object.getAsJsonObject("times"))
);
} else {
return Title.title(
GsonComponentSerializer.gson().deserializeFromTree(object.get("title")),
GsonComponentSerializer.gson().deserializeFromTree(object.get("subtitle"))
);
}
}
@NotNull
public static JsonObject serializeTitleTimes(@NotNull Title.Times times) {
JsonObject object = new JsonObject();
object.addProperty("fadeIn", times.fadeIn().toMillis());
object.addProperty("stay", times.stay().toMillis());
object.addProperty("fadeOut", times.fadeOut().toMillis());
return object;
}
@NotNull
public static Title.Times deserializeTitleTimes(@NotNull JsonObject object) {
return Title.Times.times(
Duration.ofMillis(object.get("fadeIn").getAsLong()),
Duration.ofMillis(object.get("stay").getAsLong()),
Duration.ofMillis(object.get("fadeOut").getAsLong())
);
}
@NotNull
public static JsonObject serializeSound(@NotNull Sound sound) {
JsonObject object = new JsonObject();
object.addProperty("key", sound.name().asString());
object.addProperty("source", sound.source().name());
object.addProperty("volume", sound.volume());
object.addProperty("pitch", sound.pitch());
return object;
}
@NotNull
@SuppressWarnings("PatternValidation")
public static Sound deserializeSound(@NotNull JsonObject object) {
return Sound.sound(
Key.key(object.get("key").getAsString()),
Sound.Source.valueOf(object.get("source").getAsString()),
object.get("volume").getAsFloat(),
object.get("pitch").getAsFloat()
);
}
@SuppressWarnings("unchecked")
public static <T> T caseObject(Object o) {
return (T) o;
}
}

View File

@@ -0,0 +1,36 @@
package cn.hamster3.mc.plugin.core.common.util;
import java.io.Serializable;
import java.util.Objects;
@SuppressWarnings("unused")
public class Pair<K, V> implements Serializable {
private final K key;
private final V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
return Objects.equals(key, pair.key) && Objects.equals(value, pair.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
}

View File

@@ -0,0 +1,24 @@
package cn.hamster3.mc.plugin.core.common.util.serializer;
import com.google.gson.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import java.lang.reflect.Type;
public class ComponentTypeAdapter implements JsonSerializer<Component>, JsonDeserializer<Component> {
public static final ComponentTypeAdapter INSTANCE = new ComponentTypeAdapter();
private ComponentTypeAdapter() {
}
@Override
public Component deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return GsonComponentSerializer.gson().deserializeFromTree(json);
}
@Override
public JsonElement serialize(Component src, Type typeOfSrc, JsonSerializationContext context) {
return GsonComponentSerializer.gson().serializeToTree(src);
}
}

View File

@@ -0,0 +1,23 @@
package cn.hamster3.mc.plugin.core.common.util.serializer;
import cn.hamster3.mc.plugin.core.common.data.DisplayMessage;
import com.google.gson.*;
import java.lang.reflect.Type;
public class MessageTypeAdapter implements JsonSerializer<DisplayMessage>, JsonDeserializer<DisplayMessage> {
public static final MessageTypeAdapter INSTANCE = new MessageTypeAdapter();
private MessageTypeAdapter() {
}
@Override
public DisplayMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return DisplayMessage.fromJson(json.getAsJsonObject());
}
@Override
public JsonElement serialize(DisplayMessage src, Type typeOfSrc, JsonSerializationContext context) {
return src.toJson();
}
}