From 293a1608566f3c75f10661af25607e9393b49df9 Mon Sep 17 00:00:00 2001
From: yhy <1763917516@qq.com>
Date: Wed, 3 Jun 2026 06:25:44 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 16 +
README2.md | 604 ++++++++++++++++++
pom.xml | 77 +++
.../java/com/yaohun/petsystem/PetMain.java | 265 ++++++++
.../com/yaohun/petsystem/api/PetExpAPI.java | 122 ++++
.../yaohun/petsystem/command/PetCommand.java | 4 +
.../com/yaohun/petsystem/config/Config.java | 141 ++++
.../com/yaohun/petsystem/data/PetData.java | 86 +++
.../com/yaohun/petsystem/data/PetSQL.java | 105 +++
.../com/yaohun/petsystem/data/PlayerData.java | 136 ++++
.../com/yaohun/petsystem/data/SkillData.java | 60 ++
.../yaohun/petsystem/event/PetCallEvent.java | 47 ++
.../com/yaohun/petsystem/gui/CarryGui.java | 89 +++
.../com/yaohun/petsystem/gui/FeedingGui.java | 206 ++++++
.../com/yaohun/petsystem/gui/MainGui.java | 185 ++++++
.../petsystem/gui/holder/CarryGuiHolder.java | 12 +
.../gui/holder/FeedingGuiHolder.java | 12 +
.../petsystem/gui/holder/MainGuiHolder.java | 12 +
.../petsystem/listener/PetSkillsListener.java | 94 +++
.../petsystem/listener/PlayerListener.java | 79 +++
.../petsystem/listener/RePetNameListener.java | 62 ++
.../petsystem/manage/LevelExpManager.java | 35 +
.../petsystem/manage/PetCallSession.java | 48 ++
.../yaohun/petsystem/manage/PetManager.java | 71 ++
.../petsystem/manage/PlayerManager.java | 253 ++++++++
.../com/yaohun/petsystem/model/PetNbt.java | 111 ++++
.../yaohun/petsystem/model/PetQuality.java | 49 ++
.../yaohun/petsystem/util/MessageUtil.java | 78 +++
.../com/yaohun/petsystem/util/PetUtil.java | 178 ++++++
.../petsystem/util/model/BetterModelUtil.java | 81 +++
.../petsystem/util/wolf/CustomWolf.java | 202 ++++++
src/main/resources/config.yml | 31 +
src/main/resources/lang.yml | 0
src/main/resources/plugin.yml | 6 +
.../java/com/yaohun/petsystem/PetMain.java | 265 ++++++++
.../com/yaohun/petsystem/api/PetExpAPI.java | 122 ++++
.../yaohun/petsystem/command/PetCommand.java | 4 +
.../com/yaohun/petsystem/config/Config.java | 141 ++++
.../com/yaohun/petsystem/data/PetData.java | 86 +++
.../com/yaohun/petsystem/data/PetSQL.java | 105 +++
.../com/yaohun/petsystem/data/PlayerData.java | 136 ++++
.../com/yaohun/petsystem/data/SkillData.java | 60 ++
.../yaohun/petsystem/event/PetCallEvent.java | 47 ++
.../com/yaohun/petsystem/gui/CarryGui.java | 89 +++
.../com/yaohun/petsystem/gui/FeedingGui.java | 206 ++++++
.../com/yaohun/petsystem/gui/MainGui.java | 185 ++++++
.../petsystem/gui/holder/CarryGuiHolder.java | 12 +
.../gui/holder/FeedingGuiHolder.java | 12 +
.../petsystem/gui/holder/MainGuiHolder.java | 12 +
.../petsystem/listener/PetSkillsListener.java | 94 +++
.../petsystem/listener/PlayerListener.java | 79 +++
.../petsystem/listener/RePetNameListener.java | 62 ++
.../petsystem/manage/LevelExpManager.java | 35 +
.../petsystem/manage/PetCallSession.java | 48 ++
.../yaohun/petsystem/manage/PetManager.java | 71 ++
.../petsystem/manage/PlayerManager.java | 253 ++++++++
.../com/yaohun/petsystem/model/PetNbt.java | 111 ++++
.../yaohun/petsystem/model/PetQuality.java | 49 ++
.../yaohun/petsystem/util/MessageUtil.java | 78 +++
.../com/yaohun/petsystem/util/PetUtil.java | 178 ++++++
.../petsystem/util/model/BetterModelUtil.java | 81 +++
.../petsystem/util/wolf/CustomWolf.java | 202 ++++++
src/src/main/resources/config.yml | 31 +
src/src/main/resources/lang.yml | 0
src/src/main/resources/plugin.yml | 6 +
65 files changed, 6417 insertions(+)
create mode 100644 .gitignore
create mode 100644 README2.md
create mode 100644 pom.xml
create mode 100644 src/main/java/com/yaohun/petsystem/PetMain.java
create mode 100644 src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
create mode 100644 src/main/java/com/yaohun/petsystem/command/PetCommand.java
create mode 100644 src/main/java/com/yaohun/petsystem/config/Config.java
create mode 100644 src/main/java/com/yaohun/petsystem/data/PetData.java
create mode 100644 src/main/java/com/yaohun/petsystem/data/PetSQL.java
create mode 100644 src/main/java/com/yaohun/petsystem/data/PlayerData.java
create mode 100644 src/main/java/com/yaohun/petsystem/data/SkillData.java
create mode 100644 src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/CarryGui.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/MainGui.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
create mode 100644 src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
create mode 100644 src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
create mode 100644 src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
create mode 100644 src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
create mode 100644 src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
create mode 100644 src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
create mode 100644 src/main/java/com/yaohun/petsystem/manage/PetManager.java
create mode 100644 src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
create mode 100644 src/main/java/com/yaohun/petsystem/model/PetNbt.java
create mode 100644 src/main/java/com/yaohun/petsystem/model/PetQuality.java
create mode 100644 src/main/java/com/yaohun/petsystem/util/MessageUtil.java
create mode 100644 src/main/java/com/yaohun/petsystem/util/PetUtil.java
create mode 100644 src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
create mode 100644 src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
create mode 100644 src/main/resources/config.yml
create mode 100644 src/main/resources/lang.yml
create mode 100644 src/main/resources/plugin.yml
create mode 100644 src/src/main/java/com/yaohun/petsystem/PetMain.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/command/PetCommand.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/config/Config.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/data/PetData.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/data/PetSQL.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/data/PlayerData.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/data/SkillData.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/CarryGui.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/MainGui.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/manage/PetManager.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/model/PetNbt.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/model/PetQuality.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/util/MessageUtil.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/util/PetUtil.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
create mode 100644 src/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
create mode 100644 src/src/main/resources/config.yml
create mode 100644 src/src/main/resources/lang.yml
create mode 100644 src/src/main/resources/plugin.yml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6d0414d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+target/
+out/
+lib/
+libs/
+.vscode/
+.codex/
+.idea/
+*.iml
+*.class
+*.log
+logs/
+*.db
+*.sqlite
+*.sqlite3
+.DS_Store
+Thumbs.db
\ No newline at end of file
diff --git a/README2.md b/README2.md
new file mode 100644
index 0000000..9827765
--- /dev/null
+++ b/README2.md
@@ -0,0 +1,604 @@
+# 1.AuOnlineReward 是什么?
+
+AuOnlineReward 是一个 Minecraft 1.12.2 Bukkit / Spigot 在线奖励插件。
+
+它用于根据玩家在线时间发放阶段奖励,并根据阶段奖励领取次数解锁累积奖励。
+
+核心功能:
+
+- 玩家通过 GUI 查看在线信息和奖励状态
+- 支持每日在线阶段奖励
+- 支持累积领取次数奖励
+- 支持每日在线数据刷新
+- 支持月度在线数据刷新
+- 支持配置奖励物品和奖励命令
+- 支持通过 API 获取玩家在线时间
+- 支持奖励领取事件,方便其他插件监听
+
+简单理解:
+
+```yml
+AuOnlineReward = 在线时长统计 + 在线奖励 GUI + 奖励命令执行
+玩家 = 在线积累时间并领取奖励
+管理员 = 配置奖励物品、奖励条件和发奖命令
+```
+
+------
+
+# 2.运行环境
+
+## 服务端版本
+
+```yml
+Minecraft: 1.12.2
+服务端: Bukkit / Spigot / Paper
+Java: 8
+```
+
+## 前置插件
+
+AuOnlineReward 依赖 DemonAPI。
+
+`plugin.yml` 中已经声明:
+
+```yml
+depend:
+ - DemonAPI
+```
+
+服务器启动前必须确保 DemonAPI 已放入插件目录。
+
+------
+
+# 3.安装方式
+
+## 第一步:放入插件
+
+将插件 jar 放入服务器插件目录:
+
+```yml
+plugins/AuOnlineReward.jar
+```
+
+同时放入前置插件:
+
+```yml
+plugins/DemonAPI.jar
+```
+
+## 第二步:启动服务器
+
+首次启动后会生成插件配置文件。
+
+## 第三步:检查加载
+
+控制台应能看到插件正常启用,没有缺少 DemonAPI 的报错。
+
+------
+
+# 4.目录结构
+
+## 插件配置目录
+
+默认配置和语言文件位于:
+
+```yml
+plugins/AuOnlineReward/config.yml
+plugins/AuOnlineReward/language.yml
+```
+
+## 玩家数据目录
+
+玩家在线数据会保存到:
+
+```yml
+plugins/AuData/AuOnlineReward/玩家名.yml
+```
+
+玩家数据示例:
+
+```yml
+OnlineData:
+ todayOnline: 0
+ totalOnline: 0
+ monthOnline: 0
+ totalSign: 0
+ signAmount: 0
+ currentReward: MinuteReward_30
+ collectionTime: 1717200000000
+ offlineTime: 1717200000000
+ receivedList: []
+ permanentReceived: []
+```
+
+字段说明:
+
+```yml
+todayOnline: 今日在线秒数
+totalOnline: 累积在线秒数
+monthOnline: 本月在线秒数
+totalSign: 总计领取阶段奖励次数
+signAmount: 当前轮累积领取阶段奖励次数
+currentReward: 当前可领取的阶段奖励
+collectionTime: 上次阶段奖励领取时间
+offlineTime: 上次离线时间
+receivedList: 今日已领取阶段奖励
+permanentReceived: 当前轮已领取累积奖励
+```
+
+------
+
+# 5.玩家命令
+
+## 打开在线奖励界面
+
+```yml
+/auonline open
+```
+
+玩家执行后会打开在线福利 GUI。
+
+GUI 中会显示:
+
+- 累积在线时间
+- 本月在线时间
+- 今日在线时间
+- 总计领取奖励次数
+- 阶段奖励状态
+- 累积奖励状态
+
+注意:
+
+```yml
+每日 00:10 前无法打开 GUI
+这是为了避免跨日数据刷新期间领取状态异常
+```
+
+------
+
+# 6.管理员命令
+
+管理员命令需要 OP 权限。
+
+## 查看命令帮助
+
+```yml
+/auonline
+```
+
+## 重载配置
+
+```yml
+/auonline reload
+```
+
+重载内容:
+
+- 奖励配置
+- 语言配置
+- GUI 标题
+- 奖励显示文本
+
+## 刷新所有玩家每日数据
+
+```yml
+/auonline dayrefresh
+```
+
+作用:
+
+- 清空今日在线时间
+- 重置当前阶段奖励
+- 重置今日阶段奖励领取记录
+- 重新载入在线玩家数据
+
+## 刷新指定玩家每日数据
+
+```yml
+/auonline dayrefresh 玩家名
+```
+
+只刷新指定玩家的每日在线奖励数据。
+
+## 查看玩家数据
+
+```yml
+/auonline info 玩家名
+/auonline look 玩家名
+```
+
+不填写玩家名时,默认查看执行者自己的数据。
+
+## 查看今日在线排行
+
+```yml
+/auonline topDay
+/auonline top
+```
+
+显示当前已缓存玩家的今日在线排行。
+
+------
+
+# 7.奖励类型
+
+AuOnlineReward 当前包含两类奖励。
+
+## 阶段奖励
+
+配置节点通常以 `MinuteReward_` 开头。
+
+示例:
+
+```yml
+RewardData:
+ MinuteReward_30:
+ slot: 10
+ needOnline: 1800
+ needOnlineTime: 1800
+ itemStack:
+ ==: org.bukkit.inventory.ItemStack
+ type: IRON_INGOT
+ commands:
+ - eco give %player% 500
+```
+
+字段说明:
+
+```yml
+slot: GUI 中的格子位置
+needOnline: 距离上次阶段奖励领取需要等待的秒数
+needOnlineTime: 今日在线秒数要求
+itemStack: GUI 中展示的奖励物品
+commands: 领取后执行的命令
+```
+
+阶段奖励领取成功后:
+
+- 增加当前轮阶段领取次数
+- 增加总计阶段领取次数
+- 写入今日已领取记录
+- 切换到下一个阶段奖励
+- 执行奖励命令
+- 保存玩家数据
+
+## 累积奖励
+
+配置节点通常以 `SignReward_` 开头。
+
+示例:
+
+```yml
+RewardData:
+ SignReward_15:
+ slot: 29
+ needOnline: 25
+ itemStack:
+ ==: org.bukkit.inventory.ItemStack
+ type: CHEST
+ commands:
+ - eco give %player% 10000
+```
+
+字段说明:
+
+```yml
+slot: GUI 中的格子位置
+needOnline: 需要累计领取多少次阶段奖励
+itemStack: GUI 中展示的奖励物品
+commands: 领取后执行的命令
+```
+
+累积奖励领取成功后会写入 `permanentReceived`。
+
+当领取 `SignReward_Max` 后:
+
+```yml
+signAmount 会重置为 0
+permanentReceived 会清空
+进入下一轮累积奖励
+```
+
+------
+
+# 8.奖励命令怎么写?
+
+奖励命令写在 `commands` 列表中。
+
+示例:
+
+```yml
+commands:
+ - eco give %player% 500
+ - points give %player% 10
+ - bcm &a玩家 &e%player% &a领取了在线奖励
+```
+
+支持占位符:
+
+```yml
+%player% 玩家名
+%itemName% 奖励物品显示名
+```
+
+如果命令包含 `msg:`,会向玩家发送消息,而不是由控制台执行命令。
+
+示例:
+
+```yml
+commands:
+ - "msg:&a你领取了在线奖励"
+```
+
+颜色代码支持:
+
+```yml
+&a &b &c &e &f
+```
+
+插件执行时会转换为 Minecraft 颜色符号。
+
+------
+
+# 9.语言文件怎么写?
+
+语言文件为:
+
+```yml
+plugins/AuOnlineReward/language.yml
+```
+
+主要节点:
+
+```yml
+Online-Gui:
+ Title: "§r日常福利 - 累积在线豪礼相赠"
+ Received: "§a§l★ §7领取状态: §a已领取"
+ Not-Started: "§c§l★ §7领取状态: §c未开始"
+ Pending: "§b§l★ §7领取状态: §b待领取"
+ Total-Online: "§7累积在线: §a%time%"
+ Monthly-Online: "§7本月在线: §a%time%"
+ Daily-Online: "§7今日在线: §a%time%"
+ Total-Rewards: "§7总计领取奖励: §a%amount%次"
+Message:
+ Reward-Progress: "你还需要获取§e[%amount%次]§a阶段奖励才能领取奖励。"
+ Wait-Time: "您还需要等待§e[%time%秒]§a才能领取该奖励。"
+ Daily-Requirement: "你需要今日在线§e[%time%秒]§a才能领取该奖励。"
+ Already-Claimed: "你已经领取过这个累积奖励。"
+ Not-Available: "这个阶段奖励尚未开启,暂时无法领取。"
+```
+
+修改语言文件后执行:
+
+```yml
+/auonline reload
+```
+
+------
+
+# 10.在线时间刷新机制
+
+## 在线计时
+
+插件会定时统计在线玩家的在线时长。
+
+统计字段:
+
+```yml
+todayOnline: 今日在线
+monthOnline: 本月在线
+totalOnline: 累积在线
+```
+
+玩家退出时会补算最后一段在线时间,并保存玩家数据。
+
+## 每日刷新
+
+每日刷新会处理:
+
+```yml
+todayOnline = 0
+currentReward = MinuteReward_30
+receivedList = []
+collectionTime = 当前时间
+```
+
+## 月度刷新
+
+月度刷新会处理:
+
+```yml
+monthOnline = 0
+```
+
+## 刷新性能说明
+
+批量刷新玩家文件时,插件会将 YAML 文件读写放到异步任务中执行。
+
+刷新完成后,再回到主线程重新载入在线玩家。
+
+这样可以降低大量玩家数据文件刷新时对主线程的影响。
+
+------
+
+# 11.API 调用
+
+其他插件可以通过 `OnlineAPI` 获取玩家在线时间。
+
+## 获取今日在线分钟数
+
+```java
+int minutes = OnlineAPI.getTime(playerName);
+```
+
+## 获取本月在线分钟数
+
+```java
+int minutes = OnlineAPI.getTimeMonth(playerName);
+```
+
+## 获取累积在线分钟数
+
+```java
+int minutes = OnlineAPI.getTimeTotal(playerName);
+```
+
+注意:
+
+```yml
+API 返回单位是分钟
+底层玩家数据保存单位是秒
+```
+
+------
+
+# 12.事件监听
+
+玩家成功领取奖励后会触发:
+
+```java
+OnlineRewardEvent
+```
+
+监听示例:
+
+```java
+@EventHandler
+public void onOnlineReward(OnlineRewardEvent event) {
+ Player player = event.getPlayer();
+ String playerName = event.getPlayerName();
+ String rewardKey = event.getOnlineData().getRewardKey();
+}
+```
+
+事件可用于:
+
+- 记录领奖日志
+- 触发额外奖励
+- 接入活动系统
+- 接入统计系统
+
+------
+
+# 13.配置示例
+
+## 阶段奖励示例
+
+```yml
+RewardData:
+ MinuteReward_30:
+ slot: 10
+ needOnline: 1800
+ needOnlineTime: 1800
+ itemStack:
+ ==: org.bukkit.inventory.ItemStack
+ type: IRON_INGOT
+ meta:
+ ==: ItemMeta
+ meta-type: UNSPECIFIC
+ display-name: §6§l阶段奖励 I
+ lore:
+ - §7还差 §c%time% §7才能领取
+ - §e金币 §e*500
+ commands:
+ - eco give %player% 500
+```
+
+## 累积奖励示例
+
+```yml
+RewardData:
+ SignReward_15:
+ slot: 29
+ needOnline: 25
+ itemStack:
+ ==: org.bukkit.inventory.ItemStack
+ type: CHEST
+ meta:
+ ==: ItemMeta
+ meta-type: TILE_ENTITY
+ display-name: §6§l累积打卡奖励 I
+ lore:
+ - §7还需领取 §c%amounut% §7阶段奖励
+ - §e金币 §e*10000
+ commands:
+ - eco give %player% 10000
+```
+
+注意:
+
+```yml
+阶段奖励 lore 中需要包含 %time%
+累积奖励 lore 中需要包含 %amounut%
+```
+
+------
+
+# 14.常见问题
+
+## 执行 /auonline open 没反应
+
+检查:
+
+```yml
+是否在每日 00:10 之前
+DemonAPI 是否正常加载
+language.yml 中 Title 是否存在
+config.yml 中 RewardData 是否存在
+```
+
+## 奖励无法领取
+
+检查:
+
+```yml
+今日在线秒数是否达到 needOnlineTime
+距离上次领取是否达到 needOnline
+currentReward 是否等于当前点击的阶段奖励
+该奖励是否已经在 receivedList 中
+```
+
+## 累积奖励无法领取
+
+检查:
+
+```yml
+signAmount 是否达到 needOnline
+该奖励是否已经在 permanentReceived 中
+```
+
+## 修改配置后没有变化
+
+执行:
+
+```yml
+/auonline reload
+```
+
+如果删除了奖励节点,重载后旧奖励缓存会被清理。
+
+## 玩家数据异常
+
+可以刷新指定玩家每日数据:
+
+```yml
+/auonline dayrefresh 玩家名
+```
+
+也可以检查玩家数据文件:
+
+```yml
+plugins/AuData/AuOnlineReward/玩家名.yml
+```
+
+------
+
+# 15.维护建议
+
+- 修改奖励配置前,先备份 `config.yml`。
+- 大量玩家数据刷新建议避开服务器高峰期。
+- 奖励命令应先在测试服验证,避免命令拼写错误导致奖励无法发放。
+- 不建议把高耗时命令放入奖励命令列表。
+- 修改语言或奖励配置后,使用 `/auonline reload` 重载。
+- 发布新版本时,应同步检查 `plugin.yml` 的版本号。
+- 每次更新后建议至少完成一次登录、打开 GUI、领取奖励、退出保存的冒烟测试。
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c811293
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,77 @@
+
+
+ 4.0.0
+
+ com.yaohun.main
+ AuPet
+ 1.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+ ${project.basedir}/..
+
+
+
+
+
+ papermc-repo
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+
+
+ io.papermc.paper
+ paper-api
+ 1.21.8-R0.1-SNAPSHOT
+ provided
+
+
+ local
+ demon-api
+ local
+ system
+ ${parent.project.dir}/lib/DemonAPI.jar
+
+
+ local
+ nbt-api
+ local
+ system
+ ${parent.project.dir}/lib/item-nbt-api-plugin-2.15.1.jar
+
+
+ local
+ mythicmobs
+ local
+ system
+ ${parent.project.dir}/lib/MythicMobs-5.9.1.jar
+
+
+ local
+ bettermodel
+ local
+ system
+ ${parent.project.dir}/lib/BetterModel-2.2.0-paper.jar
+
+
+ local
+ craft-data-manager
+ local
+ system
+ ${parent.project.dir}/lib/CraftDataManager.jar
+
+
+ local
+ paper-1.21.8-mapped-server
+ 1.21.8
+ system
+ ${parent.project.dir}/.gradle/caches/paperweight/taskCache/mappedServerJar.jar
+
+
+
diff --git a/src/main/java/com/yaohun/petsystem/PetMain.java b/src/main/java/com/yaohun/petsystem/PetMain.java
new file mode 100644
index 0000000..d9e9ec9
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/PetMain.java
@@ -0,0 +1,265 @@
+package com.yaohun.petsystem;
+
+import com.yaohun.petsystem.api.PetExpAPI;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PetSQL;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.gui.CarryGui;
+import com.yaohun.petsystem.gui.FeedingGui;
+import com.yaohun.petsystem.gui.MainGui;
+import com.yaohun.petsystem.listener.PetSkillsListener;
+import com.yaohun.petsystem.listener.PlayerListener;
+import com.yaohun.petsystem.listener.RePetNameListener;
+import com.yaohun.petsystem.manage.PetManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class PetMain extends JavaPlugin {
+
+ private static PetMain instance;
+ private static PlayerManager playerManager;
+
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ saveDefaultConfig();
+
+ PetSQL.init();
+
+ MessageUtil.init(this);
+ Config.reloadConfig(this);
+ PetManager.reloadPetManager();
+ playerManager = new PlayerManager();
+
+ getServer().getPluginManager().registerEvents(new MainGui(), this);
+ getServer().getPluginManager().registerEvents(new FeedingGui(), this);
+ getServer().getPluginManager().registerEvents(new CarryGui(), this);
+
+ getServer().getPluginManager().registerEvents(new PlayerListener(), this);
+ getServer().getPluginManager().registerEvents(new RePetNameListener(), this);
+
+ getServer().getPluginManager().registerEvents(new PetSkillsListener(), this);
+ }
+
+ @Override
+ public void onDisable() {
+ getPlayerManager().closeSaveAllData();
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (!sender.hasPermission("admin.use")) {
+ if(args.length == 0){
+ if (sender instanceof Player player) {
+ MainGui.openGui(player);
+ }
+ return true;
+ }
+ return true;
+ }
+ String prefix = "§f[§c灵宠§f] §a";
+ if (args.length == 0) {
+ sender.sendMessage("§r");
+ sender.sendMessage("§e==----- ======= §6宠物系统 §e======= -----==");
+ sender.sendMessage("§2/" + label + " call §f- §2召唤宠物");
+ sender.sendMessage("§2/" + label + " carry §f- §2携带手持宠物");
+ if (sender.isOp()) {
+ sender.sendMessage("§2/" + label + " addExp §e[经验] §b<玩家> §7true §f- §2给予玩家宠物");
+ sender.sendMessage("§2/" + label + " give §e[宠物] §b<玩家> §f- §2给予玩家宠物");
+ sender.sendMessage("§2/" + label + " reload §f- §2重载配置文件");
+ }
+ sender.sendMessage("§e==----- ======= §6宠物系统 §e======= -----==");
+ sender.sendMessage("§r");
+ return true;
+ }
+ if ("reload".equalsIgnoreCase(args[0])) {
+ MessageUtil.init(this);
+ Config.reloadConfig(this);
+ PetManager.reloadPetManager();
+ sender.sendMessage(prefix + "配置文件已重载完成.");
+ return true;
+ }
+ if ("open".equalsIgnoreCase(args[0])) {
+ if (sender instanceof Player player) {
+ MainGui.openGui(player);
+ }
+ return true;
+ }
+ if ("test".equalsIgnoreCase(args[0])) {
+ if (sender instanceof Player player) {
+ // 判断玩家是否携带宠物
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ // 获取宠物相关数据
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return true;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onAttack".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(
+ player,
+ mmSkill,
+ player.getLocation()
+ );
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onAttack 触发技能: "+mmSkill);
+ break;
+ } else {
+ sender.sendMessage("# 拦截 冷却中...");
+ }
+ }
+ }
+ }
+ return true;
+ }
+ if ("carry".equalsIgnoreCase(args[0])) {
+ String playerName = sender.getName();
+ if (args.length >= 2) {
+ playerName = args[1];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ getPlayerManager().carryPetStackData(player);
+ return true;
+ }
+ if ("call".equalsIgnoreCase(args[0])) {
+ String playerName = sender.getName();
+ if (args.length >= 2) {
+ playerName = args[1];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ if (playerManager.isPetCallExit(playerName)) {
+ playerManager.removePetCall(playerName);
+ sender.sendMessage(prefix + "已收回 §6" + playerName + " §a的宠物.");
+ } else {
+ playerManager.callPet(player);
+ sender.sendMessage(prefix + "已召唤 §6" + playerName + " §a的宠物.");
+ }
+ return true;
+ }
+ if ("addexp".equalsIgnoreCase(args[0])) {
+ if (args.length == 1) {
+ sender.sendMessage(prefix + "缺少数值参数.");
+ return true;
+ }
+ int exp = Integer.parseInt(args[1]);
+ // 获取玩家数据
+ String playerName = sender.getName();
+ if (args.length >= 3) {
+ playerName = args[2];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ if (args.length >= 4) {
+ ItemStack stack = player.getInventory().getItemInMainHand();
+ if (DemonAPI.itemIsNull(stack) || DemonAPI.itemIsLore(stack)) {
+ sender.sendMessage(prefix + "执行失败,你需要将灵宠拿在手中.");
+ return true;
+ }
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ sender.sendMessage(prefix + "执行失败,你需要将灵宠拿在手中.");
+ return true;
+ }
+ PetNbt petNbt = PetExpAPI.addExp(playerName, stack, exp);
+ ItemStack petStack = PetUtil.refreshPetStackData(petNbt);
+ player.getInventory().setItemInMainHand(petNbt.saveIoItem(petStack));
+ sender.sendMessage(prefix + "给予 §6手持灵宠 §a经验: §d+" + exp);
+ } else {
+ PetExpAPI.addExp(player, exp);
+ sender.sendMessage(prefix + "给予 §6" + playerName + " §a的灵宠经验: §d+" + exp);
+ }
+ return true;
+ }
+ if ("give".equalsIgnoreCase(args[0])) {
+ if (args.length == 1) {
+ sender.sendMessage(prefix + "缺少宠物参数.");
+ return true;
+ }
+ String petKey = args[1];
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ sender.sendMessage(prefix + "未找到 " + petKey + " 的相关配置.");
+ return true;
+ }
+ String playerName = sender.getName();
+ if (args.length >= 3) {
+ playerName = args[2];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null) {
+ sender.sendMessage(prefix + "未找到玩家 " + playerName);
+ return true;
+ }
+ PetNbt petNbt = new PetNbt(petData);
+ ItemStack petStack = PetUtil.getPetStackDefault(petNbt);
+ player.getInventory().addItem(petNbt.saveIoItem(petStack));
+ DemonAPI.sendMessage(sender, "玩家: §e" + playerName + " §a宠物: §6" + petKey + " §r[背包]");
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public List onTabComplete(CommandSender sender, Command command, String label, String[] args) {
+ if (sender.isOp()) {
+ if (args.length == 2 && "give".equalsIgnoreCase(args[0])) {
+ return PetManager.getPetDataMap().keySet().stream()
+ .filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
+ .collect(Collectors.toList());
+ }
+ }
+ // 如果不符合上述条件,则返回空列表
+ return Collections.emptyList();
+ }
+
+ public static PetMain inst() {
+ return instance;
+ }
+
+ public static PlayerManager getPlayerManager() {
+ return playerManager;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java b/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
new file mode 100644
index 0000000..1126330
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
@@ -0,0 +1,122 @@
+package com.yaohun.petsystem.api;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PetManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import de.tr7zw.nbtapi.NBTItem;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+public class PetExpAPI {
+
+ public static void addExp(Player player, int addExp) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ System.out.println("[日志 - 灵宠] 玩家 " + playerName + " 尚未携带宠物.");
+ return;
+ }
+ PetNbt petNbt = playerData.getPetNbt();
+ String petKey = petNbt.petData.getFileName();
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ System.out.println("[日志 - 灵宠] 未添加 " + petKey + " 的宠物配置.");
+ return;
+ }
+ int petLevel = petNbt.level;
+ if (petLevel >= petData.getMaxLevel()) {
+ return;
+ }
+ // 进化阶段判断
+ int evolution = petNbt.evolution;
+ if (evolution == 1) {
+ if (petLevel >= 20) {
+ return;
+ }
+ } else if (evolution == 2) {
+ if (petLevel >= 40) {
+ return;
+ }
+ } else if (evolution == 3) {
+ if (petLevel >= 60) {
+ return;
+ }
+ } else if (evolution == 4) {
+ if (petLevel >= 70) {
+ return;
+ }
+ } else if (evolution == 5) {
+ if (petLevel >= 75) {
+ return;
+ }
+ }
+ int needExp = LevelExpManager.getNeedExpValue(petLevel);
+ petNbt.setTotalExp(petNbt.totalExp + addExp);
+
+ int nowExp = petNbt.exp;
+ addExp = nowExp + addExp;
+ while (addExp >= needExp) {
+ if (petLevel < 0) {
+ break;
+ }
+ addExp -= needExp;
+ petLevel++;
+ needExp = LevelExpManager.getNeedExpValue(petLevel);
+ bcmupLevelEvant(playerName, petLevel);
+ playerData.refreshPetStack();
+ }
+ petNbt.setExp(addExp);
+ petNbt.setLevel(petLevel);
+ playerData.savePlayerData();
+ }
+
+ public static PetNbt addExp(String playerName, ItemStack stack, int addExp) {
+ NBTItem nbtItem = new NBTItem(stack);
+ String petKey = nbtItem.getString("petKey");
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ System.out.println("[日志 - 灵宠] 未添加 " + petKey + " 的宠物配置.");
+ return null;
+ }
+ PetNbt petNbt = new PetNbt();
+ petNbt.loadNbtData(nbtItem);
+ int petLevel = petNbt.level;
+ if (petLevel >= petData.getMaxLevel()) {
+ return petNbt;
+ }
+ int needExp = LevelExpManager.getNeedExpValue(petLevel);
+ petNbt.setTotalExp(petNbt.totalExp + addExp);
+
+ int nowExp = petNbt.exp;
+ addExp = nowExp + addExp;
+ while (addExp >= needExp) {
+ if (petLevel < 0) {
+ break;
+ }
+ addExp -= needExp;
+ petLevel++;
+ needExp = LevelExpManager.getNeedExpValue(petLevel);
+ bcmupLevelEvant(playerName, petLevel);
+ }
+ petNbt.setExp(addExp);
+ petNbt.setLevel(petLevel);
+ return petNbt;
+ }
+
+ public static void bcmupLevelEvant(String playerName, int level) {
+ boolean butt = false;
+ if (level == 10 || level == 20 || level == 30 || level == 40 || level == 50 ||
+ level == 60 || level == 70 || level == 80 || level == 90 || level == 100
+ || level == 105 || level == 110 || level == 115 || level == 120 || level == 125) {
+ butt = true;
+ }
+ if (butt) {
+ // Bukkit.broadcastMessage("§f[§c§l公告§f] §a玩家§e"+playName+"§a本月器师手礼达到了 §7[§e§lLv."+level+"§7]");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/command/PetCommand.java b/src/main/java/com/yaohun/petsystem/command/PetCommand.java
new file mode 100644
index 0000000..6572657
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/command/PetCommand.java
@@ -0,0 +1,4 @@
+package com.yaohun.petsystem.command;
+
+public class PetCommand {
+}
diff --git a/src/main/java/com/yaohun/petsystem/config/Config.java b/src/main/java/com/yaohun/petsystem/config/Config.java
new file mode 100644
index 0000000..5c44203
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/config/Config.java
@@ -0,0 +1,141 @@
+package com.yaohun.petsystem.config;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.ColorUtil;
+import me.Demon.DemonPlugin.data.GuiItemData;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.*;
+
+public class Config {
+
+ private static ItemStack petStackTemplate;
+ private static NavigableMap qualityShowMap = new TreeMap<>();
+ private static HashMap guiItemDataMap = new HashMap<>();
+
+ public static void reloadConfig(PetMain plugin) {
+ plugin.reloadConfig();
+ plugin.saveConfig();
+ FileConfiguration config = plugin.getConfig();
+ Bukkit.getConsoleSender().sendMessage("§6[宠物系统] §7参数设定:");
+ LevelExpManager.reloadLevelManager(plugin);
+ loadPetStackTemplate(config);
+ loadGuiItemData(config);
+ loadQualityShowMap(config);
+ }
+
+ private static void loadGuiItemData(FileConfiguration config) {
+ guiItemDataMap.clear();
+ ConfigurationSection section = config.getConfigurationSection("GuiItemData");
+ if (section == null) {
+ return;
+ }
+ for (String itemKey : section.getKeys(false)) {
+ guiItemDataMap.put(itemKey, new GuiItemData(itemKey, section));
+ }
+ }
+
+ private static void loadQualityShowMap(FileConfiguration config) {
+ ConfigurationSection section = config.getConfigurationSection("QualityShow");
+ if (section == null) {
+ return;
+ }
+ for (String levelKey : section.getKeys(false)) {
+ int level = Integer.parseInt(levelKey);
+ String show = section.getString(levelKey).replace("&", "§");
+ qualityShowMap.put(level, show);
+ }
+ Bukkit.getConsoleSender().sendMessage("- 资质稀有度: §f" + qualityShowMap.size() + "个");
+ }
+
+ private static void loadPetStackTemplate(FileConfiguration config) {
+ String stackType = config.getString("Pet_Stack_Template.type", "DIAMOND");
+ Material material;
+ try {
+ material = Material.valueOf(stackType.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ Bukkit.getConsoleSender().sendMessage("- 宠物模板载入: §c失败");
+ return;
+ }
+ String stackName = config.getString("Pet_Stack_Template.name", "§f宠物");
+ List stringList = config.getStringList("Pet_Stack_Template.lore");
+ List stackLore = ColorUtil.listcolor(stringList);
+ ItemStack itemStack = new ItemStack(material);
+ ItemMeta meta = itemStack.getItemMeta();
+ meta.setDisplayName(stackName);
+ meta.setLore(stackLore);
+ itemStack.setItemMeta(meta);
+ petStackTemplate = itemStack;
+
+ String itemName = DemonAPI.getItemName(itemStack.clone());
+ Bukkit.getConsoleSender().sendMessage("- 宠物模板载入: §f" + itemName);
+ }
+
+ public static ItemStack getPetStackTemplate() {
+ return petStackTemplate.clone();
+ }
+
+ public static String getLanguage(String key) {
+ HashMap hashMap = MessageUtil.getLanguageMap();
+ if (hashMap.containsKey(key)) {
+ return hashMap.get(key);
+ }
+ return "缺少参数.§7" + key;
+ }
+
+ public static ItemStack getItemStack(String itemKey) {
+ if (guiItemDataMap.containsKey(itemKey)) {
+ ItemStack itemStack = guiItemDataMap.get(itemKey).getItemStack().clone();
+ if (itemStack.hasItemMeta()) {
+ ItemMeta meta = itemStack.getItemMeta();
+ if (meta.hasLore()) {
+ List originalLore = meta.getLore();
+ if (originalLore != null) {
+ // 创建新的lore列表进行处理
+ List newLore = new ArrayList<>();
+ for (String line : originalLore) {
+ // 应用颜色转换或其他处理逻辑
+ newLore.add(DemonAPI.convertHexColor(line));
+ }
+ // 设置处理后的新lore
+ meta.setLore(newLore);
+ itemStack.setItemMeta(meta);
+ }
+ }
+ }
+ return itemStack;
+ }
+ return DemonAPI.getErrItems();
+ }
+
+
+ public static String getQualityShow(int value) {
+ Map.Entry entry = qualityShowMap.floorEntry(value);
+ return entry != null ? entry.getValue() : qualityShowMap.get(0);
+ }
+
+ public static String getHealthChar(int percent) {
+ // 限制范围 [0,100]
+ percent = Math.max(0, Math.min(100, percent));
+ // 取整到最近的 10 倍数,例如 87 → 80,93 → 90
+ int step = (percent / 10) * 10;
+ // 处理 100 特殊情况
+ if (percent == 100) {
+ step = 100;
+ }
+ return "§f";
+ }
+
+ public static String getLevelChar(int level) {
+ return "§f";
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/data/PetData.java b/src/main/java/com/yaohun/petsystem/data/PetData.java
new file mode 100644
index 0000000..ae42937
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/data/PetData.java
@@ -0,0 +1,86 @@
+package com.yaohun.petsystem.data;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public class PetData {
+ private String fileName;
+ private String petName;
+ private int customIconModelId;
+ private int maxLevel;
+ private String modelId;
+ private List accessChannelsList;
+ private String summonSkill;
+ private String deathSkill;
+ private LinkedHashMap skillDataMap = new LinkedHashMap<>();
+
+ public PetData(String fileName, FileConfiguration config) {
+ this.fileName = fileName;
+ this.petName = config.getString("PetName");
+ this.maxLevel = config.getInt("MaxLevel");
+ this.modelId = config.getString("ModelId");
+ this.customIconModelId = config.getInt("CustomIconModelId");
+ this.accessChannelsList = config.getStringList("AccessChannels");
+ this.summonSkill = config.getString("SummonSkill", "default");
+ this.deathSkill = config.getString("DeathSkill", "default");
+ loadSkillData(config);
+ }
+
+ private void loadSkillData(FileConfiguration config) {
+ ConfigurationSection section = config.getConfigurationSection("Skills");
+ if (section == null) {
+ return;
+ }
+ for (String key : section.getKeys(false)) {
+ SkillData skillData = new SkillData(key, section);
+ skillDataMap.put(key, skillData);
+ }
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public String getPetName() {
+ return petName;
+ }
+
+ public int getMaxLevel() {
+ return maxLevel;
+ }
+
+ public String getModelId() {
+ return modelId;
+ }
+
+ public int getCustomIconModelId() {
+ return customIconModelId;
+ }
+
+ public List getAccessChannelsList() {
+ return accessChannelsList;
+ }
+
+ public String getSummonSkill() {
+ return summonSkill;
+ }
+
+ public String getDeathSkill() {
+ return deathSkill;
+ }
+
+ public LinkedHashMap getSkillDataMap() {
+ return skillDataMap;
+ }
+
+ public SkillData getSkillData(String skillName) {
+ if (skillDataMap.containsKey(skillName)) {
+ return skillDataMap.get(skillName);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/data/PetSQL.java b/src/main/java/com/yaohun/petsystem/data/PetSQL.java
new file mode 100644
index 0000000..c6074b2
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/data/PetSQL.java
@@ -0,0 +1,105 @@
+package com.yaohun.petsystem.data;
+
+import com.tianyu.datamanager.DataAPI;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.bukkit.Bukkit;
+
+import java.sql.*;
+
+public class PetSQL {
+
+ private static HikariDataSource dataSource;
+ private static String host;
+ private static String port;
+ private static String database;
+ private static String tables;
+ private static String username;
+ private static String password;
+
+ // 初始化数据库连接和表结构
+ public static void init() {
+ host = DataAPI.getMysql().getHost();
+ port = DataAPI.getMysql().getPort();
+ database = DataAPI.getMysql().getDatabase();
+ tables = "aupets_data";
+ username = DataAPI.getMysql().getUser();
+ password = DataAPI.getMysql().getPassword();
+
+ setupDataSource(); // 初始化连接池
+ initTable(); // 确保表结构已经创建
+ }
+
+ // 配置并初始化 HikariCP 连接池
+ private static void setupDataSource() {
+ HikariConfig config = new HikariConfig();
+ config.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ config.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database + "?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false");
+ config.setUsername(username);
+ config.setPassword(password);
+ config.setMaximumPoolSize(3);
+ config.setMinimumIdle(1);
+ config.setIdleTimeout(300000L);
+ config.setMaxLifetime(0L);
+ dataSource = new HikariDataSource(config);
+ }
+
+ // 初始化表结构(如果表不存在)
+ private static void initTable() {
+ String createTableSQL = "CREATE TABLE IF NOT EXISTS " + tables + " (" +
+ "PLAYERNAME VARCHAR(255) NOT NULL, " +
+ "PETSTACK TEXT NOT NULL, " +
+ "PRIMARY KEY (PLAYERNAME))";
+ try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
+ stmt.execute(createTableSQL);
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("初始化表结构失败: " + e.getMessage());
+ }
+ }
+
+ // 获取玩家宠物信息
+ public static String getPetStack(String playerName) {
+ String query = "SELECT PETSTACK FROM " + tables + " WHERE PLAYERNAME = ? LIMIT 1";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, playerName);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ return rs.getString("PETSTACK");
+ }
+ }
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("获取字符串值失败: " + e.getMessage());
+ }
+ return null;
+ }
+
+
+ // 保存数据
+ public static void saveData(String playerName, String petStack) {
+ String sql = "INSERT INTO " + tables + " (PLAYERNAME, PETSTACK) " +
+ "VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE PETSTACK = VALUES(PETSTACK)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, playerName);
+ ps.setString(2, petStack);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("保存宠物数据失败: " + e.getMessage());
+ }
+ }
+
+ // 获取数据库连接
+ private static Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ // 关闭连接池
+ public static void closeConnection() {
+ if (dataSource != null && !dataSource.isClosed()) {
+ dataSource.close();
+ Bukkit.getConsoleSender().sendMessage("数据库连接已成功关闭。#1");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/data/PlayerData.java b/src/main/java/com/yaohun/petsystem/data/PlayerData.java
new file mode 100644
index 0000000..a6c999b
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/data/PlayerData.java
@@ -0,0 +1,136 @@
+package com.yaohun.petsystem.data;
+
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.util.io.BukkitObjectInputStream;
+import org.bukkit.util.io.BukkitObjectOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+
+public class PlayerData {
+
+ private String playerName;
+ private ItemStack petStack;
+ private PetNbt petNbt;
+
+ public PlayerData(String playerName) {
+ this.playerName = playerName;
+ this.petStack = deserializeItemStack(PetSQL.getPetStack(playerName));
+ if (isPetStackNull()) {
+ return;
+ }
+ PetNbt petNbt = new PetNbt();
+ NBTItem nbtItem = new NBTItem(petStack);
+ petNbt.loadNbtData(nbtItem);
+ this.petNbt = petNbt;
+ }
+
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ public PetNbt getPetNbt() {
+ return petNbt;
+ }
+
+ public ItemStack getPetStack() {
+ return petStack;
+ }
+
+ public void rePetNameStack(String newName) {
+ ItemStack stack = petStack.clone();
+ ItemMeta meta = stack.getItemMeta();
+ meta.displayName(Component.text(newName)
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+ stack.setItemMeta(meta);
+ setPetStack(stack);
+ }
+
+ public void setPetStack(ItemStack petStack) {
+ this.petStack = petStack;
+ if (isPetStackNull()) {
+ return;
+ }
+ PetNbt petNbt = new PetNbt();
+ NBTItem nbtItem = new NBTItem(petStack);
+ petNbt.loadNbtData(nbtItem);
+ this.petNbt = petNbt;
+ }
+
+ public void refreshPetStack() {
+ ItemStack itemStack = PetUtil.getPetStackDefault(petNbt);
+ ItemMeta meta = itemStack.getItemMeta();
+ meta.displayName(Component.text(petStack.getItemMeta().getDisplayName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ petStack = petNbt.saveIoItem(itemStack);
+ }
+
+ public void savePlayerData() {
+ if (DemonAPI.itemIsNull(petStack)) {
+ PetSQL.saveData(playerName, serializeItemStack(DemonAPI.getErrItems()));
+ } else {
+ PetSQL.saveData(playerName, serializeItemStack(petStack));
+ }
+ }
+
+ public boolean isPetStackNull() {
+ return petStack == null || petStack.getType().equals(Material.BARRIER);
+ }
+
+ /**
+ * 将 ItemStack 序列化为 Base64 字符串
+ *
+ * @param itemStack 要序列化的 ItemStack
+ * @return 序列化后的 Base64 字符串
+ */
+ private String serializeItemStack(ItemStack itemStack) {
+ if (itemStack == null) {
+ return "";
+ }
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
+ dataOutput.writeObject(itemStack);
+ return Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ } catch (IOException e) {
+ Bukkit.getLogger().warning("[日志 - 灵宠] 序列化 ItemStack 时出错: " + e.getMessage());
+ return "";
+ }
+ }
+
+ /**
+ * 从 Base64 字符串反序列化为 ItemStack
+ *
+ * @param data Base64 字符串
+ * @return 反序列化后的 ItemStack
+ */
+ private ItemStack deserializeItemStack(String data) {
+ if (data == null || data.isEmpty()) {
+ return null;
+ }
+ try {
+ byte[] decodedData = Base64.getDecoder().decode(data);
+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedData);
+ BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
+ return (ItemStack) dataInput.readObject();
+ }
+ } catch (Exception e) {
+ Bukkit.getLogger().warning("[日志 - 灵宠] 反序列化 ItemStack 时出错: " + e.getMessage());
+ return DemonAPI.getErrItems();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/data/SkillData.java b/src/main/java/com/yaohun/petsystem/data/SkillData.java
new file mode 100644
index 0000000..1588fea
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/data/SkillData.java
@@ -0,0 +1,60 @@
+package com.yaohun.petsystem.data;
+
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.configuration.ConfigurationSection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SkillData {
+
+ private String skillName; // 技能名
+ private String mmSkill; // MM技能
+ private int needLevel; // 触发要求等级
+ private String trigger; // 触发器
+ private long coolDown; // 冷却时间
+ private String itemName;
+ private List itemLore = new ArrayList<>();
+
+ public SkillData(String skillName, ConfigurationSection section) {
+ this.skillName = skillName;
+ this.mmSkill = section.getString(skillName + ".mmSkill");
+ this.needLevel = section.getInt(skillName + ".needLevel");
+ this.trigger = section.getString(skillName + ".trigger");
+ this.coolDown = section.getLong(skillName + ".coolDown");
+ this.itemName = DemonAPI.convertHexColor(section.getString(skillName + ".item.name", "技能 I"));
+ List itemlore = section.getStringList(skillName + ".item.lore");
+ // Bukkit.getConsoleSender().sendMessage("[调试] name: "+skillName+" itemlore = "+itemlore);
+ for (String line : itemlore) {
+ this.itemLore.add(DemonAPI.convertHexColor(line));
+ }
+ }
+
+ public String getSkillName() {
+ return skillName;
+ }
+
+ public String getMmSkill() {
+ return mmSkill;
+ }
+
+ public int getNeedLevel() {
+ return needLevel;
+ }
+
+ public String getTrigger() {
+ return trigger;
+ }
+
+ public long getCoolDown() {
+ return coolDown;
+ }
+
+ public String getItemName() {
+ return itemName;
+ }
+
+ public List getItemLore() {
+ return itemLore;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java b/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
new file mode 100644
index 0000000..629dee4
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
@@ -0,0 +1,47 @@
+package com.yaohun.petsystem.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+import java.util.UUID;
+
+public class PetCallEvent extends Event {
+
+ private final Player owner;
+ private final String cutomName;
+ private final UUID entityUuid;
+ private static final HandlerList handlers = new HandlerList();
+
+ public PetCallEvent(Player owner, String cutomName, UUID entityUuid) {
+ this.owner = owner;
+ this.cutomName = cutomName;
+ this.entityUuid = entityUuid;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ public Player getOwner() {
+ return owner;
+ }
+
+ public String getCutomName() {
+ return cutomName;
+ }
+
+ public UUID getEntityUuid() {
+ return entityUuid;
+ }
+
+ @Override
+ public String getEventName() {
+ return super.getEventName();
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/gui/CarryGui.java b/src/main/java/com/yaohun/petsystem/gui/CarryGui.java
new file mode 100644
index 0000000..a3851ad
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/CarryGui.java
@@ -0,0 +1,89 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.gui.holder.CarryGuiHolder;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+public class CarryGui implements Listener {
+
+ private static final int PET_SLOT = 4;
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ Inventory inv = Bukkit.createInventory(new CarryGuiHolder(), 9, Config.getLanguage("carry_gui_title"));
+ if (playerData.isPetStackNull()) {
+ inv.setItem(PET_SLOT, Config.getItemStack("carry_gui"));
+ } else {
+ inv.setItem(PET_SLOT, playerData.getPetStack());
+ }
+ player.openInventory(inv);
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof CarryGuiHolder)) {
+ e.setCancelled(true);
+ ItemStack stack = e.getCurrentItem();
+ if (DemonAPI.itemIsNull(stack)) {
+ return;
+ }
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (rawSlot == PET_SLOT) {
+ if (!playerData.isPetStackNull()) {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ inv.setItem(PET_SLOT, Config.getItemStack("carry_gui"));
+ }
+ return;
+ }
+ if (playerData.isPetStackNull()) {
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ MessageUtil.sendMessageKey(player, "hand_not_pet_stack", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ e.setCurrentItem(new ItemStack(Material.AIR));
+ playerData.setPetStack(stack);
+ playerData.savePlayerData();
+ String itemName = DemonAPI.getItemName(stack);
+ String message = Config.getLanguage("pet_stack_carry_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", itemName), Sound.ENTITY_PLAYER_LEVELUP);
+ MainGui.openGui(player);
+ } else {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java b/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
new file mode 100644
index 0000000..88c0211
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
@@ -0,0 +1,206 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.api.PetExpAPI;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.gui.holder.FeedingGuiHolder;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+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.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.List;
+
+public class FeedingGui implements Listener {
+
+ private final static int FOOD_SLOT = 15;
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "gui_no_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String invTitle = Config.getLanguage("main_gui_title");
+ if (playerManager.isPetCallExit(playerName)) {
+ invTitle = Config.getLanguage("main_gui_title") + Config.getLanguage("call_gui_title");
+ }
+ Inventory inv = Bukkit.createInventory(new FeedingGuiHolder(), 36, invTitle + Config.getLanguage("feeding_gui_title"));
+ // 关闭界面
+ inv.setItem(1, Config.getItemStack("close"));
+ //食物槽
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ checkFoodGuiPetState(inv, playerData);
+ player.openInventory(inv);
+ }
+
+ private static void checkFoodGuiPetState(Inventory inv, PlayerData playerData) {
+ inv.setItem(23, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(24, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(25, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(11, PetUtil.getPetGuiStack(playerData, "chongwu_feed"));
+ }
+
+ private static ItemStack getFoodButt(PetNbt petNbt) {
+ ItemStack stack = Config.getItemStack("weishibutton");
+ ItemMeta meta = stack.getItemMeta();
+ List lore = meta.getLore();
+ lore.replaceAll(s -> DemonAPI.convertHexColor(s
+ .replace("{petlevel}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))));
+ meta.setLore(lore);
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ private ItemStack getFooedStack(Inventory inv) {
+ ItemStack foodStack = inv.getItem(FOOD_SLOT);
+ if (DemonAPI.itemIsNull(foodStack) || DemonAPI.itemIsLore(foodStack)) {
+ return null;
+ }
+ return foodStack;
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ String playerName = player.getName();
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof FeedingGuiHolder)) {
+ e.setCancelled(true);
+ if (rawSlot > 35) {
+ ItemStack clickStack = e.getCurrentItem();
+ if (DemonAPI.itemIsNull(clickStack) || DemonAPI.itemIsLore(clickStack)) {
+ return;
+ }
+ NBTItem nbtItem = new NBTItem(clickStack);
+ if (nbtItem.hasKey("petAppleExp") || nbtItem.hasKey("petAppleUp")) {
+ // 判断槽位内是否已存在物品
+ ItemStack foodStack = getFooedStack(inv);
+ if (foodStack != null) {
+ player.getInventory().addItem(foodStack);
+ }
+ e.setCurrentItem(null);
+ inv.setItem(FOOD_SLOT, clickStack);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_not_apple", Sound.ENTITY_VILLAGER_NO);
+ }
+ return;
+ }
+ if (rawSlot == FOOD_SLOT) {
+ // 判断槽位内是否已存在物品
+ ItemStack foodStack = getFooedStack(inv);
+ if (foodStack != null) {
+ player.getInventory().addItem(foodStack);
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ }
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 1) {
+ MainGui.openGui(player);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 23 || rawSlot == 24 || rawSlot == 25) {
+ // 获取实物槽位中的物品
+ ItemStack foodStack = inv.getItem(FOOD_SLOT);
+ if (DemonAPI.itemIsNull(foodStack) || DemonAPI.itemIsLore(foodStack)) {
+ return;
+ }
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ String itemName = DemonAPI.getItemName(foodStack);
+ NBTItem nbtItem = new NBTItem(foodStack);
+ if (nbtItem.hasKey("petAppleExp")) {
+ // 扣除果实
+ if (foodStack.getAmount() == 1) {
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ } else {
+ foodStack.setAmount(foodStack.getAmount() - 1);
+ }
+ // 获取增加经验
+ int addExp = Integer.parseInt(nbtItem.getString("petAppleExp"));
+ PetExpAPI.addExp(player, addExp);
+ String message = Config.getLanguage("pet_feed_exp_success");
+ message = message.replace("{itemName}", itemName);
+ message = message.replace("{exp}", String.valueOf(addExp));
+ MessageUtil.sendMessage(player, message, Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
+ // 刷新宠物经验
+ checkFoodGuiPetState(inv, playerData);
+ playerData.refreshPetStack();
+ } else if (nbtItem.hasKey("petAppleUp")) {
+ PetNbt petNbt = playerData.getPetNbt();
+ // 获取进化阶段
+ int needLevel = Integer.parseInt(nbtItem.getString("petAppleUp"));
+ if (needLevel == 20) {
+ feedEvolutionEvent(player, needLevel, petNbt, 1, 2, itemName);
+ } else if (needLevel == 40) {
+ feedEvolutionEvent(player, needLevel, petNbt, 2, 3, itemName);
+ } else if (needLevel == 60) {
+ feedEvolutionEvent(player, needLevel, petNbt, 3, 4, itemName);
+ } else if (needLevel == 70) {
+ feedEvolutionEvent(player, needLevel, petNbt, 4, 5, itemName);
+ } else if (needLevel == 75) {
+ feedEvolutionEvent(player, needLevel, petNbt, 5, 6, itemName);
+ }
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_not_apple", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+ }
+ }
+
+ private void feedEvolutionEvent(Player player, int appleLevel, PetNbt petNbt, int needEvolution, int nextEvolution, String itemName) {
+ int evolution = petNbt.evolution;
+ int petLevel = petNbt.level;
+ if (evolution == nextEvolution) {
+ MessageUtil.sendMessageKey(player, "pet_feed_evolution_already", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ if (evolution == needEvolution) {
+ // 判断宠物等级是否满足要求
+ if (petLevel < appleLevel) {
+ String message = Config.getLanguage("pet_feed_evolution_need_level");
+ MessageUtil.sendMessage(player, message.replace("{level}", Config.getLevelChar(appleLevel)), Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ petNbt.setEvolution(nextEvolution);
+ String message = Config.getLanguage("pet_feed_evolution_success");
+ MessageUtil.sendMessage(player, message.replace("{itemName}", itemName), Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_evolution_request", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+
+ @EventHandler
+ public void onClose(InventoryCloseEvent e) {
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof FeedingGuiHolder)) {
+ Player player = (Player) e.getPlayer();
+ ItemStack stack = getFooedStack(inv);
+ if (stack != null) {
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ player.getInventory().addItem(stack);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/gui/MainGui.java b/src/main/java/com/yaohun/petsystem/gui/MainGui.java
new file mode 100644
index 0000000..70f50a8
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/MainGui.java
@@ -0,0 +1,185 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.gui.holder.MainGuiHolder;
+import com.yaohun.petsystem.listener.RePetNameListener;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.List;
+
+public class MainGui implements Listener {
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ CarryGui.openGui(player);
+ // MessageUtil.sendMessageKey(player, "gui_no_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String invTitle = Config.getLanguage("main_gui_title");
+ String buttonKey = "zhaohuan";
+ if (playerManager.isPetCallExit(playerName)) {
+ invTitle = Config.getLanguage("main_gui_title") + Config.getLanguage("call_gui_title");
+ buttonKey = "zhaohui";
+ }
+ Inventory inv = Bukkit.createInventory(new MainGuiHolder(), 36, invTitle);
+ // 关闭界面
+ inv.setItem(0, Config.getItemStack("close"));
+ // 宠物图鉴
+ inv.setItem(1, Config.getItemStack("pettujian"));
+ // 宠物物品显示
+ inv.setItem(11, PetUtil.getPetGuiStack(playerData, "chongwu"));
+ // 宠物技能显示
+ PetData petData = playerData.getPetNbt().petData;
+ int slot = 13;
+ for (SkillData skillData : petData.getSkillDataMap().values()) {
+ inv.setItem(slot, getSkillGoldenApple(skillData));
+ slot++;
+ }
+ // 宠物召唤按钮
+ ItemStack zhaohuanStack = Config.getItemStack(buttonKey);
+ inv.setItem(29, zhaohuanStack);
+ inv.setItem(30, zhaohuanStack);
+ // 宠物喂养按钮
+ inv.setItem(31, Config.getItemStack("weiyang"));
+ // 宠物工作按钮
+ inv.setItem(32, Config.getItemStack("gongzuo"));
+ inv.setItem(33, Config.getItemStack("gongzuo"));
+ // 宠物品质数据查询
+ inv.setItem(22, getQualificationStack(playerData, "fangs"));
+ inv.setItem(23, getQualificationStack(playerData, "survive"));
+ inv.setItem(24, getQualificationStack(playerData, "efficiency"));
+ // 仓库、Wiki按钮
+ inv.setItem(7, Config.getItemStack("cangku"));
+ inv.setItem(8, Config.getItemStack("wiki"));
+ player.openInventory(inv);
+ }
+
+ private static ItemStack getSkillGoldenApple(SkillData skillData) {
+ ItemStack stack = new ItemStack(Material.GOLDEN_APPLE);
+ ItemMeta meta = stack.getItemMeta();
+ meta.displayName(Component.text(skillData.getItemName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+ meta.setLore(skillData.getItemLore());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ private static ItemStack getQualificationStack(PlayerData playerData, String type) {
+ PetNbt petNbt = playerData.getPetNbt();
+ int value = 0;
+ if ("fangs".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getFangs();
+ } else if ("survive".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getSurvive();
+ } else if ("efficiency".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getEfficiency();
+ }
+ ItemStack stack = Config.getItemStack(type);
+ ItemMeta meta = stack.getItemMeta();
+ List lore = meta.getLore();
+ for (int i = 0; i < lore.size(); i++) {
+ String line = lore.get(i);
+ if (line.contains("{qualityShow}")) {
+ lore.set(i, DemonAPI.convertHexColor(line).replace("{qualityShow}", Config.getQualityShow(value)));
+ break;
+ }
+ }
+ meta.setLore(lore);
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ if ((e.getInventory().getHolder() instanceof MainGuiHolder)) {
+ e.setCancelled(true);
+ // 关闭界面按钮
+ if (rawSlot == 0) {
+ player.closeInventory();
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 1) {
+ player.closeInventory();
+ // 打开宠物图鉴操作
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ // Wiki按钮
+ if (rawSlot == 8) {
+ player.closeInventory();
+ MessageUtil.sendMessageKey(player, "gui_wiki_message", Sound.UI_BUTTON_CLICK);
+ }
+ // 仓库按钮
+ if (rawSlot == 7) {
+ player.closeInventory();
+ // 打开便捷仓库操作
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (rawSlot == 11) {
+ if (e.isShiftClick()) {
+ String playerName = player.getName();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (!playerData.isPetStackNull()) {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ player.closeInventory();
+ Bukkit.getScheduler().runTaskLater(PetMain.inst(), player::updateInventory, 2L);
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ return;
+ }
+ if (e.isLeftClick()) {
+ player.closeInventory();
+ RePetNameListener.addReNameUUID(player.getUniqueId());
+ MessageUtil.sendMessageKey(player, "pet_rename_add", Sound.UI_BUTTON_CLICK);
+ }
+ }
+ // 召唤宠物操作按钮
+ if (rawSlot == 29 || rawSlot == 30) {
+ player.closeInventory();
+ playerManager.callPet(player);
+ }
+ // 打开投喂宠物界面
+ if (rawSlot == 31) {
+ FeedingGui.openGui(player);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ // 打开打工系统
+ if (rawSlot == 32 || rawSlot == 33) {
+ player.closeInventory();
+ MessageUtil.sendMessageKey(player, "imperfect_function", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java b/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
new file mode 100644
index 0000000..9ae4857
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class CarryGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java b/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
new file mode 100644
index 0000000..a97e280
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class FeedingGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java b/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
new file mode 100644
index 0000000..496777d
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class MainGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java b/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
new file mode 100644
index 0000000..3e89c8d
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
@@ -0,0 +1,94 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+
+import java.util.LinkedHashMap;
+import java.util.UUID;
+
+public class PetSkillsListener implements Listener {
+
+ @EventHandler
+ public void onDamager(EntityDamageByEntityEvent e){
+ if(e.getDamager() instanceof Player player){
+ // 判断玩家是否携带宠物
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ // 获取宠物相关数据
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ int petLevel = playerData.getPetNbt().level;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ if(petLevel >= skillData.getNeedLevel()) {
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onAttack".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(
+ player,
+ mmSkill,
+ player.getLocation()
+ );
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onAttack 触发技能: "+skillData.getSkillName());
+ }
+ }
+ }
+ }
+ }
+ if(e.getEntity() instanceof Player player){
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ int petLevel = playerData.getPetNbt().level;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ if(petLevel >= skillData.getNeedLevel()) {
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onDamaged".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(player, mmSkill, player.getLocation());
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onDamaged 触发技能: "+skillData.getSkillName());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*public boolean runSkills(SkillCaster caster, SkillTrigger cause, AbstractLocation origin, AbstractEntity trigger, Consumer transformer) {
+ return new TriggeredSkill(cause, caster, origin, trigger, this.artifact.getMechanics(cause), true, meta -> {
+ transformer.getVariables().putString("equip-slot", this.equippedSlot);
+ transformer.getVariables().putObject("equip-item", this.itemStack);
+ if (r5 != null) {
+ r5.accept(transformer);
+ }
+ }, new Pair[0]).getCancelled();
+ }*/
+}
diff --git a/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java b/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
new file mode 100644
index 0000000..90a5b68
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
@@ -0,0 +1,79 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.PetUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Wolf;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.util.UUID;
+
+public class PlayerListener implements Listener {
+
+ @EventHandler
+ public void onDamage(EntityDamageByEntityEvent e) {
+ if (e.getEntity() instanceof Player) {
+ return;
+ }
+ if (!(e.getEntity() instanceof LivingEntity target)) {
+ return;
+ }
+ if (e.getDamager() instanceof Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (!playerManager.isPetCallExit(playerName)) {
+ return;
+ }
+ UUID petUUID = playerManager.getPetUUID(playerName);
+ if (petUUID == null) {
+ return;
+ }
+ Entity entity = Bukkit.getEntity(petUUID);
+ if (entity instanceof Wolf wolf) {
+ wolf.setTarget(target);
+ }
+ }
+ }
+
+ // 检查伤害 狼造成的伤害
+ @EventHandler
+ public void onDamageCheck(EntityDamageByEntityEvent e) {
+ if (e.getDamager() instanceof Wolf wolf) {
+ PlayerData playerData = PetUtil.updatePetNameTag(wolf);
+ if (playerData != null) {
+ double health = wolf.getHealth();
+ PetNbt petNbt = playerData.getPetNbt();
+ petNbt.setHealth((int) health);
+ }
+ }
+ }
+
+ @EventHandler
+ public void onDeath(EntityDeathEvent e) {
+ String ownerName = PetMain.getPlayerManager().getPetOwnerName(e.getEntity().getUniqueId());
+ if (ownerName == null) {
+ return;
+ }
+ PlayerData playerData = PetMain.getPlayerManager().getPlayerData(ownerName);
+ playerData.getPetNbt().setHealth(0);
+ PetMain.getPlayerManager().removePetCall(ownerName);
+ }
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent e){
+ Player player = e.getPlayer();
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ playerManager.removePetCall(playerName);
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java b/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
new file mode 100644
index 0000000..de4e730
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
@@ -0,0 +1,62 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class RePetNameListener implements Listener {
+
+ private static List reNameList = new ArrayList<>();
+
+ public static void addReNameUUID(UUID uuid) {
+ if (!reNameList.contains(uuid)) {
+ reNameList.add(uuid);
+ }
+ }
+
+ @EventHandler
+ public void onChat(AsyncPlayerChatEvent e) {
+ Player player = e.getPlayer();
+ UUID uuid = player.getUniqueId();
+ if (reNameList.isEmpty()) {
+ return;
+ }
+ if (!reNameList.contains(uuid)) {
+ return;
+ }
+ e.setCancelled(true);
+ reNameList.remove(uuid);
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "pet_rename_no_pets", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String reName = e.getMessage();
+ if (reName.isEmpty()) {
+ MessageUtil.sendMessageKey(player, "pet_rename_empty", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ if (reName.length() < 3) {
+ MessageUtil.sendMessageKey(player, "pet_rename_length", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String newName = reName.replace("&", "§");
+ playerData.rePetNameStack(newName);
+ playerData.savePlayerData();
+ String message = Config.getLanguage("pet_rename_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", newName), Sound.ENTITY_PLAYER_LEVELUP);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java b/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
new file mode 100644
index 0000000..42133c8
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
@@ -0,0 +1,35 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class LevelExpManager {
+
+ private static HashMap needExpMap = new HashMap<>();
+
+ public static void reloadLevelManager(PetMain plugin) {
+ File file = new File(plugin.getDataFolder() + "/Settings", "needExp.yml");
+ if (file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String levelKey : config.getKeys(false)) {
+ int level = Integer.parseInt(levelKey);
+ int needExp = config.getInt(levelKey);
+ needExpMap.put(level, needExp);
+ }
+ Bukkit.getConsoleSender().sendMessage("§7- 等级经验配置: §f" + needExpMap.size() + "条");
+ }
+
+ public static int getNeedExpValue(int level) {
+ if (needExpMap.containsKey(level)) {
+ return needExpMap.get(level);
+ }
+ return 1000000;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java b/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
new file mode 100644
index 0000000..df1db1e
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
@@ -0,0 +1,48 @@
+package com.yaohun.petsystem.manage;
+
+import kr.toxicity.model.api.tracker.Tracker;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+
+import java.util.UUID;
+
+public class PetCallSession {
+
+ private final UUID ownerUuid;
+ private final UUID entityUuid;
+ private final Tracker tracker;
+
+ public PetCallSession(UUID ownerUuid, UUID entityUuid, Tracker tracker) {
+ this.ownerUuid = ownerUuid;
+ this.entityUuid = entityUuid;
+ this.tracker = tracker;
+ }
+
+ public UUID getOwnerUuid() {
+ return ownerUuid;
+ }
+
+ public UUID getEntityUuid() {
+ return entityUuid;
+ }
+
+ public Entity getEntity() {
+ return Bukkit.getEntity(entityUuid);
+ }
+
+ public boolean isAlive() {
+ Entity entity = getEntity();
+ return entity != null && !entity.isDead();
+ }
+
+ public void close() {
+ if (tracker != null && !tracker.isClosed()) {
+ tracker.despawn();
+ tracker.close();
+ }
+ Entity entity = getEntity();
+ if (entity != null && !entity.isDead()) {
+ entity.remove();
+ }
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/manage/PetManager.java b/src/main/java/com/yaohun/petsystem/manage/PetManager.java
new file mode 100644
index 0000000..5996459
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/manage/PetManager.java
@@ -0,0 +1,71 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class PetManager {
+
+ private static HashMap petDataMap = new HashMap<>();
+
+ public static void reloadPetManager() {
+ petDataMap.clear();
+ Bukkit.getConsoleSender().sendMessage("§6[宠物系统] §7宠物类型:");
+ String folderPath = PetMain.inst().getDataFolder().getPath() + "/PetData";
+ File folder = new File(folderPath);
+ if (!folder.exists() || !folder.isDirectory()) {
+ System.out.println("[日志 - 灵宠] 未检测到任何宠物配置: " + folderPath);
+ return;
+ }
+ List ymlFiles = new ArrayList<>();
+ DemonAPI.collectYmlFiles(folder, ymlFiles);
+ if (ymlFiles.isEmpty()) {
+ System.out.println("[日志 - 灵宠] 未检测到任何宠物配置: " + folderPath);
+ return;
+ }
+ for (File file : ymlFiles) {
+ String fileName = file.getName().replace(".yml", "");
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ PetData petData = new PetData(fileName, config);
+ petDataMap.put(fileName, petData);
+ Bukkit.getConsoleSender().sendMessage("- 宠物: §f" + petData.getPetName() + " §7MaxLevel: §f" + petData.getMaxLevel());
+ }
+ }
+
+ public static HashMap getPetDataMap() {
+ return petDataMap;
+ }
+
+ public static PetData getItemNameToPetData(String itemName) {
+ for (PetData petData : petDataMap.values()) {
+ if (petData.getPetName().contains(itemName)) {
+ return petData;
+ }
+ }
+ return null;
+ }
+
+ public static PetData getPetData(String petKey) {
+ if (petDataMap.containsKey(petKey)) {
+ return petDataMap.get(petKey);
+ }
+ return null;
+ }
+
+ // 获取宠物获取渠道
+ public static List getPetAccessChannels(String petKey) {
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ return new ArrayList<>();
+ }
+ return petData.getAccessChannelsList();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java b/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
new file mode 100644
index 0000000..5e5e432
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
@@ -0,0 +1,253 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PetSQL;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.event.PetCallEvent;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.model.BetterModelUtil;
+import com.yaohun.petsystem.util.wolf.CustomWolf;
+import de.tr7zw.nbtapi.NBTItem;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import kr.toxicity.model.api.tracker.Tracker;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+public class PlayerManager {
+
+ private HashMap playerDataMap = new HashMap<>();
+ private HashMap petCallMap = new HashMap<>();
+ // private HashMap petEntityModelMap = new HashMap<>();
+
+ public PlayerData getPlayerData(String name) {
+ if (!playerDataMap.containsKey(name)) {
+ playerDataMap.put(name, new PlayerData(name));
+ }
+ return playerDataMap.get(name);
+ }
+
+ public void saveAllData() {
+ for (PlayerData playerData : playerDataMap.values()) {
+ playerData.savePlayerData();
+ }
+ }
+
+ public void closeSaveData(String playerName) {
+ PlayerData playerData = getPlayerData(playerName);
+ playerData.savePlayerData();
+ playerDataMap.remove(playerName);
+
+ removePetCall(playerName);
+ }
+
+ public void closeSaveAllData() {
+ for (PlayerData playerData : playerDataMap.values()) {
+ playerData.savePlayerData();
+ }
+ playerDataMap.clear();
+ for (PetCallSession session : petCallMap.values()) {
+ session.close();
+ }
+ PetSQL.closeConnection();
+ petCallMap.clear();
+ }
+
+ public void setPetCall(String name, UUID ownerUuid, UUID entityUuid, Tracker tracker) {
+ petCallMap.put(name, new PetCallSession(ownerUuid, entityUuid, tracker));
+ }
+
+ public void removePetCall(String name) {
+ PetCallSession session = petCallMap.remove(name);
+ if (session != null) {
+ session.close();
+ }
+ }
+
+ public boolean isPetCallExit(String name) {
+ PetCallSession session = petCallMap.get(name);
+ if (session == null) {
+ return false;
+ }
+ if (!session.isAlive()) {
+ petCallMap.remove(name);
+ session.close();
+ return false;
+ }
+ return true;
+ }
+
+ public String getPetOwnerName(UUID entityUuid) {
+ for (String name : petCallMap.keySet()) {
+ PetCallSession session = petCallMap.get(name);
+ if (session.getEntityUuid().equals(entityUuid)) {
+ return name;
+ }
+ }
+ return null;
+ }
+
+ public UUID getPetUUID(String name) {
+ if (isPetCallExit(name)) {
+ return petCallMap.get(name).getEntityUuid();
+ }
+ return null;
+ }
+/*
+ public ModeledEntity getPetEntityModel(UUID entityUuid) {
+ for (ModeledEntity entity : petEntityModelMap.values()) {
+ if (entity.getBase().getUUID().equals(entityUuid)) {
+ return entity;
+ }
+ }
+ return null;
+ }
+
+ public void setPetEntityModelMap(UUID entityUUID, ModeledEntity entityModel) {
+ petEntityModelMap.put(entityUUID, entityModel);
+ }*/
+
+ public void callPet(Player player) {
+ String carryCdKey = "callPetStackCD";
+ UUID uuid = player.getUniqueId();
+ if (CDTimeAPI.isCD(uuid, carryCdKey)) {
+ MessageUtil.sendMessageKey(player, "system_coolingDown", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ CDTimeAPI.setPlayerCD(uuid, carryCdKey, 1000 * 5);
+ String playerName = player.getName();
+ PlayerData playerData = getPlayerData(playerName);
+ if (playerData == null) {
+ return;
+ }
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "no_summons_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ // 判断玩家是否已经召唤宠物
+ if (isPetCallExit(playerName)) {
+ removePetCall(playerName);
+ MessageUtil.sendMessageKey(player, "pet_summons_recovers", Sound.UI_BUTTON_CLICK);
+ return;
+ }
+ int health = playerData.getPetNbt().health;
+ if (health < 1) {
+ MessageUtil.sendMessageKey(player, "no_summons_pets_health", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String petStackName = DemonAPI.getItemName(playerData.getPetStack());
+ CustomWolf wolf = CustomWolf.spawnCustomWolf(player, playerData.getPetNbt());
+ PetData petData = playerData.getPetNbt().petData;
+ String modelID = petData.getModelId();
+ Tracker tracker = BetterModelUtil.spawnModel(player, wolf.getBukkitEntity(), modelID);
+ if (tracker == null) {
+ wolf.getBukkitEntity().remove();
+ MessageUtil.sendMessage(player, "§c宠物模型载入失败,召唤已取消。", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ setPetCall(playerName, player.getUniqueId(), wolf.getUUID(), tracker);
+ /*ActiveModel model = ModelEngineAPI.createActiveModel(modelID);
+ if (model != null) {
+ ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(wolf.getBukkitEntity());
+ setPetEntityModelMap(wolf.getUUID(), modeledEntity);
+ modeledEntity.addModel(model, true);
+ modeledEntity.setBaseEntityVisible(false);
+ model.setHitboxVisible(true);
+ model.setShadowVisible(true);
+ String text = Config.getLanguage("pet_name_tag")
+ .replace("{name}", playerName)
+ .replace("{petName}", petStackName);
+
+ wolf.setCustomName(Component.literal(text));
+ wolf.setCustomNameVisible(true);
+
+ NameTag tag = PetUtil.getNameBone(modeledEntity, "name");
+ if (tag != null) {
+ tag.setString(text);
+ tag.setVisible(true);
+ }
+ int petLevel = playerData.getPetNbt().level;
+ double healthNow = wolf.getHealth();
+ double healthMax = wolf.getMaxHealth();
+ // 计算获得血量百分比
+ int percent = (int) Math.round((healthNow / healthMax) * 100.0);
+ String levelText = Config.getLanguage("pet_level_tag")
+ .replace("{level}", Config.getLevelChar(petLevel))
+ .replace("{health}", Config.getHealthChar(percent));
+ NameTag levelTag = PetUtil.getNameBone(modeledEntity, "level");
+ if (levelTag != null) {
+ levelTag.setString(levelText);
+ levelTag.setVisible(true);
+ }
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 模型资源 " + modelID + " 载入成功!");
+ } else {
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 模型资源 " + modelID + " 载入失败!");
+ }*/
+ String summonSkill = petData.getSummonSkill();
+ if (!"default".equalsIgnoreCase(summonSkill)) {
+ Bukkit.getScheduler().runTaskLater(PetMain.inst(), () -> {
+ if (wolf.isRemoved() || wolf.getBukkitLivingEntity().isDead()) {
+ return;
+ }
+ MythicBukkit.inst().getAPIHelper().castSkill(wolf.getBukkitLivingEntity(), summonSkill, wolf.getBukkitLivingEntity().getLocation());
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onSpawn 触发技能: "+summonSkill);
+ }, 2L);
+ }
+ // 设置宠物召唤状态
+ MessageUtil.sendMessageKey(player, "pet_summons_success", Sound.UI_BUTTON_CLICK);
+ // 发布事件
+ PetCallEvent event = new PetCallEvent(player, wolf.getBukkitEntity().getCustomName(), wolf.getUUID());
+ Bukkit.getPluginManager().callEvent(event);
+ }
+
+ public void carryPetStackData(Player player) {
+ String carryCdKey = "carryPetStackCD";
+ UUID uuid = player.getUniqueId();
+ if (CDTimeAPI.isCD(uuid, carryCdKey)) {
+ MessageUtil.sendMessageKey(player, "system_coolingDown", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ CDTimeAPI.setPlayerCD(uuid, carryCdKey, 1000 * 5);
+ String playerName = player.getName();
+ PlayerData playerData = getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ ItemStack stack = player.getInventory().getItemInMainHand();
+ if (DemonAPI.itemIsNull(stack)) {
+ MessageUtil.sendMessageKey(player, "error_empty_hand", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ MessageUtil.sendMessageKey(player, "hand_not_pet_stack", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ player.getInventory().setItemInMainHand(null);
+ playerData.setPetStack(stack);
+ playerData.savePlayerData();
+ String itemName = DemonAPI.getItemName(stack);
+ String message = Config.getLanguage("pet_stack_carry_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", itemName), Sound.ENTITY_PLAYER_LEVELUP);
+ } else {
+ removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ NBTItem nbtItem = new NBTItem(petStack);
+ if (!nbtItem.hasKey("petKey")) {
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 未检测到宠物petKey的Nbt标识.");
+ }
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/model/PetNbt.java b/src/main/java/com/yaohun/petsystem/model/PetNbt.java
new file mode 100644
index 0000000..b156afa
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/model/PetNbt.java
@@ -0,0 +1,111 @@
+package com.yaohun.petsystem.model;
+
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PetManager;
+import de.tr7zw.nbtapi.NBTItem;
+import org.bukkit.inventory.ItemStack;
+
+public class PetNbt {
+
+ private String petKey;
+ public PetData petData;
+ public int level;
+ public int exp;
+ public int maxExp;
+ public int totalExp;
+ public int damage;
+ public int health;
+ public int maxHealth;
+ public int evolution;
+ public PetQuality quality;
+
+ public PetNbt(PetData petData) {
+ this.petKey = petData.getFileName();
+ this.petData = petData;
+ this.level = 1;
+ this.maxExp = LevelExpManager.getNeedExpValue(level);
+ this.exp = 0;
+ this.totalExp = 0;
+ this.damage = 7;
+ this.health = 20;
+ this.maxHealth = 20;
+ this.evolution = 1;
+ this.quality = PetQuality.random();
+ }
+
+ public PetNbt() {
+ }
+
+ public void loadNbtData(NBTItem nbtItem) {
+ petKey = nbtItem.getString("petKey");
+ petData = PetManager.getPetData(petKey);
+ level = nbtItem.getInteger("level");
+ exp = nbtItem.getInteger("exp");
+ totalExp = nbtItem.getInteger("totalExp");
+ maxExp = LevelExpManager.getNeedExpValue(level);
+ damage = nbtItem.getInteger("damage");
+ health = nbtItem.getInteger("health");
+ maxHealth = nbtItem.getInteger("maxHealth");
+ evolution = nbtItem.getInteger("evolution");
+ int qualityFangs = nbtItem.getInteger("quality_fangs");
+ int qualitySurvive = nbtItem.getInteger("quality_survive");
+ int qualityEfficiency = nbtItem.getInteger("quality_efficiency");
+ quality = new PetQuality(qualityFangs, qualitySurvive, qualityEfficiency);
+ }
+
+ public ItemStack saveIoItem(ItemStack stack) {
+ NBTItem nbtItem = new NBTItem(stack);
+ nbtItem.setString("petKey", petKey);
+ nbtItem.setInteger("level", level);
+ nbtItem.setInteger("exp", exp);
+ nbtItem.setInteger("totalExp", totalExp);
+ nbtItem.setInteger("maxExp", LevelExpManager.getNeedExpValue(level));
+ nbtItem.setInteger("damage", damage);
+ nbtItem.setInteger("health", health);
+ nbtItem.setInteger("maxHealth", maxHealth);
+ nbtItem.setInteger("evolution", evolution);
+ nbtItem.setInteger("quality_fangs", quality.getFangs());
+ nbtItem.setInteger("quality_survive", quality.getSurvive());
+ nbtItem.setInteger("quality_efficiency", quality.getEfficiency());
+ return nbtItem.getItem();
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+
+ public void setExp(int exp) {
+ this.exp = exp;
+ }
+
+ public void setTotalExp(int totalExp) {
+ this.totalExp = totalExp;
+ }
+
+ public void setDamage(int damage) {
+ this.damage = damage;
+ }
+
+ public void setHealth(int health) {
+ this.health = health;
+ }
+
+ public void setMaxHealth(int maxHealth) {
+ this.maxHealth = maxHealth;
+ }
+
+ public void setEvolution(int evolution) {
+ this.evolution = evolution;
+ }
+
+ public void setQuality(String qualityType, int value) {
+ if ("quality_fangs".equals(qualityType)) {
+ this.quality.setFangs(value);
+ } else if ("quality_survive".equals(qualityType)) {
+ this.quality.setSurvive(value);
+ } else if ("quality_efficiency".equals(qualityType)) {
+ this.quality.setEfficiency(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/model/PetQuality.java b/src/main/java/com/yaohun/petsystem/model/PetQuality.java
new file mode 100644
index 0000000..5abfcf6
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/model/PetQuality.java
@@ -0,0 +1,49 @@
+package com.yaohun.petsystem.model;
+
+import me.Demon.DemonPlugin.Util.RandomUtil;
+
+public class PetQuality {
+
+ private int fangs;
+
+ private int survive;
+
+ private int efficiency;
+
+ public PetQuality(int fangs, int survive, int efficiency) {
+ this.fangs = fangs;
+ this.survive = survive;
+ this.efficiency = efficiency;
+ }
+
+ public static PetQuality random() {
+ int fangs = RandomUtil.getRandomInt(1, 100);
+ int survive = RandomUtil.getRandomInt(1, 100);
+ int efficiency = RandomUtil.getRandomInt(1, 100);
+ return new PetQuality(fangs, survive, efficiency);
+ }
+
+ public int getFangs() {
+ return fangs;
+ }
+
+ public void setFangs(int fangs) {
+ this.fangs = fangs;
+ }
+
+ public int getSurvive() {
+ return survive;
+ }
+
+ public void setSurvive(int survive) {
+ this.survive = survive;
+ }
+
+ public int getEfficiency() {
+ return efficiency;
+ }
+
+ public void setEfficiency(int efficiency) {
+ this.efficiency = efficiency;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yaohun/petsystem/util/MessageUtil.java b/src/main/java/com/yaohun/petsystem/util/MessageUtil.java
new file mode 100644
index 0000000..2d67c53
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/util/MessageUtil.java
@@ -0,0 +1,78 @@
+package com.yaohun.petsystem.util;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import org.bukkit.Sound;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MessageUtil {
+
+ private static HashMap languageMap = new HashMap<>();
+
+ public static HashMap getLanguageMap() {
+ return languageMap;
+ }
+
+ public static void init(PetMain plugin) {
+ File file = new File(plugin.getDataFolder(), "lang.yml");
+ if (!file.exists()) {
+ plugin.saveResource("lang.yml", false);
+ }
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String key : config.getKeys(false)) {
+ ConfigurationSection section = config.getConfigurationSection(key);
+ for (String key2 : section.getKeys(false)) {
+ String message = section.getString(key2).replace("&", "§");
+ languageMap.put(key2, message);
+ }
+ }
+ }
+
+ public static void sendMessageKey(CommandSender sender, String key, Sound sound) {
+ String message = Config.getLanguage(key);
+ sender.sendMessage(convertHexColor(message));
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ player.playSound(player.getLocation(), sound, 0.8f, 1.2f);
+ }
+ }
+
+ public static void sendMessage(CommandSender sender, String message, Sound sound) {
+ message = convertHexColor(message);
+ sender.sendMessage(message);
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ player.playSound(player.getLocation(), sound, 0.8f, 1.2f);
+ }
+ }
+
+ public static String convertHexColor(String text) {
+ if (text == null) return "";
+
+ // 匹配形如 #FFFFFF 的十六进制颜色代码
+ Pattern pattern = Pattern.compile("#[A-Fa-f0-9]{6}");
+ Matcher matcher = pattern.matcher(text);
+ StringBuffer buffer = new StringBuffer();
+ // 去掉#
+ while (matcher.find()) {
+ String hex = matcher.group().substring(1);
+ StringBuilder colorBuilder = new StringBuilder("§x");
+ for (char c : hex.toCharArray()) {
+ colorBuilder.append('§').append(c);
+ }
+ matcher.appendReplacement(buffer, colorBuilder.toString());
+ }
+ matcher.appendTail(buffer);
+ return buffer.toString();
+ }
+
+}
diff --git a/src/main/java/com/yaohun/petsystem/util/PetUtil.java b/src/main/java/com/yaohun/petsystem/util/PetUtil.java
new file mode 100644
index 0000000..025cf8c
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/util/PetUtil.java
@@ -0,0 +1,178 @@
+package com.yaohun.petsystem.util;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
+import org.bukkit.entity.Wolf;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public class PetUtil {
+
+ public static ItemStack refreshPetStackData(PetNbt petNbt) {
+ return getPetStackDefault(petNbt);
+ }
+
+ public static ItemStack getPetStackDefault(PetNbt petNbt) {
+ // 获取相关默认参数
+ String petName = petNbt.petData.getPetName();
+ ItemStack stack = Config.getPetStackTemplate();
+ ItemMeta meta = stack.getItemMeta();
+ // 获取当前显示名
+ String templateName = "{name}";
+ if (meta.hasDisplayName()) {
+ Component comp = meta.displayName();
+ if (comp != null) {
+ // 将组件转换为普通字符串
+ templateName = PlainTextComponentSerializer.plainText().serialize(comp);
+ }
+ }
+ // 替换占位符
+ String replaced = templateName.replace("{name}", petName);
+ // 正确方式:直接用 Adventure Component 设置显示名
+ // 设置新的显示名(新版 Adventure API)
+ meta.displayName(Component.text(replaced)
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ List newLore = new ArrayList<>();
+ List rawLore = meta.getLore();
+ if (rawLore == null) {
+ rawLore = new ArrayList<>();
+ }
+
+ for (String line : rawLore) {
+ String string = line
+ .replace("{name}", petName)
+ .replace("{fangs}", String.valueOf(petNbt.quality.getFangs()))
+ .replace("{survive}", String.valueOf(petNbt.quality.getSurvive()))
+ .replace("{efficiency}", String.valueOf(petNbt.quality.getEfficiency()))
+ .replace("{level}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))
+ .replace("{damage}", String.valueOf(petNbt.damage))
+ .replace("{health}", String.valueOf(petNbt.health))
+ .replace("{maxHealth}", String.valueOf(petNbt.maxHealth));
+ newLore.add(Component.text(MessageUtil.convertHexColor(string)).decoration(TextDecoration.ITALIC, false));
+ }
+ meta.lore(newLore);
+ meta.setCustomModelData(petNbt.petData.getCustomIconModelId());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ public static ItemStack getPetGuiStack(PlayerData playerData, String guiStackID) {
+ // 1.获取玩家数据
+ PetNbt petNbt = playerData.getPetNbt();
+ ItemStack petStack = playerData.getPetStack();
+
+ ItemStack stack = Config.getItemStack(guiStackID);
+ ItemMeta meta = stack.getItemMeta();
+ // 设置新的显示名(新版 Adventure API)
+ meta.displayName(Component.text(petStack.getItemMeta().getDisplayName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ List newLore = new ArrayList<>();
+ List rawLore = meta.getLore();
+ if (rawLore == null) {
+ rawLore = new ArrayList<>();
+ }
+
+ String statsName = "休息中";
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (playerManager.isPetCallExit(playerData.getPlayerName())) {
+ statsName = "出战中";
+ }
+ for (String line : rawLore) {
+ String string = line
+ .replace("{stats}", statsName)
+ .replace("{fangs}", String.valueOf(petNbt.quality.getFangs()))
+ .replace("{survive}", String.valueOf(petNbt.quality.getSurvive()))
+ .replace("{efficiency}", String.valueOf(petNbt.quality.getEfficiency()))
+ .replace("{level}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))
+ .replace("{damage}", String.valueOf(petNbt.damage))
+ .replace("{health}", String.valueOf(petNbt.health))
+ .replace("{maxHealth}", String.valueOf(petNbt.maxHealth));
+ newLore.add(Component.text(MessageUtil.convertHexColor(string)).decoration(TextDecoration.ITALIC, false));
+ }
+ meta.lore(newLore);
+ meta.setCustomModelData(petNbt.petData.getCustomIconModelId());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ /*public static NameTag getNameBone(ModeledEntity model, String stringType) {
+ if (model == null) {
+ return null;
+ }
+ if (model.getModels().size() == 0) {
+ return null;
+ }
+ Optional opt = model.getModels().values().stream().findFirst();
+ ActiveModel activeModel = null;
+ if (opt.isPresent()) {
+ activeModel = opt.get();
+ } else {
+ return null;
+ }
+ ModelBone bone = activeModel.getBone(stringType)
+ .stream()
+ .filter(modelBone -> modelBone.getBoneBehavior(BoneBehaviorTypes.NAMETAG).orElse(null) != null)
+ .findFirst().orElse(null);
+ if (bone == null) {
+ return null;
+ }
+ NameTag nameTag = bone.getBoneBehavior(BoneBehaviorTypes.NAMETAG).orElse(null);
+ return nameTag;
+ }*/
+ public static PlayerData updatePetNameTag(Wolf wolf) {
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (wolf == null || wolf.isDead()) {
+ return null;
+ }
+ UUID petUuid = wolf.getUniqueId();
+ String ownerName = playerManager.getPetOwnerName(petUuid);
+ if (ownerName == null) {
+ return null;
+ }
+ PlayerData playerData = playerManager.getPlayerData(ownerName);
+ // 判断玩家是否召唤宠物
+ if (playerManager.isPetCallExit(ownerName)) {
+ /*// 获取宠物的的模型文件
+ ModeledEntity modeledEntity = playerManager.getPetEntityModel(petUuid);
+ if (modeledEntity == null) {
+ return null;
+ }
+ int petLevel = playerData.getPetNbt().level;
+ double healthNow = wolf.getHealth();
+ double healthMax = wolf.getMaxHealth();
+ // 计算获得血量百分比
+ int percent = (int) Math.round((healthNow / healthMax) * 100.0);
+ String levelText = Config.getLanguage("pet_level_tag")
+ .replace("{level}", Config.getLevelChar(petLevel))
+ .replace("{health}", Config.getHealthChar(percent));
+ NameTag levelTag = PetUtil.getNameBone(modeledEntity, "level");
+ if (levelTag != null) {
+ levelTag.setString(levelText);
+ levelTag.setVisible(true);
+ }*/
+ return playerData;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java b/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
new file mode 100644
index 0000000..716e9d0
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
@@ -0,0 +1,81 @@
+package com.yaohun.petsystem.util.model;
+
+import kr.toxicity.model.api.BetterModel;
+import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;
+import kr.toxicity.model.api.data.renderer.ModelRenderer;
+import kr.toxicity.model.api.tracker.EntityTracker;
+import kr.toxicity.model.api.tracker.Tracker;
+import kr.toxicity.model.api.tracker.TrackerModifier;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+public class BetterModelUtil {
+
+ public static Tracker spawnModel(Player owner, Entity entity, String modelId) {
+ if (entity == null || entity.isDead()) {
+ Bukkit.getLogger().warning("宠物模型载入失败: 实体不存在");
+ return null;
+ }
+ if (modelId == null || modelId.isBlank()) {
+ Bukkit.getLogger().warning("宠物模型载入失败: 模型ID为空");
+ return null;
+ }
+
+ ModelRenderer renderer = BetterModel.modelOrNull(modelId);
+ if (renderer == null) {
+ Bukkit.getLogger().warning("宠物模型不存在: " + modelId);
+ return null;
+ }
+
+ EntityTracker tracker = renderer.create(
+ BukkitAdapter.adapt(entity),
+ getPetTrackerModifier(),
+ BetterModelUtil::refreshModelTracker
+ );
+
+ refreshModelTracker(tracker);
+ spawnVisiblePlayers(tracker, entity);
+ Bukkit.getLogger().info("宠物模型载入成功: 玩家=" + owner.getName() + ", 模型=" + modelId + ", 实体=" + entity.getUniqueId());
+
+ return tracker;
+ }
+
+ public static void spawnVisiblePlayers(Tracker tracker, Entity entity) {
+ if (tracker == null || tracker.isClosed() || entity == null) {
+ return;
+ }
+ for (Player viewer : Bukkit.getOnlinePlayers()) {
+ if (!viewer.isOnline()) {
+ continue;
+ }
+ if (!viewer.getWorld().equals(entity.getWorld())) {
+ continue;
+ }
+ if (tracker instanceof EntityTracker entityTracker) {
+ entityTracker.registry().spawnIfNotSpawned(BukkitAdapter.adapt(viewer));
+ } else {
+ tracker.show(BukkitAdapter.adapt(viewer));
+ }
+ }
+ }
+
+ public static void refreshModelTracker(Tracker tracker) {
+ if (tracker == null || tracker.isClosed()) {
+ return;
+ }
+ if (tracker instanceof EntityTracker entityTracker) {
+ entityTracker.updateBaseEntity();
+ entityTracker.refresh();
+ }
+ tracker.forceUpdate(true);
+ }
+
+ private static TrackerModifier getPetTrackerModifier() {
+ return TrackerModifier.DEFAULT.toBuilder()
+ .sightTrace(false)
+ .damageAnimation(false)
+ .damageTint(false)
+ .build();
+ }
+}
diff --git a/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java b/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
new file mode 100644
index 0000000..8e70336
--- /dev/null
+++ b/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
@@ -0,0 +1,202 @@
+package com.yaohun.petsystem.util.wolf;
+
+import com.yaohun.petsystem.model.PetNbt;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.wolf.Wolf;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeInstance;
+import org.bukkit.block.Block;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+
+import java.util.UUID;
+
+/**
+ * 自定义的狼实体类,用于实现“跟随玩家”的宠物功能。
+ * 继承原版 NMS Wolf 实体,通过 tick() 方法控制移动与传送。
+ */
+public class CustomWolf extends Wolf {
+
+ private static final double STOP_DISTANCE_SQ = 9.0D;
+ private static final double TELEPORT_DISTANCE_SQ = 144.0D;
+ private static final double LOOK_DISTANCE_SQ = 256.0D;
+ private static final double FOLLOW_SPEED = 1.2D;
+ private static final double DEFAULT_MOVEMENT_SPEED = 0.4D;
+ private static final int PATH_RECALC_TICKS = 10;
+
+ private final UUID ownerUuid;
+
+ private int timeToRecalcPath = 0;
+
+ /**
+ * 构造方法:初始化宠物实体
+ *
+ * @param world NMS 世界对象
+ * @param owner 宠物主人(Bukkit Player)
+ */
+ public CustomWolf(Level world, Player owner) {
+ super(EntityType.WOLF, world);
+ this.ownerUuid = owner.getUniqueId();
+ }
+
+ /**
+ * 每 tick 执行的逻辑,控制宠物的移动与状态
+ */
+ @Override
+ public void tick() {
+ super.tick();
+
+ Player owner = Bukkit.getPlayer(ownerUuid);
+ if (owner == null || !owner.isOnline()) {
+ this.discard();
+ return;
+ }
+ if (owner.isDead()) {
+ this.getNavigation().stop();
+ return;
+ }
+ if (!owner.getWorld().equals(this.getBukkitEntity().getWorld())) {
+ this.getNavigation().stop();
+ teleportToOwnerLocation(owner);
+ return;
+ }
+
+ net.minecraft.world.entity.player.Player nmsOwner = ((CraftPlayer) owner).getHandle();
+
+ double distanceSq = this.distanceToSqr(nmsOwner);
+
+ if (distanceSq < STOP_DISTANCE_SQ) {
+ this.getNavigation().stop();
+ return;
+ }
+
+ boolean shouldTeleport = distanceSq > TELEPORT_DISTANCE_SQ;
+
+ if (!shouldTeleport && distanceSq <= LOOK_DISTANCE_SQ) {
+ this.getLookControl().setLookAt(nmsOwner, 10.0F, this.getMaxHeadXRot());
+ }
+
+ if (--this.timeToRecalcPath <= 0) {
+ this.timeToRecalcPath = PATH_RECALC_TICKS;
+
+ if (shouldTeleport) {
+ if (!tryTeleportNearOwner(nmsOwner)) {
+ this.getNavigation().moveTo(nmsOwner, FOLLOW_SPEED);
+ }
+ } else {
+ this.getNavigation().moveTo(nmsOwner, FOLLOW_SPEED);
+ }
+ }
+ }
+
+ /**
+ * 当宠物距离主人太远时,尝试在主人周围随机传送
+ *
+ * @param nmsOwner 主人 NMS 实体
+ */
+ private boolean tryTeleportNearOwner(Entity nmsOwner) {
+ Vec3 pos = nmsOwner.position();
+
+ for (int i = 0; i < 10; i++) {
+ int x = (int) Math.floor(pos.x) + (random.nextInt(7) - 3);
+ int y = (int) Math.floor(pos.y);
+ int z = (int) Math.floor(pos.z) + (random.nextInt(7) - 3);
+
+ Location location = new Location(getBukkitEntity().getWorld(), x + 0.5D, y, z + 0.5D);
+ if (isSafeTeleportLocation(location)) {
+ this.teleportTo(location.getX(), location.getY(), location.getZ());
+ this.getNavigation().stop();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void teleportToOwnerLocation(Player owner) {
+ Location location = owner.getLocation();
+ if (isSafeTeleportLocation(location)) {
+ this.getBukkitEntity().teleport(location);
+ }
+ }
+
+ private boolean isSafeTeleportLocation(Location location) {
+ Block feet = location.getBlock();
+ Block head = feet.getRelative(0, 1, 0);
+ Block ground = feet.getRelative(0, -1, 0);
+ if (!feet.isPassable() || !head.isPassable() || !ground.getType().isSolid()) {
+ return false;
+ }
+ if (!location.getWorld().equals(getBukkitEntity().getWorld())) {
+ return true;
+ }
+ return this.level().noCollision(this, this.getBoundingBox().move(
+ location.getX() - this.getX(),
+ location.getY() - this.getY(),
+ location.getZ() - this.getZ()
+ ));
+ }
+
+ /**
+ * 重写受伤方法,防止主人伤害到自己的宠物
+ *
+ * @param source 伤害来源
+ * @param amount 伤害数值
+ * @return 是否成功造成伤害
+ */
+ @Override
+ public boolean hurtServer(ServerLevel level, DamageSource source, float amount) {
+ Entity entity = source.getEntity();
+
+ if (entity != null && entity.getUUID().equals(ownerUuid)) {
+ return false;
+ }
+ return super.hurtServer(level, source, amount);
+ }
+
+ /**
+ * 静态方法:用于在 Bukkit 世界中生成并注册一只 CustomWolf
+ *
+ * @param owner 宠物主人
+ * @return 新生成的 CustomWolf 实例
+ */
+ public static CustomWolf spawnCustomWolf(Player owner, PetNbt petNbt) {
+ CraftWorld world = (CraftWorld) owner.getWorld();
+ Level nmsWorld = world.getHandle();
+
+ CustomWolf wolf = new CustomWolf(nmsWorld, owner);
+
+ wolf.setSilent(true);
+ wolf.setOrderedToSit(false);
+ wolf.setPos(owner.getLocation().getX(), owner.getLocation().getY(), owner.getLocation().getZ());
+
+ LivingEntity livingEntity = wolf.getBukkitLivingEntity();
+ double maxHealth = Math.max(1.0D, petNbt.maxHealth);
+ double health = Math.max(1.0D, Math.min(petNbt.health, maxHealth));
+ double damage = Math.max(0.0D, petNbt.damage);
+
+ setAttribute(livingEntity, Attribute.MAX_HEALTH, maxHealth);
+ setAttribute(livingEntity, Attribute.MOVEMENT_SPEED, DEFAULT_MOVEMENT_SPEED);
+ setAttribute(livingEntity, Attribute.ATTACK_DAMAGE, damage);
+ livingEntity.setHealth(health);
+
+ nmsWorld.addFreshEntity(wolf);
+
+ return wolf;
+ }
+
+ private static void setAttribute(LivingEntity entity, Attribute attribute, double value) {
+ AttributeInstance instance = entity.getAttribute(attribute);
+ if (instance != null) {
+ instance.setBaseValue(value);
+ }
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..75039dc
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,31 @@
+NeedExpSettings:
+ 1: 10000
+ 2: 10000
+ 3: 10000
+ 4: 10000
+ 5: 10000
+ 6: 10000
+ 7: 10000
+ 8: 10000
+ 9: 10000
+ 10: 10000
+ 11: 10000
+ 12: 10000
+ 13: 10000
+ 14: 10000
+ 15: 10000
+ 16: 10000
+ 17: 10000
+ 18: 10000
+ 19: 10000
+ 20: 10000
+ 21: 10000
+ 22: 10000
+ 23: 10000
+ 24: 10000
+ 25: 10000
+ 26: 10000
+ 27: 10000
+ 28: 10000
+ 29: 10000
+ 30: -1
\ No newline at end of file
diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..66b3172
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,6 @@
+name: AuPet
+main: com.yaohun.petsystem.PetMain
+version: 1.0.1
+api-version: 1.21
+commands:
+ apet:
diff --git a/src/src/main/java/com/yaohun/petsystem/PetMain.java b/src/src/main/java/com/yaohun/petsystem/PetMain.java
new file mode 100644
index 0000000..d9e9ec9
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/PetMain.java
@@ -0,0 +1,265 @@
+package com.yaohun.petsystem;
+
+import com.yaohun.petsystem.api.PetExpAPI;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PetSQL;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.gui.CarryGui;
+import com.yaohun.petsystem.gui.FeedingGui;
+import com.yaohun.petsystem.gui.MainGui;
+import com.yaohun.petsystem.listener.PetSkillsListener;
+import com.yaohun.petsystem.listener.PlayerListener;
+import com.yaohun.petsystem.listener.RePetNameListener;
+import com.yaohun.petsystem.manage.PetManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class PetMain extends JavaPlugin {
+
+ private static PetMain instance;
+ private static PlayerManager playerManager;
+
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ saveDefaultConfig();
+
+ PetSQL.init();
+
+ MessageUtil.init(this);
+ Config.reloadConfig(this);
+ PetManager.reloadPetManager();
+ playerManager = new PlayerManager();
+
+ getServer().getPluginManager().registerEvents(new MainGui(), this);
+ getServer().getPluginManager().registerEvents(new FeedingGui(), this);
+ getServer().getPluginManager().registerEvents(new CarryGui(), this);
+
+ getServer().getPluginManager().registerEvents(new PlayerListener(), this);
+ getServer().getPluginManager().registerEvents(new RePetNameListener(), this);
+
+ getServer().getPluginManager().registerEvents(new PetSkillsListener(), this);
+ }
+
+ @Override
+ public void onDisable() {
+ getPlayerManager().closeSaveAllData();
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (!sender.hasPermission("admin.use")) {
+ if(args.length == 0){
+ if (sender instanceof Player player) {
+ MainGui.openGui(player);
+ }
+ return true;
+ }
+ return true;
+ }
+ String prefix = "§f[§c灵宠§f] §a";
+ if (args.length == 0) {
+ sender.sendMessage("§r");
+ sender.sendMessage("§e==----- ======= §6宠物系统 §e======= -----==");
+ sender.sendMessage("§2/" + label + " call §f- §2召唤宠物");
+ sender.sendMessage("§2/" + label + " carry §f- §2携带手持宠物");
+ if (sender.isOp()) {
+ sender.sendMessage("§2/" + label + " addExp §e[经验] §b<玩家> §7true §f- §2给予玩家宠物");
+ sender.sendMessage("§2/" + label + " give §e[宠物] §b<玩家> §f- §2给予玩家宠物");
+ sender.sendMessage("§2/" + label + " reload §f- §2重载配置文件");
+ }
+ sender.sendMessage("§e==----- ======= §6宠物系统 §e======= -----==");
+ sender.sendMessage("§r");
+ return true;
+ }
+ if ("reload".equalsIgnoreCase(args[0])) {
+ MessageUtil.init(this);
+ Config.reloadConfig(this);
+ PetManager.reloadPetManager();
+ sender.sendMessage(prefix + "配置文件已重载完成.");
+ return true;
+ }
+ if ("open".equalsIgnoreCase(args[0])) {
+ if (sender instanceof Player player) {
+ MainGui.openGui(player);
+ }
+ return true;
+ }
+ if ("test".equalsIgnoreCase(args[0])) {
+ if (sender instanceof Player player) {
+ // 判断玩家是否携带宠物
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ // 获取宠物相关数据
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return true;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onAttack".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(
+ player,
+ mmSkill,
+ player.getLocation()
+ );
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onAttack 触发技能: "+mmSkill);
+ break;
+ } else {
+ sender.sendMessage("# 拦截 冷却中...");
+ }
+ }
+ }
+ }
+ return true;
+ }
+ if ("carry".equalsIgnoreCase(args[0])) {
+ String playerName = sender.getName();
+ if (args.length >= 2) {
+ playerName = args[1];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ getPlayerManager().carryPetStackData(player);
+ return true;
+ }
+ if ("call".equalsIgnoreCase(args[0])) {
+ String playerName = sender.getName();
+ if (args.length >= 2) {
+ playerName = args[1];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ if (playerManager.isPetCallExit(playerName)) {
+ playerManager.removePetCall(playerName);
+ sender.sendMessage(prefix + "已收回 §6" + playerName + " §a的宠物.");
+ } else {
+ playerManager.callPet(player);
+ sender.sendMessage(prefix + "已召唤 §6" + playerName + " §a的宠物.");
+ }
+ return true;
+ }
+ if ("addexp".equalsIgnoreCase(args[0])) {
+ if (args.length == 1) {
+ sender.sendMessage(prefix + "缺少数值参数.");
+ return true;
+ }
+ int exp = Integer.parseInt(args[1]);
+ // 获取玩家数据
+ String playerName = sender.getName();
+ if (args.length >= 3) {
+ playerName = args[2];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null || !player.isOnline()) {
+ sender.sendMessage(prefix + "玩家 " + playerName + " 不在线.");
+ return true;
+ }
+ if (args.length >= 4) {
+ ItemStack stack = player.getInventory().getItemInMainHand();
+ if (DemonAPI.itemIsNull(stack) || DemonAPI.itemIsLore(stack)) {
+ sender.sendMessage(prefix + "执行失败,你需要将灵宠拿在手中.");
+ return true;
+ }
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ sender.sendMessage(prefix + "执行失败,你需要将灵宠拿在手中.");
+ return true;
+ }
+ PetNbt petNbt = PetExpAPI.addExp(playerName, stack, exp);
+ ItemStack petStack = PetUtil.refreshPetStackData(petNbt);
+ player.getInventory().setItemInMainHand(petNbt.saveIoItem(petStack));
+ sender.sendMessage(prefix + "给予 §6手持灵宠 §a经验: §d+" + exp);
+ } else {
+ PetExpAPI.addExp(player, exp);
+ sender.sendMessage(prefix + "给予 §6" + playerName + " §a的灵宠经验: §d+" + exp);
+ }
+ return true;
+ }
+ if ("give".equalsIgnoreCase(args[0])) {
+ if (args.length == 1) {
+ sender.sendMessage(prefix + "缺少宠物参数.");
+ return true;
+ }
+ String petKey = args[1];
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ sender.sendMessage(prefix + "未找到 " + petKey + " 的相关配置.");
+ return true;
+ }
+ String playerName = sender.getName();
+ if (args.length >= 3) {
+ playerName = args[2];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if (player == null) {
+ sender.sendMessage(prefix + "未找到玩家 " + playerName);
+ return true;
+ }
+ PetNbt petNbt = new PetNbt(petData);
+ ItemStack petStack = PetUtil.getPetStackDefault(petNbt);
+ player.getInventory().addItem(petNbt.saveIoItem(petStack));
+ DemonAPI.sendMessage(sender, "玩家: §e" + playerName + " §a宠物: §6" + petKey + " §r[背包]");
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public List onTabComplete(CommandSender sender, Command command, String label, String[] args) {
+ if (sender.isOp()) {
+ if (args.length == 2 && "give".equalsIgnoreCase(args[0])) {
+ return PetManager.getPetDataMap().keySet().stream()
+ .filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
+ .collect(Collectors.toList());
+ }
+ }
+ // 如果不符合上述条件,则返回空列表
+ return Collections.emptyList();
+ }
+
+ public static PetMain inst() {
+ return instance;
+ }
+
+ public static PlayerManager getPlayerManager() {
+ return playerManager;
+ }
+
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java b/src/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
new file mode 100644
index 0000000..1126330
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/api/PetExpAPI.java
@@ -0,0 +1,122 @@
+package com.yaohun.petsystem.api;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PetManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import de.tr7zw.nbtapi.NBTItem;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+public class PetExpAPI {
+
+ public static void addExp(Player player, int addExp) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ System.out.println("[日志 - 灵宠] 玩家 " + playerName + " 尚未携带宠物.");
+ return;
+ }
+ PetNbt petNbt = playerData.getPetNbt();
+ String petKey = petNbt.petData.getFileName();
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ System.out.println("[日志 - 灵宠] 未添加 " + petKey + " 的宠物配置.");
+ return;
+ }
+ int petLevel = petNbt.level;
+ if (petLevel >= petData.getMaxLevel()) {
+ return;
+ }
+ // 进化阶段判断
+ int evolution = petNbt.evolution;
+ if (evolution == 1) {
+ if (petLevel >= 20) {
+ return;
+ }
+ } else if (evolution == 2) {
+ if (petLevel >= 40) {
+ return;
+ }
+ } else if (evolution == 3) {
+ if (petLevel >= 60) {
+ return;
+ }
+ } else if (evolution == 4) {
+ if (petLevel >= 70) {
+ return;
+ }
+ } else if (evolution == 5) {
+ if (petLevel >= 75) {
+ return;
+ }
+ }
+ int needExp = LevelExpManager.getNeedExpValue(petLevel);
+ petNbt.setTotalExp(petNbt.totalExp + addExp);
+
+ int nowExp = petNbt.exp;
+ addExp = nowExp + addExp;
+ while (addExp >= needExp) {
+ if (petLevel < 0) {
+ break;
+ }
+ addExp -= needExp;
+ petLevel++;
+ needExp = LevelExpManager.getNeedExpValue(petLevel);
+ bcmupLevelEvant(playerName, petLevel);
+ playerData.refreshPetStack();
+ }
+ petNbt.setExp(addExp);
+ petNbt.setLevel(petLevel);
+ playerData.savePlayerData();
+ }
+
+ public static PetNbt addExp(String playerName, ItemStack stack, int addExp) {
+ NBTItem nbtItem = new NBTItem(stack);
+ String petKey = nbtItem.getString("petKey");
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ System.out.println("[日志 - 灵宠] 未添加 " + petKey + " 的宠物配置.");
+ return null;
+ }
+ PetNbt petNbt = new PetNbt();
+ petNbt.loadNbtData(nbtItem);
+ int petLevel = petNbt.level;
+ if (petLevel >= petData.getMaxLevel()) {
+ return petNbt;
+ }
+ int needExp = LevelExpManager.getNeedExpValue(petLevel);
+ petNbt.setTotalExp(petNbt.totalExp + addExp);
+
+ int nowExp = petNbt.exp;
+ addExp = nowExp + addExp;
+ while (addExp >= needExp) {
+ if (petLevel < 0) {
+ break;
+ }
+ addExp -= needExp;
+ petLevel++;
+ needExp = LevelExpManager.getNeedExpValue(petLevel);
+ bcmupLevelEvant(playerName, petLevel);
+ }
+ petNbt.setExp(addExp);
+ petNbt.setLevel(petLevel);
+ return petNbt;
+ }
+
+ public static void bcmupLevelEvant(String playerName, int level) {
+ boolean butt = false;
+ if (level == 10 || level == 20 || level == 30 || level == 40 || level == 50 ||
+ level == 60 || level == 70 || level == 80 || level == 90 || level == 100
+ || level == 105 || level == 110 || level == 115 || level == 120 || level == 125) {
+ butt = true;
+ }
+ if (butt) {
+ // Bukkit.broadcastMessage("§f[§c§l公告§f] §a玩家§e"+playName+"§a本月器师手礼达到了 §7[§e§lLv."+level+"§7]");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/command/PetCommand.java b/src/src/main/java/com/yaohun/petsystem/command/PetCommand.java
new file mode 100644
index 0000000..6572657
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/command/PetCommand.java
@@ -0,0 +1,4 @@
+package com.yaohun.petsystem.command;
+
+public class PetCommand {
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/config/Config.java b/src/src/main/java/com/yaohun/petsystem/config/Config.java
new file mode 100644
index 0000000..5c44203
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/config/Config.java
@@ -0,0 +1,141 @@
+package com.yaohun.petsystem.config;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.ColorUtil;
+import me.Demon.DemonPlugin.data.GuiItemData;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.*;
+
+public class Config {
+
+ private static ItemStack petStackTemplate;
+ private static NavigableMap qualityShowMap = new TreeMap<>();
+ private static HashMap guiItemDataMap = new HashMap<>();
+
+ public static void reloadConfig(PetMain plugin) {
+ plugin.reloadConfig();
+ plugin.saveConfig();
+ FileConfiguration config = plugin.getConfig();
+ Bukkit.getConsoleSender().sendMessage("§6[宠物系统] §7参数设定:");
+ LevelExpManager.reloadLevelManager(plugin);
+ loadPetStackTemplate(config);
+ loadGuiItemData(config);
+ loadQualityShowMap(config);
+ }
+
+ private static void loadGuiItemData(FileConfiguration config) {
+ guiItemDataMap.clear();
+ ConfigurationSection section = config.getConfigurationSection("GuiItemData");
+ if (section == null) {
+ return;
+ }
+ for (String itemKey : section.getKeys(false)) {
+ guiItemDataMap.put(itemKey, new GuiItemData(itemKey, section));
+ }
+ }
+
+ private static void loadQualityShowMap(FileConfiguration config) {
+ ConfigurationSection section = config.getConfigurationSection("QualityShow");
+ if (section == null) {
+ return;
+ }
+ for (String levelKey : section.getKeys(false)) {
+ int level = Integer.parseInt(levelKey);
+ String show = section.getString(levelKey).replace("&", "§");
+ qualityShowMap.put(level, show);
+ }
+ Bukkit.getConsoleSender().sendMessage("- 资质稀有度: §f" + qualityShowMap.size() + "个");
+ }
+
+ private static void loadPetStackTemplate(FileConfiguration config) {
+ String stackType = config.getString("Pet_Stack_Template.type", "DIAMOND");
+ Material material;
+ try {
+ material = Material.valueOf(stackType.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ Bukkit.getConsoleSender().sendMessage("- 宠物模板载入: §c失败");
+ return;
+ }
+ String stackName = config.getString("Pet_Stack_Template.name", "§f宠物");
+ List stringList = config.getStringList("Pet_Stack_Template.lore");
+ List stackLore = ColorUtil.listcolor(stringList);
+ ItemStack itemStack = new ItemStack(material);
+ ItemMeta meta = itemStack.getItemMeta();
+ meta.setDisplayName(stackName);
+ meta.setLore(stackLore);
+ itemStack.setItemMeta(meta);
+ petStackTemplate = itemStack;
+
+ String itemName = DemonAPI.getItemName(itemStack.clone());
+ Bukkit.getConsoleSender().sendMessage("- 宠物模板载入: §f" + itemName);
+ }
+
+ public static ItemStack getPetStackTemplate() {
+ return petStackTemplate.clone();
+ }
+
+ public static String getLanguage(String key) {
+ HashMap hashMap = MessageUtil.getLanguageMap();
+ if (hashMap.containsKey(key)) {
+ return hashMap.get(key);
+ }
+ return "缺少参数.§7" + key;
+ }
+
+ public static ItemStack getItemStack(String itemKey) {
+ if (guiItemDataMap.containsKey(itemKey)) {
+ ItemStack itemStack = guiItemDataMap.get(itemKey).getItemStack().clone();
+ if (itemStack.hasItemMeta()) {
+ ItemMeta meta = itemStack.getItemMeta();
+ if (meta.hasLore()) {
+ List originalLore = meta.getLore();
+ if (originalLore != null) {
+ // 创建新的lore列表进行处理
+ List newLore = new ArrayList<>();
+ for (String line : originalLore) {
+ // 应用颜色转换或其他处理逻辑
+ newLore.add(DemonAPI.convertHexColor(line));
+ }
+ // 设置处理后的新lore
+ meta.setLore(newLore);
+ itemStack.setItemMeta(meta);
+ }
+ }
+ }
+ return itemStack;
+ }
+ return DemonAPI.getErrItems();
+ }
+
+
+ public static String getQualityShow(int value) {
+ Map.Entry entry = qualityShowMap.floorEntry(value);
+ return entry != null ? entry.getValue() : qualityShowMap.get(0);
+ }
+
+ public static String getHealthChar(int percent) {
+ // 限制范围 [0,100]
+ percent = Math.max(0, Math.min(100, percent));
+ // 取整到最近的 10 倍数,例如 87 → 80,93 → 90
+ int step = (percent / 10) * 10;
+ // 处理 100 特殊情况
+ if (percent == 100) {
+ step = 100;
+ }
+ return "§f";
+ }
+
+ public static String getLevelChar(int level) {
+ return "§f";
+ }
+
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/data/PetData.java b/src/src/main/java/com/yaohun/petsystem/data/PetData.java
new file mode 100644
index 0000000..ae42937
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/data/PetData.java
@@ -0,0 +1,86 @@
+package com.yaohun.petsystem.data;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+public class PetData {
+ private String fileName;
+ private String petName;
+ private int customIconModelId;
+ private int maxLevel;
+ private String modelId;
+ private List accessChannelsList;
+ private String summonSkill;
+ private String deathSkill;
+ private LinkedHashMap skillDataMap = new LinkedHashMap<>();
+
+ public PetData(String fileName, FileConfiguration config) {
+ this.fileName = fileName;
+ this.petName = config.getString("PetName");
+ this.maxLevel = config.getInt("MaxLevel");
+ this.modelId = config.getString("ModelId");
+ this.customIconModelId = config.getInt("CustomIconModelId");
+ this.accessChannelsList = config.getStringList("AccessChannels");
+ this.summonSkill = config.getString("SummonSkill", "default");
+ this.deathSkill = config.getString("DeathSkill", "default");
+ loadSkillData(config);
+ }
+
+ private void loadSkillData(FileConfiguration config) {
+ ConfigurationSection section = config.getConfigurationSection("Skills");
+ if (section == null) {
+ return;
+ }
+ for (String key : section.getKeys(false)) {
+ SkillData skillData = new SkillData(key, section);
+ skillDataMap.put(key, skillData);
+ }
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public String getPetName() {
+ return petName;
+ }
+
+ public int getMaxLevel() {
+ return maxLevel;
+ }
+
+ public String getModelId() {
+ return modelId;
+ }
+
+ public int getCustomIconModelId() {
+ return customIconModelId;
+ }
+
+ public List getAccessChannelsList() {
+ return accessChannelsList;
+ }
+
+ public String getSummonSkill() {
+ return summonSkill;
+ }
+
+ public String getDeathSkill() {
+ return deathSkill;
+ }
+
+ public LinkedHashMap getSkillDataMap() {
+ return skillDataMap;
+ }
+
+ public SkillData getSkillData(String skillName) {
+ if (skillDataMap.containsKey(skillName)) {
+ return skillDataMap.get(skillName);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/data/PetSQL.java b/src/src/main/java/com/yaohun/petsystem/data/PetSQL.java
new file mode 100644
index 0000000..c6074b2
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/data/PetSQL.java
@@ -0,0 +1,105 @@
+package com.yaohun.petsystem.data;
+
+import com.tianyu.datamanager.DataAPI;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.bukkit.Bukkit;
+
+import java.sql.*;
+
+public class PetSQL {
+
+ private static HikariDataSource dataSource;
+ private static String host;
+ private static String port;
+ private static String database;
+ private static String tables;
+ private static String username;
+ private static String password;
+
+ // 初始化数据库连接和表结构
+ public static void init() {
+ host = DataAPI.getMysql().getHost();
+ port = DataAPI.getMysql().getPort();
+ database = DataAPI.getMysql().getDatabase();
+ tables = "aupets_data";
+ username = DataAPI.getMysql().getUser();
+ password = DataAPI.getMysql().getPassword();
+
+ setupDataSource(); // 初始化连接池
+ initTable(); // 确保表结构已经创建
+ }
+
+ // 配置并初始化 HikariCP 连接池
+ private static void setupDataSource() {
+ HikariConfig config = new HikariConfig();
+ config.setDriverClassName("com.mysql.cj.jdbc.Driver");
+ config.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database + "?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false");
+ config.setUsername(username);
+ config.setPassword(password);
+ config.setMaximumPoolSize(3);
+ config.setMinimumIdle(1);
+ config.setIdleTimeout(300000L);
+ config.setMaxLifetime(0L);
+ dataSource = new HikariDataSource(config);
+ }
+
+ // 初始化表结构(如果表不存在)
+ private static void initTable() {
+ String createTableSQL = "CREATE TABLE IF NOT EXISTS " + tables + " (" +
+ "PLAYERNAME VARCHAR(255) NOT NULL, " +
+ "PETSTACK TEXT NOT NULL, " +
+ "PRIMARY KEY (PLAYERNAME))";
+ try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
+ stmt.execute(createTableSQL);
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("初始化表结构失败: " + e.getMessage());
+ }
+ }
+
+ // 获取玩家宠物信息
+ public static String getPetStack(String playerName) {
+ String query = "SELECT PETSTACK FROM " + tables + " WHERE PLAYERNAME = ? LIMIT 1";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, playerName);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ return rs.getString("PETSTACK");
+ }
+ }
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("获取字符串值失败: " + e.getMessage());
+ }
+ return null;
+ }
+
+
+ // 保存数据
+ public static void saveData(String playerName, String petStack) {
+ String sql = "INSERT INTO " + tables + " (PLAYERNAME, PETSTACK) " +
+ "VALUES (?, ?) " + "ON DUPLICATE KEY UPDATE PETSTACK = VALUES(PETSTACK)";
+ try (Connection conn = getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, playerName);
+ ps.setString(2, petStack);
+ ps.executeUpdate();
+ } catch (SQLException e) {
+ Bukkit.getLogger().severe("保存宠物数据失败: " + e.getMessage());
+ }
+ }
+
+ // 获取数据库连接
+ private static Connection getConnection() throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ // 关闭连接池
+ public static void closeConnection() {
+ if (dataSource != null && !dataSource.isClosed()) {
+ dataSource.close();
+ Bukkit.getConsoleSender().sendMessage("数据库连接已成功关闭。#1");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/data/PlayerData.java b/src/src/main/java/com/yaohun/petsystem/data/PlayerData.java
new file mode 100644
index 0000000..a6c999b
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/data/PlayerData.java
@@ -0,0 +1,136 @@
+package com.yaohun.petsystem.data;
+
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.util.io.BukkitObjectInputStream;
+import org.bukkit.util.io.BukkitObjectOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Base64;
+
+public class PlayerData {
+
+ private String playerName;
+ private ItemStack petStack;
+ private PetNbt petNbt;
+
+ public PlayerData(String playerName) {
+ this.playerName = playerName;
+ this.petStack = deserializeItemStack(PetSQL.getPetStack(playerName));
+ if (isPetStackNull()) {
+ return;
+ }
+ PetNbt petNbt = new PetNbt();
+ NBTItem nbtItem = new NBTItem(petStack);
+ petNbt.loadNbtData(nbtItem);
+ this.petNbt = petNbt;
+ }
+
+ public String getPlayerName() {
+ return playerName;
+ }
+
+ public PetNbt getPetNbt() {
+ return petNbt;
+ }
+
+ public ItemStack getPetStack() {
+ return petStack;
+ }
+
+ public void rePetNameStack(String newName) {
+ ItemStack stack = petStack.clone();
+ ItemMeta meta = stack.getItemMeta();
+ meta.displayName(Component.text(newName)
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+ stack.setItemMeta(meta);
+ setPetStack(stack);
+ }
+
+ public void setPetStack(ItemStack petStack) {
+ this.petStack = petStack;
+ if (isPetStackNull()) {
+ return;
+ }
+ PetNbt petNbt = new PetNbt();
+ NBTItem nbtItem = new NBTItem(petStack);
+ petNbt.loadNbtData(nbtItem);
+ this.petNbt = petNbt;
+ }
+
+ public void refreshPetStack() {
+ ItemStack itemStack = PetUtil.getPetStackDefault(petNbt);
+ ItemMeta meta = itemStack.getItemMeta();
+ meta.displayName(Component.text(petStack.getItemMeta().getDisplayName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ petStack = petNbt.saveIoItem(itemStack);
+ }
+
+ public void savePlayerData() {
+ if (DemonAPI.itemIsNull(petStack)) {
+ PetSQL.saveData(playerName, serializeItemStack(DemonAPI.getErrItems()));
+ } else {
+ PetSQL.saveData(playerName, serializeItemStack(petStack));
+ }
+ }
+
+ public boolean isPetStackNull() {
+ return petStack == null || petStack.getType().equals(Material.BARRIER);
+ }
+
+ /**
+ * 将 ItemStack 序列化为 Base64 字符串
+ *
+ * @param itemStack 要序列化的 ItemStack
+ * @return 序列化后的 Base64 字符串
+ */
+ private String serializeItemStack(ItemStack itemStack) {
+ if (itemStack == null) {
+ return "";
+ }
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) {
+ dataOutput.writeObject(itemStack);
+ return Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ } catch (IOException e) {
+ Bukkit.getLogger().warning("[日志 - 灵宠] 序列化 ItemStack 时出错: " + e.getMessage());
+ return "";
+ }
+ }
+
+ /**
+ * 从 Base64 字符串反序列化为 ItemStack
+ *
+ * @param data Base64 字符串
+ * @return 反序列化后的 ItemStack
+ */
+ private ItemStack deserializeItemStack(String data) {
+ if (data == null || data.isEmpty()) {
+ return null;
+ }
+ try {
+ byte[] decodedData = Base64.getDecoder().decode(data);
+ try (ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedData);
+ BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) {
+ return (ItemStack) dataInput.readObject();
+ }
+ } catch (Exception e) {
+ Bukkit.getLogger().warning("[日志 - 灵宠] 反序列化 ItemStack 时出错: " + e.getMessage());
+ return DemonAPI.getErrItems();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/data/SkillData.java b/src/src/main/java/com/yaohun/petsystem/data/SkillData.java
new file mode 100644
index 0000000..1588fea
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/data/SkillData.java
@@ -0,0 +1,60 @@
+package com.yaohun.petsystem.data;
+
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.configuration.ConfigurationSection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SkillData {
+
+ private String skillName; // 技能名
+ private String mmSkill; // MM技能
+ private int needLevel; // 触发要求等级
+ private String trigger; // 触发器
+ private long coolDown; // 冷却时间
+ private String itemName;
+ private List itemLore = new ArrayList<>();
+
+ public SkillData(String skillName, ConfigurationSection section) {
+ this.skillName = skillName;
+ this.mmSkill = section.getString(skillName + ".mmSkill");
+ this.needLevel = section.getInt(skillName + ".needLevel");
+ this.trigger = section.getString(skillName + ".trigger");
+ this.coolDown = section.getLong(skillName + ".coolDown");
+ this.itemName = DemonAPI.convertHexColor(section.getString(skillName + ".item.name", "技能 I"));
+ List itemlore = section.getStringList(skillName + ".item.lore");
+ // Bukkit.getConsoleSender().sendMessage("[调试] name: "+skillName+" itemlore = "+itemlore);
+ for (String line : itemlore) {
+ this.itemLore.add(DemonAPI.convertHexColor(line));
+ }
+ }
+
+ public String getSkillName() {
+ return skillName;
+ }
+
+ public String getMmSkill() {
+ return mmSkill;
+ }
+
+ public int getNeedLevel() {
+ return needLevel;
+ }
+
+ public String getTrigger() {
+ return trigger;
+ }
+
+ public long getCoolDown() {
+ return coolDown;
+ }
+
+ public String getItemName() {
+ return itemName;
+ }
+
+ public List getItemLore() {
+ return itemLore;
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java b/src/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
new file mode 100644
index 0000000..629dee4
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/event/PetCallEvent.java
@@ -0,0 +1,47 @@
+package com.yaohun.petsystem.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+import java.util.UUID;
+
+public class PetCallEvent extends Event {
+
+ private final Player owner;
+ private final String cutomName;
+ private final UUID entityUuid;
+ private static final HandlerList handlers = new HandlerList();
+
+ public PetCallEvent(Player owner, String cutomName, UUID entityUuid) {
+ this.owner = owner;
+ this.cutomName = cutomName;
+ this.entityUuid = entityUuid;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ public Player getOwner() {
+ return owner;
+ }
+
+ public String getCutomName() {
+ return cutomName;
+ }
+
+ public UUID getEntityUuid() {
+ return entityUuid;
+ }
+
+ @Override
+ public String getEventName() {
+ return super.getEventName();
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/CarryGui.java b/src/src/main/java/com/yaohun/petsystem/gui/CarryGui.java
new file mode 100644
index 0000000..a3851ad
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/CarryGui.java
@@ -0,0 +1,89 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.gui.holder.CarryGuiHolder;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+public class CarryGui implements Listener {
+
+ private static final int PET_SLOT = 4;
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ Inventory inv = Bukkit.createInventory(new CarryGuiHolder(), 9, Config.getLanguage("carry_gui_title"));
+ if (playerData.isPetStackNull()) {
+ inv.setItem(PET_SLOT, Config.getItemStack("carry_gui"));
+ } else {
+ inv.setItem(PET_SLOT, playerData.getPetStack());
+ }
+ player.openInventory(inv);
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof CarryGuiHolder)) {
+ e.setCancelled(true);
+ ItemStack stack = e.getCurrentItem();
+ if (DemonAPI.itemIsNull(stack)) {
+ return;
+ }
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (rawSlot == PET_SLOT) {
+ if (!playerData.isPetStackNull()) {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ inv.setItem(PET_SLOT, Config.getItemStack("carry_gui"));
+ }
+ return;
+ }
+ if (playerData.isPetStackNull()) {
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ MessageUtil.sendMessageKey(player, "hand_not_pet_stack", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ e.setCurrentItem(new ItemStack(Material.AIR));
+ playerData.setPetStack(stack);
+ playerData.savePlayerData();
+ String itemName = DemonAPI.getItemName(stack);
+ String message = Config.getLanguage("pet_stack_carry_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", itemName), Sound.ENTITY_PLAYER_LEVELUP);
+ MainGui.openGui(player);
+ } else {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ }
+
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java b/src/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
new file mode 100644
index 0000000..88c0211
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/FeedingGui.java
@@ -0,0 +1,206 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.api.PetExpAPI;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.gui.holder.FeedingGuiHolder;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import de.tr7zw.nbtapi.NBTItem;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+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.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.List;
+
+public class FeedingGui implements Listener {
+
+ private final static int FOOD_SLOT = 15;
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "gui_no_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String invTitle = Config.getLanguage("main_gui_title");
+ if (playerManager.isPetCallExit(playerName)) {
+ invTitle = Config.getLanguage("main_gui_title") + Config.getLanguage("call_gui_title");
+ }
+ Inventory inv = Bukkit.createInventory(new FeedingGuiHolder(), 36, invTitle + Config.getLanguage("feeding_gui_title"));
+ // 关闭界面
+ inv.setItem(1, Config.getItemStack("close"));
+ //食物槽
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ checkFoodGuiPetState(inv, playerData);
+ player.openInventory(inv);
+ }
+
+ private static void checkFoodGuiPetState(Inventory inv, PlayerData playerData) {
+ inv.setItem(23, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(24, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(25, getFoodButt(playerData.getPetNbt()));
+ inv.setItem(11, PetUtil.getPetGuiStack(playerData, "chongwu_feed"));
+ }
+
+ private static ItemStack getFoodButt(PetNbt petNbt) {
+ ItemStack stack = Config.getItemStack("weishibutton");
+ ItemMeta meta = stack.getItemMeta();
+ List lore = meta.getLore();
+ lore.replaceAll(s -> DemonAPI.convertHexColor(s
+ .replace("{petlevel}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))));
+ meta.setLore(lore);
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ private ItemStack getFooedStack(Inventory inv) {
+ ItemStack foodStack = inv.getItem(FOOD_SLOT);
+ if (DemonAPI.itemIsNull(foodStack) || DemonAPI.itemIsLore(foodStack)) {
+ return null;
+ }
+ return foodStack;
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ String playerName = player.getName();
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof FeedingGuiHolder)) {
+ e.setCancelled(true);
+ if (rawSlot > 35) {
+ ItemStack clickStack = e.getCurrentItem();
+ if (DemonAPI.itemIsNull(clickStack) || DemonAPI.itemIsLore(clickStack)) {
+ return;
+ }
+ NBTItem nbtItem = new NBTItem(clickStack);
+ if (nbtItem.hasKey("petAppleExp") || nbtItem.hasKey("petAppleUp")) {
+ // 判断槽位内是否已存在物品
+ ItemStack foodStack = getFooedStack(inv);
+ if (foodStack != null) {
+ player.getInventory().addItem(foodStack);
+ }
+ e.setCurrentItem(null);
+ inv.setItem(FOOD_SLOT, clickStack);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_not_apple", Sound.ENTITY_VILLAGER_NO);
+ }
+ return;
+ }
+ if (rawSlot == FOOD_SLOT) {
+ // 判断槽位内是否已存在物品
+ ItemStack foodStack = getFooedStack(inv);
+ if (foodStack != null) {
+ player.getInventory().addItem(foodStack);
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ }
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 1) {
+ MainGui.openGui(player);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 23 || rawSlot == 24 || rawSlot == 25) {
+ // 获取实物槽位中的物品
+ ItemStack foodStack = inv.getItem(FOOD_SLOT);
+ if (DemonAPI.itemIsNull(foodStack) || DemonAPI.itemIsLore(foodStack)) {
+ return;
+ }
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ String itemName = DemonAPI.getItemName(foodStack);
+ NBTItem nbtItem = new NBTItem(foodStack);
+ if (nbtItem.hasKey("petAppleExp")) {
+ // 扣除果实
+ if (foodStack.getAmount() == 1) {
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ } else {
+ foodStack.setAmount(foodStack.getAmount() - 1);
+ }
+ // 获取增加经验
+ int addExp = Integer.parseInt(nbtItem.getString("petAppleExp"));
+ PetExpAPI.addExp(player, addExp);
+ String message = Config.getLanguage("pet_feed_exp_success");
+ message = message.replace("{itemName}", itemName);
+ message = message.replace("{exp}", String.valueOf(addExp));
+ MessageUtil.sendMessage(player, message, Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
+ // 刷新宠物经验
+ checkFoodGuiPetState(inv, playerData);
+ playerData.refreshPetStack();
+ } else if (nbtItem.hasKey("petAppleUp")) {
+ PetNbt petNbt = playerData.getPetNbt();
+ // 获取进化阶段
+ int needLevel = Integer.parseInt(nbtItem.getString("petAppleUp"));
+ if (needLevel == 20) {
+ feedEvolutionEvent(player, needLevel, petNbt, 1, 2, itemName);
+ } else if (needLevel == 40) {
+ feedEvolutionEvent(player, needLevel, petNbt, 2, 3, itemName);
+ } else if (needLevel == 60) {
+ feedEvolutionEvent(player, needLevel, petNbt, 3, 4, itemName);
+ } else if (needLevel == 70) {
+ feedEvolutionEvent(player, needLevel, petNbt, 4, 5, itemName);
+ } else if (needLevel == 75) {
+ feedEvolutionEvent(player, needLevel, petNbt, 5, 6, itemName);
+ }
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_not_apple", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+ }
+ }
+
+ private void feedEvolutionEvent(Player player, int appleLevel, PetNbt petNbt, int needEvolution, int nextEvolution, String itemName) {
+ int evolution = petNbt.evolution;
+ int petLevel = petNbt.level;
+ if (evolution == nextEvolution) {
+ MessageUtil.sendMessageKey(player, "pet_feed_evolution_already", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ if (evolution == needEvolution) {
+ // 判断宠物等级是否满足要求
+ if (petLevel < appleLevel) {
+ String message = Config.getLanguage("pet_feed_evolution_need_level");
+ MessageUtil.sendMessage(player, message.replace("{level}", Config.getLevelChar(appleLevel)), Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ petNbt.setEvolution(nextEvolution);
+ String message = Config.getLanguage("pet_feed_evolution_success");
+ MessageUtil.sendMessage(player, message.replace("{itemName}", itemName), Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
+ } else {
+ MessageUtil.sendMessageKey(player, "pet_feed_evolution_request", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+
+ @EventHandler
+ public void onClose(InventoryCloseEvent e) {
+ Inventory inv = e.getInventory();
+ if ((e.getInventory().getHolder() instanceof FeedingGuiHolder)) {
+ Player player = (Player) e.getPlayer();
+ ItemStack stack = getFooedStack(inv);
+ if (stack != null) {
+ inv.setItem(FOOD_SLOT, DemonAPI.getPaperAirItems());
+ player.getInventory().addItem(stack);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/MainGui.java b/src/src/main/java/com/yaohun/petsystem/gui/MainGui.java
new file mode 100644
index 0000000..70f50a8
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/MainGui.java
@@ -0,0 +1,185 @@
+package com.yaohun.petsystem.gui;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.gui.holder.MainGuiHolder;
+import com.yaohun.petsystem.listener.RePetNameListener;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.PetUtil;
+import me.Demon.DemonPlugin.DemonAPI;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.List;
+
+public class MainGui implements Listener {
+
+ public static void openGui(Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ CarryGui.openGui(player);
+ // MessageUtil.sendMessageKey(player, "gui_no_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String invTitle = Config.getLanguage("main_gui_title");
+ String buttonKey = "zhaohuan";
+ if (playerManager.isPetCallExit(playerName)) {
+ invTitle = Config.getLanguage("main_gui_title") + Config.getLanguage("call_gui_title");
+ buttonKey = "zhaohui";
+ }
+ Inventory inv = Bukkit.createInventory(new MainGuiHolder(), 36, invTitle);
+ // 关闭界面
+ inv.setItem(0, Config.getItemStack("close"));
+ // 宠物图鉴
+ inv.setItem(1, Config.getItemStack("pettujian"));
+ // 宠物物品显示
+ inv.setItem(11, PetUtil.getPetGuiStack(playerData, "chongwu"));
+ // 宠物技能显示
+ PetData petData = playerData.getPetNbt().petData;
+ int slot = 13;
+ for (SkillData skillData : petData.getSkillDataMap().values()) {
+ inv.setItem(slot, getSkillGoldenApple(skillData));
+ slot++;
+ }
+ // 宠物召唤按钮
+ ItemStack zhaohuanStack = Config.getItemStack(buttonKey);
+ inv.setItem(29, zhaohuanStack);
+ inv.setItem(30, zhaohuanStack);
+ // 宠物喂养按钮
+ inv.setItem(31, Config.getItemStack("weiyang"));
+ // 宠物工作按钮
+ inv.setItem(32, Config.getItemStack("gongzuo"));
+ inv.setItem(33, Config.getItemStack("gongzuo"));
+ // 宠物品质数据查询
+ inv.setItem(22, getQualificationStack(playerData, "fangs"));
+ inv.setItem(23, getQualificationStack(playerData, "survive"));
+ inv.setItem(24, getQualificationStack(playerData, "efficiency"));
+ // 仓库、Wiki按钮
+ inv.setItem(7, Config.getItemStack("cangku"));
+ inv.setItem(8, Config.getItemStack("wiki"));
+ player.openInventory(inv);
+ }
+
+ private static ItemStack getSkillGoldenApple(SkillData skillData) {
+ ItemStack stack = new ItemStack(Material.GOLDEN_APPLE);
+ ItemMeta meta = stack.getItemMeta();
+ meta.displayName(Component.text(skillData.getItemName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+ meta.setLore(skillData.getItemLore());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ private static ItemStack getQualificationStack(PlayerData playerData, String type) {
+ PetNbt petNbt = playerData.getPetNbt();
+ int value = 0;
+ if ("fangs".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getFangs();
+ } else if ("survive".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getSurvive();
+ } else if ("efficiency".equalsIgnoreCase(type)) {
+ value = petNbt.quality.getEfficiency();
+ }
+ ItemStack stack = Config.getItemStack(type);
+ ItemMeta meta = stack.getItemMeta();
+ List lore = meta.getLore();
+ for (int i = 0; i < lore.size(); i++) {
+ String line = lore.get(i);
+ if (line.contains("{qualityShow}")) {
+ lore.set(i, DemonAPI.convertHexColor(line).replace("{qualityShow}", Config.getQualityShow(value)));
+ break;
+ }
+ }
+ meta.setLore(lore);
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ @EventHandler
+ public void onClick(InventoryClickEvent e) {
+ int rawSlot = e.getRawSlot();
+ Player player = (Player) e.getWhoClicked();
+ if ((e.getInventory().getHolder() instanceof MainGuiHolder)) {
+ e.setCancelled(true);
+ // 关闭界面按钮
+ if (rawSlot == 0) {
+ player.closeInventory();
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ if (rawSlot == 1) {
+ player.closeInventory();
+ // 打开宠物图鉴操作
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ // Wiki按钮
+ if (rawSlot == 8) {
+ player.closeInventory();
+ MessageUtil.sendMessageKey(player, "gui_wiki_message", Sound.UI_BUTTON_CLICK);
+ }
+ // 仓库按钮
+ if (rawSlot == 7) {
+ player.closeInventory();
+ // 打开便捷仓库操作
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (rawSlot == 11) {
+ if (e.isShiftClick()) {
+ String playerName = player.getName();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (!playerData.isPetStackNull()) {
+ playerManager.removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ player.closeInventory();
+ Bukkit.getScheduler().runTaskLater(PetMain.inst(), player::updateInventory, 2L);
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ return;
+ }
+ if (e.isLeftClick()) {
+ player.closeInventory();
+ RePetNameListener.addReNameUUID(player.getUniqueId());
+ MessageUtil.sendMessageKey(player, "pet_rename_add", Sound.UI_BUTTON_CLICK);
+ }
+ }
+ // 召唤宠物操作按钮
+ if (rawSlot == 29 || rawSlot == 30) {
+ player.closeInventory();
+ playerManager.callPet(player);
+ }
+ // 打开投喂宠物界面
+ if (rawSlot == 31) {
+ FeedingGui.openGui(player);
+ player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1, 1);
+ }
+ // 打开打工系统
+ if (rawSlot == 32 || rawSlot == 33) {
+ player.closeInventory();
+ MessageUtil.sendMessageKey(player, "imperfect_function", Sound.ENTITY_VILLAGER_NO);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java b/src/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
new file mode 100644
index 0000000..9ae4857
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/holder/CarryGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class CarryGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java b/src/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
new file mode 100644
index 0000000..a97e280
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/holder/FeedingGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class FeedingGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java b/src/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
new file mode 100644
index 0000000..496777d
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/gui/holder/MainGuiHolder.java
@@ -0,0 +1,12 @@
+package com.yaohun.petsystem.gui.holder;
+
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryHolder;
+
+public class MainGuiHolder implements InventoryHolder {
+
+ @Override
+ public Inventory getInventory() {
+ return null;
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java b/src/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
new file mode 100644
index 0000000..3e89c8d
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/listener/PetSkillsListener.java
@@ -0,0 +1,94 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.data.SkillData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+
+import java.util.LinkedHashMap;
+import java.util.UUID;
+
+public class PetSkillsListener implements Listener {
+
+ @EventHandler
+ public void onDamager(EntityDamageByEntityEvent e){
+ if(e.getDamager() instanceof Player player){
+ // 判断玩家是否携带宠物
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ // 获取宠物相关数据
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ int petLevel = playerData.getPetNbt().level;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ if(petLevel >= skillData.getNeedLevel()) {
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onAttack".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(
+ player,
+ mmSkill,
+ player.getLocation()
+ );
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onAttack 触发技能: "+skillData.getSkillName());
+ }
+ }
+ }
+ }
+ }
+ if(e.getEntity() instanceof Player player){
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if(playerData.isPetStackNull()){
+ return;
+ }
+ PetData petData = playerData.getPetNbt().petData;
+ int petLevel = playerData.getPetNbt().level;
+ LinkedHashMap skillDataMap = petData.getSkillDataMap();
+ for(SkillData skillData : skillDataMap.values()){
+ if(petLevel >= skillData.getNeedLevel()) {
+ String cdKey = "skill_"+skillData.getSkillName();
+ if ("onDamaged".equalsIgnoreCase(skillData.getTrigger())) {
+ UUID uuid = player.getUniqueId();
+ if(!CDTimeAPI.isCD(uuid,cdKey)) {
+ long cooldown = skillData.getCoolDown();
+ CDTimeAPI.setPlayerCD(uuid, cdKey, cooldown);
+ String mmSkill = skillData.getMmSkill();
+ // 执行技能
+ MythicBukkit.inst().getAPIHelper().castSkill(player, mmSkill, player.getLocation());
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onDamaged 触发技能: "+skillData.getSkillName());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*public boolean runSkills(SkillCaster caster, SkillTrigger cause, AbstractLocation origin, AbstractEntity trigger, Consumer transformer) {
+ return new TriggeredSkill(cause, caster, origin, trigger, this.artifact.getMechanics(cause), true, meta -> {
+ transformer.getVariables().putString("equip-slot", this.equippedSlot);
+ transformer.getVariables().putObject("equip-item", this.itemStack);
+ if (r5 != null) {
+ r5.accept(transformer);
+ }
+ }, new Pair[0]).getCancelled();
+ }*/
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java b/src/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
new file mode 100644
index 0000000..90a5b68
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/listener/PlayerListener.java
@@ -0,0 +1,79 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import com.yaohun.petsystem.util.PetUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Wolf;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.util.UUID;
+
+public class PlayerListener implements Listener {
+
+ @EventHandler
+ public void onDamage(EntityDamageByEntityEvent e) {
+ if (e.getEntity() instanceof Player) {
+ return;
+ }
+ if (!(e.getEntity() instanceof LivingEntity target)) {
+ return;
+ }
+ if (e.getDamager() instanceof Player player) {
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (!playerManager.isPetCallExit(playerName)) {
+ return;
+ }
+ UUID petUUID = playerManager.getPetUUID(playerName);
+ if (petUUID == null) {
+ return;
+ }
+ Entity entity = Bukkit.getEntity(petUUID);
+ if (entity instanceof Wolf wolf) {
+ wolf.setTarget(target);
+ }
+ }
+ }
+
+ // 检查伤害 狼造成的伤害
+ @EventHandler
+ public void onDamageCheck(EntityDamageByEntityEvent e) {
+ if (e.getDamager() instanceof Wolf wolf) {
+ PlayerData playerData = PetUtil.updatePetNameTag(wolf);
+ if (playerData != null) {
+ double health = wolf.getHealth();
+ PetNbt petNbt = playerData.getPetNbt();
+ petNbt.setHealth((int) health);
+ }
+ }
+ }
+
+ @EventHandler
+ public void onDeath(EntityDeathEvent e) {
+ String ownerName = PetMain.getPlayerManager().getPetOwnerName(e.getEntity().getUniqueId());
+ if (ownerName == null) {
+ return;
+ }
+ PlayerData playerData = PetMain.getPlayerManager().getPlayerData(ownerName);
+ playerData.getPetNbt().setHealth(0);
+ PetMain.getPlayerManager().removePetCall(ownerName);
+ }
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent e){
+ Player player = e.getPlayer();
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ playerManager.removePetCall(playerName);
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java b/src/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
new file mode 100644
index 0000000..de4e730
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/listener/RePetNameListener.java
@@ -0,0 +1,62 @@
+package com.yaohun.petsystem.listener;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.util.MessageUtil;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class RePetNameListener implements Listener {
+
+ private static List reNameList = new ArrayList<>();
+
+ public static void addReNameUUID(UUID uuid) {
+ if (!reNameList.contains(uuid)) {
+ reNameList.add(uuid);
+ }
+ }
+
+ @EventHandler
+ public void onChat(AsyncPlayerChatEvent e) {
+ Player player = e.getPlayer();
+ UUID uuid = player.getUniqueId();
+ if (reNameList.isEmpty()) {
+ return;
+ }
+ if (!reNameList.contains(uuid)) {
+ return;
+ }
+ e.setCancelled(true);
+ reNameList.remove(uuid);
+ String playerName = player.getName();
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ PlayerData playerData = playerManager.getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "pet_rename_no_pets", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String reName = e.getMessage();
+ if (reName.isEmpty()) {
+ MessageUtil.sendMessageKey(player, "pet_rename_empty", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ if (reName.length() < 3) {
+ MessageUtil.sendMessageKey(player, "pet_rename_length", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String newName = reName.replace("&", "§");
+ playerData.rePetNameStack(newName);
+ playerData.savePlayerData();
+ String message = Config.getLanguage("pet_rename_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", newName), Sound.ENTITY_PLAYER_LEVELUP);
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java b/src/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
new file mode 100644
index 0000000..42133c8
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/manage/LevelExpManager.java
@@ -0,0 +1,35 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class LevelExpManager {
+
+ private static HashMap needExpMap = new HashMap<>();
+
+ public static void reloadLevelManager(PetMain plugin) {
+ File file = new File(plugin.getDataFolder() + "/Settings", "needExp.yml");
+ if (file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String levelKey : config.getKeys(false)) {
+ int level = Integer.parseInt(levelKey);
+ int needExp = config.getInt(levelKey);
+ needExpMap.put(level, needExp);
+ }
+ Bukkit.getConsoleSender().sendMessage("§7- 等级经验配置: §f" + needExpMap.size() + "条");
+ }
+
+ public static int getNeedExpValue(int level) {
+ if (needExpMap.containsKey(level)) {
+ return needExpMap.get(level);
+ }
+ return 1000000;
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java b/src/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
new file mode 100644
index 0000000..df1db1e
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/manage/PetCallSession.java
@@ -0,0 +1,48 @@
+package com.yaohun.petsystem.manage;
+
+import kr.toxicity.model.api.tracker.Tracker;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+
+import java.util.UUID;
+
+public class PetCallSession {
+
+ private final UUID ownerUuid;
+ private final UUID entityUuid;
+ private final Tracker tracker;
+
+ public PetCallSession(UUID ownerUuid, UUID entityUuid, Tracker tracker) {
+ this.ownerUuid = ownerUuid;
+ this.entityUuid = entityUuid;
+ this.tracker = tracker;
+ }
+
+ public UUID getOwnerUuid() {
+ return ownerUuid;
+ }
+
+ public UUID getEntityUuid() {
+ return entityUuid;
+ }
+
+ public Entity getEntity() {
+ return Bukkit.getEntity(entityUuid);
+ }
+
+ public boolean isAlive() {
+ Entity entity = getEntity();
+ return entity != null && !entity.isDead();
+ }
+
+ public void close() {
+ if (tracker != null && !tracker.isClosed()) {
+ tracker.despawn();
+ tracker.close();
+ }
+ Entity entity = getEntity();
+ if (entity != null && !entity.isDead()) {
+ entity.remove();
+ }
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/manage/PetManager.java b/src/src/main/java/com/yaohun/petsystem/manage/PetManager.java
new file mode 100644
index 0000000..5996459
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/manage/PetManager.java
@@ -0,0 +1,71 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.data.PetData;
+import me.Demon.DemonPlugin.DemonAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class PetManager {
+
+ private static HashMap petDataMap = new HashMap<>();
+
+ public static void reloadPetManager() {
+ petDataMap.clear();
+ Bukkit.getConsoleSender().sendMessage("§6[宠物系统] §7宠物类型:");
+ String folderPath = PetMain.inst().getDataFolder().getPath() + "/PetData";
+ File folder = new File(folderPath);
+ if (!folder.exists() || !folder.isDirectory()) {
+ System.out.println("[日志 - 灵宠] 未检测到任何宠物配置: " + folderPath);
+ return;
+ }
+ List ymlFiles = new ArrayList<>();
+ DemonAPI.collectYmlFiles(folder, ymlFiles);
+ if (ymlFiles.isEmpty()) {
+ System.out.println("[日志 - 灵宠] 未检测到任何宠物配置: " + folderPath);
+ return;
+ }
+ for (File file : ymlFiles) {
+ String fileName = file.getName().replace(".yml", "");
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ PetData petData = new PetData(fileName, config);
+ petDataMap.put(fileName, petData);
+ Bukkit.getConsoleSender().sendMessage("- 宠物: §f" + petData.getPetName() + " §7MaxLevel: §f" + petData.getMaxLevel());
+ }
+ }
+
+ public static HashMap getPetDataMap() {
+ return petDataMap;
+ }
+
+ public static PetData getItemNameToPetData(String itemName) {
+ for (PetData petData : petDataMap.values()) {
+ if (petData.getPetName().contains(itemName)) {
+ return petData;
+ }
+ }
+ return null;
+ }
+
+ public static PetData getPetData(String petKey) {
+ if (petDataMap.containsKey(petKey)) {
+ return petDataMap.get(petKey);
+ }
+ return null;
+ }
+
+ // 获取宠物获取渠道
+ public static List getPetAccessChannels(String petKey) {
+ PetData petData = PetManager.getPetData(petKey);
+ if (petData == null) {
+ return new ArrayList<>();
+ }
+ return petData.getAccessChannelsList();
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java b/src/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
new file mode 100644
index 0000000..5e5e432
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/manage/PlayerManager.java
@@ -0,0 +1,253 @@
+package com.yaohun.petsystem.manage;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.data.PetSQL;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.event.PetCallEvent;
+import com.yaohun.petsystem.util.MessageUtil;
+import com.yaohun.petsystem.util.model.BetterModelUtil;
+import com.yaohun.petsystem.util.wolf.CustomWolf;
+import de.tr7zw.nbtapi.NBTItem;
+import io.lumine.mythic.bukkit.MythicBukkit;
+import kr.toxicity.model.api.tracker.Tracker;
+import me.Demon.DemonPlugin.DemonAPI;
+import me.Demon.DemonPlugin.Util.CDTimeAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashMap;
+import java.util.UUID;
+
+public class PlayerManager {
+
+ private HashMap playerDataMap = new HashMap<>();
+ private HashMap petCallMap = new HashMap<>();
+ // private HashMap petEntityModelMap = new HashMap<>();
+
+ public PlayerData getPlayerData(String name) {
+ if (!playerDataMap.containsKey(name)) {
+ playerDataMap.put(name, new PlayerData(name));
+ }
+ return playerDataMap.get(name);
+ }
+
+ public void saveAllData() {
+ for (PlayerData playerData : playerDataMap.values()) {
+ playerData.savePlayerData();
+ }
+ }
+
+ public void closeSaveData(String playerName) {
+ PlayerData playerData = getPlayerData(playerName);
+ playerData.savePlayerData();
+ playerDataMap.remove(playerName);
+
+ removePetCall(playerName);
+ }
+
+ public void closeSaveAllData() {
+ for (PlayerData playerData : playerDataMap.values()) {
+ playerData.savePlayerData();
+ }
+ playerDataMap.clear();
+ for (PetCallSession session : petCallMap.values()) {
+ session.close();
+ }
+ PetSQL.closeConnection();
+ petCallMap.clear();
+ }
+
+ public void setPetCall(String name, UUID ownerUuid, UUID entityUuid, Tracker tracker) {
+ petCallMap.put(name, new PetCallSession(ownerUuid, entityUuid, tracker));
+ }
+
+ public void removePetCall(String name) {
+ PetCallSession session = petCallMap.remove(name);
+ if (session != null) {
+ session.close();
+ }
+ }
+
+ public boolean isPetCallExit(String name) {
+ PetCallSession session = petCallMap.get(name);
+ if (session == null) {
+ return false;
+ }
+ if (!session.isAlive()) {
+ petCallMap.remove(name);
+ session.close();
+ return false;
+ }
+ return true;
+ }
+
+ public String getPetOwnerName(UUID entityUuid) {
+ for (String name : petCallMap.keySet()) {
+ PetCallSession session = petCallMap.get(name);
+ if (session.getEntityUuid().equals(entityUuid)) {
+ return name;
+ }
+ }
+ return null;
+ }
+
+ public UUID getPetUUID(String name) {
+ if (isPetCallExit(name)) {
+ return petCallMap.get(name).getEntityUuid();
+ }
+ return null;
+ }
+/*
+ public ModeledEntity getPetEntityModel(UUID entityUuid) {
+ for (ModeledEntity entity : petEntityModelMap.values()) {
+ if (entity.getBase().getUUID().equals(entityUuid)) {
+ return entity;
+ }
+ }
+ return null;
+ }
+
+ public void setPetEntityModelMap(UUID entityUUID, ModeledEntity entityModel) {
+ petEntityModelMap.put(entityUUID, entityModel);
+ }*/
+
+ public void callPet(Player player) {
+ String carryCdKey = "callPetStackCD";
+ UUID uuid = player.getUniqueId();
+ if (CDTimeAPI.isCD(uuid, carryCdKey)) {
+ MessageUtil.sendMessageKey(player, "system_coolingDown", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ CDTimeAPI.setPlayerCD(uuid, carryCdKey, 1000 * 5);
+ String playerName = player.getName();
+ PlayerData playerData = getPlayerData(playerName);
+ if (playerData == null) {
+ return;
+ }
+ if (playerData.isPetStackNull()) {
+ MessageUtil.sendMessageKey(player, "no_summons_pets_brought", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ // 判断玩家是否已经召唤宠物
+ if (isPetCallExit(playerName)) {
+ removePetCall(playerName);
+ MessageUtil.sendMessageKey(player, "pet_summons_recovers", Sound.UI_BUTTON_CLICK);
+ return;
+ }
+ int health = playerData.getPetNbt().health;
+ if (health < 1) {
+ MessageUtil.sendMessageKey(player, "no_summons_pets_health", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ String petStackName = DemonAPI.getItemName(playerData.getPetStack());
+ CustomWolf wolf = CustomWolf.spawnCustomWolf(player, playerData.getPetNbt());
+ PetData petData = playerData.getPetNbt().petData;
+ String modelID = petData.getModelId();
+ Tracker tracker = BetterModelUtil.spawnModel(player, wolf.getBukkitEntity(), modelID);
+ if (tracker == null) {
+ wolf.getBukkitEntity().remove();
+ MessageUtil.sendMessage(player, "§c宠物模型载入失败,召唤已取消。", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ setPetCall(playerName, player.getUniqueId(), wolf.getUUID(), tracker);
+ /*ActiveModel model = ModelEngineAPI.createActiveModel(modelID);
+ if (model != null) {
+ ModeledEntity modeledEntity = ModelEngineAPI.createModeledEntity(wolf.getBukkitEntity());
+ setPetEntityModelMap(wolf.getUUID(), modeledEntity);
+ modeledEntity.addModel(model, true);
+ modeledEntity.setBaseEntityVisible(false);
+ model.setHitboxVisible(true);
+ model.setShadowVisible(true);
+ String text = Config.getLanguage("pet_name_tag")
+ .replace("{name}", playerName)
+ .replace("{petName}", petStackName);
+
+ wolf.setCustomName(Component.literal(text));
+ wolf.setCustomNameVisible(true);
+
+ NameTag tag = PetUtil.getNameBone(modeledEntity, "name");
+ if (tag != null) {
+ tag.setString(text);
+ tag.setVisible(true);
+ }
+ int petLevel = playerData.getPetNbt().level;
+ double healthNow = wolf.getHealth();
+ double healthMax = wolf.getMaxHealth();
+ // 计算获得血量百分比
+ int percent = (int) Math.round((healthNow / healthMax) * 100.0);
+ String levelText = Config.getLanguage("pet_level_tag")
+ .replace("{level}", Config.getLevelChar(petLevel))
+ .replace("{health}", Config.getHealthChar(percent));
+ NameTag levelTag = PetUtil.getNameBone(modeledEntity, "level");
+ if (levelTag != null) {
+ levelTag.setString(levelText);
+ levelTag.setVisible(true);
+ }
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 模型资源 " + modelID + " 载入成功!");
+ } else {
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 模型资源 " + modelID + " 载入失败!");
+ }*/
+ String summonSkill = petData.getSummonSkill();
+ if (!"default".equalsIgnoreCase(summonSkill)) {
+ Bukkit.getScheduler().runTaskLater(PetMain.inst(), () -> {
+ if (wolf.isRemoved() || wolf.getBukkitLivingEntity().isDead()) {
+ return;
+ }
+ MythicBukkit.inst().getAPIHelper().castSkill(wolf.getBukkitLivingEntity(), summonSkill, wolf.getBukkitLivingEntity().getLocation());
+ player.sendMessage("[调试 - 宠物技能] 触发条件: onSpawn 触发技能: "+summonSkill);
+ }, 2L);
+ }
+ // 设置宠物召唤状态
+ MessageUtil.sendMessageKey(player, "pet_summons_success", Sound.UI_BUTTON_CLICK);
+ // 发布事件
+ PetCallEvent event = new PetCallEvent(player, wolf.getBukkitEntity().getCustomName(), wolf.getUUID());
+ Bukkit.getPluginManager().callEvent(event);
+ }
+
+ public void carryPetStackData(Player player) {
+ String carryCdKey = "carryPetStackCD";
+ UUID uuid = player.getUniqueId();
+ if (CDTimeAPI.isCD(uuid, carryCdKey)) {
+ MessageUtil.sendMessageKey(player, "system_coolingDown", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ CDTimeAPI.setPlayerCD(uuid, carryCdKey, 1000 * 5);
+ String playerName = player.getName();
+ PlayerData playerData = getPlayerData(playerName);
+ if (playerData.isPetStackNull()) {
+ ItemStack stack = player.getInventory().getItemInMainHand();
+ if (DemonAPI.itemIsNull(stack)) {
+ MessageUtil.sendMessageKey(player, "error_empty_hand", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ NBTItem nbtItem = new NBTItem(stack);
+ if (!nbtItem.hasKey("petKey")) {
+ MessageUtil.sendMessageKey(player, "hand_not_pet_stack", Sound.ENTITY_VILLAGER_NO);
+ return;
+ }
+ player.getInventory().setItemInMainHand(null);
+ playerData.setPetStack(stack);
+ playerData.savePlayerData();
+ String itemName = DemonAPI.getItemName(stack);
+ String message = Config.getLanguage("pet_stack_carry_success");
+ MessageUtil.sendMessage(player, message.replace("{petName}", itemName), Sound.ENTITY_PLAYER_LEVELUP);
+ } else {
+ removePetCall(playerName);
+ playerData.refreshPetStack();
+ ItemStack petStack = playerData.getPetStack();
+ NBTItem nbtItem = new NBTItem(petStack);
+ if (!nbtItem.hasKey("petKey")) {
+ Bukkit.getConsoleSender().sendMessage("[日志 - 宠物] 未检测到宠物petKey的Nbt标识.");
+ }
+ player.getInventory().addItem(petStack);
+ playerData.setPetStack(DemonAPI.getErrItems());
+ playerData.savePlayerData();
+ MessageUtil.sendMessageKey(player, "pet_stack_carry_takeOut", Sound.ENTITY_PLAYER_LEVELUP);
+ }
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/model/PetNbt.java b/src/src/main/java/com/yaohun/petsystem/model/PetNbt.java
new file mode 100644
index 0000000..b156afa
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/model/PetNbt.java
@@ -0,0 +1,111 @@
+package com.yaohun.petsystem.model;
+
+import com.yaohun.petsystem.data.PetData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PetManager;
+import de.tr7zw.nbtapi.NBTItem;
+import org.bukkit.inventory.ItemStack;
+
+public class PetNbt {
+
+ private String petKey;
+ public PetData petData;
+ public int level;
+ public int exp;
+ public int maxExp;
+ public int totalExp;
+ public int damage;
+ public int health;
+ public int maxHealth;
+ public int evolution;
+ public PetQuality quality;
+
+ public PetNbt(PetData petData) {
+ this.petKey = petData.getFileName();
+ this.petData = petData;
+ this.level = 1;
+ this.maxExp = LevelExpManager.getNeedExpValue(level);
+ this.exp = 0;
+ this.totalExp = 0;
+ this.damage = 7;
+ this.health = 20;
+ this.maxHealth = 20;
+ this.evolution = 1;
+ this.quality = PetQuality.random();
+ }
+
+ public PetNbt() {
+ }
+
+ public void loadNbtData(NBTItem nbtItem) {
+ petKey = nbtItem.getString("petKey");
+ petData = PetManager.getPetData(petKey);
+ level = nbtItem.getInteger("level");
+ exp = nbtItem.getInteger("exp");
+ totalExp = nbtItem.getInteger("totalExp");
+ maxExp = LevelExpManager.getNeedExpValue(level);
+ damage = nbtItem.getInteger("damage");
+ health = nbtItem.getInteger("health");
+ maxHealth = nbtItem.getInteger("maxHealth");
+ evolution = nbtItem.getInteger("evolution");
+ int qualityFangs = nbtItem.getInteger("quality_fangs");
+ int qualitySurvive = nbtItem.getInteger("quality_survive");
+ int qualityEfficiency = nbtItem.getInteger("quality_efficiency");
+ quality = new PetQuality(qualityFangs, qualitySurvive, qualityEfficiency);
+ }
+
+ public ItemStack saveIoItem(ItemStack stack) {
+ NBTItem nbtItem = new NBTItem(stack);
+ nbtItem.setString("petKey", petKey);
+ nbtItem.setInteger("level", level);
+ nbtItem.setInteger("exp", exp);
+ nbtItem.setInteger("totalExp", totalExp);
+ nbtItem.setInteger("maxExp", LevelExpManager.getNeedExpValue(level));
+ nbtItem.setInteger("damage", damage);
+ nbtItem.setInteger("health", health);
+ nbtItem.setInteger("maxHealth", maxHealth);
+ nbtItem.setInteger("evolution", evolution);
+ nbtItem.setInteger("quality_fangs", quality.getFangs());
+ nbtItem.setInteger("quality_survive", quality.getSurvive());
+ nbtItem.setInteger("quality_efficiency", quality.getEfficiency());
+ return nbtItem.getItem();
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+
+ public void setExp(int exp) {
+ this.exp = exp;
+ }
+
+ public void setTotalExp(int totalExp) {
+ this.totalExp = totalExp;
+ }
+
+ public void setDamage(int damage) {
+ this.damage = damage;
+ }
+
+ public void setHealth(int health) {
+ this.health = health;
+ }
+
+ public void setMaxHealth(int maxHealth) {
+ this.maxHealth = maxHealth;
+ }
+
+ public void setEvolution(int evolution) {
+ this.evolution = evolution;
+ }
+
+ public void setQuality(String qualityType, int value) {
+ if ("quality_fangs".equals(qualityType)) {
+ this.quality.setFangs(value);
+ } else if ("quality_survive".equals(qualityType)) {
+ this.quality.setSurvive(value);
+ } else if ("quality_efficiency".equals(qualityType)) {
+ this.quality.setEfficiency(value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/model/PetQuality.java b/src/src/main/java/com/yaohun/petsystem/model/PetQuality.java
new file mode 100644
index 0000000..5abfcf6
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/model/PetQuality.java
@@ -0,0 +1,49 @@
+package com.yaohun.petsystem.model;
+
+import me.Demon.DemonPlugin.Util.RandomUtil;
+
+public class PetQuality {
+
+ private int fangs;
+
+ private int survive;
+
+ private int efficiency;
+
+ public PetQuality(int fangs, int survive, int efficiency) {
+ this.fangs = fangs;
+ this.survive = survive;
+ this.efficiency = efficiency;
+ }
+
+ public static PetQuality random() {
+ int fangs = RandomUtil.getRandomInt(1, 100);
+ int survive = RandomUtil.getRandomInt(1, 100);
+ int efficiency = RandomUtil.getRandomInt(1, 100);
+ return new PetQuality(fangs, survive, efficiency);
+ }
+
+ public int getFangs() {
+ return fangs;
+ }
+
+ public void setFangs(int fangs) {
+ this.fangs = fangs;
+ }
+
+ public int getSurvive() {
+ return survive;
+ }
+
+ public void setSurvive(int survive) {
+ this.survive = survive;
+ }
+
+ public int getEfficiency() {
+ return efficiency;
+ }
+
+ public void setEfficiency(int efficiency) {
+ this.efficiency = efficiency;
+ }
+}
\ No newline at end of file
diff --git a/src/src/main/java/com/yaohun/petsystem/util/MessageUtil.java b/src/src/main/java/com/yaohun/petsystem/util/MessageUtil.java
new file mode 100644
index 0000000..2d67c53
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/util/MessageUtil.java
@@ -0,0 +1,78 @@
+package com.yaohun.petsystem.util;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import org.bukkit.Sound;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MessageUtil {
+
+ private static HashMap languageMap = new HashMap<>();
+
+ public static HashMap getLanguageMap() {
+ return languageMap;
+ }
+
+ public static void init(PetMain plugin) {
+ File file = new File(plugin.getDataFolder(), "lang.yml");
+ if (!file.exists()) {
+ plugin.saveResource("lang.yml", false);
+ }
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String key : config.getKeys(false)) {
+ ConfigurationSection section = config.getConfigurationSection(key);
+ for (String key2 : section.getKeys(false)) {
+ String message = section.getString(key2).replace("&", "§");
+ languageMap.put(key2, message);
+ }
+ }
+ }
+
+ public static void sendMessageKey(CommandSender sender, String key, Sound sound) {
+ String message = Config.getLanguage(key);
+ sender.sendMessage(convertHexColor(message));
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ player.playSound(player.getLocation(), sound, 0.8f, 1.2f);
+ }
+ }
+
+ public static void sendMessage(CommandSender sender, String message, Sound sound) {
+ message = convertHexColor(message);
+ sender.sendMessage(message);
+ if (sender instanceof Player) {
+ Player player = (Player) sender;
+ player.playSound(player.getLocation(), sound, 0.8f, 1.2f);
+ }
+ }
+
+ public static String convertHexColor(String text) {
+ if (text == null) return "";
+
+ // 匹配形如 #FFFFFF 的十六进制颜色代码
+ Pattern pattern = Pattern.compile("#[A-Fa-f0-9]{6}");
+ Matcher matcher = pattern.matcher(text);
+ StringBuffer buffer = new StringBuffer();
+ // 去掉#
+ while (matcher.find()) {
+ String hex = matcher.group().substring(1);
+ StringBuilder colorBuilder = new StringBuilder("§x");
+ for (char c : hex.toCharArray()) {
+ colorBuilder.append('§').append(c);
+ }
+ matcher.appendReplacement(buffer, colorBuilder.toString());
+ }
+ matcher.appendTail(buffer);
+ return buffer.toString();
+ }
+
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/util/PetUtil.java b/src/src/main/java/com/yaohun/petsystem/util/PetUtil.java
new file mode 100644
index 0000000..025cf8c
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/util/PetUtil.java
@@ -0,0 +1,178 @@
+package com.yaohun.petsystem.util;
+
+import com.yaohun.petsystem.PetMain;
+import com.yaohun.petsystem.config.Config;
+import com.yaohun.petsystem.data.PlayerData;
+import com.yaohun.petsystem.manage.LevelExpManager;
+import com.yaohun.petsystem.manage.PlayerManager;
+import com.yaohun.petsystem.model.PetNbt;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
+import org.bukkit.entity.Wolf;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public class PetUtil {
+
+ public static ItemStack refreshPetStackData(PetNbt petNbt) {
+ return getPetStackDefault(petNbt);
+ }
+
+ public static ItemStack getPetStackDefault(PetNbt petNbt) {
+ // 获取相关默认参数
+ String petName = petNbt.petData.getPetName();
+ ItemStack stack = Config.getPetStackTemplate();
+ ItemMeta meta = stack.getItemMeta();
+ // 获取当前显示名
+ String templateName = "{name}";
+ if (meta.hasDisplayName()) {
+ Component comp = meta.displayName();
+ if (comp != null) {
+ // 将组件转换为普通字符串
+ templateName = PlainTextComponentSerializer.plainText().serialize(comp);
+ }
+ }
+ // 替换占位符
+ String replaced = templateName.replace("{name}", petName);
+ // 正确方式:直接用 Adventure Component 设置显示名
+ // 设置新的显示名(新版 Adventure API)
+ meta.displayName(Component.text(replaced)
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ List newLore = new ArrayList<>();
+ List rawLore = meta.getLore();
+ if (rawLore == null) {
+ rawLore = new ArrayList<>();
+ }
+
+ for (String line : rawLore) {
+ String string = line
+ .replace("{name}", petName)
+ .replace("{fangs}", String.valueOf(petNbt.quality.getFangs()))
+ .replace("{survive}", String.valueOf(petNbt.quality.getSurvive()))
+ .replace("{efficiency}", String.valueOf(petNbt.quality.getEfficiency()))
+ .replace("{level}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))
+ .replace("{damage}", String.valueOf(petNbt.damage))
+ .replace("{health}", String.valueOf(petNbt.health))
+ .replace("{maxHealth}", String.valueOf(petNbt.maxHealth));
+ newLore.add(Component.text(MessageUtil.convertHexColor(string)).decoration(TextDecoration.ITALIC, false));
+ }
+ meta.lore(newLore);
+ meta.setCustomModelData(petNbt.petData.getCustomIconModelId());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ public static ItemStack getPetGuiStack(PlayerData playerData, String guiStackID) {
+ // 1.获取玩家数据
+ PetNbt petNbt = playerData.getPetNbt();
+ ItemStack petStack = playerData.getPetStack();
+
+ ItemStack stack = Config.getItemStack(guiStackID);
+ ItemMeta meta = stack.getItemMeta();
+ // 设置新的显示名(新版 Adventure API)
+ meta.displayName(Component.text(petStack.getItemMeta().getDisplayName())
+ .color(NamedTextColor.GREEN)
+ .decoration(TextDecoration.ITALIC, false));
+
+ List newLore = new ArrayList<>();
+ List rawLore = meta.getLore();
+ if (rawLore == null) {
+ rawLore = new ArrayList<>();
+ }
+
+ String statsName = "休息中";
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (playerManager.isPetCallExit(playerData.getPlayerName())) {
+ statsName = "出战中";
+ }
+ for (String line : rawLore) {
+ String string = line
+ .replace("{stats}", statsName)
+ .replace("{fangs}", String.valueOf(petNbt.quality.getFangs()))
+ .replace("{survive}", String.valueOf(petNbt.quality.getSurvive()))
+ .replace("{efficiency}", String.valueOf(petNbt.quality.getEfficiency()))
+ .replace("{level}", String.valueOf(petNbt.level))
+ .replace("{exp}", String.valueOf(petNbt.exp))
+ .replace("{maxExp}", String.valueOf(LevelExpManager.getNeedExpValue(petNbt.level)))
+ .replace("{damage}", String.valueOf(petNbt.damage))
+ .replace("{health}", String.valueOf(petNbt.health))
+ .replace("{maxHealth}", String.valueOf(petNbt.maxHealth));
+ newLore.add(Component.text(MessageUtil.convertHexColor(string)).decoration(TextDecoration.ITALIC, false));
+ }
+ meta.lore(newLore);
+ meta.setCustomModelData(petNbt.petData.getCustomIconModelId());
+ stack.setItemMeta(meta);
+ return stack;
+ }
+
+ /*public static NameTag getNameBone(ModeledEntity model, String stringType) {
+ if (model == null) {
+ return null;
+ }
+ if (model.getModels().size() == 0) {
+ return null;
+ }
+ Optional opt = model.getModels().values().stream().findFirst();
+ ActiveModel activeModel = null;
+ if (opt.isPresent()) {
+ activeModel = opt.get();
+ } else {
+ return null;
+ }
+ ModelBone bone = activeModel.getBone(stringType)
+ .stream()
+ .filter(modelBone -> modelBone.getBoneBehavior(BoneBehaviorTypes.NAMETAG).orElse(null) != null)
+ .findFirst().orElse(null);
+ if (bone == null) {
+ return null;
+ }
+ NameTag nameTag = bone.getBoneBehavior(BoneBehaviorTypes.NAMETAG).orElse(null);
+ return nameTag;
+ }*/
+ public static PlayerData updatePetNameTag(Wolf wolf) {
+ PlayerManager playerManager = PetMain.getPlayerManager();
+ if (wolf == null || wolf.isDead()) {
+ return null;
+ }
+ UUID petUuid = wolf.getUniqueId();
+ String ownerName = playerManager.getPetOwnerName(petUuid);
+ if (ownerName == null) {
+ return null;
+ }
+ PlayerData playerData = playerManager.getPlayerData(ownerName);
+ // 判断玩家是否召唤宠物
+ if (playerManager.isPetCallExit(ownerName)) {
+ /*// 获取宠物的的模型文件
+ ModeledEntity modeledEntity = playerManager.getPetEntityModel(petUuid);
+ if (modeledEntity == null) {
+ return null;
+ }
+ int petLevel = playerData.getPetNbt().level;
+ double healthNow = wolf.getHealth();
+ double healthMax = wolf.getMaxHealth();
+ // 计算获得血量百分比
+ int percent = (int) Math.round((healthNow / healthMax) * 100.0);
+ String levelText = Config.getLanguage("pet_level_tag")
+ .replace("{level}", Config.getLevelChar(petLevel))
+ .replace("{health}", Config.getHealthChar(percent));
+ NameTag levelTag = PetUtil.getNameBone(modeledEntity, "level");
+ if (levelTag != null) {
+ levelTag.setString(levelText);
+ levelTag.setVisible(true);
+ }*/
+ return playerData;
+ }
+ return null;
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java b/src/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
new file mode 100644
index 0000000..716e9d0
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/util/model/BetterModelUtil.java
@@ -0,0 +1,81 @@
+package com.yaohun.petsystem.util.model;
+
+import kr.toxicity.model.api.BetterModel;
+import kr.toxicity.model.api.bukkit.platform.BukkitAdapter;
+import kr.toxicity.model.api.data.renderer.ModelRenderer;
+import kr.toxicity.model.api.tracker.EntityTracker;
+import kr.toxicity.model.api.tracker.Tracker;
+import kr.toxicity.model.api.tracker.TrackerModifier;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+
+public class BetterModelUtil {
+
+ public static Tracker spawnModel(Player owner, Entity entity, String modelId) {
+ if (entity == null || entity.isDead()) {
+ Bukkit.getLogger().warning("宠物模型载入失败: 实体不存在");
+ return null;
+ }
+ if (modelId == null || modelId.isBlank()) {
+ Bukkit.getLogger().warning("宠物模型载入失败: 模型ID为空");
+ return null;
+ }
+
+ ModelRenderer renderer = BetterModel.modelOrNull(modelId);
+ if (renderer == null) {
+ Bukkit.getLogger().warning("宠物模型不存在: " + modelId);
+ return null;
+ }
+
+ EntityTracker tracker = renderer.create(
+ BukkitAdapter.adapt(entity),
+ getPetTrackerModifier(),
+ BetterModelUtil::refreshModelTracker
+ );
+
+ refreshModelTracker(tracker);
+ spawnVisiblePlayers(tracker, entity);
+ Bukkit.getLogger().info("宠物模型载入成功: 玩家=" + owner.getName() + ", 模型=" + modelId + ", 实体=" + entity.getUniqueId());
+
+ return tracker;
+ }
+
+ public static void spawnVisiblePlayers(Tracker tracker, Entity entity) {
+ if (tracker == null || tracker.isClosed() || entity == null) {
+ return;
+ }
+ for (Player viewer : Bukkit.getOnlinePlayers()) {
+ if (!viewer.isOnline()) {
+ continue;
+ }
+ if (!viewer.getWorld().equals(entity.getWorld())) {
+ continue;
+ }
+ if (tracker instanceof EntityTracker entityTracker) {
+ entityTracker.registry().spawnIfNotSpawned(BukkitAdapter.adapt(viewer));
+ } else {
+ tracker.show(BukkitAdapter.adapt(viewer));
+ }
+ }
+ }
+
+ public static void refreshModelTracker(Tracker tracker) {
+ if (tracker == null || tracker.isClosed()) {
+ return;
+ }
+ if (tracker instanceof EntityTracker entityTracker) {
+ entityTracker.updateBaseEntity();
+ entityTracker.refresh();
+ }
+ tracker.forceUpdate(true);
+ }
+
+ private static TrackerModifier getPetTrackerModifier() {
+ return TrackerModifier.DEFAULT.toBuilder()
+ .sightTrace(false)
+ .damageAnimation(false)
+ .damageTint(false)
+ .build();
+ }
+}
diff --git a/src/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java b/src/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
new file mode 100644
index 0000000..8e70336
--- /dev/null
+++ b/src/src/main/java/com/yaohun/petsystem/util/wolf/CustomWolf.java
@@ -0,0 +1,202 @@
+package com.yaohun.petsystem.util.wolf;
+
+import com.yaohun.petsystem.model.PetNbt;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.animal.wolf.Wolf;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeInstance;
+import org.bukkit.block.Block;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+
+import java.util.UUID;
+
+/**
+ * 自定义的狼实体类,用于实现“跟随玩家”的宠物功能。
+ * 继承原版 NMS Wolf 实体,通过 tick() 方法控制移动与传送。
+ */
+public class CustomWolf extends Wolf {
+
+ private static final double STOP_DISTANCE_SQ = 9.0D;
+ private static final double TELEPORT_DISTANCE_SQ = 144.0D;
+ private static final double LOOK_DISTANCE_SQ = 256.0D;
+ private static final double FOLLOW_SPEED = 1.2D;
+ private static final double DEFAULT_MOVEMENT_SPEED = 0.4D;
+ private static final int PATH_RECALC_TICKS = 10;
+
+ private final UUID ownerUuid;
+
+ private int timeToRecalcPath = 0;
+
+ /**
+ * 构造方法:初始化宠物实体
+ *
+ * @param world NMS 世界对象
+ * @param owner 宠物主人(Bukkit Player)
+ */
+ public CustomWolf(Level world, Player owner) {
+ super(EntityType.WOLF, world);
+ this.ownerUuid = owner.getUniqueId();
+ }
+
+ /**
+ * 每 tick 执行的逻辑,控制宠物的移动与状态
+ */
+ @Override
+ public void tick() {
+ super.tick();
+
+ Player owner = Bukkit.getPlayer(ownerUuid);
+ if (owner == null || !owner.isOnline()) {
+ this.discard();
+ return;
+ }
+ if (owner.isDead()) {
+ this.getNavigation().stop();
+ return;
+ }
+ if (!owner.getWorld().equals(this.getBukkitEntity().getWorld())) {
+ this.getNavigation().stop();
+ teleportToOwnerLocation(owner);
+ return;
+ }
+
+ net.minecraft.world.entity.player.Player nmsOwner = ((CraftPlayer) owner).getHandle();
+
+ double distanceSq = this.distanceToSqr(nmsOwner);
+
+ if (distanceSq < STOP_DISTANCE_SQ) {
+ this.getNavigation().stop();
+ return;
+ }
+
+ boolean shouldTeleport = distanceSq > TELEPORT_DISTANCE_SQ;
+
+ if (!shouldTeleport && distanceSq <= LOOK_DISTANCE_SQ) {
+ this.getLookControl().setLookAt(nmsOwner, 10.0F, this.getMaxHeadXRot());
+ }
+
+ if (--this.timeToRecalcPath <= 0) {
+ this.timeToRecalcPath = PATH_RECALC_TICKS;
+
+ if (shouldTeleport) {
+ if (!tryTeleportNearOwner(nmsOwner)) {
+ this.getNavigation().moveTo(nmsOwner, FOLLOW_SPEED);
+ }
+ } else {
+ this.getNavigation().moveTo(nmsOwner, FOLLOW_SPEED);
+ }
+ }
+ }
+
+ /**
+ * 当宠物距离主人太远时,尝试在主人周围随机传送
+ *
+ * @param nmsOwner 主人 NMS 实体
+ */
+ private boolean tryTeleportNearOwner(Entity nmsOwner) {
+ Vec3 pos = nmsOwner.position();
+
+ for (int i = 0; i < 10; i++) {
+ int x = (int) Math.floor(pos.x) + (random.nextInt(7) - 3);
+ int y = (int) Math.floor(pos.y);
+ int z = (int) Math.floor(pos.z) + (random.nextInt(7) - 3);
+
+ Location location = new Location(getBukkitEntity().getWorld(), x + 0.5D, y, z + 0.5D);
+ if (isSafeTeleportLocation(location)) {
+ this.teleportTo(location.getX(), location.getY(), location.getZ());
+ this.getNavigation().stop();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void teleportToOwnerLocation(Player owner) {
+ Location location = owner.getLocation();
+ if (isSafeTeleportLocation(location)) {
+ this.getBukkitEntity().teleport(location);
+ }
+ }
+
+ private boolean isSafeTeleportLocation(Location location) {
+ Block feet = location.getBlock();
+ Block head = feet.getRelative(0, 1, 0);
+ Block ground = feet.getRelative(0, -1, 0);
+ if (!feet.isPassable() || !head.isPassable() || !ground.getType().isSolid()) {
+ return false;
+ }
+ if (!location.getWorld().equals(getBukkitEntity().getWorld())) {
+ return true;
+ }
+ return this.level().noCollision(this, this.getBoundingBox().move(
+ location.getX() - this.getX(),
+ location.getY() - this.getY(),
+ location.getZ() - this.getZ()
+ ));
+ }
+
+ /**
+ * 重写受伤方法,防止主人伤害到自己的宠物
+ *
+ * @param source 伤害来源
+ * @param amount 伤害数值
+ * @return 是否成功造成伤害
+ */
+ @Override
+ public boolean hurtServer(ServerLevel level, DamageSource source, float amount) {
+ Entity entity = source.getEntity();
+
+ if (entity != null && entity.getUUID().equals(ownerUuid)) {
+ return false;
+ }
+ return super.hurtServer(level, source, amount);
+ }
+
+ /**
+ * 静态方法:用于在 Bukkit 世界中生成并注册一只 CustomWolf
+ *
+ * @param owner 宠物主人
+ * @return 新生成的 CustomWolf 实例
+ */
+ public static CustomWolf spawnCustomWolf(Player owner, PetNbt petNbt) {
+ CraftWorld world = (CraftWorld) owner.getWorld();
+ Level nmsWorld = world.getHandle();
+
+ CustomWolf wolf = new CustomWolf(nmsWorld, owner);
+
+ wolf.setSilent(true);
+ wolf.setOrderedToSit(false);
+ wolf.setPos(owner.getLocation().getX(), owner.getLocation().getY(), owner.getLocation().getZ());
+
+ LivingEntity livingEntity = wolf.getBukkitLivingEntity();
+ double maxHealth = Math.max(1.0D, petNbt.maxHealth);
+ double health = Math.max(1.0D, Math.min(petNbt.health, maxHealth));
+ double damage = Math.max(0.0D, petNbt.damage);
+
+ setAttribute(livingEntity, Attribute.MAX_HEALTH, maxHealth);
+ setAttribute(livingEntity, Attribute.MOVEMENT_SPEED, DEFAULT_MOVEMENT_SPEED);
+ setAttribute(livingEntity, Attribute.ATTACK_DAMAGE, damage);
+ livingEntity.setHealth(health);
+
+ nmsWorld.addFreshEntity(wolf);
+
+ return wolf;
+ }
+
+ private static void setAttribute(LivingEntity entity, Attribute attribute, double value) {
+ AttributeInstance instance = entity.getAttribute(attribute);
+ if (instance != null) {
+ instance.setBaseValue(value);
+ }
+ }
+}
diff --git a/src/src/main/resources/config.yml b/src/src/main/resources/config.yml
new file mode 100644
index 0000000..75039dc
--- /dev/null
+++ b/src/src/main/resources/config.yml
@@ -0,0 +1,31 @@
+NeedExpSettings:
+ 1: 10000
+ 2: 10000
+ 3: 10000
+ 4: 10000
+ 5: 10000
+ 6: 10000
+ 7: 10000
+ 8: 10000
+ 9: 10000
+ 10: 10000
+ 11: 10000
+ 12: 10000
+ 13: 10000
+ 14: 10000
+ 15: 10000
+ 16: 10000
+ 17: 10000
+ 18: 10000
+ 19: 10000
+ 20: 10000
+ 21: 10000
+ 22: 10000
+ 23: 10000
+ 24: 10000
+ 25: 10000
+ 26: 10000
+ 27: 10000
+ 28: 10000
+ 29: 10000
+ 30: -1
\ No newline at end of file
diff --git a/src/src/main/resources/lang.yml b/src/src/main/resources/lang.yml
new file mode 100644
index 0000000..e69de29
diff --git a/src/src/main/resources/plugin.yml b/src/src/main/resources/plugin.yml
new file mode 100644
index 0000000..66b3172
--- /dev/null
+++ b/src/src/main/resources/plugin.yml
@@ -0,0 +1,6 @@
+name: AuPet
+main: com.yaohun.petsystem.PetMain
+version: 1.0.1
+api-version: 1.21
+commands:
+ apet: