init: 项目初始化

项目初始化
This commit is contained in:
2022-08-06 01:15:48 +08:00
commit 75b06aae41
33 changed files with 2610 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.gradle
.idea
build

65
build.gradle Normal file
View File

@@ -0,0 +1,65 @@
plugins {
id 'java'
}
group 'cn.hamster3.mc'
version '1.0.0-SNAPSHOT'
subprojects {
apply plugin: 'java-library'
apply plugin: 'maven-publish'
group = rootProject.group
version = rootProject.version
repositories {
maven {
url = "https://maven.airgame.net/maven-public/"
}
}
dependencies {
compileOnly group: 'org.jetbrains', name: 'annotations', version: '21.0.1'
}
tasks.withType(JavaCompile) {
options.setEncoding("UTF-8")
}
tasks.withType(Jar) {
from([rootProject.file("LICENSE")])
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
// withJavadocJar()
withSourcesJar()
}
jar {
destinationDir(rootProject.buildDir)
}
publishing {
publications {
mavenJava(MavenPublication) {
from getProject().getComponents().java
}
}
repositories {
maven {
def releasesRepoUrl = 'https://maven.airgame.net/maven-releases/'
def snapshotsRepoUrl = 'https://maven.airgame.net/maven-snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username rootProject.getProperties().getOrDefault("maven_username", "")
password rootProject.getProperties().getOrDefault("maven_password", "")
}
}
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,62 @@
setArchivesBaseName("HamsterCore-Bukkit")
evaluationDependsOn(':hamster-core-common')
configurations {
api.extendsFrom apiShade
implementation.extendsFrom implementationShade
}
dependencies {
compileOnly project(":hamster-core-common")
compileOnly 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT'
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
//noinspection GradlePackageUpdate
apiShade 'com.zaxxer:HikariCP:4.0.3'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
apiShade 'com.squareup.okhttp3:okhttp:4.10.0'
// https://mvnrepository.com/artifact/net.kyori/adventure-platform-bukkit
apiShade 'net.kyori:adventure-platform-bukkit:4.1.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
}
test {
useJUnitPlatform()
}
processResources {
inputs.property "version", project.version
filesMatching("plugin.yml") {
expand "version": project.version
}
}
task addSource {
doLast {
sourceSets {
main {
java.srcDirs += [
project(':hamster-core-common').sourceSets.main.java
]
resources.srcDirs += [
project(':hamster-core-common').sourceSets.main.resources
]
}
}
}
}
classes.dependsOn(addSource)
jar {
from([
configurations.implementationShade.collect {
it.isDirectory() ? it : zipTree(it)
},
configurations.apiShade.collect {
it.isDirectory() ? it : zipTree(it)
}
])
}

View File

@@ -0,0 +1,49 @@
package cn.hamster3.core.bukkit;
import cn.hamster3.core.bukkit.page.listener.PageListener;
import cn.hamster3.core.common.constant.ConstantObjects;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class HamsterCorePlugin extends JavaPlugin {
private static HamsterCorePlugin instance;
public static HamsterCorePlugin getInstance() {
return instance;
}
public static void sync(Runnable runnable) {
Bukkit.getScheduler().runTask(instance, runnable);
}
public static void async(Runnable runnable) {
Bukkit.getScheduler().runTaskAsynchronously(instance, runnable);
}
@Override
public void onLoad() {
instance = this;
}
@Override
public void onEnable() {
long start = System.currentTimeMillis();
getLogger().info("仓鼠核心正在启动...");
Bukkit.getPluginManager().registerEvents(PageListener.INSTANCE, this);
getLogger().info("已注册 PageListener.");
long time = System.currentTimeMillis() - start;
getLogger().info("仓鼠核心已启动,总计耗时 " + time + " ms.");
}
@Override
public void onDisable() {
long start = System.currentTimeMillis();
getLogger().info("仓鼠核心正在关闭...");
ConstantObjects.WORKER_EXECUTOR.shutdownNow();
getLogger().info("已暂停 WORKER_EXECUTOR.");
ConstantObjects.SCHEDULED_EXECUTOR.shutdownNow();
getLogger().info("已暂停 SCHEDULED_EXECUTOR.");
long time = System.currentTimeMillis() - start;
getLogger().info("仓鼠核心已关闭,总计耗时 " + time + " ms.");
}
}

View File

@@ -0,0 +1,11 @@
package cn.hamster3.core.bukkit.api;
import cn.hamster3.core.common.api.CommonAPI;
@SuppressWarnings("unused")
public class BukkitAPI extends CommonAPI {
public static BukkitAPI getInstance() {
return (BukkitAPI) instance;
}
}

View File

@@ -0,0 +1,184 @@
package cn.hamster3.core.bukkit.page;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@SuppressWarnings("unused")
public class ButtonGroup {
private final String name;
private final PageConfig config;
private final HashMap<Character, String> buttonNameMap;
/**
* 实例化这个按钮组
*
* @param pageConfig Page 设定
* @param config 按钮组设定
*/
public ButtonGroup(PageConfig pageConfig, ConfigurationSection config) {
this.config = pageConfig;
name = config.getName();
buttonNameMap = new HashMap<>();
for (String key : config.getKeys(false)) {
buttonNameMap.put(key.charAt(0), config.getString(key));
}
}
/**
* 以图形字符来获取按钮名称
* <p>
* 若未找到则返回 "empty"
*
* @param graphicKey 图形字符
* @return 按钮名称
*/
@NotNull
public String getButtonName(char graphicKey) {
return buttonNameMap.getOrDefault(graphicKey, "empty");
}
/**
* 以索引位置来获取按钮名称
* <p>
* 若未找到则返回 "empty"
*
* @param index 索引位置
* @return 按钮名称
*/
@NotNull
public String getButtonName(int index) {
return buttonNameMap.getOrDefault(config.getButtonKey(index), "empty");
}
/**
* 获取按钮在 GUI 中的第一个索引位置
*
* @param buttonName 按钮名称
* @return 按钮在 GUI 中的第一个索引位置
*/
public int getButtonIndex(String buttonName) {
Character graphicKey = getGraphicKey(buttonName);
if (graphicKey == null) {
return -1;
}
List<String> graphic = config.getGraphic();
for (int i = 0; i < graphic.size(); i++) {
char[] chars = graphic.get(i).toCharArray();
for (int j = 0; j < chars.length; j++) {
if (chars[j] == graphicKey) {
return i * 9 + j;
}
}
}
return -1;
}
/**
* 获得按钮在 GUI 中全部的索引位置
*
* @param buttonName 按钮名称
* @return 按钮在 GUI 中全部的索引位置
*/
public ArrayList<Integer> getButtonAllIndex(String buttonName) {
ArrayList<Integer> list = new ArrayList<>();
Character graphicKey = getGraphicKey(buttonName);
if (graphicKey == null) {
return list;
}
List<String> graphic = config.getGraphic();
for (int i = 0; i < graphic.size(); i++) {
char[] chars = graphic.get(i).toCharArray();
for (int j = 0; j < chars.length; j++) {
if (chars[j] == graphicKey) {
list.add(i * 9 + j);
}
}
}
return list;
}
/**
* 以按钮名称来获取图形字符
*
* @param buttonName 按钮名称
* @return 图形中的字符
*/
@Nullable
public Character getGraphicKey(String buttonName) {
for (Map.Entry<Character, String> entry : buttonNameMap.entrySet()) {
if (entry.getValue().equalsIgnoreCase(buttonName)) {
return entry.getKey();
}
}
return null;
}
/**
* 以图形字符来获取按钮物品
*
* @param graphicKey 图形字符
* @return 按钮物品
*/
public ItemStack getButton(char graphicKey) {
return getButton(getButtonName(graphicKey));
}
/**
* 以按钮名称来获取按钮物品
*
* @param buttonName 按钮名称
* @return 按钮物品
*/
public ItemStack getButton(String buttonName) {
ItemStack stack = config.getButtons().get(buttonName);
if (stack != null) {
stack = stack.clone();
}
return stack;
}
/**
* 获取这个按钮组的名称
*
* @return 按钮组名称
*/
public String getName() {
return name;
}
/**
* 获取把图形字符映射到按钮名称的表
*
* @return 把图形字符映射到按钮名称的表
*/
public HashMap<Character, String> getButtonNameMap() {
return buttonNameMap;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ButtonGroup)) return false;
ButtonGroup that = (ButtonGroup) o;
return name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "ButtonGroup{" +
"name='" + name + '\'' +
", buttonNameMap=" + buttonNameMap +
'}';
}
}

View File

@@ -0,0 +1,195 @@
package cn.hamster3.core.bukkit.page;
import cn.hamster3.core.bukkit.HamsterCorePlugin;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public class PageConfig implements InventoryHolder {
@NotNull
private final Plugin plugin;
@NotNull
private final ConfigurationSection config;
@NotNull
private final String title;
@NotNull
private final List<String> graphic;
@NotNull
private final ArrayList<ButtonGroup> buttonGroups;
@NotNull
private final HashMap<String, Sound> buttonSounds;
@NotNull
private final HashMap<String, ItemStack> buttons;
@NotNull
private final Inventory inventory;
public PageConfig(@NotNull Plugin plugin, @NotNull ConfigurationSection config) {
this.plugin = plugin;
this.config = config;
title = config.getString("title", "").replace("&", "§");
List<String> graphicString = config.getStringList("graphic");
if (graphicString.size() > 6) {
graphicString = graphicString.subList(0, 6);
}
for (int i = 0; i < graphicString.size(); i++) {
String s = graphicString.get(i);
if (s.length() > 9) {
s = s.substring(0, 9);
}
graphicString.set(i, s);
}
graphic = graphicString;
inventory = Bukkit.createInventory(this, graphicString.size() * 9, title);
buttons = new HashMap<>();
ConfigurationSection buttonsConfig = config.getConfigurationSection("buttons");
for (String key : buttonsConfig.getKeys(false)) {
buttons.put(key, buttonsConfig.getItemStack(key));
}
buttonGroups = new ArrayList<>();
ConfigurationSection buttonGroupsConfig = config.getConfigurationSection("groups");
for (String key : buttonGroupsConfig.getKeys(false)) {
buttonGroups.add(
new ButtonGroup(this, buttonGroupsConfig.getConfigurationSection(key))
);
}
ButtonGroup group = getButtonGroup("default");
for (int i = 0; i < graphicString.size(); i++) {
char[] chars = graphicString.get(i).toCharArray();
for (int j = 0; j < chars.length; j++) {
char c = chars[j];
int index = i * 9 + j;
inventory.setItem(index, group.getButton(c));
}
}
buttonSounds = new HashMap<>();
ConfigurationSection buttonSoundConfig = config.getConfigurationSection("sounds");
for (String key : buttonSoundConfig.getKeys(false)) {
try {
buttonSounds.put(key, Sound.valueOf(buttonSoundConfig.getString(key)));
} catch (IllegalArgumentException e) {
HamsterCorePlugin.getInstance().getLogger().warning("初始化 PageConfig 时遇到了一个异常:");
e.printStackTrace();
}
}
}
/**
* 获取把 buttonName 映射到 展示物品 的表
*
* @return 把 buttonName 映射到 展示物品 的表
*/
@NotNull
public HashMap<String, ItemStack> getButtons() {
return buttons;
}
/**
* 获取索引位置上的 graphicKey
*
* @param index 索引
* @return 若超出 graphic 范围则返回 null
*/
@Nullable
public Character getButtonKey(int index) {
if (index < 0) return null;
if (index / 9 >= graphic.size()) return null;
String s = graphic.get(index / 9);
return s.charAt(index % 9);
}
/**
* 获取该显示物品对应的 buttonName
*
* @param stack 显示物品
* @return 按钮名称,若无法找到则返回 "empty"
*/
@NotNull
public String getButtonName(@Nullable ItemStack stack) {
if (stack == null) {
return "empty";
}
for (Map.Entry<String, ItemStack> entry : buttons.entrySet()) {
if (entry.getValue().isSimilar(stack)) {
return entry.getKey();
}
}
return "empty";
}
@Nullable
public Sound getButtonSound(@NotNull String buttonName) {
return buttonSounds.get(buttonName);
}
@NotNull
public ButtonGroup getButtonGroup(@NotNull String groupName) {
for (ButtonGroup group : buttonGroups) {
if (group.getName().equalsIgnoreCase(groupName)) {
return group;
}
}
return buttonGroups.get(0);
}
@NotNull
public Plugin getPlugin() {
return plugin;
}
@NotNull
public ConfigurationSection getConfig() {
return config;
}
@NotNull
public String getTitle() {
return title;
}
@NotNull
public List<String> getGraphic() {
return graphic;
}
@NotNull
public ArrayList<ButtonGroup> getButtonGroups() {
return buttonGroups;
}
@NotNull
@Override
public Inventory getInventory() {
return inventory;
}
@Override
public String toString() {
return "PageConfig{" +
", title='" + title + '\'' +
", graphic=" + graphic +
", buttonMap=" + buttons +
", buttonGroups=" + buttonGroups +
'}';
}
}

View File

@@ -0,0 +1,133 @@
package cn.hamster3.core.bukkit.page;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unused")
public interface PageElement {
/**
* 获取展示物品
* <p>
* 若返回 null 则使用 config 中的全局设置值
*
* @param player 占位符显示的目标玩家
* @return 展示物品
*/
default ItemStack getDisplayItem(HumanEntity player) {
return null;
}
/**
* 获取展示物品的显示材质
* <p>
* 若返回 null 则使用 config 中的全局设置值
*
* @param player 占位符显示的目标玩家
* @return 展示物品的显示材质
*/
default Material getMaterial(HumanEntity player) {
return null;
}
/**
* 替换物品的信息
*
* @param player 玩家
* @param stack 物品
* @param globalVariable 全局变量
*/
default void replaceItemInfo(HumanEntity player, ItemStack stack, HashMap<String, String> globalVariable) {
if (stack == null) {
return;
}
Material type = getMaterial(player);
if (type != null) {
stack.setType(type);
}
ItemMeta meta = stack.getItemMeta();
replaceMetaInfo(player, meta, globalVariable);
stack.setItemMeta(meta);
}
/**
* 替换物品的信息
*
* @param player 玩家
* @param meta 物品信息
* @param globalVariable 全局变量
*/
default void replaceMetaInfo(HumanEntity player, ItemMeta meta, HashMap<String, String> globalVariable) {
if (meta == null) {
return;
}
Map<String, String> replacer = getVariable(player, globalVariable);
if (meta.hasDisplayName()) {
String displayName = replaceDisplayName(player, meta.getDisplayName(), globalVariable);
meta.setDisplayName(displayName);
}
List<String> lore = meta.getLore();
if (lore != null) {
lore = replaceLore(player, lore, globalVariable);
meta.setLore(lore);
}
}
/**
* 替换物品展示名称
*
* @param player 玩家
* @param displayName 物品原名称
* @param globalVariable 全局变量
* @return 替换后的物品展示名称
*/
default String replaceDisplayName(HumanEntity player, String displayName, HashMap<String, String> globalVariable) {
return replacePlaceholder(player, displayName, globalVariable);
}
/**
* 替换物品 lore 信息
*
* @param player 玩家
* @param lore 物品原 lore 信息
* @param globalReplacer 全局变量
* @return 替换后的物品 lore 信息
*/
default List<String> replaceLore(HumanEntity player, List<String> lore, HashMap<String, String> globalReplacer) {
if (lore == null) {
return null;
}
Map<String, String> replacer = getVariable(player, globalReplacer);
for (int i = 0; i < lore.size(); i++) {
String s = lore.get(i);
for (Map.Entry<String, String> entry : replacer.entrySet()) {
s = s.replace(entry.getKey(), entry.getValue());
}
lore.set(i, s);
}
return lore;
}
default String replacePlaceholder(HumanEntity player, String string, HashMap<String, String> globalVariable) {
Map<String, String> replacer = getVariable(player, globalVariable);
for (Map.Entry<String, String> entry : replacer.entrySet()) {
string = string.replace(entry.getKey(), entry.getValue());
}
return string;
}
default Map<String, String> getVariable(HumanEntity player, HashMap<String, String> globalVariable) {
return globalVariable;
}
}

View File

@@ -0,0 +1,51 @@
package cn.hamster3.core.bukkit.page;
import cn.hamster3.core.bukkit.HamsterCorePlugin;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
public abstract class PageManager {
private static final HashMap<String, PageConfig> PAGE_CONFIG = new HashMap<>();
public static PageConfig getPageConfig(Class<?> clazz) {
PageConfig pageConfig = PAGE_CONFIG.get(clazz.getName());
if (pageConfig != null) {
return pageConfig;
}
if (!clazz.isAnnotationPresent(PluginPage.class)) {
throw new IllegalArgumentException(clazz.getName() + " 未被 @PluginPage 注解修饰!");
}
PluginPage annotation = clazz.getAnnotation(PluginPage.class);
String value = annotation.value();
Plugin plugin = Bukkit.getPluginManager().getPlugin(value);
File pageFolder = new File(plugin.getDataFolder(), "pages");
if (pageFolder.mkdirs()) {
HamsterCorePlugin.getInstance().getLogger().info("" + value + " 创建页面配置文件夹...");
}
String pageFileName = clazz.getSimpleName() + ".yml";
File pageFile = new File(pageFolder, pageFileName);
if (pageFile.exists()) {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(pageFile);
pageConfig = new PageConfig(plugin, configuration);
PAGE_CONFIG.put(clazz.getName(), pageConfig);
return pageConfig;
}
try {
Files.copy(plugin.getResource("/" + pageFileName), pageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(pageFile);
pageConfig = new PageConfig(plugin, configuration);
PAGE_CONFIG.put(clazz.getName(), pageConfig);
return pageConfig;
}
}

View File

@@ -0,0 +1,15 @@
package cn.hamster3.core.bukkit.page;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PluginPage {
/**
* @return 插件名称
*/
String value();
}

View File

@@ -0,0 +1,50 @@
package cn.hamster3.core.bukkit.page.handler;
import cn.hamster3.core.bukkit.page.ButtonGroup;
import cn.hamster3.core.bukkit.page.PageConfig;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.Inventory;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* 固定页面的 GUI
*/
@SuppressWarnings("unused")
public class FixedPageHandler extends PageHandler {
public FixedPageHandler(@NotNull HumanEntity player) {
super(player);
}
public FixedPageHandler(@NotNull HumanEntity player, @NotNull String title) {
super(player, title);
}
public FixedPageHandler(@NotNull PageConfig pageConfig, @NotNull HumanEntity player) {
super(pageConfig, player);
}
public FixedPageHandler(@NotNull PageConfig pageConfig, @NotNull String title, @NotNull HumanEntity player) {
super(pageConfig, title, player);
}
@Override
public void initPage() {
HumanEntity player = getPlayer();
Inventory inventory = getInventory();
PageConfig config = getPageConfig();
ButtonGroup group = getButtonGroup();
List<String> graphic = config.getGraphic();
for (int i = 0; i < graphic.size(); i++) {
char[] chars = graphic.get(i).toCharArray();
for (int j = 0; j < chars.length; j++) {
char c = chars[j];
int index = i * 9 + j;
inventory.setItem(index, group.getButton(c));
}
}
}
}

View File

@@ -0,0 +1,130 @@
package cn.hamster3.core.bukkit.page.handler;
import cn.hamster3.core.bukkit.HamsterCorePlugin;
import cn.hamster3.core.bukkit.page.ButtonGroup;
import cn.hamster3.core.bukkit.page.PageConfig;
import cn.hamster3.core.bukkit.page.PageManager;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.*;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.jetbrains.annotations.NotNull;
/**
* GUI 处理类
*/
@SuppressWarnings("unused")
public abstract class PageHandler implements InventoryHolder {
private final PageConfig pageConfig;
private final HumanEntity player;
private final Inventory inventory;
public PageHandler(@NotNull HumanEntity player) {
try {
pageConfig = PageManager.getPageConfig(getClass());
} catch (Exception e) {
throw new IllegalArgumentException("加载界面配置时遇到了一个异常!", e);
}
this.player = player;
inventory = Bukkit.createInventory(this, pageConfig.getInventory().getSize(), pageConfig.getTitle());
}
public PageHandler(@NotNull HumanEntity player, @NotNull String title) {
try {
pageConfig = PageManager.getPageConfig(getClass());
} catch (Exception e) {
throw new IllegalArgumentException("加载界面配置时遇到了一个异常!", e);
}
this.player = player;
inventory = Bukkit.createInventory(this, pageConfig.getInventory().getSize(), title);
}
public PageHandler(@NotNull PageConfig pageConfig, @NotNull HumanEntity player) {
this(pageConfig, pageConfig.getTitle(), player);
}
public PageHandler(@NotNull PageConfig pageConfig, @NotNull String title, @NotNull HumanEntity player) {
this.pageConfig = pageConfig;
this.player = player;
inventory = Bukkit.createInventory(this, pageConfig.getInventory().getSize(), title);
}
public abstract void initPage();
public void onOpen(@NotNull InventoryOpenEvent event) {
}
public void onClick(@NotNull InventoryClickEvent event) {
if (event.getSlot() == event.getRawSlot()) {
return;
}
event.setCancelled(true);
}
public void onClickInside(@NotNull InventoryClickEvent event) {
event.setCancelled(true);
}
public void onClickInside(@NotNull ClickType clickType, @NotNull InventoryAction action, int index) {
}
public void onPlayButtonSound(@NotNull ClickType clickType, @NotNull InventoryAction action, int index) {
Sound sound = getPageConfig().getButtonSound(getButtonGroup().getButtonName(index));
if (sound == null) {
return;
}
if (!(player instanceof Player)) {
return;
}
((Player) player).playSound(player.getLocation(), sound, 1, 1);
}
public void onDrag(@NotNull InventoryDragEvent event) {
}
public void onDragInside(@NotNull InventoryDragEvent event) {
event.setCancelled(true);
}
public void onClose(@NotNull InventoryCloseEvent event) {
}
public void show() {
show(true);
}
public void show(boolean init) {
if (init) {
initPage();
}
HamsterCorePlugin.sync(() -> player.openInventory(getInventory()));
}
public void close() {
HamsterCorePlugin.sync(player::closeInventory);
}
@NotNull
public PageConfig getPageConfig() {
return pageConfig;
}
@NotNull
public HumanEntity getPlayer() {
return player;
}
@NotNull
public ButtonGroup getButtonGroup() {
return getPageConfig().getButtonGroup("default");
}
@NotNull
@Override
public Inventory getInventory() {
return inventory;
}
}

View File

@@ -0,0 +1,185 @@
package cn.hamster3.core.bukkit.page.handler;
import cn.hamster3.core.bukkit.page.ButtonGroup;
import cn.hamster3.core.bukkit.page.PageConfig;
import cn.hamster3.core.bukkit.page.PageElement;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 支持翻页的 GUI
*
* @param <E> 页面元素
*/
@SuppressWarnings("unused")
public abstract class PageableHandler<E extends PageElement> extends FixedPageHandler {
private String previewButtonName = "preview";
private String nextButtonName = "next";
private String barrierButtonName = "barrier";
private String elementButtonName = "element";
private int page;
private HashMap<Integer, E> elementSlot;
public PageableHandler(@NotNull HumanEntity player, int page) {
super(player);
this.page = page;
}
public PageableHandler(@NotNull HumanEntity player, @NotNull String title, int page) {
super(player, title);
this.page = page;
}
public PageableHandler(@NotNull PageConfig pageConfig, @NotNull HumanEntity player, int page) {
super(pageConfig, player);
this.page = page;
}
public PageableHandler(@NotNull PageConfig pageConfig, @NotNull String title, @NotNull HumanEntity player, int page) {
super(pageConfig, title, player);
this.page = page;
}
@NotNull
public abstract List<E> getPageElements();
public abstract void onClickElement(@NotNull ClickType clickType, @NotNull InventoryAction action, @NotNull E element);
@NotNull
public String getElementButtonName(@NotNull E element) {
return elementButtonName;
}
public void initElementButton(@NotNull E element, @NotNull ItemStack displayItem, HashMap<String, String> replacer) {
element.replaceItemInfo(getPlayer(), displayItem, replacer);
}
@Override
public void initPage() {
super.initPage();
List<E> elements = getPageElements();
ButtonGroup group = getButtonGroup();
Inventory inventory = getInventory();
HumanEntity player = getPlayer();
ArrayList<Integer> buttonIndexes = group.getButtonAllIndex(elementButtonName);
int pageSize = buttonIndexes.size(); // 一页有多少个按钮
elementSlot = new HashMap<>();
HashMap<String, String> replacer = getReplacer();
for (int i = 0; i < pageSize; i++) {
// 元素在当前 page 中的索引位置
int elementIndex = page * pageSize + i;
// 按钮在 GUI 中的索引位置
int buttonIndex = buttonIndexes.get(i);
if (elementIndex >= elements.size()) {
inventory.setItem(buttonIndex, null);
continue;
}
E element = elements.get(elementIndex);
elementSlot.put(buttonIndex, element);
ItemStack elementDisplayItem = element.getDisplayItem(player);
if (elementDisplayItem != null) {
elementDisplayItem = elementDisplayItem.clone();
element.replaceItemInfo(player, elementDisplayItem, replacer);
inventory.setItem(buttonIndex, elementDisplayItem);
continue;
}
ItemStack button = group.getButton(getElementButtonName(element));
if (button == null) {
inventory.setItem(buttonIndex, null);
continue;
}
ItemStack elementItem = button.clone();
initElementButton(element, elementItem, replacer);
inventory.setItem(buttonIndex, elementItem);
}
if (page == 0) {
// 如果页面已在首页则撤掉上一页按钮
inventory.setItem(group.getButtonIndex(previewButtonName), group.getButton(barrierButtonName));
}
if (elements.size() <= (page + 1) * pageSize) {
// 如果页面显示超出已有元素数量则撤掉下一页按钮
inventory.setItem(group.getButtonIndex(nextButtonName), group.getButton(barrierButtonName));
}
}
@Override
public void onClickInside(@NotNull InventoryClickEvent event) {
event.setCancelled(true);
int slot = event.getSlot();
E e = elementSlot.get(slot);
if (e != null) {
onClickElement(event.getClick(), event.getAction(), e);
return;
}
String name = getPageConfig().getButtonName(event.getCurrentItem());
if (name.equalsIgnoreCase(nextButtonName)) {
showNextPage();
return;
}
if (name.equalsIgnoreCase(previewButtonName)) {
showPreviewPage();
}
}
public void showPreviewPage() {
page--;
show();
}
public void showNextPage() {
page++;
show();
}
@NotNull
public HashMap<Integer, E> getElementSlot() {
return elementSlot;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public void setPreviewButtonName(String previewButtonName) {
this.previewButtonName = previewButtonName;
}
public void setNextButtonName(String nextButtonName) {
this.nextButtonName = nextButtonName;
}
public void setBarrierButtonName(String barrierButtonName) {
this.barrierButtonName = barrierButtonName;
}
public void setElementButtonName(String elementButtonName) {
this.elementButtonName = elementButtonName;
}
public HashMap<String, String> getReplacer() {
return new HashMap<>();
}
}

View File

@@ -0,0 +1,120 @@
package cn.hamster3.core.bukkit.page.listener;
import cn.hamster3.core.bukkit.HamsterCorePlugin;
import cn.hamster3.core.bukkit.page.handler.PageHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.inventory.Inventory;
/**
* GUI 监听器
*/
public class PageListener implements Listener {
public static final PageListener INSTANCE = new PageListener();
private PageListener() {
}
@EventHandler
public void onInventoryOpen(InventoryOpenEvent event) {
Inventory inventory = event.getView().getTopInventory();
if (!(inventory.getHolder() instanceof PageHandler)) {
return;
}
PageHandler pageHandler = (PageHandler) inventory.getHolder();
pageHandler.onOpen(event);
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
Inventory inventory = event.getView().getTopInventory();
if (!(inventory.getHolder() instanceof PageHandler)) {
return;
}
PageHandler pageHandler = (PageHandler) inventory.getHolder();
try {
pageHandler.onClick(event);
} catch (Exception e) {
HamsterCorePlugin.getInstance().getLogger().warning(String.format("执行 %s 的 onClick(event) 时遇到了一个异常: ", pageHandler.getClass().getName()));
e.printStackTrace();
}
if (event.isCancelled()) {
return;
}
int index = event.getRawSlot();
if (index < 0) {
return;
}
if (index != event.getSlot()) {
return;
}
try {
pageHandler.onClickInside(event);
} catch (Exception e) {
HamsterCorePlugin.getInstance().getLogger().warning(String.format("执行 %s 的 onClickInside(event) 时遇到了一个异常: ", pageHandler.getClass().getName()));
e.printStackTrace();
}
try {
pageHandler.onClickInside(event.getClick(), event.getAction(), index);
} catch (Exception e) {
HamsterCorePlugin.getInstance().getLogger().warning(String.format(
"执行 %s 的 onClickInside(%s, %s, %d) 时遇到了一个异常: ",
pageHandler.getClass().getName(),
event.getClick().name(),
event.getAction().name(),
index
));
e.printStackTrace();
}
try {
pageHandler.onPlayButtonSound(event.getClick(), event.getAction(), index);
} catch (Exception e) {
HamsterCorePlugin.getInstance().getLogger().warning(String.format(
"执行 %s 的 onPlayButtonSound(%s, %s, %d) 时遇到了一个异常: ",
pageHandler.getClass().getName(),
event.getClick().name(),
event.getAction().name(),
index
));
}
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
Inventory inventory = event.getView().getTopInventory();
if (!(inventory.getHolder() instanceof PageHandler)) {
return;
}
PageHandler pageHandler = (PageHandler) inventory.getHolder();
try {
pageHandler.onDrag(event);
} catch (Exception e) {
HamsterCorePlugin.getInstance().getLogger().warning(String.format("执行 %s 的 onDrag(event) 时遇到了一个异常: ", pageHandler.getClass().getName()));
e.printStackTrace();
}
if (event.isCancelled()) {
return;
}
int size = inventory.getSize();
for (Integer slot : event.getRawSlots()) {
if (slot < size) {
pageHandler.onDragInside(event);
break;
}
}
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
Inventory inventory = event.getView().getTopInventory();
if (!(inventory.getHolder() instanceof PageHandler)) {
return;
}
PageHandler pageHandler = (PageHandler) inventory.getHolder();
pageHandler.onClose(event);
}
}

View File

@@ -0,0 +1,194 @@
package cn.hamster3.core.bukkit.util;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@SuppressWarnings("unused")
public class BukkitUtils {
@NotNull
public static String getMCVersion() {
return Bukkit.getBukkitVersion().split("-")[0];
}
@NotNull
public static String getNMSVersion() {
return Bukkit.getServer().getClass().getName().split("\\.")[3];
}
@NotNull
public static Package getNMSPackage() {
String nmsVersion = getNMSVersion();
return Package.getPackage("net.minecraft.server." + nmsVersion);
}
@NotNull
public static Class<?> getNMSClass(@NotNull String className) throws ClassNotFoundException {
String nmsVersion = getNMSVersion();
return Class.forName("net.minecraft.server." + nmsVersion + "." + className);
}
/**
* 让玩家以最高权限执行命令
*
* @param player 玩家
* @param command 要执行的命令
*/
public static void opCommand(@NotNull Player player, @NotNull String command) {
boolean isOp = player.isOp();
player.setOp(true);
Bukkit.dispatchCommand(player, command);
player.setOp(isOp);
}
/**
* 获取玩家的头颅
* 在1.11以上的服务端中获取头颅材质是在服务器上运行的
* 因此建议使用异步线程调用该方法
*
* @param uuid 要获取的玩家
* @return 玩家的头颅物品
*/
@NotNull
public static ItemStack getPlayerHead(@NotNull UUID uuid) {
return getPlayerHead(Bukkit.getOfflinePlayer(uuid));
}
/**
* 获取玩家的头颅
* 在1.11以上的服务端中获取头颅材质是在服务器上运行的
* 因此建议使用异步线程调用该方法
*
* @param offlinePlayer 要获取的玩家
* @return 玩家的头颅物品
*/
@NotNull
public static ItemStack getPlayerHead(@NotNull OfflinePlayer offlinePlayer) {
ItemStack stack;
try {
stack = new ItemStack(Material.valueOf("PLAYER_HEAD"));
} catch (IllegalArgumentException e) {
stack = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3);
}
SkullMeta meta = (SkullMeta) stack.getItemMeta();
if (meta != null) {
meta.setOwningPlayer(offlinePlayer);
}
stack.setItemMeta(meta);
return stack;
}
/**
* 获取玩家的头颅
* 在1.11以上的服务端中获取头颅材质是在服务器上运行的
* 因此建议使用异步线程调用该方法
*
* @param name 要获取的玩家
* @return 玩家的头颅物品
*/
@NotNull
@SuppressWarnings("deprecation")
public static ItemStack getPlayerHead(@NotNull String name) {
ItemStack stack;
try {
stack = new ItemStack(Material.valueOf("PLAYER_HEAD"));
} catch (IllegalArgumentException e) {
stack = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3);
}
SkullMeta meta = (SkullMeta) stack.getItemMeta();
meta.setOwner(name);
stack.setItemMeta(meta);
return stack;
}
/**
* 判断物品是否为空
* 当对象为null时返回true
* 当物品的Material为AIR时返回true
* 当物品的数量小于1时返回true
*
* @param stack 物品
* @return 是否为空
*/
public static boolean isEmptyItemStack(ItemStack stack) {
return stack == null || stack.getType() == Material.AIR || stack.getAmount() < 1;
}
/**
* 给予玩家一个物品, 当玩家背包满时
* 将会在玩家附近生成这个物品的掉落物
*
* @param player 玩家
* @param stack 物品
*/
public static void giveItem(@NotNull HumanEntity player, @NotNull ItemStack stack) {
if (isEmptyItemStack(stack)) {
return;
}
World world = player.getWorld();
for (ItemStack dropItem : player.getInventory().addItem(stack).values()) {
world.dropItem(player.getLocation(), dropItem);
}
}
/**
* 获取物品的名称
* 当物品为null时返回"null"
* 当物品拥有DisplayName时返回DisplayName
* 否则返回物品的Material的name
*
* @param stack 物品
* @return 物品的名称
*/
@NotNull
public static String getItemName(ItemStack stack) {
if (stack == null) {
return "null";
}
if (stack.hasItemMeta()) {
ItemMeta meta = stack.getItemMeta();
if (meta.hasDisplayName()) {
return meta.getDisplayName();
}
}
return stack.getType().name();
}
/**
* 创建 SQL 连接池
*
* @param config SQL 配置
* @return SQL 连接池
*/
@NotNull
public static HikariDataSource getHikariDataSource(@NotNull ConfigurationSection config) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(config.getString("driver"));
hikariConfig.setJdbcUrl(config.getString("url"));
hikariConfig.setUsername(config.getString("user"));
hikariConfig.setPassword(config.getString("password"));
hikariConfig.setMaximumPoolSize(config.getInt("maximumPoolSize", 3));
hikariConfig.setMinimumIdle(config.getInt("minimumIdle", 1));
hikariConfig.setIdleTimeout(config.getLong("idleTimeout", 5 * 60 * 1000));
hikariConfig.setMaxLifetime(config.getLong("maxLifetime", 0));
return new HikariDataSource(hikariConfig);
}
// todo public static TextComponent getItemDisplayInfo(@NotNull ItemStack stack)
}

View File

@@ -0,0 +1,3 @@
name: HamsterCore
main: cn.hamster3.core.bukkit.HamsterCorePlugin
version: ${version}

View File

@@ -0,0 +1,29 @@
setArchivesBaseName("HamsterCore-Common")
dependencies {
// https://mvnrepository.com/artifact/com.google.code.gson/gson
//noinspection GradlePackageUpdate
compileOnly 'com.google.code.gson:gson:2.8.0'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
api 'com.squareup.okhttp3:okhttp:4.10.0'
// https://mvnrepository.com/artifact/net.kyori/adventure-api
api 'net.kyori:adventure-api:4.11.0'
// https://mvnrepository.com/artifact/net.kyori/adventure-text-minimessage
api 'net.kyori:adventure-text-minimessage:4.11.0'
// // https://mvnrepository.com/artifact/net.kyori/adventure-platform-api
// implementation 'net.kyori:adventure-platform-api:4.1.2'
// https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson
api 'net.kyori:adventure-text-serializer-gson:4.11.0'
// https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-legacy
api 'net.kyori:adventure-text-serializer-legacy:4.11.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
}
test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,17 @@
package cn.hamster3.core.common.api;
@SuppressWarnings("unused")
public abstract class CommonAPI {
protected static CommonAPI instance;
public static CommonAPI getInstance() {
return instance;
}
public void reportError(String apiKey, String projectID, Throwable exception) {
}
public void reportFile(String apiKey, String projectID, String filename) {
}
}

View File

@@ -0,0 +1,68 @@
package cn.hamster3.core.common.constant;
import cn.hamster3.core.common.data.Message;
import com.google.gson.*;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Type;
import java.util.concurrent.*;
public interface ConstantObjects {
/**
* GSON 工具
*/
Gson GSON = new GsonBuilder()
.registerTypeAdapter(Message.class, MessageTypeAdapter.INSTANCE)
.create();
/**
* GSON 工具会使用格式化输出、且解析中包含null参数
*/
Gson GSON_HUMAN = new GsonBuilder()
.registerTypeAdapter(Message.class, MessageTypeAdapter.INSTANCE)
.serializeNulls()
.setPrettyPrinting()
.create();
/**
* 调度器线程
*/
ScheduledExecutorService SCHEDULED_EXECUTOR = Executors.newScheduledThreadPool(1, new APIThreadFactory("HamsterCore - Scheduler"));
/**
* 异步线程
*/
ExecutorService WORKER_EXECUTOR = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 60, TimeUnit.MINUTES, new SynchronousQueue<>(), new APIThreadFactory("HamsterCore - Executor"));
class APIThreadFactory implements ThreadFactory {
private final String name;
private int threadID;
public APIThreadFactory(String name) {
this.name = name;
threadID = 0;
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
threadID++;
return new Thread(runnable, name + "#" + threadID);
}
}
class MessageTypeAdapter implements JsonSerializer<Message>, JsonDeserializer<Message> {
public static final MessageTypeAdapter INSTANCE = new MessageTypeAdapter();
private MessageTypeAdapter() {
}
@Override
public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new Message().json(json);
}
@Override
public JsonElement serialize(Message src, Type typeOfSrc, JsonSerializationContext context) {
return src.saveToJson();
}
}
}

View File

@@ -0,0 +1,220 @@
package cn.hamster3.core.common.data;
import cn.hamster3.core.common.constant.ConstantObjects;
import cn.hamster3.core.common.util.SerializeUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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")
public class Message {
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 Message() {
}
public void show(@NotNull Audience audience) {
if (message != null) {
audience.sendMessage(message);
}
if (actionbar != null) {
audience.sendActionBar(actionbar);
}
if (title != null) {
audience.showTitle(title);
}
if (sound != null) {
audience.playSound(sound);
}
}
public void show(@NotNull Audience audience, TextReplacementConfig replacement) {
if (message != null) {
audience.sendMessage(message.compact().replaceText(replacement));
}
if (actionbar != null) {
audience.sendActionBar(actionbar.replaceText(replacement));
}
if (title != null) {
audience.showTitle(Title.title(
title.title().replaceText(replacement),
title.subtitle().replaceText(replacement),
title.times()
));
}
if (sound != null) {
audience.playSound(sound);
}
}
public JsonObject saveToJson() {
JsonObject object = new JsonObject();
if (message != null) {
object.add("message", GsonComponentSerializer.gson().serializeToTree(message.compact()));
}
if (actionbar != null) {
object.add("actionbar", GsonComponentSerializer.gson().serializeToTree(actionbar.compact()));
}
if (title != null) {
object.add("title", SerializeUtils.serializeTitle(title));
}
if (sound != null) {
object.add("sound", SerializeUtils.serializeSound(sound));
}
return object;
}
@NotNull
public Message message(@NotNull String message) {
this.message = Component.text(message);
return this;
}
@NotNull
public Message message(@NotNull Component message) {
this.message = message;
return this;
}
@NotNull
public Message actionbar(@NotNull String message) {
this.actionbar = Component.text(message);
return this;
}
@NotNull
public Message actionbar(@NotNull Component message) {
this.actionbar = message;
return this;
}
@NotNull
public Message title(@NotNull String title, @NotNull String subtitle) {
this.title = Title.title(Component.text(title), Component.text(subtitle));
return this;
}
@NotNull
public Message title(@NotNull String title, @NotNull String subtitle, int fadeIn, int stay, int fadeOut) {
this.title = Title.title(
Component.text(title),
Component.text(subtitle),
Title.Times.times(
Ticks.duration(fadeIn),
Ticks.duration(stay),
Ticks.duration(fadeOut)
)
);
return this;
}
@NotNull
public Message title(@NotNull Component title, @NotNull Component subtitle) {
this.title = Title.title(title, subtitle);
return this;
}
@NotNull
public Message title(@NotNull Component title, @NotNull Component subtitle, int fadeIn, int stay, int fadeOut) {
this.title = Title.title(
title,
subtitle,
Title.Times.times(
Ticks.duration(fadeIn),
Ticks.duration(stay),
Ticks.duration(fadeOut)
)
);
return this;
}
@NotNull
public Message title(@Nullable Title title) {
this.title = title;
return this;
}
@NotNull
@SuppressWarnings("PatternValidation")
public Message sound(@NotNull String sound) {
this.sound = Sound.sound(Key.key(sound), Sound.Source.MASTER, 1, 1);
return this;
}
@NotNull
@SuppressWarnings("PatternValidation")
public Message sound(@NotNull String namespace, @NotNull String path) {
this.sound = Sound.sound(Key.key(namespace, path), Sound.Source.MASTER, 1, 1);
return this;
}
@NotNull
@SuppressWarnings("PatternValidation")
public Message sound(@NotNull String sound, float volume, float pitch) {
this.sound = Sound.sound(Key.key(sound), Sound.Source.MASTER, volume, pitch);
return this;
}
@NotNull
@SuppressWarnings("PatternValidation")
public Message sound(@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
@SuppressWarnings("PatternValidation")
public Message sound(@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 Message sound(@Nullable Sound sound) {
this.sound = sound;
return this;
}
@NotNull
@SuppressWarnings("UnusedReturnValue")
public Message json(@NotNull JsonElement element) {
if (!element.isJsonObject()) {
message = Component.text(element.toString());
return this;
}
JsonObject object = element.getAsJsonObject();
if (object.has("message")) {
message = GsonComponentSerializer.gson().deserializeFromTree(object.get("message"));
}
if (object.has("actionbar")) {
actionbar = GsonComponentSerializer.gson().deserializeFromTree(object.get("actionbar"));
}
if (object.has("title")) {
title = SerializeUtils.deserializeTitle(object.getAsJsonObject("title"));
}
if (object.has("sound")) {
sound = SerializeUtils.deserializeSound(object.getAsJsonObject("sound"));
}
return this;
}
@Override
public String toString() {
return ConstantObjects.GSON.toJson(this);
}
}

View File

@@ -0,0 +1,61 @@
package cn.hamster3.core.common.thread;
import cn.hamster3.core.common.constant.ConstantObjects;
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 ticks;
protected int tick;
private ScheduledFuture<?> future;
public CountdownThread(long interval, long ticks) {
this.interval = interval;
this.ticks = ticks;
tick = 0;
}
public void start() {
start(interval);
}
public void start(long initialDelay) {
future = ConstantObjects.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(this, initialDelay, interval, TimeUnit.MILLISECONDS);
}
@Override
public void run() {
if (tick == ticks) {
try {
onFinish();
} catch (Exception e) {
e.printStackTrace();
}
cancel();
return;
}
try {
onTick(tick);
} catch (Exception e) {
e.printStackTrace();
}
tick++;
}
/**
* 计时器运行一个 tick
*
* @param tick 时间刻度,以 0 开始,到 interval-1 结束
*/
protected void onTick(int tick) {
}
public void cancel() {
future.cancel(false);
}
protected abstract void onFinish();
}

View File

@@ -0,0 +1,204 @@
package cn.hamster3.core.common.util;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Stack;
/**
* 算数表达式求值
* 传入算数表达式,将返回一个浮点值结果
* 如果计算过程错误将返回一个NaN
* <p>
* 我也忘了这个类是哪里抄来的
* 反正它运行起来比直接用JavaScript引擎计算要快很多
*/
@SuppressWarnings("unused")
public class 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,9 @@
package cn.hamster3.core.common.util;
@SuppressWarnings("unused")
public final class CaseUtils {
@SuppressWarnings("unchecked")
public static <T> T caseObject(Object o) {
return (T) o;
}
}

View File

@@ -0,0 +1,35 @@
package cn.hamster3.core.common.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("unused")
public class FileUtils {
@SuppressWarnings("IOStreamConstructor")
public static void zipCompressionFolder(File folder, File zipFile) throws IOException {
ZipOutputStream stream = new ZipOutputStream(new FileOutputStream(zipFile));
putFileToZipStream(stream, "", folder);
stream.close();
}
public static void putFileToZipStream(ZipOutputStream stream, String path, 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();
}
}

View File

@@ -0,0 +1,81 @@
package cn.hamster3.core.common.util;
import cn.hamster3.core.common.constant.ConstantObjects;
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.time.Duration;
public class SerializeUtils {
@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;
}
@NotNull
public static Title deserializeTitle(@NotNull JsonObject object) {
if (object.has("times")) {
return Title.title(
ConstantObjects.GSON.fromJson(object.get("title"), Component.class),
ConstantObjects.GSON.fromJson(object.get("subtitle"), Component.class),
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()
);
}
}

View File

@@ -0,0 +1,63 @@
setArchivesBaseName("HamsterCore-Proxy")
evaluationDependsOn(':hamster-core-common')
configurations {
api.extendsFrom apiShade
implementation.extendsFrom implementationShade
}
dependencies {
compileOnly project(":hamster-core-common")
//noinspection GradlePackageUpdate
compileOnly('net.md-5:bungeecord-api:1.17-R0.1-SNAPSHOT')
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
//noinspection GradlePackageUpdate
apiShade 'com.zaxxer:HikariCP:4.0.3'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
apiShade 'com.squareup.okhttp3:okhttp:4.10.0'
// https://mvnrepository.com/artifact/net.kyori/adventure-platform-bungeecord
apiShade 'net.kyori:adventure-platform-bungeecord:4.1.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
}
test {
useJUnitPlatform()
}
processResources {
inputs.property "version", project.version
filesMatching("bungee.yml") {
expand "version": project.version
}
}
task addSource {
doLast {
sourceSets {
main {
java.srcDirs += [
project(':hamster-core-common').sourceSets.main.java
]
resources.srcDirs += [
project(':hamster-core-common').sourceSets.main.resources
]
}
}
}
}
classes.dependsOn(addSource)
jar {
from([
configurations.implementationShade.collect {
it.isDirectory() ? it : zipTree(it)
},
configurations.apiShade.collect {
it.isDirectory() ? it : zipTree(it)
}
])
}

View File

@@ -0,0 +1,37 @@
package cn.hamster3.core.proxy;
import cn.hamster3.core.common.constant.ConstantObjects;
import net.md_5.bungee.api.plugin.Plugin;
public class HamsterCorePlugin extends Plugin {
private static HamsterCorePlugin instance;
public static HamsterCorePlugin getInstance() {
return instance;
}
@Override
public void onLoad() {
instance = this;
}
@Override
public void onEnable() {
long start = System.currentTimeMillis();
getLogger().info("仓鼠核心正在启动...");
long time = System.currentTimeMillis() - start;
getLogger().info("仓鼠核心已启动,总计耗时 " + time + " ms.");
}
@Override
public void onDisable() {
long start = System.currentTimeMillis();
getLogger().info("仓鼠核心正在关闭...");
ConstantObjects.WORKER_EXECUTOR.shutdownNow();
getLogger().info("已暂停 WORKER_EXECUTOR.");
ConstantObjects.SCHEDULED_EXECUTOR.shutdownNow();
getLogger().info("已暂停 SCHEDULED_EXECUTOR.");
long time = System.currentTimeMillis() - start;
getLogger().info("仓鼠核心已关闭,总计耗时 " + time + " ms.");
}
}

View File

@@ -0,0 +1,3 @@
name: HamsterCore-Proxy
main: cn.hamster3.core.proxy.HamsterCorePlugin
version: ${version}

5
settings.gradle Normal file
View File

@@ -0,0 +1,5 @@
rootProject.name = 'hamster-core'
include 'hamster-core-common'
include 'hamster-core-bukkit'
include 'hamster-core-proxy'