This commit is contained in:
2024-03-28 01:14:05 +08:00
commit c4892410c4
34 changed files with 1902 additions and 0 deletions

117
.gitignore vendored Normal file
View File

@@ -0,0 +1,117 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/
# Gradle
.gradle/
build/

27
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,27 @@
# 使用的镜像
image: gradle:8.5-jdk17
# 阶段
stages:
- build
# 缓存路径配置
cache:
paths:
- .gradle
# 启用此流水线的条件
workflow:
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
before_script:
- chmod +x ./gradlew
# 配置build阶段的工作
package_job:
stage: build
tags:
- main
script:
- echo "Gradle building started..."
- ./gradlew shadowJar
artifacts:
paths:
- build/libs/*.jar

35
build.gradle.kts Normal file
View File

@@ -0,0 +1,35 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.github.johnrengelman.shadow") version "8.1.1"
kotlin("jvm") version "1.9.22"
}
group = "xyz.fortern"
version = "1.0"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
repositories {
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
// Paper API https://mvnrepository.com/artifact/net.kyori/adventure-api
compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
// Adventure API
compileOnly("net.kyori:adventure-api:4.14.0")
// Kotlin Stdlib https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib
implementation(kotlin("stdlib"))
// Kotlin Reflect https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
runtimeOnly(kotlin("reflect"))
}

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

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored Normal file
View File

@@ -0,0 +1,249 @@
#!/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/HEAD/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
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# 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
if ! command -v java >/dev/null 2>&1
then
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
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
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
# 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"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# 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" "$@"

92
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,92 @@
@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=.
@rem This is normally unused
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% equ 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% equ 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!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
settings.gradle.kts Normal file
View File

@@ -0,0 +1 @@
rootProject.name = "hello-paper"

View File

@@ -0,0 +1,185 @@
package xyz.fortern
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.plugin.PluginLogger
import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.scoreboard.Criteria
import org.bukkit.scoreboard.DisplaySlot
import org.bukkit.scoreboard.RenderType
import xyz.fortern.command.executor.AbstractCommandExecutor
import xyz.fortern.event.MyEventHandler
import xyz.fortern.event.OnTakeAwardListener
import xyz.fortern.event.PlayerMessageHandler
import xyz.fortern.recipe.MyCraftingRecipe
import xyz.fortern.recipe.MyFurnaceRecipe
import java.io.IOException
import java.net.JarURLConnection
import java.util.*
import java.util.logging.Logger
class HelloPaper : JavaPlugin() {
/**
* 日志记录器
*/
private val logger: Logger = PluginLogger(this)
override fun getLogger(): Logger = logger
/**
* 全局计分板
*/
val scoreboard by lazy {
Bukkit.getScoreboardManager().newScoreboard
}
companion object {
/**
* 此plugin对象的实例可以通过类名访问到
*/
@JvmStatic
private lateinit var instance: HelloPaper
/**
* 获取插件的实例
*/
@JvmStatic
fun getInstance() = instance
/**
* 侧边栏名称
*/
@JvmStatic
var SIDEBAR_NAME = "side-bar"
}
init {
instance = this
}
// Plugin startup logic
override fun onEnable() {
logger.info("Hello, Minecraft! by logger")
val javaVersion = System.getProperty("java.version")
val javafxVersion = System.getProperty("javafx.version")
logger.info("Hello, JavaFX ${javafxVersion}, running on Java ${javaVersion}.")
//注册事件处理器
//var pluginManager = getServer().getPluginManager()
val pluginManager = Bukkit.getPluginManager()
pluginManager.registerEvents(MyEventHandler(), this)
pluginManager.registerEvents(OnTakeAwardListener(), this)
pluginManager.registerEvents(PlayerMessageHandler(), this)
//注册所有的命令执行器
val classLoader = HelloPaper::class.java.classLoader
val packageName = "xyz/fortern/command/executor/impl"
val resource = classLoader.getResource(packageName)
if (resource != null) {
try {
val urls = getClassLoader().getResources(packageName)
while (urls.hasMoreElements()) {
val url = urls.nextElement()
val protocol = url.protocol
/*
* 为什么不要写成 "jar" == protocol
* Yoda 表示法错在哪里
* https://www.yinwang.org/blog-cn/2013/04/16/yoda-notation
* 另一层问题,以人的阅读习惯,应当左侧为变量,右侧为常量,比如"他是小明吗""他"是一个变量,"小明"是个常量,而"小明是他吗"就会很奇怪
*/
if (protocol == "jar") {
val jarURLConnection = url.openConnection() as JarURLConnection
val jarFile = jarURLConnection.jarFile
val jarEntries = jarFile.entries()
while (jarEntries.hasMoreElements()) {
val jarEntry = jarEntries.nextElement()
var jarEntryName = jarEntry.name
if (jarEntryName.startsWith(packageName) && jarEntryName.endsWith(".class")) {
jarEntryName = jarEntryName.substring(0, jarEntryName.length - 6).replace('/', '.')
try {
val aClass = Class.forName(jarEntryName)
if (AbstractCommandExecutor::class.java.isAssignableFrom(aClass)) {
val executor = aClass.getConstructor().newInstance() as AbstractCommandExecutor
val commandName = executor.command
//获取在plugin.yml中定义的命令
val command = getCommand(commandName)
if (command == null)
logger.warning("命令未定义:${commandName}")
else
command.setExecutor(executor)
}
} catch (e: ReflectiveOperationException) {
logger.warning(e.message)
}
}
}
}
}
} catch (e: IOException) {
logger.warning(e.message)
}
}
/*
添加所有熔炉配方
*/
MyFurnaceRecipe.createRecipes(this).forEach(Bukkit::addRecipe)
/*
添加工作台合成配方,有摆放形状和无摆放形状两种
*/
MyCraftingRecipe.createShapedRecipes(this).forEach(Bukkit::addRecipe)
MyCraftingRecipe.createShapelessRecipes(this).forEach(Bukkit::addRecipe)
//初始化计分板
scoreBoardHandler()
logger.info("onEnable complete")
}
override fun onDisable() {
// Plugin shutdown logic
logger.info("onDisabled complete")
}
/**
* 计分板初始化,仅在插件加载时调用一次
*/
private fun scoreBoardHandler() {
//玩家名字标签下方
val nameBelowObjective = scoreboard.registerNewObjective(
"name-below",
Criteria.FOOD,//此处指定了数值的来源
Component.text("饥饿值").color(NamedTextColor.GREEN)
)
nameBelowObjective.displaySlot = DisplaySlot.BELOW_NAME
//设置 玩家列表计分项
val tabListObjective = scoreboard.registerNewObjective(
"tab-list",
Criteria.HEALTH,//此处指定了数值的来源
null//此处不会展示名称因此传null即可
)
tabListObjective.displaySlot = DisplaySlot.PLAYER_LIST//设置显示位置为
tabListObjective.renderType = RenderType.HEARTS//设置显示形式为心形,心形只在玩家列表处有效
//侧栏计分板,通过修改队伍后缀实现信息变化,而不是注销重新注册
val sidebarObjective = scoreboard.registerNewObjective(
SIDEBAR_NAME,
Criteria.DUMMY,
Component.text("规则列表")
)
sidebarObjective.getScore("${ChatColor.GOLD}系统时间:").score = 0
sidebarObjective.displaySlot = DisplaySlot.SIDEBAR
//设置队伍
val team = scoreboard.registerNewTeam("rule-info")
team.addEntry("${ChatColor.GOLD}系统时间:")
team.suffix(Component.text(Date().toString()))
}
}

View File

@@ -0,0 +1,17 @@
package xyz.fortern.command.executor
import org.bukkit.command.CommandExecutor
import xyz.fortern.HelloPaper
import java.util.logging.Logger
/**
* 抽象的指令执行器,封装共有的属性
*/
abstract class AbstractCommandExecutor(
/**
* 命令的主名称
*/
val command: String,
) : CommandExecutor {
val logger: Logger = HelloPaper.getInstance().logger
}

View File

@@ -0,0 +1,76 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.enchantments.Enchantment
import org.bukkit.entity.Player
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import xyz.fortern.command.executor.AbstractCommandExecutor
import xyz.fortern.holder.GlobalChestHolder
class AwardCommandExecutor : AbstractCommandExecutor("award") {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
//0个参数的情况仅玩家有效
if (args.isEmpty()) {
return if (sender is Player) {
doAware(sender, sender)
true
} else {
false
}
}
//1个参数的情况看这个参数是不是玩家
if (args.size == 1) {
val player = Bukkit.getPlayerExact(args[0])
return if (player == null) {
sender.sendMessage("玩家${ChatColor.BLUE}${args[0]}${ChatColor.WHITE}不存在")
false
} else {
doAware(sender, player)
true
}
}
return false
}
/**
* 向某个物品栏添加奖励物品
*
* @param inventory 要添加物品的物品栏
* @return true - 添加成功, false - 添加失败
*/
private fun award(inventory: Inventory): Boolean {
val index = inventory.firstEmpty()//获取第一个空槽位
return if (index != -1) {
//生成一个物品槽
val itemStack = ItemStack(Material.FEATHER)
val itemMeta = itemStack.itemMeta
itemMeta.displayName(Component.text("${ChatColor.RED}小鸡毛"))
val loreList = listOf(Component.text("风属性"), Component.text("火属性"))
itemMeta.lore(loreList)
itemMeta.addEnchant(Enchantment.LUCK, 3, true)
itemStack.itemMeta = itemMeta
inventory.setItem(index, itemStack)
true
} else false
}
private fun doAware(sender: CommandSender, player: Player) {
val uuid = player.uniqueId
var inventory = GlobalChestHolder.getInventory(uuid)
if (inventory == null) {
inventory = Bukkit.createInventory(player, 9, Component.text("${player.name}的私人背包"))
GlobalChestHolder.addInventory(uuid, inventory)
}
if (award(inventory))
sender.sendMessage("${ChatColor.GREEN}添加成功")
else
sender.sendMessage("${ChatColor.RED}添加失败")
}
}

View File

@@ -0,0 +1,36 @@
package xyz.fortern.command.executor.impl
import org.bukkit.Bukkit
import org.bukkit.boss.BarColor
import org.bukkit.boss.BarFlag
import org.bukkit.boss.BarStyle
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import xyz.fortern.HelloPaper
import xyz.fortern.command.executor.AbstractCommandExecutor
import java.util.concurrent.atomic.AtomicInteger
class BossCommandExecutor : AbstractCommandExecutor("boss") {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
val plugin = HelloPaper.getInstance()
//创建一个BOSS条
val bossBar = Bukkit.createBossBar("究极BOSS", BarColor.GREEN, BarStyle.SEGMENTED_6, BarFlag.CREATE_FOG)
//每一个在线玩家都能看到BOSS条
Bukkit.getOnlinePlayers().forEach { player -> bossBar.addPlayer(player) }
val scheduler = Bukkit.getScheduler()
val atomicInteger = AtomicInteger(6)
//Kotlin搞笑呢0参和1参的lambda都可以省略参数列表结果自己搞不清楚对应的究竟是0参的Runnable还是1参的Consumer最终还是要我手动指定形参列表
val taskId = scheduler.runTaskTimerAsynchronously(
plugin,
{ -> bossBar.progress = atomicInteger.decrementAndGet() * (1.0 / 6) },
//Runnable { bossBar.progress = atomicInteger.decrementAndGet() * (1.0 / 6) },
0, 2 * 20
).taskId
scheduler.runTaskLater(plugin, { ->
bossBar.removeAll()
bossBar.isVisible = false
scheduler.cancelTask(taskId)
}, (6 * 2 * 20).toLong())
return true
}
}

View File

@@ -0,0 +1,20 @@
package xyz.fortern.command.executor.impl
import org.bukkit.ChatColor
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
class GodCommandExecutor : AbstractCommandExecutor("god") {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
logger.info("进入god的指令执行器所传指令为$label")
return if (sender is Player) {
val invulnerable = sender.isInvulnerable
sender.isInvulnerable = !invulnerable
sender.sendMessage("你已进入${if (invulnerable) "${ChatColor.GREEN}普通" else "${ChatColor.RED}上帝"}${ChatColor.WHITE}模式")
true
} else false
}
}

View File

@@ -0,0 +1,132 @@
package xyz.fortern.command.executor.impl
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.block.TileState
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor
import org.bukkit.entity.Player
import org.bukkit.persistence.PersistentDataType
import xyz.fortern.HelloPaper
import xyz.fortern.command.executor.AbstractCommandExecutor
import xyz.fortern.persistent.DatetimeTagType
import xyz.fortern.persistent.UUIDTagType
import java.time.ZonedDateTime
import java.util.*
class MyNbtCommandExecutor : AbstractCommandExecutor("my-nbt"), TabExecutor {
private val subCommands = listOf("seti", "geti", "setb", "getb")
private val uuidNamespace = NamespacedKey(HelloPaper.getInstance(), "playerUUID")
private val timeNamespace = NamespacedKey(HelloPaper.getInstance(), "updateTime")
private val msgNamespace = NamespacedKey(HelloPaper.getInstance(), "msg")
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
if (sender !is Player) return false
when (args[0]) {
subCommands[0] -> {
setNbtToItem(sender)
}
subCommands[1] -> {
getNbtFromItem(sender)
}
subCommands[2] -> {
setNbtToBlock(sender)
}
subCommands[3] -> {
getNbtFromBlock(sender)
}
else -> return false
}
return true
}
private fun setNbtToItem(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
val dataContainer = itemMeta.persistentDataContainer
dataContainer.set(uuidNamespace, UUIDTagType, player.uniqueId)
dataContainer.set(timeNamespace, DatetimeTagType, ZonedDateTime.now())
dataContainer.set(msgNamespace, PersistentDataType.STRING, "默认的自定义物品信息")
itemStack.itemMeta = itemMeta
player.sendMessage("添加自定义信息成功")
} else {
player.sendMessage("手中没有物品")
}
}
private fun getNbtFromItem(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
//存储自定义信息的容器
//ItemMate实现了PersistentDataHolder接口意味着任何ItemMate都可以添加信息
val dataContainer = itemMeta.persistentDataContainer
//存入自定义信息
val uuid = dataContainer.get(uuidNamespace, UUIDTagType)
val zonedDateTime = dataContainer.get(timeNamespace, DatetimeTagType)
val s = dataContainer.get(msgNamespace, PersistentDataType.STRING)
player.sendMessage("物品名称:${itemStack.type.translationKey()}\nUUID$uuid\n更新时间:$zonedDateTime\n信息:$s")
} else {
player.sendMessage("手中没有物品")
}
}
private fun setNbtToBlock(player: Player) {
val block = player.getTargetBlockExact(5)
if (block != null) {
val state = block.state
if (state is TileState) {
//TileState继承了PersistentDataHolder接口这意味着只有只有继承了TileState的方块才能存储自定义信息
val dataContainer = state.persistentDataContainer
dataContainer.set(uuidNamespace, UUIDTagType, player.uniqueId)
dataContainer.set(timeNamespace, DatetimeTagType, ZonedDateTime.now())
dataContainer.set(msgNamespace, PersistentDataType.STRING, "默认的自定义方块信息")
state.update(false, false)
player.sendMessage("添加自定义信息成功")
} else {
player.sendMessage("该方块无法添加自定义信息")
}
} else {
player.sendMessage("没有目标方块")
}
}
private fun getNbtFromBlock(player: Player) {
val block = player.getTargetBlockExact(5)
if (block != null) {
val state = block.state
if (state is TileState) {
//TileState继承了PersistentDataHolder接口这意味着只有只有继承了TileState的方块才能存储自定义信息
val dataContainer = state.persistentDataContainer
val uuid = dataContainer.get(uuidNamespace, UUIDTagType)
val zonedDateTime = dataContainer.get(timeNamespace, DatetimeTagType)
val s = dataContainer.get(msgNamespace, PersistentDataType.STRING)
player.sendMessage("方块名称:${state.type.translationKey()}\nUUID$uuid\n更新时间:$zonedDateTime\n信息:$s")
}
} else {
player.sendMessage("没有目标方块")
}
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
label: String,
args: Array<String>,
): List<String>? {
if (args.size == 1) {
return if (args[0].isEmpty())
subCommands
else
subCommands.filter { it.startsWith(args[0].lowercase(Locale.getDefault())) }
}
return null
}
}

View File

@@ -0,0 +1,28 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
import xyz.fortern.holder.GlobalChestHolder
class OpenCommandExecutor : AbstractCommandExecutor("open") {
/**
* 打开一个玩家特有的物品栏
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
return if (sender is Player) {
val playerUuid = sender.uniqueId
var privateInventory = GlobalChestHolder.getInventory(playerUuid)
if (privateInventory === null) {
privateInventory = Bukkit.createInventory(sender, 9, Component.text(sender.name + "的私人背包"))
GlobalChestHolder.addInventory(playerUuid, privateInventory)
}
sender.openInventory(privateInventory)
true
} else false
}
}

View File

@@ -0,0 +1,107 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
import net.md_5.bungee.api.chat.TextComponent
import net.md_5.bungee.api.chat.TranslatableComponent
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor
import org.bukkit.entity.EntityType
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
import java.util.*
class PrintCommandExecutor : AbstractCommandExecutor("print"), TabExecutor {
private val subcommands = listOf("common", "color", "comp")
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
if (args.size == 1 && args[0].isEmpty())
return false
if (sender is Player) {
when (args[0]) {
"common" -> f0(sender)
"color" -> f1(sender)
"comp" -> f2(sender)
}
}
return false
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
label: String,
args: Array<String>,
): List<String>? {
if (args.size == 1) {
return if (args[0].isEmpty())
subcommands
else
subcommands.filter { it.startsWith(args[0].lowercase(Locale.getDefault())) }
}
return null
}
/**
* 发送普通的颜色消息,使用枚举中的颜色
*/
private fun f0(player: Player) {
//用法1ChatColor拼接
val message = "${ChatColor.GOLD}你好${ChatColor.BOLD}${ChatColor.RED}世界"
player.sendMessage(message)
//用法2使用ChatColor提供的静态方法
val message1 = ChatColor.translateAlternateColorCodes('&', "&6你好&l&c世界")
player.sendMessage(message1)
}
/**
* 使用自定义颜色发送消息
*/
private fun f1(player: Player) {
val color1 = net.md_5.bungee.api.ChatColor.of("#FCF3CF")
val color2 = net.md_5.bungee.api.ChatColor.of("#F9E79F")
val color3 = net.md_5.bungee.api.ChatColor.of("#F7DC6F")
val color4 = net.md_5.bungee.api.ChatColor.of("#F4D03F")
val color5 = net.md_5.bungee.api.ChatColor.of("#F1C40F")
val color6 = net.md_5.bungee.api.ChatColor.of("#D4AC0D")
val message = "${color1}${color2}${color3}${color4}${color5}${color6}"
player.sendMessage(message)
}
/**
* 发送带有组件的消息
*/
private fun f2(player: Player) {
val url = "https://space.bilibili.com/34710876"
val textComponent1 = Component.text("点击").color(NamedTextColor.GREEN)
.append(
Component.text("此处")
.color(NamedTextColor.RED)
.hoverEvent(HoverEvent.showText(Component.text(url)))
.clickEvent(ClickEvent.openUrl(url))
)
.append(Component.text("跳转到Fortern的B站主页").color(NamedTextColor.GREEN))
player.sendMessage(textComponent1)
val textComponent2 = Component.text("游戏中点击")
.append(Component.keybind("key.use").color(NamedTextColor.RED).decoration(TextDecoration.BOLD, true))
.append(Component.text("用于"))
.append(Component.text("提交物品").decorate(TextDecoration.OBFUSCATED))
player.sendMessage(textComponent2)
val textComponent3 = Component.text("item")
.hoverEvent(HoverEvent.showEntity(EntityType.PLAYER, player.uniqueId, player.displayName()))
player.sendMessage(textComponent3)
val textComponent4 = Component.text("物品:")
.append(Component.translatable(Material.DIRT).color(NamedTextColor.GOLD))
player.sendMessage(textComponent4)
//使用spigot的API
player.spigot().sendMessage(TextComponent("物品:"), TranslatableComponent(Material.DIRT.translationKey()))
player.sendActionBar(Component.text("物品栏上方的文字"))
}
}

View File

@@ -0,0 +1,18 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import xyz.fortern.HelloPaper
import xyz.fortern.command.executor.AbstractCommandExecutor
import java.util.*
class ScoreCommandExecutor : AbstractCommandExecutor("score") {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
val scoreboard = HelloPaper.getInstance().scoreboard
scoreboard.getTeam("rule-info")?.suffix(Component.text(Date().toString()))
return true
}
}

View File

@@ -0,0 +1,20 @@
package xyz.fortern.command.executor.impl
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.FallingBlock
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
class SpawnCommandExecutor: AbstractCommandExecutor("spawn") {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
if(sender !is Player) return false
//生成下落方块
sender.world.spawn(sender.location.toCenterLocation(), FallingBlock::class.java) { entity ->
entity.blockData = Bukkit.createBlockData(Material.TNT)
}
return true
}
}

View File

@@ -0,0 +1,78 @@
package xyz.fortern.command.executor.impl
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.scheduler.BukkitRunnable
import xyz.fortern.HelloPaper.Companion.getInstance
import xyz.fortern.command.executor.AbstractCommandExecutor
class StuckCommandExecutor : AbstractCommandExecutor("stuck") {
private val plugin = getInstance()
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
if (args.isEmpty()) return false
when (args[0]) {
"1" -> //BukkitRunnable runTask 同步阻塞线程调用
object : BukkitRunnable() {
override fun run() {
sender.sendMessage("主线程停止3秒")
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
sender.sendMessage("主线程恢复运行")
}
}
}.runTask(plugin)
"2" -> //BukkitRunnable runTaskAsynchronously 异步阻塞线程调用
object : BukkitRunnable() {
override fun run() {
sender.sendMessage("异步线程停止3秒主线程不受影响")
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
sender.sendMessage("异步线程恢复运行")
}
}
}.runTaskAsynchronously(plugin)
"3" -> {
sender.sendMessage("异步线程3*20tick后开始执行")
object : BukkitRunnable() {
override fun run() {
sender.sendMessage("异步线程停止3秒主线程不受影响")
try {
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
sender.sendMessage("异步线程恢复运行")
}
}
}.runTaskLaterAsynchronously(plugin, (3 * 20).toLong())
}
"4" -> { //周期性执行任务
val task = object : BukkitRunnable() {
override fun run() {
sender.sendMessage("周期性执行的线程任务每2秒执行1次")
}
}
val bukkitTask = task.runTaskTimerAsynchronously(plugin, 20, (2 * 20).toLong())
//让任务调度器在几秒钟后停止task的执行
Bukkit.getScheduler().runTaskLater(plugin, Runnable { bukkitTask.cancel() }, (20 * 20).toLong())
}
"5" -> {
val scheduler = Bukkit.getScheduler() //获取线程调度器
scheduler.runTaskLater(plugin, Runnable { sender.sendMessage("使用线程调度器执行原生Runnable") }, 0)
}
}
return true
}
}

View File

@@ -0,0 +1,34 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
class SuicideCommandExecutor : AbstractCommandExecutor("suicide") {
/**
* 自杀指令处理
*
* @param sender Source of the command
* @param command Command which was executed
* @param label Alias of the command which was used
* @param args Passed command arguments
* @return 指令执行是否成功
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
logger.info("进入suicide的指令执行器所传指令为$label")
return if (sender is Player) {
// 发送广播
sender.getServer().broadcast(
Component.text("再见了,世界!\n").color(NamedTextColor.YELLOW)
.append(sender.displayName().append(Component.text("去世了:/")).color(NamedTextColor.BLUE))
)
// 设置血量为0
sender.health = 0.0
true
} else false
}
}

View File

@@ -0,0 +1,141 @@
package xyz.fortern.command.executor.impl
import org.bukkit.Material
import org.bukkit.attribute.Attribute
import org.bukkit.attribute.AttributeModifier
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.command.TabExecutor
import org.bukkit.enchantments.Enchantment
import org.bukkit.entity.Player
import org.bukkit.inventory.EquipmentSlot
import org.bukkit.inventory.ItemFlag
import xyz.fortern.command.executor.AbstractCommandExecutor
import java.util.*
class SuperItemCommandExecutor : AbstractCommandExecutor("strengthen"), TabExecutor {
private val subCommands = listOf("add", "remove", "hide", "show")
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
if (sender !is Player || args.isEmpty()) return false
when (args[0]) {
subCommands[0] -> {
addAttribute(sender)
}
subCommands[1] -> {
removeAttribute(sender)
}
subCommands[2] -> {
hideAttribute(sender)
}
subCommands[3] -> {
showAttribute(sender)
}
else -> return false
}
return true
}
override fun onTabComplete(
sender: CommandSender,
command: Command,
label: String,
args: Array<String>,
): List<String>? {
if (args.size == 1) {
return if (args[0].isEmpty())
subCommands
else
subCommands.filter { it.startsWith(args[0].lowercase(Locale.getDefault())) }
}
return null
}
private fun addAttribute(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
itemMeta.isUnbreakable = true
itemMeta.addEnchant(Enchantment.LUCK, 100, true)
//移除所有位置的物品属性
for (slot in EquipmentSlot.values()) {
itemMeta.removeAttributeModifier(slot)
}
itemMeta.addAttributeModifier(
Attribute.GENERIC_MOVEMENT_SPEED,
AttributeModifier(
UUID.randomUUID(),
"增幅玩家移速",
0.5,
AttributeModifier.Operation.ADD_NUMBER,
EquipmentSlot.HAND
)
)
itemMeta.addAttributeModifier(
Attribute.GENERIC_ATTACK_DAMAGE,
AttributeModifier(
UUID.randomUUID(),
"增加玩家伤害",
19.0,
AttributeModifier.Operation.ADD_NUMBER,
EquipmentSlot.HAND
)
)
itemMeta.addAttributeModifier(
Attribute.GENERIC_MAX_HEALTH,
AttributeModifier(
UUID.randomUUID(),
"增加最大血量",
40.0,
AttributeModifier.Operation.ADD_NUMBER,
EquipmentSlot.HEAD
)
)
itemStack.itemMeta = itemMeta
} else {
player.sendMessage("主手没有物品")
}
}
private fun removeAttribute(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
itemMeta.isUnbreakable = false
itemMeta.removeEnchant(Enchantment.LUCK)
//移除所有位置的物品属性
for (slot in EquipmentSlot.values()) {
itemMeta.removeAttributeModifier(slot)
}
itemStack.itemMeta = itemMeta
} else {
player.sendMessage("主手没有物品")
}
}
private fun hideAttribute(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE)
itemStack.itemMeta = itemMeta
} else {
player.sendMessage("主手没有物品")
}
}
private fun showAttribute(player: Player) {
val itemStack = player.inventory.itemInMainHand
if (itemStack.type != Material.AIR) {
val itemMeta = itemStack.itemMeta
itemMeta.removeItemFlags(ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE)
itemStack.itemMeta = itemMeta
} else {
player.sendMessage("主手没有物品")
}
}
}

View File

@@ -0,0 +1,26 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.title.Title
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import xyz.fortern.command.executor.AbstractCommandExecutor
import java.time.Duration
class TitleCommandExecutor : AbstractCommandExecutor("title") {
private val title = Title.title(
Component.text("欢迎来到Fortern的世界", NamedTextColor.WHITE),
Component.text("喵呜", NamedTextColor.GRAY),
Title.Times.times(Duration.ofMillis(500), Duration.ofMillis(3000), Duration.ofMillis(1000))
)
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
return if (sender is Player) {
sender.showTitle(title)
true
} else false
}
}

View File

@@ -0,0 +1,32 @@
package xyz.fortern.command.executor.impl
import net.kyori.adventure.text.Component
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.EntityType
import org.bukkit.entity.Player
import org.bukkit.entity.Villager
import xyz.fortern.command.executor.AbstractCommandExecutor
import xyz.fortern.recipe.MyMerchantRecipes
class VillagerCommandExecutor : AbstractCommandExecutor("villager") {
private val myRecipes = MyMerchantRecipes.createMerchantRecipes()
/**
* village命令执行器
*/
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
return if (sender is Player) {
//生成村民对象
val world = sender.world
val location = sender.location
val villager = world.spawnEntity(location, EntityType.VILLAGER) as Villager
//修改村民信息
villager.setAI(false)
villager.customName(Component.text("奸商"))
villager.recipes = myRecipes
true
} else false
}
}

View File

@@ -0,0 +1,21 @@
package xyz.fortern.command.renderer
import io.papermc.paper.chat.ChatRenderer
import net.kyori.adventure.audience.Audience
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.TextColor
import org.bukkit.entity.Player
/**
* 自定义聊天渲染器
*/
class ForternChatRenderer : ChatRenderer {
override fun render(source: Player, sourceDisplayName: Component, message: Component, viewer: Audience): Component {
return Component.text("[")
.append(Component.text("mojo").color(TextColor.color(0x66CCFF)))
.append(Component.text("]<"))
.append(sourceDisplayName)
.append(Component.text("> "))
.append(message)
}
}

View File

@@ -0,0 +1,89 @@
package xyz.fortern.event
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.bukkit.DyeColor
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.block.data.type.Beehive
import org.bukkit.entity.Player
import org.bukkit.entity.Sheep
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.inventory.EquipmentSlot
import xyz.fortern.HelloPaper
class MyEventHandler : Listener {
val logger = HelloPaper.getInstance().logger
/**
* 玩家进入服务器时的处理事件
*
* @param event 当前事件
*/
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
//玩家进入服务器后,获取玩家位置,在附近播放升级音效
val player = event.player
val location = player.location
location.world?.playSound(location, Sound.ENTITY_PLAYER_LEVELUP, .5f, 1f)
//并发布全局广播消息
event.joinMessage(
Component.text("欢迎玩家 ").color(NamedTextColor.GREEN)
.append(Component.text(player.name).color(NamedTextColor.RED))
)
player.scoreboard = HelloPaper.getInstance().scoreboard
}
/**
* 玩家攻击羊,使羊变色,且没有伤害
*
* @param event 当前事件
*/
@EventHandler
fun onPlayerHitSheep(event: EntityDamageByEntityEvent) {
val damager = event.damager
if (damager is Player) {
val entity = event.entity
if (entity is Sheep) {
val colors = DyeColor.entries
val i = (0..colors.size).random()
entity.color = colors[i]
event.isCancelled = true//取消事件的后续处理?
}
}
}
@EventHandler
fun getHoneycombInfo(event: PlayerInteractEvent) {
val clickedBlock = event.clickedBlock ?: return
val blockData = clickedBlock.blockData
if (blockData !is Beehive)
return
if (event.hand == EquipmentSlot.OFF_HAND)
return
val player = event.player
if (player.inventory.itemInMainHand.type != Material.AIR)
return
val honeyLevel = blockData.honeyLevel
val blockState = clickedBlock.state as org.bukkit.block.Beehive
val count = blockState.entityCount
player.sendActionBar(
Component.text("蜂蜜等级:").color(NamedTextColor.GREEN)
.append(Component.text(honeyLevel).color(NamedTextColor.RED))
.append(Component.text(" "))
.append(Component.text("蜜蜂数量:").color(NamedTextColor.GREEN))
.append(Component.text(count).color(NamedTextColor.RED))
)
}
}

View File

@@ -0,0 +1,30 @@
package xyz.fortern.event
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import xyz.fortern.holder.GlobalChestHolder
class OnTakeAwardListener: Listener {
/**
* 当玩家试图在自定义物品栏拿走特殊物品时会失败
*
* @param event 物品栏点击事件
*/
@EventHandler
fun onTakeAward(event: InventoryClickEvent) {
val inventory = event.inventory
val player = event.whoClicked
val privateInventory = GlobalChestHolder.getInventory(player.uniqueId)
if (inventory === privateInventory) {
val clickedStack = event.currentItem
if (clickedStack != null && clickedStack.type === Material.FEATHER) {
event.isCancelled = true
player.sendMessage(ChatColor.GREEN.toString() + "哈哈哈,放弃吧,你拿不到的")
}
}
}
}

View File

@@ -0,0 +1,14 @@
package xyz.fortern.event
import io.papermc.paper.event.player.AsyncChatEvent
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import xyz.fortern.command.renderer.ForternChatRenderer
class PlayerMessageHandler: Listener {
private val renderer = ForternChatRenderer()
@EventHandler
fun onChat(event: AsyncChatEvent) {
event.renderer(renderer)
}
}

View File

@@ -0,0 +1,22 @@
package xyz.fortern.holder
import org.bukkit.inventory.Inventory
import java.util.*
/**
* 全部的箱子集合
*/
object GlobalChestHolder {
private val inventoryMap: MutableMap<UUID, Inventory> = HashMap()
operator fun contains(uuid: UUID): Boolean {
return inventoryMap.containsKey(uuid)
}
fun getInventory(uuid: UUID): Inventory? {
return inventoryMap[uuid]
}
fun addInventory(uuid: UUID, inventory: Inventory) {
inventoryMap[uuid] = inventory//inventoryMap.put(uuid, inventory)
}
}

View File

@@ -0,0 +1,32 @@
package xyz.fortern.persistent
import org.bukkit.persistence.PersistentDataAdapterContext
import org.bukkit.persistence.PersistentDataType
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
/**
* 自定义容器存储类型将时间类以Long时间戳存储
*/
object DatetimeTagType : PersistentDataType<Long, ZonedDateTime> {
@Suppress("UNCHECKED_CAST")
override fun getPrimitiveType(): Class<Long> {
//此处如果返回Long::class.java编译器会进行“优化”将其变为原始类型long的Class从而导致Bukkit报错
//我们所期望的是java.lang.Long的Class
return java.lang.Long::class.java as Class<Long>
}
override fun getComplexType(): Class<ZonedDateTime> {
return ZonedDateTime::class.java
}
override fun toPrimitive(complex: ZonedDateTime, context: PersistentDataAdapterContext): Long {
return complex.toInstant().toEpochMilli()
}
override fun fromPrimitive(primitive: Long, context: PersistentDataAdapterContext): ZonedDateTime {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(primitive), ZoneId.systemDefault())
}
}

View File

@@ -0,0 +1,35 @@
package xyz.fortern.persistent
import org.bukkit.persistence.PersistentDataAdapterContext
import org.bukkit.persistence.PersistentDataType
import java.nio.ByteBuffer
import java.util.*
/**
* 自定义容器存储类型将UUID以byte数组存储
*
* @author Fortern
*/
object UUIDTagType : PersistentDataType<ByteArray, UUID> {
override fun getPrimitiveType(): Class<ByteArray> {
return ByteArray::class.java
}
override fun getComplexType(): Class<UUID> {
return UUID::class.java
}
override fun toPrimitive(complex: UUID, context: PersistentDataAdapterContext): ByteArray {
val byteBuffer = ByteBuffer.wrap(ByteArray(16))
byteBuffer.putLong(complex.mostSignificantBits)
byteBuffer.putLong(complex.leastSignificantBits)
return byteBuffer.array()
}
override fun fromPrimitive(primitive: ByteArray, context: PersistentDataAdapterContext): UUID {
val byteBuffer = ByteBuffer.wrap(primitive)
val mostSignificantBits = byteBuffer.long
val leastSignificantBits = byteBuffer.long
return UUID(mostSignificantBits, leastSignificantBits)
}
}

View File

@@ -0,0 +1,57 @@
package xyz.fortern.recipe
import net.kyori.adventure.text.Component
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.ShapedRecipe
import org.bukkit.inventory.ShapelessRecipe
import org.bukkit.plugin.Plugin
/**
* 合成配方类
*/
object MyCraftingRecipe {
fun createShapedRecipes(plugin: Plugin): Array<ShapedRecipe> {
val itemStack = ItemStack(Material.BOW)
val itemMeta = itemStack.itemMeta
itemMeta.addEnchant(Enchantment.MENDING, 1, false)
itemMeta.addEnchant(Enchantment.ARROW_INFINITE, 1, false)
val shapedRecipe1 = ShapedRecipe(NamespacedKey(plugin, "craft_god_bow"), itemStack)
/*
* [0 1 2|
* |3 4 5|
* |6 7 8]
* 合成栏为3*3的格子每个格子用一个字符表示每一行3个格子在一起组成一个字符串shape
* shape的长度为1,2或3而shape参数的数量也是1,2或3
* 多个shape字符串从上到下排列形成配方表
* 每种符号表示一种物品,空格表示此处没有物品
*/
/**
* 下面这行代码表示的配方是这样:
* [0 |
* | 1|
* 两个物品以左上右下的对角方式放置
*/
shapedRecipe1.shape("0 ", " 1")//原材料的摆放
//摆放方式中字符代表的物品
shapedRecipe1.setIngredient('0', Material.BLAZE_POWDER)
shapedRecipe1.setIngredient('1', Material.SLIME_BALL)
return arrayOf(shapedRecipe1)
}
fun createShapelessRecipes(plugin: Plugin): Array<ShapelessRecipe> {
val itemStack = ItemStack(Material.BOW)
val itemMeta = itemStack.itemMeta
itemMeta.addEnchant(Enchantment.MENDING, 1, false)
itemMeta.addEnchant(Enchantment.ARROW_INFINITE, 1, false)
itemMeta.displayName(Component.text("神弓"))
val shapelessRecipe = ShapelessRecipe(NamespacedKey(plugin, "craft_gooood_bow"), itemStack)
shapelessRecipe.addIngredient(3, Material.FEATHER)
shapelessRecipe.addIngredient(1, Material.GUNPOWDER)
return arrayOf(
shapelessRecipe
)
}
}

View File

@@ -0,0 +1,31 @@
package xyz.fortern.recipe
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.inventory.FurnaceRecipe
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.Plugin
/**
* 我的熔炉配方
*/
object MyFurnaceRecipe {
fun createRecipes(plugin: Plugin): Array<FurnaceRecipe> {
return arrayOf(
FurnaceRecipe(
NamespacedKey(plugin, "dirt_to_coarse_dirt"),
ItemStack(Material.COARSE_DIRT),
Material.DIRT,
10f,
5 * 20
),
FurnaceRecipe(
NamespacedKey(plugin, "warped_stem_to_charcoal"),
ItemStack(Material.CHARCOAL),
Material.WARPED_STEM,
10f,
5 * 20
)
)
}
}

View File

@@ -0,0 +1,21 @@
package xyz.fortern.recipe
import org.bukkit.Material
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.MerchantRecipe
object MyMerchantRecipes {
fun createMerchantRecipes(): List<MerchantRecipe> {
//创建一个配方指定交易得到的ItemStack和最大交易次数
val merchantRecipe1 = MerchantRecipe(ItemStack(Material.DIAMOND_BLOCK, 2), 12)
//给配方指定交易所需放物品
merchantRecipe1.ingredients = listOf(ItemStack(Material.COARSE_DIRT, 3))
val merchantRecipe2 = MerchantRecipe(ItemStack(Material.FEATHER, 2), 12)
merchantRecipe2.ingredients =
listOf(ItemStack(Material.COARSE_DIRT, 3), ItemStack(Material.DIRT, 1))
return listOf(
merchantRecipe1,
merchantRecipe2
)
}
}

View File

@@ -0,0 +1,72 @@
name: Hello-Paper
version: '${project.version}'
main: xyz.fortern.HelloPaper
api-version: 1.20
authors: [ Fortern ]
description: Fortern第一次尝试开发插件
#定义详细的指令信息
commands:
#指令名
suicide:
#指令描述
description: 自杀
#错误使用提示
usage: 直接输入/<command>用于自杀
#指令别名
aliases: [ killme, killmyself ]
#指令权限不写默认为OP
permission: fortern.suicide
#无权限提示
permission-message: 你没有权限<permission>~
god:
description: 在上帝模式与普通模式间切换
usage: 输入/<command>用于在上帝模式与普通模式间切换
aliases:
- g
- g1
- g2
permission: fortern.op
permission-message: 你没有权限<permission>
open:
description: 打开玩家的私人背包
usage: /<command>用于打开玩家的私人背包
permission: fortern.common
award:
description: 在某玩家背包中生成特殊物品
usage: /<command> [player_name]
villager:
description: 生成一个可以交易特殊物品的村民
usage: /<command>用于生成一只可以交易特殊物品的村民,仅管理员可用
permission: fortern.op
stuck:
description: 执行同步或异步线程
usage: /<command> [1|2|3...]
print:
description: 打印一条消息的命令
title:
description: 显示标题的命令
boss:
description: 产生一个boss条
score:
description: 创建自定义的计分板
strengthen:
description: 获取或移除一个超级物品。只有玩家可以执行该指令。
usage: /<command> [add|remove]
my-nbt:
description: 自定义NBT标签只有玩家可以执行该指令
usage: /<command> [get|set]
spawn:
description: 生成一个下落的方块
#permissions权限声明
permissions:
fortern.suicide:
description: 可以自杀的权限
default: true #所有玩家
fortern.op:
description: op的权限
default: op
fortern.common:
description: 普通玩家权限
default: true