Compare commits

..
2 Commits
Author SHA1 Message Date
ypsdf1 b79754039f chore: 移除非项目文件,添加gitignore规则
- 从仓库追踪中移除txt/、工作簿1.xlsx、unnamed.patch等本地开发文件
- 更新.gitignore排除这些文件类型
2026-05-14 21:08:55 +08:00
ypsdf1 63d0a99cff Remove: 删除误传的radiopack.zip 2026-05-14 21:03:41 +08:00
52 changed files with 4277 additions and 22766 deletions
-32
View File
@@ -1,32 +0,0 @@
---
name: Bug 反馈
about: 报告一个 Bug 以帮助改进插件
title: "[Bug] "
labels: bug
assignees: ''
---
## Bug 描述
请简明扼要地描述 Bug 是什么。
## 复现步骤
1. 进入 ...
2. 点击 ...
3. 出现错误 ...
## 期望行为
描述你期望发生的事情。
## 实际行为
描述实际发生了什么。
## 服务端信息
- 服务器类型和版本(如 Paper 1.21.4):
- 插件版本:
## 截图 / 日志
如有相关截图或报错日志,请粘贴在此处。
## 其他信息
补充任何你认为有助于解决问题的信息。
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: 问题排查指南
url: https://github.com/ypsdf1/sdf1_login
about: 在提交 Bug 前,请先查阅文档确认是否已有相关说明。
@@ -1,20 +0,0 @@
---
name: 功能新增
about: 提出一个新功能的需求建议
title: "[Feature] "
labels: enhancement
assignees: ''
---
## 功能描述
请描述你希望添加的新功能。
## 动机
为什么需要这个功能?它能解决什么问题?
## 建议的实现方式
如果对实现方式有想法,请在此描述。
## 额外信息
补充任何你认为有助于理解需求的上下文。
-17
View File
@@ -1,17 +0,0 @@
---
name: 建议提交
about: 对插件提出改进建议或反馈
title: "[Suggestion] "
labels: suggestion
assignees: ''
---
## 建议内容
请描述你的建议或想法。
## 背景
什么场景下产生的这个建议?
## 预期效果
你认为采用这个建议后能达到什么效果?
-1
View File
@@ -43,4 +43,3 @@ txt/
*.xlsx
*.patch
新建*.txt
群戏归类.txt
+54 -481
View File
@@ -1,539 +1,112 @@
# Sdf1系列登录插件使用说明
# 特别说明:本插件仅有简中语言版本,如需除简中以外的任何版本,请下载插件源码,自行修改!
## 1. 功能介绍
| 名字 | 描述/作用 | 施工状态 |
| :--- | :--- | :--- |
| 登录 | 用户登录/注册 | 已完成 |
| 世界垃圾箱 | 清理服务器凋落物 | 已完成 |
| 世界垃圾箱 | 清理服务器凋落物 | 半成品 |
| 工单系统 | 用户提交工单 | 已完成 |
| 签到系统 | 签到积分换奖励 | 已完成 |
| 新人礼包 | 新玩家7日礼包 | 已完成 |
| 挂机检测 | 挂机检测踢出 | 已完成 |
| 自定义菜单 | 可以自己diy指令 | 已完成 |
| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 已完工 |
| 商城系统| 内置游戏商人可供销售商品 | 已完工 |
| 虚拟货币 | 供签到、游戏商人使用的货币系统 | 已完工 |
## 2. 安装说明
## 2.安装说明:
| 前置插件 | 选装类型 | 功能作用 |
| :--- | :--- | :--- |
| [CY_beibao](https://github.com/ypsdf1/CY_beibao) | 可选,推荐 | 云背包,积分商城可购买背包容量
| EssentialsX | 可选 | 主流经济系统
| [sdf1](https://github.com/ypsdf1/sdf1_plugn) | 可选,推荐 | CDK兑换系统
| 前置插件 | 选装类型 |
| :--- | :--- |
| CY_beibao | 可选 |
| EssentialsX | 可选 |
### 2.1 下载
您可以从GitHub和Gitee下载到插件,将其丢入服务器plugin目录,重启服务器即可。
您可以从GitHub和Gitee下载插件,将其放入服务器plugin目录,重启服务器即可。
### 2.2. 配置文件
### 2.2 配置文件
#### 2.2.1 消息.txt
此文件控制着整个插件的所有提示消息,您可以根据自己的需求修改,修改后需要执行 /sdf1_login reload 重载插件。支持[颜色代码](https://zh.minecraft.wiki/w/%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%BB%A3%E7%A0%81)<br>
同时兼容`<b> </b>`标签,对段落加粗,`\n 和 <br>`进行段落换行
此文件控制整个插件的所有提示消息,修改后执行 /sdf1_login reload 重载插件。
#### 2.2.2 挂机白名单.txt
挂机踢出开启时,该配置下的玩家不受影响。
```txt
# ==========================================
# Sdf1_login 消息配置文件
# ==========================================
# 颜色代码使用 § 符号
# §0黑色 §1深蓝 §2深绿 §3深青
# §4深红 §5深紫 §6金色 §7灰色
# §8深灰 §9蓝色 §a绿色 §b浅蓝
# §c红色 §d粉色 §e黄色 §f白色
# §l粗体 §n下划线 §o斜体 §r重置
#
# 占位符用 {xxx} 表示,运行时自动替换
# 支持 <b>粗体</b> 标签
# 支持 \n 或 <br> 换行
# 修改后执行 /sdf1_login reload 重载
# ==========================================
#### 2.2.3 SMTP设置.txt
控制SMTP邮件服务器配置,用于找回密码时发送邮件。
# ---- 登录注册相关 ----
# login_timeout: 登录超时被踢出时的提示
login_timeout=§c登录超时,请重新登录
# not_registered: 未注册玩家收到的提示
not_registered=§e您尚未注册,请使用 /reg <密码>
# not_logged_in: 已注册但未登录的提示
not_logged_in=§e您尚未登录,请使用 /login <密码>
# already_registered: 已注册玩家再次注册时的提示
already_registered=§c您已注册过账号
# already_logged_in: 已登录玩家再次登录时的提示
already_logged_in=§c您已登录
# reg_success: 注册成功提示
reg_success=§a注册成功!欢迎来到服务器
# login_success: 登录成功提示
login_success=§a登录成功!
# login_failed: 密码错误提示
login_failed=§c密码错误,请重试
#### 2.2.4 插件设置.txt
管控整个插件,包括账号注册限制、管理员密码等。
# ---- 密码相关 ----
# password_wrong: 旧密码验证失败
password_wrong=§c密码错误
# password_changed: 密码修改成功
password_changed=§a密码修改成功!
# password_format_error: 密码格式不符合要求
password_format_error=§c密码需6位以上,含大小写字母和数字
# password_same: 新密码与旧密码相同
password_same=§c新密码不能与旧密码相同
# reset_no_email: 找回密码时未绑定邮箱
reset_no_email=§c您未绑定邮箱,无法找回密码
# reset_sent: 临时密码已发送到邮箱
reset_sent=§a临时密码已发送到您的邮箱,请查收
#### 2.2.5 chat.txt
控制聊天过滤,处理违规链接。
# ---- 签到积分相关 ----
# checkin_already: 今日已签到
checkin_already=§c您今日已签到
# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率
checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}
# points_insufficient: 积分不足
points_insufficient=§c积分不足,当前: {points}
# points_purchase_success: 积分购买成功
points_purchase_success=§a购买成功!消耗 {count} 积分
## 3. 指令
# ---- 挂机检测相关 ----
# afk_set_enabled: 挂机检测开启
afk_set_enabled=§a挂机检测已开启
# afk_set_disabled: 挂机检测关闭
afk_set_disabled=§c挂机检测已关闭
# afk_kick: 因挂机被踢出
afk_kick=§c因挂机过久被踢出
- Sdf1_login 主指令
- Sdf1_login reload 重载配置
- reg 注册账号
- login 登录账号
- l 登录账号
# ---- 邮箱相关 ----
# email_set: 邮箱设置成功
email_set=§a邮箱设置成功:{email}
# email_format_error: 邮箱格式错误
email_format_error=§c邮箱格式不正确
# ---- 邀请相关 ----
# invite_code_generated: 邀请码生成
invite_code_generated=§a邀请码:{code}
# invite_used: 使用邀请码成功
invite_used=§a邀请成功,邀请人:{player}
# invite_self: 使用自己的邀请码
invite_self=§c不能使用自己的邀请码
# invite_not_found: 邀请码不存在
invite_not_found=§c邀请码不存在
# invite_referral_bonus: 被邀请人签到后邀请人获得奖励
invite_referral_bonus=§a邀请奖励!获得 {points} 积分
# ---- 新人礼包相关 ----
# gift_not_ready: 礼包条件未满足
gift_not_ready=§c礼包条件未满足
# gift_claimed: 礼包领取成功
gift_claimed=§a礼包领取成功!
# ---- 审批与IP限制 ----
# need_approval: 注册需审批
need_approval=§e注册需审批 (工单#{id})
# ip_max_accounts: IP注册已达上限
ip_max_accounts=§cIP注册已达上限
# ---- 管理员操作 ----
# admin_delete_confirm: 管理员删除账号确认提示
admin_delete_confirm=§c请在聊天中输入玩家名确认删除
# ---- 聊天过滤 ----
# chat_muted: 禁言玩家尝试聊天
chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天
# chat_url_blocked: 发送违规链接被拦截
# {url}=检测到的链接地址
chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}
# chat_url_violation: 当前违规次数
# {count}=当前违规次数
chat_url_violation=§7当前违规次数: §e{count}
# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数
chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)
# chat_url_broadcast: 全服通报,{player}=玩家名
chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚
# ---- 通用 ----
# 未知参数: 未知命令或权限不足时的提示
未知参数=§c未知参数或权限不足
# only_player: 仅玩家可用的命令
# only_player=§c此命令仅玩家可用
```
### 2.2.2 挂机白名单.txt
当挂机踢出开启时,该配置下的玩家不受挂机提出影响
### 2.2.3 SMTP设置.txt
此文件控制着SMTP邮件服务器的配置,用于找回密码时发送邮件。<br>可通过GUI进行配置,也可以手动修改此配置文件。修改后,重载插件即可生效
```txt
# ===== SMTP 邮件服务器配置 =====
smtp地址=smtp.example.com
smtp端口=465
smtp账号=mail@example.com
smtp密码=example.com
发件人名称=Sdf1_login
smtp加密=true
验证码接收邮箱=example@example.com
```
### 2.2.4 插件设置.txt
此配置管控这整个插件,包括玩家能注册多少个账号、默认的管理员面板密码多少、tag包含什么视为管理员等
```txt
# ===== Sdf1_login 插件设置 =====
管理标签=admin
管理密码=qweasd
每IP最大账号数=3
审批模式=manual
自动审批延迟分钟=30
登录超时秒数=60
挂机踢出=false
挂机超时秒数=300
服务商积分倍率=2.0
基础经济奖励=100
经济奖励每评分=50.0
报单人积分奖励=10
# ---- 签到盲盒奖励 ----
签到固定金额=0
签到最小金额=10
签到最大金额=100
签到给积分=true
签到积分数量=10
```
### 2.2.5 chat.txt
该文件控制着聊天过滤,当玩家发送违规链接时,会自动根据配置规则进行处理。<br>
可用的处罚规则:
- warn: 警告
- mute: 禁言
- kick: 踢出
- ban: 封禁
- banip:封禁IP
```txt
# Sdf1_chat 聊天过滤配置
通知管理员: true
全服通报: true
禁言时长: 300
白名单:
*.minecraft.net
baidu.com
github.com
处罚规则:
1:warn:0
2:warn:0
3:mute:300
5:mute:600
8:kick:0
10:ban:0
白名单玩家:
```
### 2.2.6 radio_config.txt
```txt
# ============ Sdf1 Radio Config ============
#
# resource-pack-url
# 填外部地址(HTTP或HTTPS均可)
# 例: https://example.com/radiopack.zip
# 留空 = 使用内置HTTP服务
#
# http-port
# 内置HTTP端口(仅resource-pack-url为空时生效)
#
# ============ Settings ===============
resource-pack-url=https://*.ypshidifu.cn/|*.mcserver.ccwu.cc
http-port=443
```
- radio_config这里可以使用通配符域名,当使用通配符时,系统随机生成url,拒绝过资源包的玩家,每次都会收到提示。并且自1.2版本起,拒绝资源包=放弃游玩服务器。如果您无需广播功能,可以不放ogg文件进入`./plugin/radio/ogg`。这样服务端不会自动生成资源包,也不会强制下载了
### 2.2.7 菜单.txt
- 自定义菜单配置文件
```json
# 服务器菜单
# 使用类json格式
// 支持使用://Java单行注释、#yml单行注释、/* */块注释,也支持<!--- --->的块注释
{
标题:主城传送
指令:/dom tp 主城区 # 这里如果是xxx.txt则表示进入二级目录
权限类型:玩家
图标:COMPASS
}
{
标题:随机传送
指令:/rtp
权限类型:玩家
图标:ENDER_PEARL
}
{
标题:擂台比武
指令:/dom tp PVP
权限类型:玩家
图标:netherite_spear
}
{
标题:领地操作
指令:/dom
权限类型:玩家
图标:paper
}
{
标题:逛商店
指令:/dom tp shop
权限类型:玩家
图标:honeycomb
}
{
标题:传送开关
指令:/tptoggle
权限类型:玩家
图标:redstone_torch
}
{
标题:返回上一个位置
指令:/back
权限类型:玩家
图标:red_bed
}
{
标题:前往小黑塔
指令:/dom tp 小黑塔
权限类型:玩家
图标:dragon_head
}
{
标题:云背包
指令:/cy
权限类型:玩家
图标:potato
}
{
标题:自杀
指令:/suicide
权限类型:玩家
图标:wooden_axe
}
{
标题:游戏商人
指令:/shop
权限类型:玩家
图标:shield
格子:28
}
```
### 2.2.8 商品配置
- 配置此处的文件,负责展示游戏内所销售的商品。<br>
- 一个文件名,对应一个商品分类,使用md文件格式
```
# 建筑材料
| ID | 品名 | 材质 | 购入价 | 售出价 | 库存 | 本小时销量 | 总销量 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| STONE | 石头 | STONE | 10 | -1 | -1 | 0 | 0 |
| COBBLESTONE | 圆石 | COBBLESTONE | 5 | -1 | -1 | 0 | 26 |
| OAK_PLANKS | 橡木木板 | OAK_PLANKS | 8 | 3 | -1 | 0 | 0 |
| BRICKS | 砖块 | BRICKS | 12 | -1 | -1 | 0 | 0 |
| GLASS | 玻璃 | GLASS | 15 | 6 | -1 | 0 | 0 |
| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK | 20 | 8 | -1 | 0 | 0 |
| AMETHYST_BLOCK | 紫水晶 | AMETHYST_BLOCK | 10 | 0 | -1 | 0 | 999 |
| OCHRE_FROGLIGHT | 赭黄蛙鸣灯 | OCHRE_FROGLIGHT | 100 | 0 | -1 | 0 | 1000 |
```
## 2.3 商城部分介绍
1. 自助退款,买多买错,不用慌,一件退单很安心<br>
2. 购物车,买的多怕买错,先加购物车,后续统一结算<br>
3. 害怕订单找不到?一键查看历史订单<br>
4. 玩家要耍赖皮,拿着小票来找麻烦怎么办?退款订单自动贴标,拒绝白嫖
5. 想查看玩家消费记录?小手一点,明明白白<br>
## 2.4 打印流水部分介绍
1. 管理员怀疑某个玩家刷钱了,怎么办?/printer 玩家名,一检查流水,很安心。很多人都刷了怎么办?/printer不加参数,一键导出全服流水,查他个底朝天<br>
2. 忘记删除怎么办?插件每分钟自动检测,旧文件超过24小时,自动删
## 3. 指令参数及介绍
- Sdf1_login 主指令<br>
- Sdf1_login reload 重载配置文件<br>
- reg 注册账号<br>
- login 登录账号<br>
- l 登录账号<br>
- sdf1_login del 删除玩家账号<br>
- sdf1_login radio 停止播放广播<br>
- sdf1_login radio reload 重载广播并重新打包资源包<br>
- menu 开/关 是否开启菜单<br>
- printer 打印流水数据,支持不带参数&带玩家名为参数<br>
- sdf1_login printer 作用同printer<br>
- sdf1_login shop 商城
- protect 区域防护
## 4. 自定义菜单物品
- 打开主界面,选择"我的"进入<br>
- 将你的自定义物品与雪球交换<br>
- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了
## 5. pvp战神榜
- 通过此功能,你可以在服务器里制作大服务器同款的击杀排行榜,而且能精准定位指定区域<br>
- 并且有着网游同款的击杀通报<br>
- 播报逻辑是:<br>
- 3杀<br>
- 5杀<br>
- 5杀以上每杀一次都是一条播报<br>
- 终结者通报,触发连杀的玩家被终结也会有全服通报
## 6. 区域防护
- 通过此功能,您可在服务器里需要防护的地方(比如展示框不想被玩家随便破坏、主城不准玩家袭击等)加以防护。<br>
- 还能够保护指定区域的玩家不受任何伤害。<br>
| 参数 | 效果 |
| :---: | :---: |
| 和平模式 | 所有敌对生物全部进虚空 |
| 禁止袭击 | 立即清理玩家身上的袭击效果,传灾厄村民进虚空 |
| 清除XX效果 | 立即清除玩家身上的效果
| 给与xx效果 | 根据配置文件,给予玩家指定等级、时长的效果
## 生物属性对照表
| 中文名称 | 类型 | 附加属性 |
| :---: | :----: | :----: |
| 溺尸 | 近战/远程 | 亡灵、水生 |
| 骷髅 | 远程 | 亡灵 |
| 流浪者 | 远程 | 亡灵、寒冷群系变种 |
| 沼骸 | 远程 | 亡灵、沼泽变种 |
| 焦骸 | 远程 | 亡灵、沙漠变种、免疫阳光 |
| 苦力怕 | 近战(爆炸) | 无 |
| 幻翼 | 远程(俯冲) | 亡灵、夜间生成 |
| 掠夺者 | 远程 | 灾厄村民、袭击成员 |
| 卫道士 | 近战 | 灾厄村民、袭击成员 |
| 幻术师 | 远程 | 灾厄村民、Java专属、隐身分身 |
| 旋风人 | 远程 | 试炼密室生成、免疫弹射物、强击退 |
| 监守者 | 近战/声波远程 | 深暗之域、附带盲视效果 |
| 远古守卫者 | 近战/激光远程 | 海底神殿、水下、附加挖掘疲劳 |
| 守卫者 | 近战/激光远程 | 海底神殿、水下 |
配置文件解析:<br>
- 支持注释格式:`Java的//单行注释`、`yml的#单行注释`、`HTML的<!--- --->注释`和`Java/* */块注释`
```txt
# 区域: 主城区
起点: world,-130,-713
终点: world,-427,-560
高度范围: 0-255
没收物品: 蘑菇,毒蘑菇 #没收哪些物品(末地推荐这两个)
// 禁止放置方块 #保护区内,非区白/全白玩家禁止放置方块
// 禁止破坏方块 # 同上
禁止PVP # 玩家之间不能PVP
禁止摔伤 # 摔落无法造成任何伤害
禁止饥饿 # 不会消耗饱食度
禁止一切伤害 # 任何怪物都无法造成伤害
# 禁止丢弃物品
# 禁止末影珍珠
# 禁止使用弓箭
# 禁止骑乘
// 和平模式 // 所有的敌对生物全部传送进虚空
禁止袭击
禁止交互展示框 # 核心,任何玩家不能触碰展示框。区白/全白玩家除外
// 清除所有效果 # 清理所有负面效果
效果 村庄英雄 255 999999 # 给予玩家效果。格式为: 效果 效果名字 等级(最高255) 时间
效果 生命提升 255 999999
效果 抗性提升 255 999999
效果 抗火 255 999999
# 禁止药水
禁止爆炸
禁止使用物品: TNT
# 效果: 夜视 1 999
进入提示: 欢迎来到保护区 # 是否开启进入提示,即自定义提示
离开提示: 已离开保护区
没收提示: 你的违禁品已被没收
通报批评: §c§l{player} §f在{area}违规携带违禁品,被没收 §e{count}§f件! # 通报违禁品。{player} 玩家名 {count}数量 {area}区域
# 惩罚命令: ban {player} 60s 违规
```
## 运行截图
## 4. 运行截图
主界面
![](img/login.png)
![主界面](img/login.png)
邀请数据
![](img/login2.png)
![邀请数据](img/login2.png)
积分商店
![](img/login3.png)
![积分商店](img/login3.png)
任务中心
![](img/login4.png)
![任务中心](img/login4.png)
垃圾箱
![](img/login5.png)
![垃圾箱](img/login5.png)
管理员面板
![](img/login6.png)
![管理员面板](img/login6.png)
管理面板-用户管理
![](img/login7.png)
![用户管理](img/login7.png)
工单中心
![](img/login8.png)
![工单中心](img/login8.png)
工单管理
![](img/login9.png)
![工单管理](img/login9.png)
我的工单
![](img/login10.png)
![我的工单](img/login10.png)
提交工单
![](img/login11.png)
![提交工单](img/login11.png)
服务商列表
![](img/login12.png)
![服务商列表](img/login12.png)
服务商抢单大厅
![](img/login13.png)
![抢单大厅](img/login13.png)
服务商-接单界面
![](img/login14.png)
管理员-工单详情页面
![](img/login15.png)
![接单界面](img/login14.png)
商城主界面
![](img/shop1.png)
管理员-工单详情
商城购物车
![](img/cart1.png)
![工单详情](img/login15.png)
商城退单
![](img/shop2.png)
## 5. 下载地址
## 4. 下载地址
- GitHub:[https://github.com/ypsdf1/sdf1_login](https://github.com/ypsdf1/sdf1_login)
- Gitee:[https://gitee.com/nihaoshidifu/sdf1_login](https://gitee.com/nihaoshidifu/sdf1_login)
- GitHub: https://github.com/ypsdf1/sdf1_login
- Gitee: https://gitee.com/nihaoshidifu/sdf1_login
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

-1
View File
@@ -33,7 +33,6 @@
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
File diff suppressed because it is too large Load Diff
-697
View File
@@ -1,697 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.File;
import java.sql.*;
import java.util.*;
public class BondManager {
private final Main plugin;
private Connection db;
// ===== 交易类型常量 =====
public static final String TX_ADMIN_GIVE = "admin_give";
public static final String TX_ADMIN_DEDUCT = "admin_deduct";
public static final String TX_TRANSFER_OUT = "transfer_out";
public static final String TX_TRANSFER_IN = "transfer_in";
public static final String TX_REDEEM = "redeem";
public static final String TX_SHOP_BUY = "shop_buy";
public static final String TX_DAILY_SIGN = "daily_sign";
public static final String TX_FREEZE = "freeze";
public static final String TX_UNFREEZE = "unfreeze";
public static final int MAX_WATER_DAYS = 14;
public static final int ADMIN_MAX_WATER_DAYS = 30;
public BondManager(Main plugin) {
this.plugin = plugin;
initDB();
}
// ======================== 数据库初始化 ========================
private void initDB() {
try {
Class.forName("org.sqlite.JDBC");
File dbFile = new File(
plugin.getDataFolder(), "bond.db");
db = DriverManager.getConnection(
"jdbc:sqlite:" + dbFile.getAbsolutePath());
Statement st = db.createStatement();
st.execute("PRAGMA journal_mode=WAL");
// 债券余额(含冻结状态)
st.execute("CREATE TABLE IF NOT EXISTS bonds ("
+ "player_name TEXT PRIMARY KEY,"
+ "amount INTEGER DEFAULT 0,"
+ "status TEXT DEFAULT 'normal')");
// 兼容旧表
try {
st.execute("ALTER TABLE bonds "
+ "ADD COLUMN status "
+ "TEXT DEFAULT 'normal'");
} catch (SQLException ignored) {}
// 旧流水(保留兼容)
st.execute("CREATE TABLE IF NOT EXISTS bond_log ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "player_name TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "reason TEXT DEFAULT '',"
+ "time INTEGER DEFAULT 0)");
// 新交易流水表
st.execute("CREATE TABLE IF NOT EXISTS "
+ "bond_transaction ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "player_name TEXT NOT NULL,"
+ "type TEXT NOT NULL,"
+ "amount INTEGER NOT NULL,"
+ "target_player TEXT DEFAULT '',"
+ "operator TEXT DEFAULT '',"
+ "reason TEXT DEFAULT '',"
+ "balance_after INTEGER DEFAULT 0,"
+ "time INTEGER DEFAULT 0)");
try {
st.execute("CREATE INDEX "
+ "IF NOT EXISTS idx_tx_pt "
+ "ON bond_transaction("
+ "player_name, time)");
} catch (SQLException ignored) {}
try {
st.execute("ALTER TABLE bond_transaction "
+ "ADD COLUMN balance_before "
+ "INTEGER DEFAULT 0");
} catch (SQLException ignored) {}
st.close();
} catch (Exception e) {
throw new RuntimeException(
"[Bond] DB init failed: "
+ e.getMessage(), e);
}
}
// ======================== 账户状态 ========================
public boolean isFrozen(String player) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT status FROM bonds "
+ "WHERE player_name=?");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String s = rs.getString("status");
rs.close(); ps.close();
return "frozen".equals(s);
}
rs.close(); ps.close();
} catch (SQLException ignored) {}
return false;
}
public String getAccountStatus(String player) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT status FROM bonds "
+ "WHERE player_name=?");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String s = rs.getString("status");
rs.close(); ps.close();
return s != null ? s : "normal";
}
rs.close(); ps.close();
} catch (SQLException ignored) {}
return "normal";
}
public void freezeAccount(String player) {
ensureAccount(player);
try {
PreparedStatement ps = db.prepareStatement(
"UPDATE bonds SET status='frozen' "
+ "WHERE player_name=?");
ps.setString(1, player);
ps.executeUpdate(); ps.close();
} catch (SQLException e) { e.printStackTrace(); }
}
public void unfreezeAccount(String player) {
ensureAccount(player);
try {
PreparedStatement ps = db.prepareStatement(
"UPDATE bonds SET status='normal' "
+ "WHERE player_name=?");
ps.setString(1, player);
ps.executeUpdate(); ps.close();
} catch (SQLException e) { e.printStackTrace(); }
}
private void ensureAccount(String player) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT OR IGNORE INTO bonds "
+ "(player_name,amount,status) "
+ "VALUES (?,0,'normal')");
ps.setString(1, player);
ps.executeUpdate(); ps.close();
} catch (SQLException ignored) {}
}
// ======================== 余额查询 ========================
public int getBonds(String player) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT amount FROM bonds "
+ "WHERE player_name=?");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
int v = 0;
if (rs.next()) v = rs.getInt("amount");
rs.close(); ps.close();
return v;
} catch (SQLException e) { return 0; }
}
/** 该玩家是否在 bond 表中有真实记录 */
public boolean hasBondAccount(String player) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT 1 FROM bonds "
+ "WHERE player_name=? "
+ "AND amount > 0");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
boolean exists = rs.next();
rs.close(); ps.close();
return exists;
} catch (SQLException e) {
return false;
}
}
// ======================== 增加债券 ========================
public int addBonds(String player, int amount) {
return addBondsInternal(player, amount,
TX_ADMIN_GIVE, "", "",
"管理员发放");
}
public int addBonds(String player, int amount,
String type,
String targetPlayer,
String operator,
String reason) {
if (isFrozen(player)) return -1;
return addBondsInternal(player, amount,
type, targetPlayer, operator, reason);
}
private int addBondsInternal(String player,
int amount,
String type,
String targetPlayer,
String operator,
String reason) {
int before = getBonds(player);
int after = before + amount;
try {
PreparedStatement ps = db.prepareStatement(
"INSERT INTO bonds "
+ "(player_name,amount,status)"
+ " VALUES (?,?, 'normal') "
+ "ON CONFLICT(player_name) "
+ "DO UPDATE SET amount=?");
ps.setString(1, player);
ps.setInt(2, after);
ps.setInt(3, after);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
logTransaction(player, type, amount,
targetPlayer, operator, reason,
before, after);
logLegacy(player, amount);
return after;
}
// ======================== 扣除债券 ========================
private boolean deductBondsInternal(String player,
int amount,
String type,
String targetPlayer,
String operator,
String reason) {
int before = getBonds(player);
if (before < amount) return false;
int after = before - amount;
try {
PreparedStatement ps = db.prepareStatement(
"UPDATE bonds SET amount=? "
+ "WHERE player_name=?");
ps.setInt(1, after);
ps.setString(2, player);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
logTransaction(player, type, amount,
targetPlayer, operator, reason,
before, after);
logLegacy(player, -amount);
return true;
}
// ======================== 流水写入 ========================
private void logTransaction(String player,
String type,
int amount,
String targetPlayer,
String operator,
String reason,
int balanceBefore,
int balanceAfter) {
try {
// 合并模式:shop_buy 30秒内同商品合并
if ("shop_buy".equals(type)
&& reason != null
&& !reason.isEmpty()) {
long mergeWindow =
System.currentTimeMillis() - 30000L;
PreparedStatement chk =
db.prepareStatement(
"SELECT id, amount "
+ "FROM bond_transaction "
+ "WHERE player_name=? "
+ "AND type=? "
+ "AND reason=? "
+ "AND time > ? "
+ "ORDER BY time DESC "
+ "LIMIT 1");
chk.setString(1, player);
chk.setString(2, type);
chk.setString(3, reason);
chk.setLong(4, mergeWindow);
ResultSet rs = chk.executeQuery();
if (rs.next()) {
int existId = rs.getInt("id");
int existAmt = rs.getInt("amount");
rs.close();
chk.close();
PreparedStatement upd =
db.prepareStatement(
"UPDATE bond_transaction "
+ "SET amount=?, "
+ "balance_after=?, "
+ "time=? "
+ "WHERE id=?");
upd.setInt(1, existAmt + amount);
upd.setInt(2, balanceAfter);
upd.setLong(3,
System.currentTimeMillis());
upd.setInt(4, existId);
upd.executeUpdate();
upd.close();
return;
}
rs.close();
chk.close();
}
// 原始插入
PreparedStatement ps = db.prepareStatement(
"INSERT INTO bond_transaction "
+ "(player_name, type, amount,"
+ " target_player, operator,"
+ " reason, balance_before,"
+ " balance_after, time)"
+ " VALUES (?,?,?,?,?,?,?,?,?)");
ps.setString(1, player);
ps.setString(2, type);
ps.setInt(3, amount);
ps.setString(4,
targetPlayer != null ? targetPlayer : "");
ps.setString(5,
operator != null ? operator : "");
ps.setString(6,
reason != null ? reason : "");
ps.setInt(7, balanceBefore);
ps.setInt(8, balanceAfter);
ps.setLong(9, System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// ======================== 扣除债券 ========================
/** 完整版:含冻结检查 + 流水记录 */
public boolean deductBonds(String player, int amount,
String type,
String targetPlayer,
String operator,
String reason) {
if (isFrozen(player)) return false;
return deductBondsInternal(player, amount,
type, targetPlayer, operator, reason);
}
public boolean checkoutCart(String player,
String[] itemNames,
int[] subtotals) {
int total = 0;
for (int s : subtotals) total += s;
if (isFrozen(player)) return false;
int before = getBonds(player);
if (before < total) return false;
int after = before - total;
try {
PreparedStatement ps = db.prepareStatement(
"INSERT INTO bonds "
+ "(player_name,amount,status) "
+ "VALUES (?,?,'normal') "
+ "ON CONFLICT(player_name) "
+ "DO UPDATE SET amount=?");
ps.setString(1, player);
ps.setInt(2, after);
ps.setInt(3, after);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
int running = before;
for (int i = 0; i < itemNames.length; i++) {
int next = running - subtotals[i];
logTransaction(player, "shop_buy", subtotals[i],
"", "商店系统",
"购物车购买-" + itemNames[i],
running, next);
running = next;
}
logLegacy(player, -total);
return true;
}
public boolean setBonds(String player, int amount) {
ensureAccount(player);
try {
PreparedStatement ps = db.prepareStatement(
"INSERT INTO bonds "
+ "(player_name,amount,status) "
+ "VALUES (?,?, 'normal') "
+ "ON CONFLICT(player_name) "
+ "DO UPDATE SET amount=?");
ps.setString(1, player);
ps.setInt(2, amount);
ps.setInt(3, amount);
ps.executeUpdate(); ps.close();
} catch (SQLException e) { e.printStackTrace(); }
return true;
}
// ======================== 转账 ========================
public TransferResult transfer(String from,
String to,
int amount) {
if (from.equalsIgnoreCase(to))
return new TransferResult(false,
"不能给自己转账");
if (isFrozen(from))
return new TransferResult(false,
"你的账户已被冻结");
if (isFrozen(to))
return new TransferResult(false,
to + " 的账户已被冻结");
if (isSameIP(from, to))
return new TransferResult(false,
"不能与同IP玩家转账");
if (!deductBondsInternal(from, amount,
TX_TRANSFER_OUT, to, from,
"转账给" + to))
return new TransferResult(false, "债券不足");
addBondsInternal(to, amount,
TX_TRANSFER_IN, from, from,
"收到来自" + from + "的转账");
return new TransferResult(true, "转账成功");
}
// ======================== 同IP检测 ========================
public boolean isSameIP(String p1, String p2) {
String ip1 = getPlayerIPAny(p1);
String ip2 = getPlayerIPAny(p2);
if (ip1 == null || ip2 == null) return false;
return ip1.equals(ip2);
}
/** 获取同IP下的所有其他在线玩家 */
public List<String> getSameIPPlayers(String player) {
String ip = getPlayerIPAny(player);
if (ip == null) return Collections.emptyList();
List<String> list = new ArrayList<>();
for (Player p :
plugin.getServer().getOnlinePlayers()) {
String pIp = plugin.getPlayerIP(p);
if (pIp != null && pIp.equals(ip)
&& !p.getName()
.equalsIgnoreCase(player)) {
list.add(p.getName());
}
}
return list;
}
private String getPlayerIPAny(String player) {
// 在线:直接取
Player p = plugin.getServer()
.getPlayerExact(player);
if (p != null) {
String ip = plugin.getPlayerIP(p);
if (ip != null) return ip;
}
// 离线:查主库
try {
Object v = plugin.getDb()
.getField(player, "last_login_ip");
if (v != null) return v.toString();
} catch (Exception ignored) {}
try {
Object v = plugin.getDb()
.getField(player, "register_ip");
if (v != null) return v.toString();
} catch (Exception ignored) {}
return null;
}
// ======================== 流水查询 ========================
public List<Map<String, Object>> getTransactions(
String player, int days) {
days = Math.min(days, MAX_WATER_DAYS);
long cutoff = System.currentTimeMillis()
- (long) days * 24 * 60 * 60 * 1000;
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.prepareStatement(
"SELECT * FROM bond_transaction "
+ "WHERE player_name=? "
+ "AND time>=? "
+ "ORDER BY time DESC");
ps.setString(1, player);
ps.setLong(2, cutoff);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("type", rs.getString("type"));
row.put("amount",
rs.getInt("amount"));
row.put("target_player",
rs.getString("target_player"));
row.put("operator",
rs.getString("operator"));
row.put("reason",
rs.getString("reason"));
row.put("balance_after",
rs.getInt("balance_after"));
row.put("balance_before", rs.getInt("balance_before"));
row.put("time", rs.getLong("time"));
list.add(row);
}
rs.close(); ps.close();
} catch (SQLException e) { e.printStackTrace(); }
return list;
}
// 放在 getTransactions 方法后面:
/**
* 获取该玩家全部流水(不限时间,供打印使用)
*/
public List<Map<String, Object>>
getAllTransactions(String player) {
List<Map<String, Object>> list =
new ArrayList<>();
try {
PreparedStatement ps = db.prepareStatement(
"SELECT * FROM bond_transaction "
+ "WHERE player_name=? "
+ "ORDER BY time ASC");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("player_name",
rs.getString("player_name"));
row.put("type", rs.getString("type"));
row.put("amount",
rs.getInt("amount"));
row.put("target_player",
rs.getString("target_player"));
row.put("operator",
rs.getString("operator"));
row.put("reason",
rs.getString("reason"));
row.put("balance_after",
rs.getInt("balance_after"));
row.put("balance_before", rs.getInt("balance_before"));
row.put("time", rs.getLong("time"));
list.add(row);
}
rs.close(); ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
// 放在 getAllTransactions 方法后面:
/**
* 获取所有有流水记录的玩家名(去重)
*/
public List<String> getAllPlayerNames() {
List<String> list = new ArrayList<>();
try {
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(
"SELECT DISTINCT player_name "
+ "FROM bond_transaction "
+ "ORDER BY player_name ASC");
while (rs.next()) {
list.add(rs.getString("player_name"));
}
rs.close(); st.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public List<Map<String, Object>>
getTransferTransactions(
String player, int days) {
days = Math.min(days, MAX_WATER_DAYS);
long cutoff = System.currentTimeMillis()
- (long) days * 24 * 60 * 60 * 1000;
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.prepareStatement(
"SELECT * FROM bond_transaction "
+ "WHERE player_name=? "
+ "AND time>=? "
+ "AND (type=? OR type=?) "
+ "ORDER BY time DESC");
ps.setString(1, player);
ps.setLong(2, cutoff);
ps.setString(3, TX_TRANSFER_OUT);
ps.setString(4, TX_TRANSFER_IN);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
row.put("id", rs.getInt("id"));
row.put("type", rs.getString("type"));
row.put("amount",
rs.getInt("amount"));
row.put("target_player",
rs.getString("target_player"));
row.put("reason",
rs.getString("reason"));
row.put("balance_after",
rs.getInt("balance_after"));
row.put("balance_before", rs.getInt("balance_before"));
row.put("time", rs.getLong("time"));
list.add(row);
}
rs.close(); ps.close();
} catch (SQLException e) { e.printStackTrace(); }
return list;
}
private void logLegacy(String player, int amount) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT INTO bond_log "
+ "(player_name,amount,time) "
+ "VALUES (?,?,?)");
ps.setString(1, player);
ps.setInt(2, amount);
ps.setLong(3,
System.currentTimeMillis());
ps.executeUpdate(); ps.close();
} catch (SQLException ignored) {}
}
public Map<String, Object> getLog(
String player, int limit) {
Map<String, Object> info = new LinkedHashMap<>();
info.put("balance", getBonds(player));
return info;
}
// ======================== 转账结果 ========================
public static class TransferResult {
public final boolean success;
public final String message;
public TransferResult(boolean success,
String message) {
this.success = success;
this.message = message;
}
}
}
-378
View File
@@ -1,378 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
public class BondPrinter {
private final Main plugin;
private final File outputDir;
private static final SimpleDateFormat FILE_SDF =
new SimpleDateFormat(
"yyyy-MM-dd_HH-mm-ss");
private static final SimpleDateFormat CELL_SDF =
new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
private static final LinkedHashMap<String, String>
TYPE_NAMES = new LinkedHashMap<>();
static {
TYPE_NAMES.put("admin_give", "管理员发放");
TYPE_NAMES.put("admin_deduct", "管理员扣除");
TYPE_NAMES.put("transfer_out", "转出");
TYPE_NAMES.put("transfer_in", "转入");
TYPE_NAMES.put("redeem", "CDK兑换");
TYPE_NAMES.put("shop_buy", "商城购买");
TYPE_NAMES.put("shop_sell", "商城出售"); // ← 补上
TYPE_NAMES.put("daily_sign", "签到奖励");
TYPE_NAMES.put("freeze", "冻结");
TYPE_NAMES.put("unfreeze", "解冻");
}
public BondPrinter(Main plugin) {
this.plugin = plugin;
this.outputDir = new File(
plugin.getDataFolder(), "printer");
if (!this.outputDir.exists()) {
this.outputDir.mkdirs();
}
}
// ============ 打印单个玩家 ============
public void printPlayer(String player,
CommandSender sender) {
Bukkit.getScheduler()
.runTaskAsynchronously(plugin,
() -> doPrintPlayer(
player, sender));
}
private void doPrintPlayer(String player,
CommandSender sender) {
try {
List<Map<String, Object>> txs =
plugin.getBonds()
.getAllTransactions(player);
String fileName = player + "_"
+ FILE_SDF.format(new Date())
+ ".xls";
File file = new File(outputDir, fileName);
deleteOld(player);
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(file),
StandardCharsets.UTF_8));
try {
writeXmlHeader(pw,
escXml(player));
writeXmlRow(pw,
new String[]{
"时间", "类型",
"金额", "对象",
"操作者", "理由",
"操作前余额",
"操作后余额"});
for (Map<String, Object> tx : txs) {
writeXmlRow(pw, new String[]{
txTime(tx),
txTypeName(tx),
String.valueOf(
txInt(tx,
"amount")),
txStr(tx,
"target_player"),
txStr(tx, "operator"),
txStr(tx, "reason"),
String.valueOf(
txInt(tx,
"balance_before")),
String.valueOf(
txInt(tx,
"balance_after"))
});
}
writeXmlFooter(pw);
pw.flush();
} finally {
pw.close();
}
if (sender != null) {
sender.sendMessage("§a[打印] §f"
+ player + " §a流水已导出");
sender.sendMessage("§7文件: §f"
+ fileName);
sender.sendMessage("§7共 §f"
+ txs.size()
+ " §7条,§724小时后删除");
}
} catch (Exception e) {
if (sender != null) {
sender.sendMessage("§c导出失败: "
+ e.getMessage());
}
plugin.getLogger().severe(
"[Printer] " + e.getMessage());
}
}
// ============ 打印全服 ============
public void printAll(CommandSender sender) {
Bukkit.getScheduler()
.runTaskAsynchronously(plugin,
() -> doPrintAll(sender));
}
private void doPrintAll(CommandSender sender) {
try {
List<String> players =
plugin.getBonds()
.getAllPlayerNames();
if (players.isEmpty()) {
if (sender != null) {
sender.sendMessage("§c没有流水数据");
}
return;
}
String fileName = "全服流水_"
+ FILE_SDF.format(new Date())
+ ".xls";
File file = new File(outputDir, fileName);
// ===== 收集所有流水 =====
// key = 时间戳+玩家+金额,用于去重
// value = 一行数据
List<String[]> allRows = new ArrayList<>();
Set<String> seenKeys = new HashSet<>();
for (String player : players) {
List<Map<String, Object>> txs =
plugin.getBonds()
.getAllTransactions(player);
for (Map<String, Object> tx : txs) {
int txId = ((Number) tx.get("id"))
.intValue();
String dedupKey =
String.valueOf(txId);
if (!seenKeys.add(dedupKey)) {
continue;
}
String timeStr = txTime(tx);
long timeMs = 0;
Object t = tx.get("time");
if (t != null) {
timeMs =
((Number) t).longValue();
}
allRows.add(new String[]{
player,
timeStr,
txTypeName(tx),
String.valueOf(
txInt(tx, "amount")),
txStr(tx, "target_player"),
txStr(tx, "operator"),
txStr(tx, "reason"),
String.valueOf(
txInt(tx,
"balance_before")),
String.valueOf(
txInt(tx,
"balance_after")),
// 额外存时间戳用于排序
String.valueOf(timeMs)
});
}
}
// ===== 按时间戳排序 =====
allRows.sort((a, b) -> {
long ta = Long.parseLong(
a[a.length - 1]);
long tb = Long.parseLong(
b[b.length - 1]);
return Long.compare(ta, tb);
});
// ===== 写入文件 =====
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(file),
StandardCharsets.UTF_8));
try {
writeXmlHeader(pw, "全服流水");
writeXmlRow(pw, new String[]{
"玩家", "时间", "类型",
"金额", "对象", "操作者",
"理由", "操作前余额",
"操作后余额"
});
for (String[] row : allRows) {
// 去掉最后的时间戳列
String[] cells =
new String[row.length - 1];
System.arraycopy(row, 0,
cells, 0, cells.length);
writeXmlRow(pw, cells);
}
writeXmlFooter(pw);
pw.flush();
if (sender != null) {
sender.sendMessage(
"§a[打印] §f全服流水 §a已导出");
sender.sendMessage("§7文件: §f"
+ fileName);
sender.sendMessage("§7共 §f"
+ players.size()
+ " 名玩家,§f"
+ allRows.size() + " §7条");
sender.sendMessage(
"§7按时间从早到晚排序");
sender.sendMessage(
"§724小时后自动删除");
}
} finally {
pw.close();
}
} catch (Exception e) {
if (sender != null) {
sender.sendMessage("§c导出失败: "
+ e.getMessage());
}
plugin.getLogger().severe(
"[Printer] " + e.getMessage());
}
}
// ============ 清理 ============
public void cleanupOldFiles() {
long now = System.currentTimeMillis();
long maxAge = 24L * 60 * 60 * 1000;
File[] files = outputDir.listFiles(
(d, n) -> n.endsWith(".xls"));
if (files == null) {
return;
}
int count = 0;
for (File f : files) {
if (now - f.lastModified() > maxAge) {
if (f.delete()) {
count++;
}
}
}
if (count > 0) {
plugin.getLogger().info(
"[Printer] 清理 "
+ count + " 个过期文件");
}
}
private void deleteOld(String player) {
File[] old = outputDir.listFiles(
(d, n) -> n.startsWith(player + "_")
&& n.endsWith(".xls"));
if (old != null) {
for (File f : old) {
f.delete();
}
}
}
// ============ XML工具 ============
private void writeXmlHeader(PrintWriter pw,
String sheetName) {
pw.println("<?xml version=\"1.0\"?>");
pw.println("<?mso-application "
+ "progid=\"Excel.Sheet\"?>");
pw.println("<Workbook xmlns="
+ "\"urn:schemas-microsoft-com"
+ ":office:spreadsheet\"");
pw.println(" xmlns:ss="
+ "\"urn:schemas-microsoft-com"
+ ":office:spreadsheet\">");
pw.println(" <Worksheet ss:Name=\""
+ sheetName + "\">");
pw.println(" <Table>");
}
private void writeXmlRow(PrintWriter pw,
String[] cells) {
pw.println(" <Row>");
for (String cell : cells) {
pw.println(" <Cell>"
+ "<Data ss:Type=\"String\">"
+ escXml(cell != null ? cell : "")
+ "</Data></Cell>");
}
pw.println(" </Row>");
}
private void writeXmlFooter(PrintWriter pw) {
pw.println(" </Table>");
pw.println(" </Worksheet>");
pw.println("</Workbook>");
}
private String escXml(String val) {
if (val == null) {
return "";
}
return val.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;");
}
private String txTime(Map<String, Object> tx) {
Object t = tx.get("time");
if (t == null) return "";
long ms = ((Number) t).longValue();
return CELL_SDF.format(new Date(ms));
}
private String txTypeName(
Map<String, Object> tx) {
String type = (String) tx.get("type");
return TYPE_NAMES.getOrDefault(type, type);
}
private int txInt(Map<String, Object> tx,
String key) {
Object v = tx.get(key);
if (v == null) return 0;
return ((Number) v).intValue();
}
private String txStr(Map<String, Object> tx,
String key) {
Object v = tx.get(key);
return v != null ? v.toString() : "";
}
}
-293
View File
@@ -1,293 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.*;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.nio.charset.StandardCharsets;
public class CDKManager {
private final Main plugin;
private Connection db;
private final Map<String, CDKInput> pendingInputs
= new ConcurrentHashMap<>();
public CDKManager(Main plugin) {
this.plugin = plugin;
initDB();
}
private void initDB() {
try {
Class.forName("org.sqlite.JDBC");
File dbFile = new File(
plugin.getDataFolder(), "bond.db");
db = DriverManager.getConnection(
"jdbc:sqlite:" + dbFile.getAbsolutePath());
Statement st = db.createStatement();
st.execute("PRAGMA journal_mode=WAL");
st.execute("CREATE TABLE IF NOT EXISTS cdk ("
+ "code TEXT PRIMARY KEY,"
+ "amount INTEGER DEFAULT 0,"
+ "type TEXT DEFAULT 'bond',"
+ "used INTEGER DEFAULT 0,"
+ "used_by TEXT DEFAULT '',"
+ "created_time INTEGER DEFAULT 0,"
+ "used_time INTEGER DEFAULT 0)");
st.close();
} catch (Exception e) {
throw new RuntimeException(
"[CDK] DB init failed: "
+ e.getMessage(), e);
}
}
// ===== CDK兑换(供SDF1调用) =====
/**
* 兑换CDK,返回结果字符串
* @return "success:金额:余额前:余额后"
* 或 "fail:原因"
*/
public String redeem(String code, String player) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT amount,type,used FROM cdk "
+ "WHERE code=?");
ps.setString(1, code);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close(); ps.close();
return "fail:not_found";
}
int amount = rs.getInt("amount");
String type = rs.getString("type");
int used = rs.getInt("used");
rs.close(); ps.close();
if (used == 1)
return "fail:already_used";
if (plugin.getBonds()
.isFrozen(player))
return "fail:frozen";
PreparedStatement up = db.prepareStatement(
"UPDATE cdk SET used=1,"
+ "used_by=?,used_time=? "
+ "WHERE code=?");
up.setString(1, player);
up.setLong(2,
System.currentTimeMillis());
up.setString(3, code);
up.executeUpdate(); up.close();
if ("bond".equals(type)) {
int bef = plugin.getBonds()
.getBonds(player);
plugin.getBonds().addBonds(player,
amount,
BondManager.TX_REDEEM,
"", "CDK系统",
"CDK兑换: " + code);
int aft = plugin.getBonds()
.getBonds(player);
return "success:" + amount
+ ":" + bef + ":" + aft;
}
return "fail:unknown_type";
} catch (SQLException e) {
return "fail:" + e.getMessage();
}
}
// ===== 聊天输入系统 =====
public void requestInput(Player p,
String type,
String context) {
CDKInput input = new CDKInput();
input.type = type;
input.context = context;
input.time = System.currentTimeMillis();
pendingInputs.put(p.getName(), input);
}
public boolean isListening(String playerName) {
return pendingInputs
.containsKey(playerName);
}
public void onChat(Player p, String msg) {
CDKInput input = pendingInputs
.remove(p.getName());
if (input == null) return;
if (System.currentTimeMillis()
- input.time > 60000) {
p.sendMessage("§c输入超时");
return;
}
if ("cdk".equals(input.type)) {
String result = redeem(
msg.trim(), p.getName());
if (result != null
&& result.startsWith("success:")) {
String[] parts = result.split(":");
int amt = Integer.parseInt(parts[1]);
int bef = Integer.parseInt(parts[2]);
int aft = Integer.parseInt(parts[3]);
p.sendMessage("§e§l[CDK] §f兑换成功!"
+ "获得 §c" + amt + "§f债券");
p.sendMessage("§7余额: §e" + bef
+ " §7-> §a" + aft);
} else {
p.sendMessage("§c§l[CDK] §f"
+ (result != null
? result.replace("fail:", "")
: "无效的兑换码"));
}
return;
}
if ("econ".equals(input.type)) {
handleEconInput(p, msg.trim(),
input.context);
return;
}
if ("bond".equals(input.type)) {
handleBondInput(p, msg.trim(),
input.context);
}
}
private void handleEconInput(Player admin,
String msg,
String target) {
net.milkbowl.vault.economy.Economy econ
= null;
try {
var reg = plugin.getServer()
.getServicesManager()
.getRegistration(
net.milkbowl.vault
.economy
.Economy.class);
if (reg != null)
econ = reg.getProvider();
} catch (Exception ignored) {}
if (econ == null) {
admin.sendMessage("§c经济系统不可用");
return;
}
if (!msg.startsWith("+")
&& !msg.startsWith("-")) {
admin.sendMessage("§c格式: §a+100"
+ " §7或 §c-50");
return;
}
try {
String numStr =
msg.substring(1).trim();
double amount =
Double.parseDouble(numStr);
if (amount <= 0) {
admin.sendMessage("§c金额必须>0");
return;
}
if (msg.startsWith("-")) {
Player tp = plugin.getServer()
.getPlayerExact(target);
if (tp == null) {
admin.sendMessage("§c" + target
+ " 不在线");
return;
}
if (!econ.has(tp, amount)) {
admin.sendMessage("§c余额不足");
return;
}
econ.withdrawPlayer(tp, amount);
admin.sendMessage("§a扣款成功");
} else {
econ.depositPlayer(target, amount);
admin.sendMessage("§a到账成功");
}
} catch (NumberFormatException e) {
admin.sendMessage("§c格式错误");
}
}
private void handleBondInput(Player admin,
String msg,
String target) {
if (!msg.startsWith("+")
&& !msg.startsWith("-")) {
admin.sendMessage("§c格式: §a+100"
+ " §7或 §c-50");
return;
}
try {
String sign = msg.substring(0, 1);
String rest =
msg.substring(1).trim();
String[] parts =
rest.split("\\s+", 2);
int amount = Integer.parseInt(parts[0]);
String reason = parts.length >= 2
? parts[1].trim()
: "管理员手动调整";
if (amount <= 0) {
admin.sendMessage("§c金额必须>0");
return;
}
String opName = admin.getName();
String txType = "-".equals(sign)
? BondManager.TX_ADMIN_DEDUCT
: BondManager.TX_ADMIN_GIVE;
if ("-".equals(sign)) {
if (plugin.getBonds()
.isFrozen(target)) {
admin.sendMessage("§c账户冻结");
return;
}
if (plugin.getBonds().deductBonds(
target, amount, txType,
"", opName, reason)) {
admin.sendMessage("§a扣除成功");
} else {
admin.sendMessage("§c债券不足");
}
} else {
if (plugin.getBonds()
.isFrozen(target)) {
admin.sendMessage("§c账户冻结");
return;
}
plugin.getBonds().addBonds(target,
amount, txType,
"", opName, reason);
admin.sendMessage("§a发放成功");
}
} catch (NumberFormatException e) {
admin.sendMessage("§c格式错误");
}
}
public void cancelInput(String player) {
pendingInputs.remove(player);
}
static class CDKInput {
String type;
String context;
long time;
}
}
@@ -37,16 +37,6 @@ public class ChatFilterManager {
private boolean notifyAdmin = true;
private boolean notifyAll = false;
private int muteDuration = 300;
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean v) {
enabled = v;
}
private static final Pattern URL_PATTERN =
Pattern.compile(
@@ -116,7 +106,6 @@ public class ChatFilterManager {
if (line.startsWith("\uFEFF"))
line = line.substring(1);
String t = line.trim();
if (t.isEmpty()
|| t.startsWith("#"))
continue;
@@ -139,12 +128,6 @@ public class ChatFilterManager {
String[] kv = t.split(":", 2);
String k = kv[0].trim();
String v = kv[1].trim();
if (equals("启用过滤")
|| equals("enabled")) {
enabled = parseBool(v);
continue;
}
if (k.equals("通知管理员")) {
notifyAdmin = parseBool(v);
continue;
@@ -255,7 +238,6 @@ public class ChatFilterManager {
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# Sdf1_chat 聊天过滤配置");
pw.println("启用过滤: " + enabled);
pw.println("通知管理员: true");
pw.println("全服通报: false");
pw.println("禁言时长: 300");
+13 -31
View File
@@ -36,10 +36,7 @@ public class ChatInputManager {
TICKET_ADMIN_TYPE,
TICKET_ADMIN_TITLE,
TICKET_ADMIN_DESC,
TICKET_ADMIN_PROVIDER,
BACK_CHECK_ECONOMY_CONFIRM,
USER_MGMT_SEARCH,
TICKET_ADMIN_PROVIDER
}
public static class PlayerState {
@@ -51,7 +48,6 @@ public class ChatInputManager {
public int ticketId = 0;
public int smtpStep = 0;
public int scoreValue = 0;
}
private final Map<String, PlayerState> states =
@@ -67,11 +63,6 @@ public class ChatInputManager {
public void reset(Player p) {
states.remove(p.getName());
}
public boolean isInFlow(Player p) {
PlayerState state = states.get(p.getName());
return state != null
&& state.type != InputType.NONE;
}
public void handleInput(Player p, String msg,
Main mainPlugin) {
@@ -551,32 +542,23 @@ public class ChatInputManager {
}
case BACK_CHECKIN: {
String result =
mainPlugin.getCheckIn()
.backCheckIn(p, msg);
if (result == null) {
// 等待选择(NEED_CONFIRM)
int cost = 20;
if (!mainPlugin.getDb()
.deductPoints(
p.getName(), cost)) {
p.sendMessage(
"§c积分不足,补签需 "
+ cost + " 积分");
reset(p);
return;
}
if (result.startsWith(
"NEED_CONFIRM:")) {
String[] parts =
result.split(":");
// 存储到Main的Map
mainPlugin.pendingBackCheck.put(
p.getName(),
parts[1] + ":" + parts[2]);
return;
}
if (result.startsWith("§c")) {
p.sendMessage(result);
} else {
p.sendMessage(result);
}
p.sendMessage(
"§a补签成功!扣除 "
+ cost + " 积分。"
+ "日期: " + msg);
reset(p);
return;
}
case INVITE_INPUT_CODE: {
String code = msg.trim();
+39 -227
View File
@@ -3,7 +3,6 @@ package Sdf1_login;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class CheckInManager {
@@ -14,20 +13,14 @@ public class CheckInManager {
this.plugin = plugin;
}
// ===== 今日是否已签到 =====
public boolean isCheckedInToday(
String name) {
public boolean isCheckedInToday(String name) {
String last = (String) plugin.getDb()
.getField(name,
"last_checkin_date");
String today = new SimpleDateFormat(
"yyyy-MM-dd")
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
return today.equals(last);
}
// ===== 签到 =====
// ===== 签到 =====
public String checkIn(Player p) {
String name = p.getName();
if (isCheckedInToday(name)) {
@@ -38,8 +31,7 @@ public class CheckInManager {
Object streakObj = plugin.getDb()
.getField(name, "checkin_streak");
int streak = streakObj != null
? ((Number) streakObj).intValue()
: 0;
? ((Number) streakObj).intValue() : 0;
String lastDate = (String) plugin.getDb()
.getField(name, "last_checkin_date");
@@ -48,14 +40,11 @@ public class CheckInManager {
if (lastDate != null && !lastDate.isEmpty()) {
try {
java.util.Date lastD =
new SimpleDateFormat("yyyy-MM-dd")
.parse(lastDate);
java.util.Date todayD =
new SimpleDateFormat("yyyy-MM-dd")
.parse(today);
long diff = todayD.getTime()
- lastD.getTime();
java.util.Date lastD = new SimpleDateFormat(
"yyyy-MM-dd").parse(lastDate);
java.util.Date todayD = new SimpleDateFormat(
"yyyy-MM-dd").parse(today);
long diff = todayD.getTime() - lastD.getTime();
if (diff > 86400000L * 2) {
streak = 0;
} else if (diff > 86400000L) {
@@ -73,37 +62,18 @@ public class CheckInManager {
else if (streak >= 14) multi = 1.3;
else if (streak >= 7) multi = 1.1;
int basePoints =
plugin.getConfig2().checkinPoints;
int points = (int) (basePoints * multi);
int points = (int) (1 * multi);
plugin.getDb().addPoints(name, points);
plugin.getDb().setField(name,
"checkin_streak", streak);
plugin.getDb().setField(name,
"last_checkin_date", today);
plugin.getDb().setField(
name, "checkin_streak", streak);
plugin.getDb().setField(
name, "last_checkin_date", today);
Object totalObj = plugin.getDb()
.getField(name, "total_checkin_days");
int total = totalObj != null
? ((Number) totalObj).intValue()
: 0;
plugin.getDb().setField(name,
"total_checkin_days", total + 1);
// ★ 债券盲盒奖励(6参数版,写入流水)★
int bondMin =
plugin.getConfig2().checkinBondMin;
int bondMax =
plugin.getConfig2().checkinBondMax;
if (bondMin <= 0) bondMin = 1;
if (bondMax < bondMin) bondMax = bondMin;
int bondReward = bondMin
+ new java.util.Random()
.nextInt(bondMax - bondMin + 1);
plugin.getBonds().addBonds(name, bondReward,
"daily_sign", "", "签到系统",
"每日签到连续" + streak + "天");
? ((Number) totalObj).intValue() : 0;
plugin.getDb().setField(
name, "total_checkin_days", total + 1);
return plugin.getConfig2()
.msg("checkin_success")
@@ -112,191 +82,33 @@ public class CheckInManager {
.replace("{streak}",
String.valueOf(streak))
.replace("{multi}",
String.valueOf(multi))
+ " §e+" + bondReward + "债券";
String.valueOf(multi));
}
// ===== 获取连续天数 =====
public int getStreak(String name) {
Object val = plugin.getDb()
.getField(name,
"checkin_streak");
if (val instanceof Number) {
return ((Number) val)
.intValue();
}
return 0;
}
// 标准化日期
private String normalizeDate(String input) {
String s = input.trim().replace("/", "-");
if (s.matches("\\d{8}")) {
return s.substring(0, 4) + "-"
+ s.substring(4, 6) + "-"
+ s.substring(6, 8);
}
return s;
}
// ===== 补签入口 =====
public String backCheckIn(
Player p, String dateStr) {
public String backCheckIn(Player p, String dateStr) {
String name = p.getName();
String normalized =
normalizeDate(dateStr);
// 严格日期验证
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy-MM-dd");
sdf.setLenient(false);
java.util.Date target;
if (!plugin.getDb().deductPoints(name, 10)) {
return plugin.getConfig2()
.msg("checkin_already");
}
try {
target = sdf.parse(normalized);
} catch (Exception ex) {
return "§c无效日期: " + dateStr;
java.util.Date target = new SimpleDateFormat(
"yyyy-MM-dd").parse(dateStr);
java.util.Date now = new SimpleDateFormat(
"yyyy-MM-dd").parse(
new SimpleDateFormat("yyyy-MM-dd")
.format(new Date()));
long diff = now.getTime() - target.getTime();
if (diff < 0 || diff > 3 * 86400000L) {
plugin.getDb().addPoints(name, 10);
return plugin.getConfig2()
.msg("checkin_already");
}
String reformat = sdf.format(target);
if (!reformat.equals(normalized)) {
return "§c无效日期: " + dateStr;
}
String today = sdf.format(new Date());
if (normalized.compareTo(today) >= 0) {
return "§c今天不需要补签";
}
String threeDaysAgo = getPastDate(3);
if (normalized.compareTo(
threeDaysAgo) < 0) {
return "§c只能补签3天内"
+ "(最多到 " + threeDaysAgo + ")";
}
Object val = plugin.getDb()
.getField(name, "backcheck_dates");
String existing = val != null
? val.toString() : "";
boolean alreadyBacked = false;
if (!existing.isEmpty()) {
for (String d : existing.split(",")) {
if (normalizeDate(d.trim())
.equals(normalized)) {
alreadyBacked = true;
break;
plugin.getDb().addPoints(name, 1);
return "§a补签成功!日期: " + dateStr;
} catch (Exception e) {
plugin.getDb().addPoints(name, 10);
return "§c日期格式错误,请用 yyyy-MM-dd";
}
}
}
if (alreadyBacked) {
return "§c该日期已补签: " + normalized;
}
Object normalCheck =
plugin.getDb().getField(
name, "last_checkin_date");
String lastCheckin = normalCheck != null
? normalCheck.toString() : "";
if (lastCheckin.equals(normalized)) {
return "§c该日期已签到: " + normalized;
}
// ★ 补签消耗:3债券 + 10积分
// 积分不够时:用 9债券(3倍)代替 ★
int bondCost = 3;
int pointCost = 10;
int bondAltCost = 9; // 3倍债券替代
int myBonds = plugin.getBonds()
.getBonds(name);
Object ptsObj = plugin.getDb()
.getField(name, "points");
int myPoints = ptsObj != null
? ((Number) ptsObj).intValue()
: 0;
// 方案A:3债券 + 10积分
boolean canA = myBonds >= bondCost
&& myPoints >= pointCost;
// 方案B:9债券(积分不够时)
boolean canB = myBonds >= bondAltCost
&& myPoints < pointCost;
if (!canA && !canB) {
if (myPoints < pointCost
&& myBonds < bondAltCost) {
return "§c补签费用不足"
+ "(需3债券+10积分,"
+ "或9债券代替)"
+ " 当前: "
+ myBonds + "债券 "
+ myPoints + "积分";
}
if (myBonds < bondCost) {
return "§c债券不足(需"
+ bondCost + "枚)";
}
return "§c积分不足(需"
+ pointCost + "分)";
}
// 执行扣款
if (canA) {
// 方案A:3债券 + 10积分
plugin.getBonds().deductBonds(
name, bondCost,
"admin_deduct", "",
"补签系统", "补签扣款");
plugin.getDb().deductPoints(
name, pointCost);
} else {
// 方案B:9债券代替
plugin.getBonds().deductBonds(
name, bondAltCost,
"admin_deduct", "",
"补签系统", "补签扣款(债券替代)");
}
doBackCheckIn(p, normalized);
String costMsg = canA
? bondCost + "债券+" + pointCost + "积分"
: bondAltCost + "债券(替代)";
return "§a补签成功!日期: "
+ normalized
+ " §7消耗: " + costMsg;
}
// ===== 执行补签 =====
// ===== 执行补签 =====
public void doBackCheckIn(
Player p, String dateStr) {
String name = p.getName();
Object val = plugin.getDb()
.getField(name, "backcheck_dates");
String existing = val != null
? val.toString() : "";
String updated = existing.isEmpty()
? dateStr
: existing + "," + dateStr;
plugin.getDb().setField(name,
"backcheck_dates", updated);
int streak = getStreak(name);
plugin.getDb().setField(name,
"checkin_streak", streak + 1);
// ★ 补签不发放奖励,只记录
}
// ===== 获取N天前日期 =====
private String getPastDate(
int daysAgo) {
Calendar cal =
Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH,
-daysAgo);
return new SimpleDateFormat(
"yyyy-MM-dd")
.format(cal.getTime());
}
}
+300 -210
View File
@@ -4,15 +4,14 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static java.lang.Double.parseDouble;
public class ConfigManager {
private final File dataFolder;
private final Map<String, String> messages
= new LinkedHashMap<>();
private final Map<String, String> smtpSettings
= new LinkedHashMap<>();
private final Map<String, String> messages = new LinkedHashMap<>();
private final Map<String, String> smtpSettings = new LinkedHashMap<>();
// ===== 公共字段 =====
public String adminTag = "admin";
public String adminPassword = "qweasd";
public int maxAccountsPerIP = 3;
@@ -26,35 +25,11 @@ public class ConfigManager {
public long smtpVerifyExpire = 0;
public long smtpLastSendTime = 0;
public String smtpConfiguringPlayer = "";
public String broadcastServerUrl = "";
public int httpPort = 8080;
public String garbageTag = "admin";
public double checkinRewardFixed = 0;
public double checkinRewardMin = 0;
public double checkinRewardMax = 0;
public boolean checkinGivePoints = true;
public int checkinPoints = 10;
public String checkinRewardType = "none";
public int backCheckPointMultiplier = 5;
public int checkinBondMin = 1;
public int checkinBondMax = 3;
public double providerPointsPerScore = 2.0;
public int baseEconomyReward = 100;
public double providerEconomyPerScore = 50.0;
public int requesterPointsReward = 10;
public boolean garbageEnabled = true;
public int garbageInterval = 300;
public int garbageMaxRounds = 1;
// ★ 构造函数接收 File,和 Main.java 调用一致 ★
public ConfigManager(File dataFolder) {
this.dataFolder = dataFolder;
}
public String rewardChannel = "债券"; // bonds = 债券,economy = Vault经济
// ==================== 消息 ====================
@@ -71,33 +46,161 @@ public class ConfigManager {
val = val.replace("</b>", "&r");
val = val.replace("<br>", "\n");
val = val.replace("\\n", "\n");
// &颜色码 转换为 §颜色码
val = val.replaceAll(
"&([0-9a-fk-orA-FK-OR])", "§$1");
return val;
}
private void createDefaultMessages(File f) {
List<String> L = new ArrayList<>();
L.add("# ===== Sdf1_login 消息配置 =====");
L.add("login_timeout=§c登录超时");
L.add("not_registered=§e您尚未注册");
L.add("not_logged_in=§e您尚未登录");
L.add("already_registered=§c您已注册过");
L.add("# ==========================================");
L.add("# Sdf1_login 消息配置文件");
L.add("# ==========================================");
L.add("# 颜色代码使用 § 符号");
L.add("# §0黑色 §1深蓝 §2深绿 §3深青");
L.add("# §4深红 §5深紫 §6金色 §7灰色");
L.add("# §8深灰 §9蓝色 §a绿色 §b浅蓝");
L.add("# §c红色 §d粉色 §e黄色 §f白色");
L.add("# §l粗体 §n下划线 §o斜体 §r重置");
L.add("#");
L.add("# 占位符用 {xxx} 表示,运行时自动替换");
L.add("# 支持 <b>粗体</b> 标签");
L.add("# 支持 \\n 或 <br> 换行");
L.add("# 修改后执行 /sdf1_login reload 重载");
L.add("# ==========================================");
L.add("");
// --- 登录/注册 ---
L.add("# ---- 登录注册相关 ----");
L.add("# login_timeout: 登录超时被踢出时的提示");
L.add("login_timeout=§c登录超时,请重新登录");
L.add("# not_registered: 未注册玩家收到的提示");
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
L.add("# not_logged_in: 已注册但未登录的提示");
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
L.add("# already_registered: 已注册玩家再次注册时的提示");
L.add("already_registered=§c您已注册过账号");
L.add("# already_logged_in: 已登录玩家再次登录时的提示");
L.add("already_logged_in=§c您已登录");
L.add("reg_success=§a注册成功!");
L.add("# reg_success: 注册成功提示");
L.add("reg_success=§a注册成功!欢迎来到服务器");
L.add("# login_success: 登录成功提示");
L.add("login_success=§a登录成功!");
L.add("login_failed=§c密码错误");
L.add("# login_failed: 密码错误提示");
L.add("login_failed=§c密码错误,请重试");
L.add("");
// --- 密码 ---
L.add("# ---- 密码相关 ----");
L.add("# password_wrong: 旧密码验证失败");
L.add("password_wrong=§c密码错误");
L.add("# password_changed: 密码修改成功");
L.add("password_changed=§a密码修改成功!");
L.add("password_format_error=§c密码需6位以上含大小写和数字");
L.add("# password_format_error: 密码格式不符合要求");
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
L.add("# password_same: 新密码与旧密码相同");
L.add("password_same=§c新密码不能与旧密码相同");
L.add("# reset_no_email: 找回密码时未绑定邮箱");
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
L.add("# reset_sent: 临时密码已发送到邮箱");
L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收");
L.add("");
// --- 签到积分 ---
L.add("# ---- 签到积分相关 ----");
L.add("# checkin_already: 今日已签到");
L.add("checkin_already=§c您今日已签到");
L.add("checkin_success=§a签到成功!获得 {points} 积分");
L.add("chat_muted=§c你已被禁言");
L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率");
L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}");
L.add("# points_insufficient: 积分不足");
L.add("points_insufficient=§c积分不足,当前: {points}");
L.add("# points_purchase_success: 积分购买成功");
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
L.add("");
// --- 挂机 ---
L.add("# ---- 挂机检测相关 ----");
L.add("# afk_set_enabled: 挂机检测开启");
L.add("afk_set_enabled=§a挂机检测已开启");
L.add("# afk_set_disabled: 挂机检测关闭");
L.add("afk_set_disabled=§c挂机检测已关闭");
L.add("# afk_kick: 因挂机被踢出");
L.add("afk_kick=§c因挂机过久被踢出");
L.add("");
// --- 邮箱 ---
L.add("# ---- 邮箱相关 ----");
L.add("# email_set: 邮箱设置成功");
L.add("email_set=§a邮箱设置成功:{email}");
L.add("# email_format_error: 邮箱格式错误");
L.add("email_format_error=§c邮箱格式不正确");
L.add("");
// --- 邀请 ---
L.add("# ---- 邀请相关 ----");
L.add("# invite_code_generated: 邀请码生成");
L.add("invite_code_generated=§a邀请码:{code}");
L.add("# invite_used: 使用邀请码成功");
L.add("invite_used=§a邀请成功,邀请人:{player}");
L.add("# invite_self: 使用自己的邀请码");
L.add("invite_self=§c不能使用自己的邀请码");
L.add("# invite_not_found: 邀请码不存在");
L.add("invite_not_found=§c邀请码不存在");
L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励");
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
L.add("");
// --- 礼包 ---
L.add("# ---- 新人礼包相关 ----");
L.add("# gift_not_ready: 礼包条件未满足");
L.add("gift_not_ready=§c礼包条件未满足");
L.add("# gift_claimed: 礼包领取成功");
L.add("gift_claimed=§a礼包领取成功!");
L.add("");
// --- 审批/IP ---
L.add("# ---- 审批与IP限制 ----");
L.add("# need_approval: 注册需审批");
L.add("need_approval=§e注册需审批 (工单#{id})");
L.add("# ip_max_accounts: IP注册已达上限");
L.add("ip_max_accounts=§cIP注册已达上限");
L.add("");
// --- 管理员 ---
L.add("# ---- 管理员操作 ----");
L.add("# admin_delete_confirm: 管理员删除账号确认提示");
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
L.add("");
// --- 聊天过滤 ---
L.add("# ---- 聊天过滤 ----");
L.add("# chat_muted: 禁言玩家尝试聊天");
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天");
L.add("# chat_url_blocked: 发送违规链接被拦截");
L.add("# {url}=检测到的链接地址");
L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}");
L.add("# chat_url_violation: 当前违规次数");
L.add("# {count}=当前违规次数");
L.add("chat_url_violation=§7当前违规次数: §e{count}");
L.add("# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数");
L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)");
L.add("# chat_url_broadcast: 全服通报,{player}=玩家名");
L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚");
L.add("");
// --- 通用 ---
L.add("# ---- 通用 ----");
L.add("# 未知参数: 未知命令或权限不足时的提示");
L.add("未知参数=§c未知参数或权限不足");
L.add("# only_player: 仅玩家可用的命令");
L.add("# only_player=§c此命令仅玩家可用");
writeLines(f, L);
}
// ==================== SMTP ====================
public void loadSmtp() {
@@ -106,21 +209,58 @@ public class ConfigManager {
if (!file.exists()) createDefaultSmtp(file);
loadMap(file, smtpSettings);
}
private double parseDouble(String s, double def) {
try { return Double.parseDouble(s.trim()); }
catch (Exception e) { return def; }
}
public String getSmtp(String key) {
return smtpSettings.getOrDefault(key, "");
}
/** 获取挂机白名单列表 */
public List<String> getAfkWhitelist() {
List<String> list = new ArrayList<>();
File file = new File(dataFolder, "挂机白名单.txt");
if (!file.exists()) {
try {
file.createNewFile();
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file),
StandardCharsets.UTF_8))) {
w.write("# 每行一个玩家名,前面加 # 为注释");
w.newLine();
w.write("# 以下玩家不会被挂机踢出");
w.newLine();
}
} catch (IOException ignored) {}
}
try (BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(file),
StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
list.add(line);
}
}
} catch (IOException ignored) {}
return list;
}
/** 设置SMTP单项(聊天输入用) */
public void setSmtp(String key, String value) {
smtpSettings.put(key, value);
}
/** 将当前SMTP设置写回文件 */
public void saveSmtp() {
File file = new File(dataFolder, "SMTP设置.txt");
List<String> L = new ArrayList<>();
L.add("# ===== SMTP 配置 =====");
for (Map.Entry<String, String> e
: smtpSettings.entrySet()) {
L.add("# ===== SMTP 邮件服务器配置 =====");
for (Map.Entry<String, String> e : smtpSettings.entrySet()) {
L.add(e.getKey() + "=" + e.getValue());
}
writeLines(file, L);
@@ -128,10 +268,10 @@ public class ConfigManager {
private void createDefaultSmtp(File f) {
List<String> L = new ArrayList<>();
L.add("# ===== SMTP 配置 =====");
L.add("# ===== SMTP 邮件服务器配置 =====");
L.add("smtp地址=smtp.example.com");
L.add("smtp端口=465");
L.add("smtp账号=");
L.add("smtp账号=your_email@example.com");
L.add("smtp密码=");
L.add("发件人名称=Sdf1_login");
L.add("smtp加密=true");
@@ -145,104 +285,46 @@ public class ConfigManager {
if (!file.exists()) createDefaultSettings(file);
Map<String, String> m = new LinkedHashMap<>();
loadMap(file, m);
adminTag = m.getOrDefault("管理标签", adminTag);
adminPassword = m.getOrDefault("管理密码", adminPassword);
maxAccountsPerIP = parseInt(m.getOrDefault("每IP最大账号数", "3"), 3);
maxAccountsPerIP = parseInt(
m.getOrDefault("每IP最大账号数", "3"), 3);
approvalMode = m.getOrDefault("审批模式", approvalMode);
autoApproveDelayMinutes = parseInt(m.getOrDefault("自动审批延迟分钟", "30"), 30);
loginTimeout = parseInt(m.getOrDefault("登录超时秒数", "60"), 60);
afkEnabled = Boolean.parseBoolean(m.getOrDefault("挂机踢出", "false"));
afkTimeout = parseInt(m.getOrDefault("挂机超时秒数", "300"), 300);
smtpSsl = Boolean.parseBoolean(m.getOrDefault("smtp加密", "true"));
httpPort = parseInt(m.getOrDefault("http-port", "8080"), 8080);
broadcastServerUrl = m.getOrDefault("resource-pack-url", "");
autoApproveDelayMinutes = parseInt(
m.getOrDefault("自动审批延迟分钟", "30"), 30);
loginTimeout = parseInt(
m.getOrDefault("登录超时秒数", "60"), 60);
afkEnabled = Boolean.parseBoolean(
m.getOrDefault("挂机踢出", "false"));
afkTimeout = parseInt(
m.getOrDefault("挂机超时秒数", "300"), 300);
smtpSsl = Boolean.parseBoolean(
m.getOrDefault("smtp加密", "true"));
providerPointsPerScore = parseDouble(
m.getOrDefault("服务商积分倍率", "2.0"),
2.0);
baseEconomyReward = parseInt(
m.getOrDefault("基础经济奖励", "100"),
100);
providerEconomyPerScore = parseDouble(
m.getOrDefault("经济奖励每评分", "50.0"),
50.0);
requesterPointsReward = parseInt(
m.getOrDefault("报单人积分奖励", "10"),
10);
garbageEnabled = Boolean.parseBoolean(
m.getOrDefault("垃圾站_启用", "true"));
garbageInterval = parseInt(
m.getOrDefault("垃圾站_清理间隔秒",
"300"), 300);
garbageMaxRounds = parseInt(
m.getOrDefault("垃圾站_保留轮数",
"1"), 1);
providerPointsPerScore = parseDouble(m.getOrDefault("服务商积分倍率", "2.0"), 2.0);
baseEconomyReward = parseInt(m.getOrDefault("基础经济奖励", "100"), 100);
providerEconomyPerScore = parseDouble(m.getOrDefault("经济奖励每评分", "50.0"), 50.0);
requesterPointsReward = parseInt(m.getOrDefault("报单人积分奖励", "10"), 10);
garbageEnabled = Boolean.parseBoolean(m.getOrDefault("垃圾站_启用", "true"));
garbageInterval = parseInt(m.getOrDefault("垃圾站_清理间隔秒", "300"), 300);
garbageMaxRounds = parseInt(m.getOrDefault("垃圾站_保留轮数", "1"), 1);
checkinRewardFixed = parseDouble(m.getOrDefault("签到固定金额", "0"), 0);
checkinRewardMin = parseDouble(m.getOrDefault("签到最小金额", "0"), 0);
checkinRewardMax = parseDouble(m.getOrDefault("签到最大金额", "0"), 0);
checkinGivePoints = Boolean.parseBoolean(m.getOrDefault("签到给积分", "true"));
checkinPoints = parseInt(m.getOrDefault("签到积分数量", "10"), 10);
backCheckPointMultiplier = parseInt(m.getOrDefault("补签积分倍率", "5"), 5);
checkinBondMin = parseInt(m.getOrDefault("签到债券最小", "1"), 1);
checkinBondMax = parseInt(m.getOrDefault("签到债券最大", "3"), 3);
if (checkinBondMax < checkinBondMin) checkinBondMax = checkinBondMin;
if (checkinRewardFixed > 0) checkinRewardType = "fixed";
else if (checkinRewardMin > 0 && checkinRewardMax > checkinRewardMin) checkinRewardType = "range";
else checkinRewardType = "none";
rewardChannel = m.getOrDefault("奖励发放方式", "债券");
rewardChannel = normalizeRewardChannel(rewardChannel);
// 验证合法值
if (!"economy".equalsIgnoreCase(rewardChannel)
&& !"bonds".equalsIgnoreCase(rewardChannel)) {
rewardChannel = "bonds";
}
// ★ 自动补充新配置项 ★
boolean needAppend = false;
List<String> newKeys = new ArrayList<>();
if (!m.containsKey("签到债券最小")) { needAppend = true; newKeys.add("签到债券最小=" + checkinBondMin); }
if (!m.containsKey("签到债券最大")) { needAppend = true; newKeys.add("签到债券最大=" + checkinBondMax); }
if (!m.containsKey("奖励发放方式")) {
needAppend = true;
newKeys.add("奖励发放方式=债券");
}
if (needAppend) {
try {
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.newLine();
for (String key : newKeys) { bw.write(key); bw.newLine(); }
bw.flush(); bw.close();
System.out.println("[Config] 已补充签到债券配置");
} catch (IOException ignored) {}
if (!m.containsKey("奖励发放方式")) {
try {
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.newLine();
bw.write("奖励发放方式=bonds");
bw.newLine();
bw.flush(); bw.close();
} catch (IOException ignored) {}
}
}
}
/**
* 奖励方式标准化:中英文全兼容
* "债券" "bonds" "Bond" "BONDS" → "bonds"
* "经济" "economy" "Economy" "ECONOMY" → "economy"
* 其他任何值 → "bonds"(默认债券)
*/
private String normalizeRewardChannel(String raw) {
if (raw == null) return "bonds";
String s = raw.trim().toLowerCase();
// 中文
if (s.contains("债券")) return "bonds";
if (s.contains("经济")) return "economy";
// 英文
if (s.contains("bond")) return "bonds";
if (s.contains("econ")) return "economy";
if (s.contains("Vault")) return "economy";
// 兜底
return "bonds";
}
public void saveSettings() {
File file = new File(dataFolder, "插件设置.txt");
List<String> L = new ArrayList<>();
@@ -255,22 +337,23 @@ public class ConfigManager {
L.add("登录超时秒数=" + loginTimeout);
L.add("挂机踢出=" + afkEnabled);
L.add("挂机超时秒数=" + afkTimeout);
L.add("resource-pack-url=" + broadcastServerUrl);
L.add("http-port=" + httpPort);
// 垃圾站设置
L.add("# ---- 垃圾站 ----");
L.add("垃圾站_启用=" + garbageEnabled);
L.add("垃圾站_清理间隔秒=" + garbageInterval);
L.add("垃圾站_保留轮数=" + garbageMaxRounds);
L.add("签到固定金额=" + checkinRewardFixed);
L.add("签到最小金额=" + checkinRewardMin);
L.add("签到最大金额=" + checkinRewardMax);
L.add("签到给积分=" + checkinGivePoints);
L.add("签到积分数量=" + checkinPoints);
L.add("签到债券最小=" + checkinBondMin);
L.add("签到债券最大=" + checkinBondMax);
L.add("补签积分倍率=" + backCheckPointMultiplier);
L.add("奖励发放方式=债券");
writeLines(file, L);
}
public double providerPointsPerScore = 2.0;
public int baseEconomyReward = 100;
public double providerEconomyPerScore = 50.0;
public int requesterPointsReward = 10;
public boolean garbageEnabled = true;
public int garbageInterval = 300;
public int garbageMaxRounds = 1;
private void createDefaultSettings(File f) {
List<String> L = new ArrayList<>();
@@ -283,74 +366,24 @@ public class ConfigManager {
L.add("登录超时秒数=60");
L.add("挂机踢出=false");
L.add("挂机超时秒数=300");
L.add("resource-pack-url=");
L.add("http-port=8080");
L.add("服务商积分倍率=" + providerPointsPerScore);
L.add("基础经济奖励=" + baseEconomyReward);
L.add("经济奖励每评分=" + providerEconomyPerScore);
L.add("报单人积分奖励=" + requesterPointsReward);
L.add("# ---- 垃圾站 ----");
L.add("垃圾站_启用=true");
L.add("垃圾站_清理间隔秒=300");
L.add("垃圾站_保留轮数=1");
L.add("签到固定金额=0");
L.add("签到最小金额=50");
L.add("签到最大金额=500");
L.add("签到给积分=true");
L.add("签到积分数量=10");
L.add("签到债券最小=1");
L.add("签到债券最大=3");
L.add("补签积分倍率=5");
L.add("奖励发放方式=债券");
writeLines(f, L);
}
public List<String> getAfkWhitelist() {
List<String> list = new ArrayList<>();
File file = new File(dataFolder, "挂机白名单.txt");
if (!file.exists()) {
try {
file.createNewFile();
FileWriter fw = new FileWriter(file);
fw.write("# 每行一个玩家名\n");
fw.flush();
fw.close();
} catch (IOException ignored) {}
return list;
}
try (BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(file),
StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#"))
list.add(line);
}
} catch (IOException ignored) {}
return list;
}
// ==================== 其他 ====================
public String generateSmtpVerifyCode() {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
java.util.Random r = new java.util.Random();
for (int i = 0; i < 6; i++) sb.append(chars.charAt(r.nextInt(chars.length())));
smtpVerifyCode = sb.toString();
smtpVerifyExpire = System.currentTimeMillis() + 300000L;
return smtpVerifyCode;
}
public boolean checkSmtpVerifyCode(String code) {
if (System.currentTimeMillis() > smtpVerifyExpire) { smtpVerifyCode = ""; return false; }
return smtpVerifyCode.equalsIgnoreCase(code);
}
public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; }
public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; }
public void recordSmtpSendTime() { smtpLastSendTime = System.currentTimeMillis(); }
// ==================== 文件工具 ====================
// ==================== 通用读写 ====================
private void loadMap(File file, Map<String, String> map) {
try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
try (BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
@@ -358,15 +391,72 @@ public class ConfigManager {
int eq = line.indexOf('=');
if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim());
}
} catch (IOException ignored) {}
} catch (IOException e) {
System.err.println("[Sdf1_login] 读取失败: " + file.getName());
}
}
private void writeLines(File file, List<String> lines) {
try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
for (String l : lines) { w.write(l); w.newLine(); }
} catch (IOException ignored) {}
} catch (IOException e) {
System.err.println("[Sdf1_login] 写入失败: " + file.getName());
}
}
private int parseInt(String s, int def) {
try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; }
}
/**
* 生成6位验证码(字母+数字)
*/
public String generateSmtpVerifyCode() {
String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
StringBuilder sb = new StringBuilder();
java.util.Random r = new java.util.Random();
for (int i = 0; i < 6; i++) {
sb.append(chars.charAt(r.nextInt(
chars.length())));
}
smtpVerifyCode = sb.toString();
smtpVerifyExpire =
System.currentTimeMillis() + 300000L;
return smtpVerifyCode;
}
/**
* 检查验证码是否有效
*/
public boolean checkSmtpVerifyCode(String code) {
if (System.currentTimeMillis() > smtpVerifyExpire) {
smtpVerifyCode = "";
return false;
}
return smtpVerifyCode.equalsIgnoreCase(code);
}
/**
* 清除验证码
*/
public void clearSmtpVerifyCode() {
smtpVerifyCode = "";
smtpVerifyExpire = 0;
}
/**
* 检查发送间隔是否>=1分钟
*/
public boolean canSendSmtpVerify() {
return System.currentTimeMillis()
- smtpLastSendTime >= 60000L;
}
public void recordSmtpSendTime() {
smtpLastSendTime = System.currentTimeMillis();
}
private double parseDouble(String s, double def) { try { return Double.parseDouble(s.trim()); } catch (Exception e) { return def; } }
private int parseInt(String s, int def) { try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } }
}
-577
View File
@@ -1,577 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
public class CypayCommand
implements CommandExecutor, TabCompleter {
private final Main plugin;
private static final SimpleDateFormat SDF =
new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
public CypayCommand(Main plugin) {
this.plugin = plugin;
}
private boolean hasAdminPerm(CommandSender s) {
if (s.isOp()) return true;
if (!(s instanceof Player)) return true;
Player p = (Player) s;
String tag =
plugin.getConfig2().adminTag;
return !tag.isEmpty()
&& p.getScoreboardTags().contains(tag);
}
private int parseAmount(String s) {
return Integer.parseInt(
s.trim().replaceAll("[+]", ""));
}
private List<String> filter(
List<String> list, String prefix) {
String low = prefix.toLowerCase();
return list.stream()
.filter(x -> x.toLowerCase()
.startsWith(low))
.collect(Collectors.toList());
}
@Override
public boolean onCommand(CommandSender sender,
Command cmd,
String label,
String[] args) {
// ===== /cypay — 查自己 =====
if (args.length == 0) {
String name = sender instanceof Player
? sender.getName() : "Console";
int bonds =
plugin.getBonds().getBonds(name);
String status = plugin.getBonds()
.getAccountStatus(name);
sender.sendMessage(
"§e§l[债券] §f" + name
+ " 余额: §a" + bonds);
sender.sendMessage(
"§e§l[债券] §f状态: "
+ ("frozen".equals(status)
? "§c冻结中"
: "§a正常"));
return true;
}
// ===== /cypay import =====
/* if (args.length == 2
&& "import".equalsIgnoreCase(
args[0])) {
if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
String file = args[1];
if (!file.endsWith(".txt"))
file += ".txt";
int result = plugin.getCDK()
.importFromFile(file);
if (result == -1)
sender.sendMessage("§c文件不存在: "
+ file);
else
sender.sendMessage("§a导入完成: "
+ result + " 条");
return true;
}*/
// ===== /cypay freeze =====
if (args.length == 2
&& "freeze".equalsIgnoreCase(
args[0])) {
if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
String target = args[1];
if (plugin.getBonds().isFrozen(target)) {
sender.sendMessage("§c" + target
+ " 已处于冻结状态");
return true;
}
plugin.getBonds().freezeAccount(target);
sender.sendMessage("§a已冻结 " + target
+ " 的债券账户");
Player tp = plugin.getServer()
.getPlayerExact(target);
if (tp != null)
tp.sendMessage(
"§c§l[系统] 你的债券账户"
+ "已被管理员冻结");
return true;
}
// ===== /cypay unfreeze =====
if (args.length == 2
&& "unfreeze".equalsIgnoreCase(
args[0])) {
if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
String target = args[1];
if (!plugin.getBonds()
.isFrozen(target)) {
sender.sendMessage("§c" + target
+ " 未被冻结");
return true;
}
plugin.getBonds().unfreezeAccount(target);
sender.sendMessage("§a已解冻 " + target
+ " 的债券账户");
Player tp = plugin.getServer()
.getPlayerExact(target);
if (tp != null)
tp.sendMessage(
"§a§l[系统] 你的债券账户"
+ "已被解冻");
return true;
}
// ===== /cypay water [days] =====
if (args.length >= 2
&& "water".equalsIgnoreCase(
args[0])) {
if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
String target = args[1];
int days = 7;
if (args.length >= 3) {
try {
days = Integer.parseInt(args[2]);
} catch (NumberFormatException ignored) {
}
}
days = Math.min(days,
BondManager.ADMIN_MAX_WATER_DAYS);
if (days <= 0) days = 1;
List<Map<String, Object>> txs =
plugin.getBonds()
.getTransactions(
target, days);
sender.sendMessage(
"§e§l===== " + target
+ " 流水记录 (近"
+ days + "天) =====");
sender.sendMessage("§f当前余额: §a"
+ plugin.getBonds()
.getBonds(target)
+ " §f状态: "
+ (plugin.getBonds()
.isFrozen(target)
? "§c冻结" : "§a正常"));
if (txs.isEmpty()) {
sender.sendMessage("§7 (暂无记录)");
} else {
for (Map<String, Object> tx : txs) {
sender.sendMessage(
formatTxLine(tx));
}
}
sender.sendMessage(
"§e§l=========================="
+ "==========");
return true;
}
// ===== /cypay printer [player] =====
if ("printer".equalsIgnoreCase(args[0])) {
if (!hasAdminPerm(sender)
&& !(sender instanceof Player)
== false) {
// 控制台也可以用
if (!(sender instanceof Player))
; // 放行
else if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
}
if (args.length == 1) {
// 打印全服
sender.sendMessage(
"§e正在导出全服流水...");
plugin.getBonds(); // 确保实例就绪
if (plugin.getBondPrinter() != null) {
plugin.getBondPrinter()
.printAll(sender);
} else {
sender.sendMessage("§cPrinter未初始化");
}
return true;
}
if (args.length == 2) {
// 打印指定玩家
String target = args[1];
sender.sendMessage("§e正在导出 "
+ target + " 的流水...");
if (plugin.getBondPrinter() != null) {
plugin.getBondPrinter()
.printPlayer(target, sender);
} else {
sender.sendMessage("§cPrinter未初始化");
}
return true;
}
}
// ===== /cypay give/remove =====
if (args.length == 3) {
String action = args[0].toLowerCase();
if ("give".equals(action)
|| "remove".equals(action)) {
if (!hasAdminPerm(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
String target = args[1];
try {
int amount =
parseAmount(args[2]);
if (amount <= 0) {
sender.sendMessage(
"§c金额必须大于0");
return true;
}
String txType =
"give".equals(action)
? BondManager
.TX_ADMIN_GIVE
: BondManager
.TX_ADMIN_DEDUCT;
String opName =
sender instanceof Player
? sender.getName()
: "Console";
if ("give".equals(action)) {
plugin.getBonds().addBonds(
target, amount,
txType, "",
opName, "管理员发放");
sender.sendMessage(
"§a已给 " + target
+ " " + amount
+ " 债券");
Player tp = plugin.getServer()
.getPlayerExact(target);
if (tp != null)
tp.sendMessage(
"§a管理员给了你 "
+ amount
+ " 债券");
} else {
if (plugin.getBonds()
.deductBonds(target,
amount,
txType, "",
opName,
"管理员扣除")) {
sender.sendMessage(
"§a已扣除 " + target
+ " " + amount
+ " 债券");
Player tp = plugin
.getServer()
.getPlayerExact(
target);
if (tp != null)
tp.sendMessage(
"§c管理员扣除了"
+ "你的 "
+ amount
+ " 债券");
} else {
sender.sendMessage(
"§c" + target
+ " 债券不足"
+ "或账户冻结");
}
}
} catch (NumberFormatException e) {
sender.sendMessage(
"§c金额格式错误");
}
return true;
}
}
// ===== /cypay <player> <amount> — 仅玩家转账 =====
if (args.length == 2) {
try {
int amount = parseAmount(args[1]);
if (amount <= 0) {
sender.sendMessage(
"§c金额必须大于0");
return true;
}
// ★ 控制台禁止转账
if (!(sender instanceof Player)) {
sender.sendMessage(
"§c控制台不能转账,请使用 "
+ "/cypay give <玩家> <金额>");
return true;
}
Player p = (Player) sender;
String target = args[0];
BondManager.TransferResult r =
plugin.getBonds().transfer(
p.getName(),
target, amount);
if (r.success) {
p.sendMessage("§e§l[债券转账] §f向 §a"
+ target + " §f转出 §c"
+ amount + " §f枚债券");
Player tp = plugin.getServer()
.getPlayerExact(target);
if (tp != null)
tp.sendMessage("§e§l[债券转账] §f收到 §a"
+ p.getName()
+ " §f转入 §a"
+ amount + " §f枚债券");
} else {
p.sendMessage("§c" + r.message
+ ",当前: "
+ plugin.getBonds()
.getBonds(p.getName()));
}
return true;
} catch (NumberFormatException ignored) {
}
}
// ===== /cypay <player> — 查指定玩家 =====
if (args.length == 1) {
String target = args[0];
// ★ 验证玩家在 login.db 中存在 ★
if (!plugin.getDb().userExists(target)) {
sender.sendMessage("§c玩家 " + target
+ " 不存在于服务器");
return true;
}
// String target = args[0];
if (hasAdminPerm(sender)) {
int bonds = plugin.getBonds()
.getBonds(target);
String status = plugin.getBonds()
.getAccountStatus(target);
sender.sendMessage(
"§e§l[债券] §f" + target
+ " 余额: §a"
+ bonds);
sender.sendMessage(
"§e§l[债券] §f状态: "
+ ("frozen".equals(
status)
? "§c冻结中"
: "§a正常"));
} else {
int bonds = plugin.getBonds()
.getBonds(
sender.getName());
sender.sendMessage(
"§e§l[债券] §f你的余额: §a"
+ bonds);
}
return true;
}
showHelp(sender);
return true;
}
// ===== 流水行格式化 =====
private String formatTxLine(
Map<String, Object> tx) {
String type = (String) tx.get("type");
int amount = ((Number) tx.get("amount"))
.intValue();
String tgt = (String) tx.get("target_player");
String reason = (String) tx.get("reason");
int bal = ((Number) tx.get("balance_after"))
.intValue();
long time = ((Number) tx.get("time"))
.longValue();
boolean pos = amount > 0;
String sign = pos ? "+" : "";
String color = pos ? "§a" : "§c";
StringBuilder sb = new StringBuilder();
sb.append("§7[")
.append(SDF.format(new Date(time)))
.append("] ")
.append(formatTxType(type))
.append(" ")
.append(color).append(sign)
.append(amount).append("§7");
if (tgt != null && !tgt.isEmpty())
sb.append(" §e↔ ").append(tgt);
if (reason != null && !reason.isEmpty())
sb.append(" §7(").append(reason)
.append(")");
sb.append(" §7余额:§f").append(bal);
return sb.toString();
}
private String formatTxType(String type) {
if (type == null) return "§7未知";
switch (type) {
case BondManager.TX_ADMIN_GIVE:
return "§a管理员发放";
case BondManager.TX_ADMIN_DEDUCT:
return "§c管理员扣除";
case BondManager.TX_TRANSFER_OUT:
return "§c转出";
case BondManager.TX_TRANSFER_IN:
return "§a转入";
case BondManager.TX_REDEEM:
return "§aCDK兑换";
case BondManager.TX_SHOP_BUY:
return "§e商城购买";
case BondManager.TX_DAILY_SIGN:
return "§a签到奖励";
case BondManager.TX_FREEZE:
return "§c冻结";
case BondManager.TX_UNFREEZE:
return "§a解冻";
default:
return "§7" + type;
}
}
private void showHelp(CommandSender sender) {
sender.sendMessage("§e§l债券命令帮助:");
sender.sendMessage(
"§e/cypay §7- 查询自己余额");
sender.sendMessage(
"§e/cypay <玩家> §7- 查余额(管理员)");
sender.sendMessage(
"§e/cypay <玩家> <金额> §7- 转账");
if (hasAdminPerm(sender)) {
sender.sendMessage(
"§e/cypay give <玩家> <金额> §7- 给债券");
sender.sendMessage(
"§e/cypay remove <玩家> <金额> §7- 扣债券");
sender.sendMessage(
"§e/cypay import <文件> §7- 导入CDK");
sender.sendMessage(
"§e/cypay freeze <玩家> §7- 冻结");
sender.sendMessage(
"§e/cypay unfreeze <玩家> §7- 解冻");
sender.sendMessage(
"§e/cypay water <玩家> [天数] §7- 流水");
sender.sendMessage(
"§e/cypay printer §7- 导出全服流水xlsx");
sender.sendMessage(
"§e/cypay printer <玩家> §7- 导出指定玩家xlsx");
}
}
@Override
public List<String> onTabComplete(
CommandSender sender, Command command,
String label, String[] args) {
List<String> list = new ArrayList<>();
if (args.length == 1) {
String last = args[0].toLowerCase();
if (hasAdminPerm(sender)) {
list.add("give");
list.add("remove");
list.add("import");
list.add("freeze");
list.add("unfreeze");
list.add("water");
list.add("printer");
File cdkDir = new File(
plugin.getDataFolder(),
"cdk");
if (cdkDir.exists()) {
File[] files = cdkDir.listFiles(
(d, n) -> n
.endsWith(".txt"));
if (files != null)
for (File f : files)
list.add(f.getName());
}
}
for (Player o :
Bukkit.getOnlinePlayers())
list.add(o.getName());
return filter(list, last);
}
if (args.length == 2) {
String first = args[0].toLowerCase();
if ("give".equals(first)
|| "remove".equals(first)
|| "freeze".equals(first)
|| "unfreeze".equals(first)
|| "water".equals(first)) {
for (Player o :
Bukkit.getOnlinePlayers())
list.add(o.getName());
return filter(list,
args[1].toLowerCase());
}
list.add("10");
list.add("50");
list.add("100");
return filter(list,
args[1].toLowerCase());
}
if (args.length == 3) {
String first = args[0].toLowerCase();
if ("give".equals(first)
|| "remove".equals(first)) {
list.add("10");
list.add("50");
list.add("100");
list.add("999");
return filter(list,
args[2].toLowerCase());
}
if ("water".equals(first)) {
list.add("1");
list.add("3");
list.add("7");
list.add("14");
return filter(list,
args[2].toLowerCase());
}
}
return list;
}
}
+1 -354
View File
@@ -60,11 +60,9 @@ public class DatabaseManager {
+ "tasks_completed TEXT DEFAULT '',"
+ "premium_uuid TEXT DEFAULT '',"
+ "register_ip TEXT DEFAULT '',"
+ "register_type TEXT DEFAULT 'manual',"
+ "backcheck_dates TEXT DEFAULT ''"
+ "register_type TEXT DEFAULT 'manual'"
+ ")");
// 安全添加 users 缺失列
safeAdd(st, "tasks_completed",
"TEXT DEFAULT ''");
@@ -162,40 +160,6 @@ public class DatabaseManager {
+ "alert_date TEXT NOT NULL,"
+ "notified INTEGER DEFAULT 0,"
+ "notified_time INTEGER DEFAULT 0)");
// PVP区域表
st.execute("CREATE TABLE IF NOT EXISTS "
+ "pvp_regions ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "name TEXT NOT NULL UNIQUE,"
+ "world TEXT NOT NULL,"
+ "x1 INTEGER NOT NULL,"
+ "y1 INTEGER NOT NULL,"
+ "z1 INTEGER NOT NULL,"
+ "x2 INTEGER NOT NULL,"
+ "y2 INTEGER NOT NULL,"
+ "z2 INTEGER NOT NULL,"
+ "create_time INTEGER DEFAULT 0"
+ ")");
// PVP击杀总榜表
st.execute("CREATE TABLE IF NOT EXISTS "
+ "pvp_kills ("
+ "player_name TEXT NOT NULL,"
+ "region_name TEXT NOT NULL,"
+ "kills INTEGER DEFAULT 0,"
+ "deaths INTEGER DEFAULT 0,"
+ "PRIMARY KEY(player_name, region_name)"
+ ")");
// 菜单图标表
st.execute("CREATE TABLE IF NOT EXISTS "
+ "menu_icons ("
+ "player_name TEXT PRIMARY KEY,"
+ "icon_b64 TEXT DEFAULT '',"
+ "item_name TEXT DEFAULT '',"
+ "save_time INTEGER DEFAULT 0"
+ ")");
// 工单表
st.execute("CREATE TABLE IF NOT EXISTS "
@@ -275,76 +239,6 @@ public class DatabaseManager {
} catch (Exception ignored) {
}
}
// ==================== 菜单图标 ====================
public void saveMenuIcon(String name,
String iconB64, String itemName) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT OR REPLACE INTO menu_icons "
+ "(player_name, icon_b64, "
+ "item_name, save_time) "
+ "VALUES (?,?,?,?)");
ps.setString(1, name);
ps.setString(2, iconB64);
ps.setString(3, itemName);
ps.setLong(4,
System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getMenuIcon(String name) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT icon_b64 FROM menu_icons "
+ "WHERE player_name=?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
String b64 = null;
if (rs.next()) {
b64 = rs.getString("icon_b64");
}
rs.close();
ps.close();
return b64;
} catch (SQLException e) {
return null;
}
}
public void deleteMenuIcon(String name) {
try {
PreparedStatement ps = db.prepareStatement(
"DELETE FROM menu_icons "
+ "WHERE player_name=?");
ps.setString(1, name);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public boolean hasMenuIcon(String name) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT 1 FROM menu_icons "
+ "WHERE player_name=? "
+ "AND icon_b64 IS NOT NULL "
+ "AND icon_b64 != '' "
+ "LIMIT 1");
ps.setString(1, name);
boolean r = ps.executeQuery().next();
ps.close();
return r;
} catch (SQLException e) {
return false;
}
}
// ==================== 用户操作 ====================
@@ -853,252 +747,6 @@ public class DatabaseManager {
return false;
}
}
// ==================== PVP区域 ====================
public void savePVPRegion(String name,
String world,
int x1, int y1, int z1,
int x2, int y2, int z2) {
try {
PreparedStatement ps =
db.prepareStatement(
"INSERT OR REPLACE "
+ "INTO pvp_regions "
+ "(name, world, "
+ "x1, y1, z1, "
+ "x2, y2, z2, "
+ "create_time) "
+ "VALUES "
+ "(?,?,?,?,?,?,?,?,?)");
ps.setString(1, name);
ps.setString(2, world);
ps.setInt(3, x1);
ps.setInt(4, y1);
ps.setInt(5, z1);
ps.setInt(6, x2);
ps.setInt(7, y2);
ps.setInt(8, z2);
ps.setLong(9,
System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Map<String, Object>
getPVPRegion(String name) {
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT * FROM "
+ "pvp_regions "
+ "WHERE name=?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
for (int i = 1; i <= rs
.getMetaData()
.getColumnCount(); i++) {
row.put(rs.getMetaData()
.getColumnName(i),
rs.getObject(i));
}
rs.close();
ps.close();
return row;
}
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public List<Map<String, Object>>
getAllPVPRegions() {
List<Map<String, Object>> list =
new ArrayList<>();
try {
Statement st =
db.createStatement();
ResultSet rs =
st.executeQuery(
"SELECT * FROM "
+ "pvp_regions");
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
for (int i = 1; i <= rs
.getMetaData()
.getColumnCount(); i++) {
row.put(rs.getMetaData()
.getColumnName(i),
rs.getObject(i));
}
list.add(row);
}
rs.close();
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public void deletePVPRegion(String name) {
try {
PreparedStatement ps =
db.prepareStatement(
"DELETE FROM "
+ "pvp_regions "
+ "WHERE name=?");
ps.setString(1, name);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// ==================== PVP击杀 ====================
public int getPVPKills(
String player, String region) {
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT kills FROM "
+ "pvp_kills "
+ "WHERE "
+ "player_name=? "
+ "AND region_name=?");
ps.setString(1, player);
ps.setString(2, region);
ResultSet rs = ps.executeQuery();
int kills = 0;
if (rs.next())
kills = rs.getInt("kills");
rs.close();
ps.close();
return kills;
} catch (SQLException e) {
return 0;
}
}
public int getPVPDeaths(
String player, String region) {
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT deaths FROM "
+ "pvp_kills "
+ "WHERE "
+ "player_name=? "
+ "AND region_name=?");
ps.setString(1, player);
ps.setString(2, region);
ResultSet rs = ps.executeQuery();
int deaths = 0;
if (rs.next())
deaths = rs.getInt("deaths");
rs.close();
ps.close();
return deaths;
} catch (SQLException e) {
return 0;
}
}
public void addPVPKill(
String player, String region) {
try {
PreparedStatement ps =
db.prepareStatement(
"INSERT INTO pvp_kills "
+ "(player_name, "
+ "region_name, "
+ "kills, deaths) "
+ "VALUES (?,?,1,0) "
+ "ON CONFLICT"
+ "(player_name, "
+ "region_name) "
+ "DO UPDATE SET "
+ "kills=kills+1");
ps.setString(1, player);
ps.setString(2, region);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void addPVPDeath(
String player, String region) {
try {
PreparedStatement ps =
db.prepareStatement(
"INSERT INTO pvp_kills "
+ "(player_name, "
+ "region_name, "
+ "kills, deaths) "
+ "VALUES (?,?,0,1) "
+ "ON CONFLICT"
+ "(player_name, "
+ "region_name) "
+ "DO UPDATE SET "
+ "deaths=deaths+1");
ps.setString(1, player);
ps.setString(2, region);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public List<Map<String, Object>>
getPVPKillTop(String region, int limit) {
List<Map<String, Object>> list =
new ArrayList<>();
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT player_name, "
+ "kills, deaths "
+ "FROM pvp_kills "
+ "WHERE "
+ "region_name=? "
+ "AND kills>0 "
+ "ORDER BY kills "
+ "DESC LIMIT ?");
ps.setString(1, region);
ps.setInt(2, limit);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
row.put("player_name",
rs.getString("player_name"));
row.put("kills",
rs.getInt("kills"));
row.put("deaths",
rs.getInt("deaths"));
list.add(row);
}
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
// ==================== 安全报警 ====================
@@ -1125,7 +773,6 @@ public class DatabaseManager {
} catch (SQLException e) {
e.printStackTrace();
}
}
public List<Map<String, Object>>
@@ -59,15 +59,6 @@ public class EmailManager {
+ "\n请尽快登录并修改密码!");
}
public boolean sendBody(String to, String subject, String body) {
if (!isConfigured()) {
logger.warning("[Sdf1_login] SMTP未配置");
return false;
}
return sendEmailSync(to, subject, body);
}
private boolean sendEmailSync(String to,
String subject, String body) {
final boolean[] result = {false};
File diff suppressed because it is too large Load Diff
+46 -9
View File
@@ -230,8 +230,10 @@ public class GarbageManager {
}
public void saveItem(ItemStack stack) {
{
try {
String data = stackToBase64(stack);
String data =
stackToBase64(stack);
if (data.isEmpty()) return;
PreparedStatement ps = db.prepareStatement(
"INSERT INTO items "
@@ -248,8 +250,7 @@ public class GarbageManager {
} catch (Exception e) {
e.printStackTrace();
}
}
}}
private int deleteOldItems(int beforeRound) {
try {
@@ -343,9 +344,11 @@ public class GarbageManager {
}
public void openRecyclePage(Player p, int page) {
Inventory g = Bukkit.createInventory(
null, 54, "§6§l垃圾回收站");
// 获取全部存储物品
List<Map<String, Object>> allItems =
getStoredItems(999);
int total = allItems.size();
@@ -377,6 +380,38 @@ public class GarbageManager {
int amount = ((Number) item
.get("amount")).intValue();
ItemStack stack = base64ToStack(data);
if (stack != null) {
try {
byte[] bytes =
stack.serializeAsBytes();
String b64 = java.util.Base64
.getEncoder()
.encodeToString(bytes);
int len = b64.length();
String f = len > 15
? b64.substring(10, 15) : "短";
String m = len >= 5
? b64.substring(len / 2,
Math.min(len / 2 + 5, len))
: "短";
String b = len > 10
? b64.substring(len - 10, len - 5)
: "短";
plugin.getLogger().info(
"[垃圾站加载] ID#" + id
+ " " + stack.getType()
+ " x" + amount
+ " base64[" + len + "]"
+ " 前=" + f
+ " 中=" + m
+ " 后=" + b);
} catch (Exception ex) {
plugin.getLogger().warning(
"[垃圾站加载] 序列化异常: "
+ ex.getMessage());
}
}
if (stack != null) {
stack.setAmount(amount);
ItemMeta im =
@@ -394,7 +429,10 @@ public class GarbageManager {
slot++;
}
// 49 格:上一页
// 45-48 格:额外投放区(空位)
// 52-53 格:额外投放区(空位)
// 49 格:翻页-上一页
if (page > 0) {
g.setItem(49, mk(Material.ARROW,
"§e上一页 §7("
@@ -413,8 +451,9 @@ public class GarbageManager {
"§7当前页: §f" + (page + 1)
+ "/" + totalPages));
// 51 格:下一页
boolean canNext = page < totalPages - 1;
// 51 格:翻页-下一页
boolean canNext = total >= pageSize
&& page < totalPages - 1;
if (canNext) {
g.setItem(51, mk(Material.ARROW,
"§e下一页 §7("
@@ -424,15 +463,13 @@ public class GarbageManager {
g.setItem(51, mk(Material.ARROW,
"§7已是最后一页"));
}
// 52 格:返回
// 52格:返回按钮
g.setItem(52, mk(Material.ARROW,
"§7返回",
"§7返回主菜单"));
p.openInventory(g);
}
public int getRecyclePage(String name) {
return recyclePages
.getOrDefault(name, 0);
+1 -1
View File
@@ -22,7 +22,7 @@ public class GiftManager {
public GiftManager(Main plugin) {
this.plugin = plugin;
this.giftFolder = new File(
plugin.getDataFolder(), "任务面板");
plugin.getDataFolder(), "新人礼包");
}
public void loadStages() {
+13 -56
View File
@@ -16,34 +16,38 @@ public class IPGroupManager {
this.maxAccounts = maxAccounts;
}
/**
* 检查该IP是否还能注册新账号
*/
public boolean canRegister(String ip) {
if (maxAccounts <= 0) return true;
if (ip == null || ip.isEmpty()) return true;
int count = getAccountCount(ip);
boolean can = count < maxAccounts;
plugin.getLogger().info(
"[IP限制] IP=" + ip
+ " 已注册=" + count
+ " 上限=" + maxAccounts
+ " 允许=" + can);
return can;
return queryAccountsByIP(ip).size() < maxAccounts;
}
/**
* 获取该IP下已注册的账号数量
*/
public int getAccountCount(String ip) {
if (ip == null || ip.isEmpty()) return 0;
return queryAccountsByIP(ip).size();
}
/**
* 获取该IP下所有账号名
*/
public List<String> getAccounts(String ip) {
return queryAccountsByIP(ip);
}
/**
* 直接通过数据库连接查询指定IP下的所有账号
*/
private List<String> queryAccountsByIP(String ip) {
List<String> accounts = new ArrayList<>();
Connection conn = plugin.getDb().getDb();
if (conn == null) return accounts;
try {
// 主查询:ip_address 列
PreparedStatement ps = conn.prepareStatement(
"SELECT player_name FROM users "
+ "WHERE ip_address = ?");
@@ -55,53 +59,6 @@ public class IPGroupManager {
}
rs.close();
ps.close();
// 兜底:如果主列没数据,查 last_login_ip
if (accounts.isEmpty()) {
PreparedStatement ps2 = conn.prepareStatement(
"SELECT player_name FROM users "
+ "WHERE last_login_ip = ? "
+ "AND (ip_address IS NULL "
+ "OR ip_address = '' "
+ "OR ip_address != ?)");
ps2.setString(1, ip);
ps2.setString(2, ip);
ResultSet rs2 = ps2.executeQuery();
while (rs2.next()) {
String name = rs2.getString("player_name");
if (!accounts.contains(name))
accounts.add(name);
}
rs2.close();
ps2.close();
}
// 兜底2:查 register_ip
if (accounts.isEmpty()) {
PreparedStatement ps3 = conn.prepareStatement(
"SELECT player_name FROM users "
+ "WHERE register_ip = ? "
+ "AND (ip_address IS NULL "
+ "OR ip_address = '') "
+ "AND (last_login_ip IS NULL "
+ "OR last_login_ip = '' "
+ "OR last_login_ip != ?)");
ps3.setString(1, ip);
ps3.setString(2, ip);
ResultSet rs3 = ps3.executeQuery();
while (rs3.next()) {
String name = rs3.getString("player_name");
if (!accounts.contains(name))
accounts.add(name);
}
rs3.close();
ps3.close();
}
plugin.getLogger().info(
"[IP限制] 查询IP=" + ip
+ " 结果数=" + accounts.size()
+ " 账号=" + accounts);
} catch (Exception e) {
e.printStackTrace();
}
+14 -23
View File
@@ -66,8 +66,6 @@ public class LoginManager {
plugin.getDb().createUser(
name, hash, salt);
plugin.getLoggedIn().add(name);
p.setAllowFlight(false);
p.setFlying(false);
plugin.getDb().setLoggedIn(name, true);
plugin.getDb().setField(name,
"last_login_time",
@@ -118,16 +116,7 @@ public class LoginManager {
public boolean handleLogin(Player p,
String[] args) {
String name = p.getName();
// ★ 在 handleLogin 方法开头
if (plugin.getNeedsPasswordChange().contains(name)
&& plugin.getLoggedIn().contains(name)) {
// 已登录但还没改密码,只允许执行 /sdf1_login pw
p.sendMessage("§c请先修改密码: /sdf1_login pw <旧密码> <新密码>");
return true;
}
try {
if (plugin.getLoggedIn().contains(name)) {
p.sendMessage(plugin.getConfig2()
@@ -162,8 +151,6 @@ public class LoginManager {
name, hash);
if (matchMain) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
@@ -188,21 +175,25 @@ public class LoginManager {
name, hash);
if (matchTemp) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
p.setAllowFlight(false);
p.setFlying(false);
plugin.getDb().setField(name, "last_login_time",
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name, "last_online_check",
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
// ★ 确保这行存在
plugin.getNeedsPasswordChange().add(name);
p.sendMessage(plugin.getConfig2().msg("login_success"));
p.sendMessage("§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
p.sendMessage("§7用法: /sdf1_login pw");
plugin.getNeedsPasswordChange()
.add(name);
p.sendMessage(plugin.getConfig2()
.msg("login_success"));
p.sendMessage(
"§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
p.sendMessage(
"§7用法: /sdf1_login pw");
activateCY(p, name);
return true;
}
File diff suppressed because it is too large Load Diff
@@ -1,152 +0,0 @@
package Sdf1_login;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
public class MenuIconManager {
private final Main plugin;
private final NamespacedKey iconKey;
public MenuIconManager(Main plugin) {
this.plugin = plugin;
this.iconKey = new NamespacedKey(
plugin, "custom_menu_icon");
}
// ========== GUIManager 调用的方法 ==========
/**
* Base64 → ItemStack
* 对应 GUIManager 中的
* plugin.getMenuIconMgr().deserializeItem(...)
*/
public ItemStack deserializeItem(String b64) {
if (b64 == null || b64.isEmpty())
return null;
try {
byte[] data =
Base64.getDecoder().decode(b64);
ByteArrayInputStream bais =
new ByteArrayInputStream(data);
BukkitObjectInputStream is =
new BukkitObjectInputStream(bais);
Object obj = is.readObject();
is.close();
if (obj instanceof ItemStack)
return (ItemStack) obj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* ItemStack → Base64
* 对应 GUIManager 中的
* plugin.getMenuIconMgr().serializeItem(...)
*/
public String serializeItem(ItemStack item) {
if (item == null) return null;
try {
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
BukkitObjectOutputStream os =
new BukkitObjectOutputStream(baos);
os.writeObject(item);
os.close();
return Base64.getEncoder()
.encodeToString(
baos.toByteArray());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 给物品打上菜单图标标记
* 对应 GUIManager 中的
* plugin.getMenuIconMgr().ensureMenuIconTag(...)
*/
public void ensureMenuIconTag(ItemStack item) {
if (item == null) return;
ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.getPersistentDataContainer().set(
iconKey,
PersistentDataType.STRING,
"menu");
item.setItemMeta(meta);
}
/**
* 获取图标Key
* 对应 GUIManager 中的
* plugin.getMenuIconMgr().getIconKey()
*/
public NamespacedKey getIconKey() {
return iconKey;
}
// ========== LoginManager / Main 调用的方法 ==========
/**
* 获取玩家自定义图标物品
* 有返回物品,无返回null
*/
public ItemStack getIcon(Player player) {
String b64 = plugin.getDb()
.getMenuIcon(player.getName());
if (b64 == null || b64.isEmpty())
return null;
ItemStack item = deserializeItem(b64);
if (item == null) return null;
ensureMenuIconTag(item);
return item;
}
/**
* 保存图标到DB
*/
public void saveIcon(Player player,
ItemStack item) {
String b64 = serializeItem(item);
if (b64 != null) {
plugin.getDb().saveMenuIcon(
player.getName(), b64,
item.getType().name());
}
}
/**
* 删除图标
*/
public void removeIcon(Player player) {
plugin.getDb().deleteMenuIcon(
player.getName());
}
/**
* 检查物品是否是自定义菜单图标
*/
public boolean isCustomMenuIcon(
ItemStack item) {
if (item == null) return false;
ItemMeta meta = item.getItemMeta();
if (meta == null) return false;
return meta.getPersistentDataContainer()
.has(iconKey,
PersistentDataType.STRING);
}
}
-452
View File
@@ -1,452 +0,0 @@
package Sdf1_login;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.event.*;
import org.bukkit.event.inventory.*;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.nio.file.Files;
public class MenuManager implements Listener {
private final Main plugin;
private final List<MenuItem> items =
new ArrayList<>();
private final Set<String> chatWait =
ConcurrentHashMap.newKeySet();
private final Map<String, String> chatField =
new ConcurrentHashMap<>();
private final Map<String, Integer> chatIdx =
new ConcurrentHashMap<>();
// ===== 菜单项 =====
public static class MenuItem {
public String title = "新菜单项";
public String command = "";
public String permType = "玩家";
public Integer slot = null;
public ItemStack icon;
public MenuItem() {
icon = randomIcon();
}
public MenuItem(String t,
String c, String p) {
title = t;
command = c;
permType = p;
icon = randomIcon();
}
static ItemStack randomIcon() {
Material[] pool = {
Material.COMPASS,
Material.ENDER_PEARL,
Material.DIAMOND,
Material.GOLD_INGOT,
Material.EMERALD,
Material.NETHER_STAR,
Material.BEACON,
Material.ENCHANTING_TABLE,
Material.CHEST,
Material.FIREWORK_ROCKET,
Material.BOOK,
Material.ELYTRA,
Material.TOTEM_OF_UNDYING
};
Material m = pool[
(int) (Math.random()
* pool.length)];
ItemStack it =
new ItemStack(m);
ItemMeta im = it.getItemMeta();
im.setDisplayName("§e默认图标");
it.setItemMeta(im);
return it;
}
}
public MenuManager(Main plugin) {
this.plugin = plugin;
}
public List<MenuItem> getItems() {
return items;
}
// ===== 读取菜单.txt =====
public void loadMenu() {
items.clear();
File f = new File(
plugin.getDataFolder(),
"菜单.txt");
if (!f.exists()) createDefault(f);
parseFile(f);
plugin.getLogger().info(
"[Menu] 加载 "
+ items.size()
+ " 个菜单项");
}
private void parseFile(File f) {
plugin.getLogger().info("[Menu] ===== 扫描 =====");
plugin.getLogger().info("[Menu] 路径: "
+ f.getAbsolutePath());
plugin.getLogger().info("[Menu] 大小: "
+ f.length());
try {
List<String> lines = Files.readAllLines(
f.toPath(), StandardCharsets.UTF_8);
plugin.getLogger().info("[Menu] 总行数: "
+ lines.size());
boolean inBlock = false;
boolean inHtml = false;
StringBuilder buf = new StringBuilder();
boolean inItem = false;
int parsed = 0;
for (int lineNum = 1;
lineNum <= lines.size();
lineNum++) {
String raw = lines.get(lineNum - 1);
String l = raw.trim();
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": 原文[" + l.length()
+ "字符] " + l);
// ===== 第1优先:跨行块注释 =====
if (inBlock) {
if (l.contains("*/")) {
inBlock = false;
l = l.substring(
l.indexOf("*/") + 2)
.trim();
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": 块注释结束");
} else {
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": [块注释中]");
continue;
}
}
// ===== 第2优先:跨行HTML注释 =====
if (inHtml) {
if (l.contains("-->")) {
inHtml = false;
l = l.substring(
l.indexOf("-->") + 3)
.trim();
} else {
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": [HTML注释中]");
continue;
}
}
// ===== 第3优先:整行注释(最高裁决) =====
// 以 # 或 // 开头的行,直接跳过
// 不管后面包含什么 /* <!-- 等
if (l.startsWith("#")
|| l.startsWith("//")) {
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": [整行注释,跳过]");
continue;
}
// ===== 第4优先:块注释开始 =====
if (l.contains("/*")) {
l = l.substring(0, l.indexOf("/*"))
.trim();
inBlock = true;
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": 进入/*块注释"
+ " 剩余[" + l + "]");
if (l.isEmpty()) {
continue;
}
}
// ===== 第5优先:HTML注释开始 =====
if (l.contains("<!--")) {
l = l.substring(0,
l.indexOf("<!--")).trim();
inHtml = true;
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": 进入<!--注释");
if (l.isEmpty()) {
continue;
}
}
// ===== 第6优先:行内注释 =====
int hashPos = l.indexOf("#");
int slashPos = l.indexOf("//");
if (hashPos >= 0) {
l = l.substring(0, hashPos).trim();
}
if (slashPos >= 0) {
if (hashPos < 0
|| slashPos < hashPos) {
l = l.substring(0, slashPos)
.trim();
}
}
if (l.isEmpty()) {
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": 空");
continue;
}
plugin.getLogger().info(
"[Menu] L" + lineNum
+ ": [保留] " + l);
if (l.equals("{")) {
inItem = true;
buf.setLength(0);
plugin.getLogger().info(
"[Menu] === 块开始 ===");
continue;
}
if (l.equals("}")) {
if (inItem) {
plugin.getLogger().info(
"[Menu] === 块结束 ===");
parseBlock(buf.toString());
parsed++;
plugin.getLogger().info(
"[Menu] 当前菜单项: "
+ items.size());
}
inItem = false;
continue;
}
if (inItem) {
buf.append(l).append("\n");
}
}
plugin.getLogger().info(
"[Menu] ===== 完毕 =====");
plugin.getLogger().info(
"[Menu] 块数: " + parsed);
plugin.getLogger().info(
"[Menu] 菜单项: "
+ items.size());
} catch (Exception e) {
plugin.getLogger().warning(
"[Menu] 异常: "
+ e.getMessage());
}
}
private void parseBlock(String block) {
String title = "";
String cmd = "";
String perm = "玩家";
String iconMat = "";
int slotNum = -1;
for (String line : block.split("\n")) {
line = line.trim();
if (line.isEmpty()) continue;
String v = "";
int idx = line.indexOf(":");
if (idx < 0) idx = line.indexOf(":");
if (idx >= 0) {
v = line.substring(idx + 1).trim();
if (v.startsWith("\"")
&& v.endsWith("\"")
&& v.length() >= 2) {
v = v.substring(1, v.length() - 1);
}
}
if (line.startsWith("标题")) {
title = v;
} else if (line.startsWith("指令")) {
cmd = v;
if (!cmd.isEmpty()
&& !cmd.startsWith("/")) {
cmd = "/" + cmd;
}
} else if (line.startsWith("权限")) {
perm = v;
} else if (line.startsWith("图标")) {
iconMat = v;
} else if (line.startsWith("格子")) {
try {
int s = Integer.parseInt(v);
if (s >= 1 && s <= 54) {
slotNum = s;
}
} catch (NumberFormatException ignored) {
}
}
}
if (title.isEmpty()) return;
MenuItem mi = new MenuItem(title, cmd, perm);
if (slotNum >= 1 && slotNum <= 54) {
mi.slot = slotNum;
}
if (!iconMat.isEmpty()) {
try {
String matName = iconMat
.replace("minecraft:", "")
.replace("bukkit:", "")
.trim().toUpperCase();
Material m =
Material.valueOf(matName);
mi.icon = new ItemStack(m);
ItemMeta im =
mi.icon.getItemMeta();
im.setDisplayName(
"§e" + title);
mi.icon.setItemMeta(im);
} catch (Exception e) {
plugin.getLogger().warning(
"[Menu] 无法解析: "
+ iconMat);
}
}
items.add(mi);
}
private void createDefault(File f) {
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets
.UTF_8))) {
pw.println("# 服务器菜单");
pw.println("# {}定义菜单项");
pw.println("# 支持"
+ " # // /* */ <!-- -->");
pw.println();
pw.println("{");
pw.println(" 标题:主城传送");
pw.println(" 指令:/spawn");
pw.println(" 权限类型:玩家");
pw.println(" 图标:COMPASS");
pw.println("}");
pw.println();
pw.println("{");
pw.println(" 标题:随机传送");
pw.println(" 指令:/rtp");
pw.println(" 权限类型:玩家");
pw.println(" 图标:ENDER_PEARL");
pw.println("}");
} catch (Exception ignored) {
}
}
// ===== 保存 =====
public void saveMenu() {
File f = new File(
plugin.getDataFolder(),
"菜单.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# 服务器菜单");
pw.println();
for (MenuItem mi : items) {
pw.println("{");
pw.println(" 标题:"
+ mi.title);
pw.println(" 指令:"
+ mi.command);
pw.println(" 权限类型:"
+ mi.permType);
// ★ 保存实际材质名 ★
pw.println(" 图标:"
+ mi.icon.getType().name());
pw.println("}");
pw.println();
}
} catch (Exception e) {
plugin.getLogger().warning(
"[Menu] 保存失败: "
+ e.getMessage());
}
}
// ===== 聊天编辑 =====
public boolean isEditing(String name) {
return chatWait.contains(name);
}
public void editTitle(
Player p, int idx) {
chatIdx.put(p.getName(), idx);
chatWait.add(p.getName());
chatField.put(p.getName(), "title");
p.closeInventory();
p.sendMessage("§e请输入菜单标题:");
}
public void editCommand(
Player p, int idx) {
chatIdx.put(p.getName(), idx);
chatWait.add(p.getName());
chatField.put(p.getName(), "command");
p.closeInventory();
p.sendMessage("§e请输入指令"
+ "(自动补全/):");
}
public void onChat(Player p, String input) {
String name = p.getName();
if (!chatWait.remove(name)) return;
String field = chatField.remove(name);
Integer idx = chatIdx.get(name);
if (idx == null || idx >= items.size()) return;
MenuItem mi = items.get(idx);
if ("title".equals(field)) {
mi.title = input;
p.sendMessage("§a标题: " + input);
} else if ("command".equals(field)) {
if (!input.startsWith("/"))
input = "/" + input;
mi.command = input;
p.sendMessage("§a指令: " + input);
}
Bukkit.getScheduler().runTask(plugin, () ->
plugin.gui.openEditor(p, idx));
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -4,142 +4,115 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player
.PlayerResourcePackStatusEvent;
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent
.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
public class RadioDownloadListener
implements Listener {
private final Logger log;
private final Main plugin;
private final java.util.logging.Logger log;
private final ConcurrentHashMap<UUID, Integer>
failCount = new ConcurrentHashMap<>();
private final ConcurrentHashMap<UUID, Boolean>
blocked = new ConcurrentHashMap<>();
private static final int MAX_FAILED = 3;
private final Map<UUID, Integer> retryCount =
new ConcurrentHashMap<>();
private static final int MAX_RETRY = 2;
public RadioDownloadListener(Main plugin) {
this.plugin = plugin;
this.log = plugin.getLogger();
}
public void onPlayerQuit(UUID uuid) {
failCount.remove(uuid);
blocked.remove(uuid);
}
@EventHandler
public void onStatus(
PlayerResourcePackStatusEvent e) {
Player p = e.getPlayer();
UUID uuid = p.getUniqueId();
String status =
e.getStatus().name();
String status = e.getStatus().name();
log.info("[Radio] === 资源包状态 ===");
log.info("[Radio] 玩家: "
+ p.getName());
log.info("[Radio] UUID: " + uuid);
log.info("[Radio] 状态: "
+ status);
log.info("[Radio] 原始状态枚举: "
+ e.getStatus().getClass()
.getSimpleName()
+ "."
+ e.getStatus().name());
if (blocked.containsKey(uuid)) {
log.info("[Radio] 已熔断,跳过: "
+ p.getName());
return;
}
log.info("[Radio] " + p.getName()
+ " status: " + status);
switch (status) {
case "SUCCESS":
log.info("[Radio] "
+ p.getName()
+ " ✓ 资源包加载成功");
failCount.remove(uuid);
blocked.remove(uuid);
break;
case "ACCEPTED":
log.info("[Radio] "
+ p.getName()
+ " 客户端已接受,"
+ "等待下载...");
break;
case "DECLINED":
log.warning("[Radio] "
+ p.getName()
+ " ✗ 拒绝了资源包");
log.warning("[Radio] "
+ "可能原因: "
+ "1.客户端设置禁用资源包"
+ " 2.已缓存拒绝记录"
+ " 3.SSL证书不匹配");
handleFailure(p, uuid);
case "DOWNLOADED":
break;
case "SUCCESS":
log.info("[Radio] " + p.getName()
+ " 资源包加载成功");
retryCount.remove(uuid);
break;
case "FAILED_DOWNLOAD":
log.warning("[Radio] "
+ p.getName()
+ " ✗ 下载失败");
log.warning("[Radio] "
+ "可能原因: "
+ "1.URL无法访问"
+ " 2.SSL证书错误"
+ " 3.网络超时"
+ " 4.文件不存在(404)");
handleFailure(p, uuid);
handleRetry(p, uuid);
break;
case "DECLINED":
log.info("[Radio] " + p.getName()
+ " 拒绝下载");
p.sendMessage(
"§e[Radio] §f你拒绝了资源包");
retryCount.remove(uuid);
break;
case "DISCARDED":
log.warning("[Radio] " + p.getName()
+ " DISCARDED");
p.sendMessage(
"§c[Radio] §f资源包下载失败"
+ ",可能原因:");
p.sendMessage(
"§71. CDN/防火墙拦截");
p.sendMessage(
"§72. SSL证书问题");
retryCount.remove(uuid);
break;
case "INVALID_URL":
log.warning("[Radio] " + p.getName()
+ " URL无效");
retryCount.remove(uuid);
break;
default:
log.info("[Radio] "
+ p.getName()
+ " 未知状态: "
+ status);
log.info("[Radio] " + p.getName()
+ " 未知: " + status);
break;
}
}
private void handleFailure(Player p, UUID uuid) {
int failNum = failCount.getOrDefault(uuid, 0) + 1;
failCount.put(uuid, failNum);
private void handleRetry(Player p, UUID uuid) {
int count = retryCount
.getOrDefault(uuid, 0) + 1;
retryCount.put(uuid, count);
log.warning("[Radio] " + p.getName()
+ " 失败次数: " + failNum + "/" + MAX_FAILED);
+ " 下载失败 (" + count
+ "/" + MAX_RETRY + ")");
if (failNum >= MAX_FAILED) {
blocked.put(uuid, true);
log.severe("[Radio] " + p.getName()
+ " 已达最大失败次数,停止重试,"
+ "玩家继续留在服务器");
// 不踢出,只记录熔断,玩家正常游玩
p.sendMessage("§e§l[Radio] 资源包加载失败"
+ ",跳过资源包,部分画面、音频可能出现问题"
+ "\n§d§l若想继续下载,请退出重进" +
"\n§b§l若持续失败,请重启客户端");
return;
if (count <= MAX_RETRY) {
p.sendMessage(
"§e[Radio] §f下载失败,重试中... ("
+ count + "/" + MAX_RETRY + ")");
Bukkit.getScheduler().runTaskLater(
plugin,
() -> {
if (p.isOnline()
&& plugin.radio != null) {
plugin.radio
.sendResourcePack(p);
}
}, 60L);
} else {
p.sendMessage(
"§c[Radio] §f资源包加载失败,"
+ "部分音乐无法播放");
retryCount.remove(uuid);
}
// 打了包没测试
p.sendMessage("§e§l[Radio] 下载失败,"
+ "重试... (" + failNum + "/" + MAX_FAILED + ")");
// 5秒后重试
Bukkit.getScheduler()
.runTaskLater(plugin, () -> {
if (p.isOnline() && plugin.radio != null) {
plugin.radio.sendResourcePack(p);
}
}, 100L);
}
}
File diff suppressed because it is too large Load Diff
@@ -1,62 +0,0 @@
package Sdf1_login;
import org.bukkit.Material;
import java.util.UUID;
public class RefundRecord {
public final UUID playerUUID;
public final String playerName;
public final String itemId;
public final String itemName;
public final Material material;
public final Material shulkerMaterial;
public int amount;
public int totalPaid;
public final long timestamp;
// 普通购买
public RefundRecord(UUID uuid, String name,
String itemId, String itemName,
Material material, int amount,
int totalPaid, long timestamp) {
this.playerUUID = uuid;
this.playerName = name;
this.itemId = itemId;
this.itemName = itemName;
this.material = material;
this.shulkerMaterial = null;
this.amount = amount;
this.totalPaid = totalPaid;
this.timestamp = timestamp;
}
// 盒装购买
public RefundRecord(UUID uuid, String name,
String itemId, String itemName,
Material material,
Material shulkerMat,
int amount, int totalPaid,
long timestamp) {
this.playerUUID = uuid;
this.playerName = name;
this.itemId = itemId;
this.itemName = itemName;
this.material = material;
this.shulkerMaterial = shulkerMat;
this.amount = amount;
this.totalPaid = totalPaid;
this.timestamp = timestamp;
}
public boolean isExpired() {
return System.currentTimeMillis()
- timestamp > 300000L;
}
public int getSecondsLeft() {
long left = 300000L
- (System.currentTimeMillis()
- timestamp);
return (int) Math.max(0, left / 1000);
}
}
@@ -1,170 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class SalesStatsManager {
private final Main plugin;
private final Map<String, Integer> sessionSales =
new ConcurrentHashMap<>();
private int sessionRevenue = 0;
private int sessionOrders = 0;
private int sessionItems = 0;
private int sessionRefunds = 0;
private int sessionRefundAmount = 0;
private long sessionStart = System.currentTimeMillis();
// 状态
private boolean waitingForEmpty = false;
private boolean hasReported = false;
private BukkitRunnable waitTask = null;
public SalesStatsManager(Main plugin) {
this.plugin = plugin;
startMonitor();
}
// ★ 每10秒检测一次
private void startMonitor() {
new BukkitRunnable() {
@Override
public void run() {
int online = Bukkit.getOnlinePlayers().size();
if (online > 0) {
// 有玩家在线 → 重置等待状态
if (waitingForEmpty) {
// 刚有人进来了,清除已汇报标记
// 下次没人时会重新汇报
}
waitingForEmpty = false;
hasReported = false;
cancelWaitTask();
} else if (!waitingForEmpty && !hasReported) {
// 没人了,且还没汇报过 → 开始1分钟倒计时
waitingForEmpty = true;
startWaitCountdown();
}
}
}.runTaskTimer(plugin, 200L, 200L);
}
// ★ 1分钟等待
private void startWaitCountdown() {
cancelWaitTask();
waitTask = new BukkitRunnable() {
@Override
public void run() {
int online = Bukkit.getOnlinePlayers().size();
if (online > 0) {
// 等待期间有人进来 → 取消,等下次没人
waitingForEmpty = false;
hasReported = false;
waitTask = null;
cancel();
return;
}
// 1分钟没人 → 汇报一次
if (!hasReported) {
hasReported = true;
report();
}
waitingForEmpty = false;
waitTask = null;
cancel();
}
};
waitTask.runTaskLater(plugin, 1200L); // 60秒
}
private void cancelWaitTask() {
if (waitTask != null) {
waitTask.cancel();
waitTask = null;
}
}
// ===== 记录 =====
public void recordSale(String itemName, int price) {
sessionSales.merge(itemName, 1, Integer::sum);
sessionRevenue += price;
sessionOrders++;
sessionItems++;
}
public void recordRefund(int amount) {
sessionRefunds++;
sessionRefundAmount += amount;
}
// ===== 汇报 =====
public void report() {
if (sessionItems == 0 && sessionRefunds == 0) {
// plugin.getLogger().info("[Sales] 无数据,跳过");
return;
}
long minutes =
(System.currentTimeMillis() - sessionStart)
/ 60000;
if (minutes < 1) minutes = 1;
StringBuilder sb = new StringBuilder();
sb.append("§6§l========== 销量汇总 ==========");
sb.append("\n§7时长: §f").append(minutes).append("分钟");
sb.append("\n§7订单: §f").append(sessionOrders).append("笔");
sb.append("\n§7商品: §f").append(sessionItems).append("件");
sb.append("\n§7成交额: §6").append(sessionRevenue).append("枚");
sb.append("\n§7退款: §c").append(sessionRefunds)
.append("次(§c").append(sessionRefundAmount)
.append("枚§7)");
sb.append("\n§6§l------------------------------");
if (!sessionSales.isEmpty()) {
sb.append("\n§7销量排行:");
List<Map.Entry<String, Integer>> sorted =
new ArrayList<>(sessionSales.entrySet());
sorted.sort((a, b) ->
b.getValue() - a.getValue());
int rank = 1;
for (Map.Entry<String, Integer> e : sorted) {
if (rank > 10) break;
sb.append("\n§e").append(rank).append(". ")
.append(e.getKey())
.append(" §7x").append(e.getValue());
rank++;
}
}
sb.append("\n§6§l==============================");
String msg = sb.toString();
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.isOp()) {
for (String line : msg.split("\n"))
p.sendMessage(line);
}
}
plugin.getLogger().info("[Sales] 汇报完成");
resetSession();
}
public void resetSession() {
sessionSales.clear();
sessionRevenue = 0;
sessionOrders = 0;
sessionItems = 0;
sessionRefunds = 0;
sessionRefundAmount = 0;
sessionStart = System.currentTimeMillis();
}
}
@@ -1,32 +0,0 @@
package Sdf1_login;
import java.util.ArrayList;
import java.util.List;
public class ShopCategory {
private final String name;
private final String fileName;
private final List<ShopItem> items;
public ShopCategory(String name, String fileName) {
this.name = name;
this.fileName = fileName;
this.items = new ArrayList<>();
}
public String getName() { return name; }
public String getFileName() { return fileName; }
public List<ShopItem> getItems() { return items; }
public ShopItem getItem(String id) {
for (ShopItem item : items) {
if (item.getId().equalsIgnoreCase(id))
return item;
}
return null;
}
public void addItem(ShopItem item) {
items.add(item);
}
}
-50
View File
@@ -1,50 +0,0 @@
package Sdf1_login;
import org.bukkit.Material;
public class ShopItem {
private final String id;
private final String displayName;
private final Material material;
private final int buyPrice;
private final int sellPrice;
private int stock;
private int hourlySales;
private int totalSales;
//打包了 直接测试替换就好了。
public ShopItem(String id, String displayName,
Material material, int buyPrice,
int sellPrice, int stock,
int hourlySales, int totalSales) {
this.id = id;
this.displayName = displayName;
this.material = material;
this.buyPrice = buyPrice;
this.sellPrice = sellPrice;
this.stock = stock;
this.hourlySales = hourlySales;
this.totalSales = totalSales;
}
public int getEffectiveSellPrice() {
if (sellPrice <= 0) return 0;
return sellPrice;
}
public String getId() { return id; }
public String getDisplayName() { return displayName; }
public Material getMaterial() { return material; }
public int getBuyPrice() { return buyPrice; }
public int getSellPrice() { return sellPrice; }
public int getStock() { return stock; }
public int getHourlySales() { return hourlySales; }
public int getTotalSales() { return totalSales; }
public void setStock(int s) { this.stock = s; }
public void setHourlySales(int s) { this.hourlySales = s; }
public void setTotalSales(int s) { this.totalSales = s; }
public void addSales(int n) {
this.hourlySales += n;
this.totalSales += n;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,370 +0,0 @@
package Sdf1_login;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.*;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;
public class UserGroupManager {
private final Main plugin;
private Connection db;
private final Map<String, UserGroupConfig> groupConfigs = new ConcurrentHashMap<>();
public UserGroupManager(Main plugin) {
this.plugin = plugin;
initDB();
loadGroupConfigs();
startExpiryChecker();
}
// ===== DB =====
private void initDB() {
try {
Class.forName("org.sqlite.JDBC");
File dbFile = new File(plugin.getDataFolder(), "usergroup.db");
db = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
Statement st = db.createStatement();
st.execute("PRAGMA journal_mode=WAL");
st.execute("CREATE TABLE IF NOT EXISTS user_group_config ("
+ "group_name TEXT PRIMARY KEY,"
+ "display_name TEXT DEFAULT '',"
+ "display_color TEXT DEFAULT '§f',"
+ "display_emoji TEXT DEFAULT '',"
+ "priority INTEGER DEFAULT 0,"
+ "is_permanent INTEGER DEFAULT 1,"
+ "duration_minutes INTEGER DEFAULT 0,"
+ "pack_url TEXT DEFAULT '',"
+ "pack_hash TEXT DEFAULT '',"
+ "pack_prompt TEXT DEFAULT 'Custom Resource Pack',"
+ "pack_size INTEGER DEFAULT 0)");
st.execute("CREATE TABLE IF NOT EXISTS user_group_member ("
+ "player_name TEXT NOT NULL,"
+ "group_name TEXT NOT NULL,"
+ "added_by TEXT DEFAULT 'system',"
+ "added_time INTEGER DEFAULT 0,"
+ "expiry_time INTEGER DEFAULT 0,"
+ "PRIMARY KEY(player_name, group_name))");
st.close();
} catch (Exception e) {
throw new RuntimeException("[UserGroup] DB init failed: " + e.getMessage(), e);
}
}
// ===== 配置加载 =====
public void loadGroupConfigs() {
groupConfigs.clear();
File dir = new File(plugin.getDataFolder(), "用户组");
if (!dir.exists()) dir.mkdirs();
File[] files = dir.listFiles((d, n) -> n.endsWith(".txt"));
if (files == null) return;
for (File f : files) {
try {
List<String> lines = java.nio.file.Files.readAllLines(
f.toPath(), StandardCharsets.UTF_8);
UserGroupConfig cfg = parseGroupConfig(lines);
if (cfg != null) {
groupConfigs.put(cfg.name, cfg);
saveGroupConfigToDB(cfg);
}
} catch (Exception e) {
plugin.getLogger().warning("[UserGroup] Failed to load: " + f.getName());
}
}
}
private UserGroupConfig parseGroupConfig(List<String> lines) {
UserGroupConfig cfg = new UserGroupConfig();
boolean inBlock = false;
for (String raw : lines) {
String l = raw;
// 块注释
if (l.contains("<!--")) { inBlock = true; continue; }
if (l.contains("-->")) { inBlock = false; continue; }
if (l.contains("/*")) { inBlock = true; continue; }
if (l.contains("*/")) { inBlock = false; continue; }
if (inBlock) continue;
// 行注释
l = l.trim();
if (l.isEmpty()) continue;
if (l.startsWith("#") || l.startsWith("//")) continue;
// 行内注释
int h = l.indexOf('#');
int s = l.indexOf("//");
if (h >= 0) l = l.substring(0, h).trim();
if (s >= 0 && (h < 0 || s < h)) l = l.substring(0, s).trim();
if (l.isEmpty()) continue;
// 解析键值对
String key = l;
String val = "";
int idx = l.indexOf('\uff1a');
if (idx < 0) idx = l.indexOf(':');
if (idx >= 0) {
key = l.substring(0, idx).trim();
val = l.substring(idx + 1).trim();
if (val.startsWith("\"") && val.endsWith("\""))
val = val.substring(1, val.length() - 1);
}
switch (key) {
case "组名":
case "name": cfg.name = val; break;
case "显示名":
case "display": cfg.displayName = val; break;
case "颜色":
case "color": cfg.displayColor = val; break;
case "符号":
case "emoji": cfg.displayEmoji = val; break;
case "优先级":
case "priority":
try { cfg.priority = Integer.parseInt(val); } catch (Exception ignored) {}
break;
case "类型":
case "type":
cfg.permanent = val.equalsIgnoreCase("永久") || val.equalsIgnoreCase("permanent");
break;
case "时长":
case "duration":
try { cfg.durationMinutes = Integer.parseInt(val); } catch (Exception ignored) {}
break;
case "资源包URL":
case "pack_url": cfg.packUrl = val; break;
case "资源包Hash":
case "pack_hash": cfg.packHash = val; break;
case "资源包提示":
case "pack_prompt": cfg.packPrompt = val; break;
case "资源包大小":
case "pack_size":
try { cfg.packSize = Integer.parseInt(val); } catch (Exception ignored) {}
break;
case "本地文件":
case "local_file": cfg.localFile = val; break;
}
}
if (cfg.name == null || cfg.name.isEmpty()) return null;
return cfg;
}
private void saveGroupConfigToDB(UserGroupConfig cfg) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT OR REPLACE INTO user_group_config "
+ "(group_name, display_name, display_color, display_emoji,"
+ " priority, is_permanent, duration_minutes,"
+ " pack_url, pack_hash, pack_prompt, pack_size)"
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?)");
ps.setString(1, cfg.name);
ps.setString(2, cfg.displayName);
ps.setString(3, cfg.displayColor);
ps.setString(4, cfg.displayEmoji);
ps.setInt(5, cfg.priority);
ps.setInt(6, cfg.permanent ? 1 : 0);
ps.setInt(7, cfg.durationMinutes);
ps.setString(8, cfg.packUrl);
ps.setString(9, cfg.packHash);
ps.setString(10, cfg.packPrompt);
ps.setInt(11, cfg.packSize);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// ===== 玩家操作 =====
public boolean addPlayer(String player, String groupName, String addedBy, long durationMinutes) {
UserGroupConfig cfg = groupConfigs.get(groupName);
if (cfg == null) return false;
long now = System.currentTimeMillis();
long expiry = cfg.permanent ? 0 : now + durationMinutes * 60 * 1000;
if (!cfg.permanent && durationMinutes > 0) expiry = now + durationMinutes * 60 * 1000;
else if (!cfg.permanent) expiry = now + cfg.durationMinutes * 60 * 1000;
try {
PreparedStatement ps = db.prepareStatement(
"INSERT OR REPLACE INTO user_group_member "
+ "(player_name, group_name, added_by, added_time, expiry_time)"
+ " VALUES (?,?,?,?,?)");
ps.setString(1, player);
ps.setString(2, groupName);
ps.setString(3, addedBy);
ps.setLong(4, now);
ps.setLong(5, expiry);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
Player p = plugin.getServer().getPlayerExact(player);
if (p != null) sendResourcePack(p);
return true;
}
public boolean removePlayer(String player, String groupName) {
try {
PreparedStatement ps = db.prepareStatement(
"DELETE FROM user_group_member WHERE player_name=? AND group_name=?");
ps.setString(1, player);
ps.setString(2, groupName);
int rows = ps.executeUpdate();
ps.close();
return rows > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public List<Map<String, Object>> getPlayerGroups(String player) {
List<Map<String, Object>> list = new ArrayList<>();
try {
PreparedStatement ps = db.prepareStatement(
"SELECT * FROM user_group_member WHERE player_name=?");
ps.setString(1, player);
ResultSet rs = ps.executeQuery();
long now = System.currentTimeMillis();
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("group_name", rs.getString("group_name"));
row.put("added_by", rs.getString("added_by"));
long expiry = rs.getLong("expiry_time");
row.put("expiry_time", expiry);
row.put("expired", expiry > 0 && expiry <= now);
list.add(row);
}
rs.close(); ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public UserGroupConfig getHighestGroup(String player) {
List<Map<String, Object>> groups = getPlayerGroups(player);
long now = System.currentTimeMillis();
UserGroupConfig best = null;
for (Map<String, Object> g : groups) {
long expiry = (long) g.get("expiry_time");
if (expiry > 0 && expiry <= now) continue;
String name = (String) g.get("group_name");
UserGroupConfig cfg = groupConfigs.get(name);
if (cfg != null && (best == null || cfg.priority > best.priority)) {
best = cfg;
}
}
return best;
}
public boolean isPlayerInGroup(String player, String groupName) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT 1 FROM user_group_member WHERE player_name=? AND group_name=?");
ps.setString(1, player);
ps.setString(2, groupName);
boolean exists = ps.executeQuery().next();
ps.close();
return exists;
} catch (SQLException e) {
return false;
}
}
// ===== 过期自动移除 =====
private void startExpiryChecker() {
new BukkitRunnable() {
@Override
public void run() {
checkExpiry();
}
}.runTaskTimerAsynchronously(plugin, 1200L, 1200L); // 每分钟检查
}
private void checkExpiry() {
try {
long now = System.currentTimeMillis();
PreparedStatement ps = db.prepareStatement(
"SELECT player_name, group_name FROM user_group_member "
+ "WHERE expiry_time > 0 AND expiry_time <= ?");
ps.setLong(1, now);
ResultSet rs = ps.executeQuery();
List<String[]> expired = new ArrayList<>();
while (rs.next()) {
expired.add(new String[]{
rs.getString("player_name"),
rs.getString("group_name")});
}
rs.close(); ps.close();
for (String[] e : expired) {
PreparedStatement del = db.prepareStatement(
"DELETE FROM user_group_member WHERE player_name=? AND group_name=?");
del.setString(1, e[0]);
del.setString(2, e[1]);
del.executeUpdate();
del.close();
plugin.getLogger().info("[UserGroup] 过期移除: " + e[0] + " -> " + e[1]);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// ===== 资源包分发 =====
public void sendResourcePack(Player p) {
UserGroupConfig cfg = getHighestGroup(p.getName());
if (cfg == null || cfg.packUrl == null || cfg.packUrl.isEmpty()) return;
String url = cfg.packUrl;
String prompt = cfg.packPrompt != null
? cfg.packPrompt : "Custom Resource Pack";
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
try {
p.setResourcePack(url, new byte[0], prompt, false);
plugin.getLogger().info("[UserGroup] 资源包已发送: "
+ p.getName() + " -> " + cfg.displayName);
} catch (Exception e) {
plugin.getLogger().warning(
"[UserGroup] 资源包发送失败: " + e.getMessage());
}
}, 40L);
}
public Map<String, UserGroupConfig> getGroupConfigs() {
return groupConfigs;
}
// ===== 配置类 =====
public static class UserGroupConfig {
public String name;
public String displayName = "";
public String displayColor = "§f";
public String displayEmoji = "";
public int priority = 0;
public boolean permanent = true;
public int durationMinutes = 0;
public String packUrl = "";
public String packHash = "";
public String packPrompt = "Custom Resource Pack";
public int packSize = 0;
public String localFile = "";
}
}
+9 -24
View File
@@ -42,9 +42,9 @@ commands:
签到:
description: 每日签到(中文)
usage: /签到
#oa:
# description: 申请删除账号
# usage: /oa [玩家名]
oa:
description: 申请删除账号
usage: /oa [玩家名]
删除账号:
description: 申请删除账号(中文)
usage: /删除账号 [玩家名]
@@ -60,12 +60,18 @@ commands:
deny:
description: 拒绝OA请求
usage: /deny <编号> [理由]
sdf1debug:
description: 调试指令
usage: /sdf1debug <玩家> take.<阶段>
email:
description: 绑定邮箱
usage: /email <邮箱>
绑定:
description: 绑定邮箱(中文)
usage: /绑定 <邮箱>
调试:
description: 调试模式
usage: /调试
绑定邮箱:
description: 绑定邮箱
usage: /绑定邮箱
@@ -81,24 +87,3 @@ commands:
menu:
description: 雪球菜单开关控制
usage: /menu <开/关>
pvp :
description: PVP榜单
usage: /pvp
cypay :
description: 草原探险债券
usage: /cypay
printer :
description: 打印债券流水
usage: /printer
shop :
description: 游戏商人
usage: /shop
商店:
description: 打开商店
usage: /商店
#testorder:
# description: 测试订单中心
# usage: /testorder
protect:
description: 区域保护
usage: /protect
-989
View File
@@ -1,989 +0,0 @@
Index: .gitattributes
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/.gitattributes b/.gitattributes
--- a/.gitattributes (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/.gitattributes (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -1,6 +1,5 @@
+*.md text=auto encoding=UTF-8
*.png binary
*.jpg binary
*.jpeg binary
-*.gif binary
-*.ico binary
-*.bmp binary
\ No newline at end of file
+*.gif binary
\ No newline at end of file
Index: src/main/java/Sdf1_login/ConfigManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/ConfigManager.java b/src/main/java/Sdf1_login/ConfigManager.java
--- a/src/main/java/Sdf1_login/ConfigManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/ConfigManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -312,6 +312,14 @@
requesterPointsReward = parseInt(
m.getOrDefault("报单人积分奖励", "10"),
10);
+ garbageEnabled = Boolean.parseBoolean(
+ m.getOrDefault("垃圾站_启用", "true"));
+ garbageInterval = parseInt(
+ m.getOrDefault("垃圾站_清理间隔秒",
+ "300"), 300);
+ garbageMaxRounds = parseInt(
+ m.getOrDefault("垃圾站_保留轮数",
+ "1"), 1);
}
@@ -331,12 +339,10 @@
L.add("挂机超时秒数=" + afkTimeout);
// 垃圾站设置
L.add("# ---- 垃圾站 ----");
- L.add("垃圾站_启用: "
- + garbageEnabled);
- L.add("垃圾站_清理间隔秒: "
- + garbageInterval);
- L.add("垃圾站_保留轮数: "
- + garbageMaxRounds);
+ L.add("垃圾站_启用=" + garbageEnabled);
+ L.add("垃圾站_清理间隔秒=" + garbageInterval);
+ L.add("垃圾站_保留轮数=" + garbageMaxRounds);
+
writeLines(file, L);
}
@@ -364,6 +370,11 @@
L.add("基础经济奖励=" + baseEconomyReward);
L.add("经济奖励每评分=" + providerEconomyPerScore);
L.add("报单人积分奖励=" + requesterPointsReward);
+ L.add("# ---- 垃圾站 ----");
+ L.add("垃圾站_启用=true");
+ L.add("垃圾站_清理间隔秒=300");
+ L.add("垃圾站_保留轮数=1");
+
writeLines(f, L);
}
Index: src/main/java/Sdf1_login/DatabaseManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java
--- a/src/main/java/Sdf1_login/DatabaseManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/DatabaseManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -201,7 +201,18 @@
+ "active INTEGER DEFAULT 1,"
+ "join_time INTEGER DEFAULT 0"
+ ")");
-
+ // 垃圾回收站物品表
+ st.execute("CREATE TABLE IF NOT EXISTS "
+ + "items ("
+ + "id INTEGER PRIMARY KEY "
+ + "AUTOINCREMENT,"
+ + "player_name TEXT NOT NULL,"
+ + "item_data TEXT DEFAULT '',"
+ + "amount INTEGER DEFAULT 1,"
+ + "create_time INTEGER DEFAULT 0"
+ + ")");
+ safeAdd(st, "menu_snowball",
+ "INTEGER DEFAULT 1");
st.close();
logger.info("[Sdf1_login] 数据库初始化完成");
} catch (Exception e) {
Index: src/main/java/Sdf1_login/GUIManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java
--- a/src/main/java/Sdf1_login/GUIManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GUIManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -489,6 +489,12 @@
"§e设置密码"));
g.setItem(16, mkItem(Material.BARRIER,
"§c删除用户", "§7二次验证删除"));
+ // 雪球菜单检测(slot 14)
+ g.setItem(14, mkItem(
+ Material.SNOWBALL,
+ "§e§l雪球菜单",
+ "§7点击为该玩家发放菜单雪球"));
+
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
Index: src/main/java/Sdf1_login/GarbageManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GarbageManager.java b/src/main/java/Sdf1_login/GarbageManager.java
--- a/src/main/java/Sdf1_login/GarbageManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GarbageManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -231,26 +231,26 @@
public void saveItem(ItemStack stack) {
{
- try {
- String data =
- stackToBase64(stack);
- if (data.isEmpty()) return;
- PreparedStatement ps = db.prepareStatement(
- "INSERT INTO items "
- + "(item_data, amount, "
- + "round, create_time) "
- + "VALUES (?,?,?,?)");
- ps.setString(1, data);
- ps.setInt(2, stack.getAmount());
- ps.setInt(3, currentRound);
- ps.setLong(4,
- System.currentTimeMillis());
- ps.executeUpdate();
- ps.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }}
+ try {
+ String data =
+ stackToBase64(stack);
+ if (data.isEmpty()) return;
+ PreparedStatement ps = db.prepareStatement(
+ "INSERT INTO items "
+ + "(item_data, amount, "
+ + "round, create_time) "
+ + "VALUES (?,?,?,?)");
+ ps.setString(1, data);
+ ps.setInt(2, stack.getAmount());
+ ps.setInt(3, currentRound);
+ ps.setLong(4,
+ System.currentTimeMillis());
+ ps.executeUpdate();
+ ps.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }}
private int deleteOldItems(int beforeRound) {
try {
@@ -381,6 +381,38 @@
.get("amount")).intValue();
ItemStack stack = base64ToStack(data);
if (stack != null) {
+ try {
+ byte[] bytes =
+ stack.serializeAsBytes();
+ String b64 = java.util.Base64
+ .getEncoder()
+ .encodeToString(bytes);
+ int len = b64.length();
+ String f = len > 15
+ ? b64.substring(10, 15) : "短";
+ String m = len >= 5
+ ? b64.substring(len / 2,
+ Math.min(len / 2 + 5, len))
+ : "短";
+ String b = len > 10
+ ? b64.substring(len - 10, len - 5)
+ : "短";
+ plugin.getLogger().info(
+ "[垃圾站加载] ID#" + id
+ + " " + stack.getType()
+ + " x" + amount
+ + " base64[" + len + "]"
+ + " 前=" + f
+ + " 中=" + m
+ + " 后=" + b);
+ } catch (Exception ex) {
+ plugin.getLogger().warning(
+ "[垃圾站加载] 序列化异常: "
+ + ex.getMessage());
+ }
+ }
+
+ if (stack != null) {
stack.setAmount(amount);
ItemMeta im =
stack.getItemMeta();
@@ -431,6 +463,10 @@
g.setItem(51, mk(Material.ARROW,
"§7已是最后一页"));
}
+ // 52格:返回按钮
+ g.setItem(52, mk(Material.ARROW,
+ "§7返回",
+ "§7返回主菜单"));
p.openInventory(g);
}
@@ -459,6 +495,12 @@
openRecyclePage(p, page + 1);
return true;
}
+ // 52:返回
+ if (slot == 52) {
+ plugin.getGui().openMain(p);
+ return true;
+ }
+
// 50/52/53:不处理
if (slot >= 49 && slot <= 53)
return true;
Index: src/main/java/Sdf1_login/GiftManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GiftManager.java b/src/main/java/Sdf1_login/GiftManager.java
--- a/src/main/java/Sdf1_login/GiftManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GiftManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -155,55 +155,18 @@
}
public boolean canClaim(Player p, int stage) {
- Map<String, Object> user = plugin.getDb()
- .getUser(p.getName());
- if (user.isEmpty()) return false;
- int curStage = ((Number) user.getOrDefault(
- "gift_stage", 0)).intValue();
- if (stage > curStage + 1) return false;
- if (stage < 1 || stage > stages.size())
- return false;
- String claimed = (String) user.getOrDefault(
- "gift_claimed", "");
- if (claimed.contains("[" + stage + "]"))
- return false;
-
- // debug 绕过:检查 tasks_completed
- String completed = (String) user.getOrDefault(
- "tasks_completed", "");
- if (completed.contains("[" + stage + "]"))
- return true;
-
- // 正常条件检查
- long dbTime = ((Number) user.getOrDefault(
- "total_online_time", 0L)).longValue();
- long sessionTime = 0;
- Object lastObj = plugin.getDb().getField(
- p.getName(), "last_online_check");
- if (lastObj != null && plugin.getLoggedIn()
- .contains(p.getName())) {
- long last = ((Number) lastObj).longValue();
- if (last > 0) {
- sessionTime =
- System.currentTimeMillis() - last;
- }
- }
- long onlineTime = dbTime + sessionTime;
- int broken = ((Number) user.getOrDefault(
- "blocks_broken", 0)).intValue();
- int placed = ((Number) user.getOrDefault(
- "blocks_placed", 0)).intValue();
+ String name = p.getName();
+ int curStage = ((Number) plugin.getDb()
+ .getField(name, "gift_stage"))
+ .intValue();
+ String claimed = (String) plugin.getDb()
+ .getField(name, "gift_claimed");
+ if (claimed == null) claimed = "";
+ return curStage >= stage
+ && !claimed.contains(
+ String.valueOf(stage));
+ }
- switch (stage) {
- case 1: return true;
- case 2: return onlineTime >= 3600000;
- case 3: return broken >= 100
- && onlineTime >= 86400000L;
- case 4: return (broken + placed) >= 300
- && onlineTime >= 604800000L;
- default: return false;
- }
- }
public void claimReward(Player p, int stage) {
if (!canClaim(p, stage)) return;
Index: src/main/java/Sdf1_login/Main.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java
--- a/src/main/java/Sdf1_login/Main.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/Main.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -196,6 +196,8 @@
getCommand("sdf1debug").setExecutor(this);
if (getCommand("oa") != null)
getCommand("oa").setExecutor(this);
+ if (getCommand("menu") != null)
+ getCommand("menu").setExecutor(this);
getServer().getPluginManager()
@@ -1099,6 +1101,9 @@
return;
}
}
+ if (isFrozen(p)) {
+ p.setAllowFlight(true);
+ }
if (!"manual".equals(config.approvalMode)
&& !db.userExists(name)) {
@@ -1238,36 +1243,82 @@
private static final String MENU_SNOWBALL_TAG =
"sdf1_menu";
+ // 雪球菜单
public void giveMenuSnowball(Player p) {
- // 检查背包里是否已有菜单雪球
+ // 检查开关
+ Object val = db.getField(
+ p.getName(), "menu_snowball");
+ if (val != null) {
+ int on = val instanceof Number
+ ? ((Number) val).intValue()
+ : 1;
+ if (on == 0) {
+ return; // 开关关了,不发放
+ }
+ }
+ // 检查是否已有
for (ItemStack it : p.getInventory()
.getContents()) {
if (isMenuSnowball(it)) {
- return; // 已有,不重复发
+ return;
}
}
- // 没有则发放
+ // 发放
ItemStack snow = new ItemStack(
Material.SNOWBALL);
ItemMeta im = snow.getItemMeta();
if (im != null) {
im.setDisplayName(
"§e§l[菜单] §f右键打开主菜单");
- List<String> lore = new ArrayList<>();
+ List<String> lore =
+ new ArrayList<>();
lore.add("§7右键点击打开功能菜单");
lore.add("§8" + MENU_SNOWBALL_TAG);
im.setLore(lore);
snow.setItemMeta(im);
}
- // 放到最后一个空格,避免覆盖
- int emptySlot =
- p.getInventory()
- .firstEmpty();
- if (emptySlot >= 0) {
+ int slot = p.getInventory()
+ .firstEmpty();
+ if (slot >= 0) {
+ p.getInventory().setItem(
+ slot, snow);
+ } else {
+ p.getWorld().dropItemNaturally(
+ p.getLocation(), snow);
+ }
+ }
+
+ /**
+ * 手动发放雪球菜单(绕过开关检查)
+ */
+ public void forceGiveMenuSnowball(
+ Player p) {
+ // 只检查是否已有
+ for (ItemStack it : p.getInventory()
+ .getContents()) {
+ if (isMenuSnowball(it)) {
+ return;
+ }
+ }
+ ItemStack snow = new ItemStack(
+ Material.SNOWBALL);
+ ItemMeta im = snow.getItemMeta();
+ if (im != null) {
+ im.setDisplayName(
+ "§e§l[菜单] §f右键打开主菜单");
+ List<String> lore =
+ new ArrayList<>();
+ lore.add("§7右键点击打开功能菜单");
+ lore.add("§8" + MENU_SNOWBALL_TAG);
+ im.setLore(lore);
+ snow.setItemMeta(im);
+ }
+ int slot = p.getInventory()
+ .firstEmpty();
+ if (slot >= 0) {
p.getInventory().setItem(
- emptySlot, snow);
+ slot, snow);
} else {
- // 背包满,丢在地上
p.getWorld().dropItemNaturally(
p.getLocation(), snow);
}
@@ -1296,21 +1347,24 @@
@EventHandler
public void onMove(PlayerMoveEvent e) {
Player p = e.getPlayer();
+ if (!isFrozen(p)) return;
+
Location from = e.getFrom();
Location to = e.getTo();
- if (isFrozen(p)) {
- if (to != null && (from.getX() != to.getX()
- || from.getY() != to.getY()
- || from.getZ() != to.getZ())) {
- e.setTo(e.getFrom());
- }
+ if (to == null) return;
+
+ // 允许转头(无位移)
+ if (from.getBlockX() == to.getBlockX()
+ && from.getBlockY() == to.getBlockY()
+ && from.getBlockZ() == to.getBlockZ()) {
return;
}
- if (to != null && (from.getX() != to.getX()
- || from.getY() != to.getY()
- || from.getZ() != to.getZ())) {
- afk.recordAction(p.getUniqueId());
- }
+
+ // 有位移 → 取消
+ e.setCancelled(true);
+
+ // 防止"服务器未启用飞行"踢出
+ p.setAllowFlight(true);
}
@EventHandler
@@ -1484,7 +1538,8 @@
- // ===== Inventory Click =====
+ // ===== Inventory Click =====
+ // ===== Inventory Click =====
@EventHandler
public void onInvClick(InventoryClickEvent e) {
if (!(e.getWhoClicked() instanceof Player))
@@ -1494,12 +1549,41 @@
e.setCancelled(true);
return;
}
- String title =
- e.getView().getTitle();
+ String title = e.getView().getTitle();
int slot = e.getRawSlot();
- if (needsPasswordChange.contains(p.getName())
- && !title.equals("§6§l任务面板")) {
+ // Shift+点击背包物品 → 存入垃圾箱
+ if (e.isShiftClick()
+ && e.getClickedInventory() != null
+ && e.getClickedInventory()
+ == p.getInventory()
+ && e.getCurrentItem() != null
+ && e.getCurrentItem().getType()
+ != Material.AIR
+ && !isMenuSnowball(
+ e.getCurrentItem())
+ && title.equals(
+ "§6§l垃圾回收站")) {
+ e.setCancelled(true);
+ ItemStack clicked =
+ e.getCurrentItem().clone();
+ garbage.saveItem(clicked);
+ e.getInventory().setItem(
+ e.getSlot(), null);
+ Bukkit.getScheduler()
+ .runTaskLater(this,
+ () -> {
+ garbage.openRecyclePage(p,
+ garbage.getRecyclePage(
+ p.getName()));
+ }, 1L);
+ return;
+ }
+
+ if (needsPasswordChange
+ .contains(p.getName())
+ && !title.equals(
+ "§6§l任务面板")) {
e.setCancelled(true);
p.sendMessage("§c请先修改密码!");
return;
@@ -1516,12 +1600,15 @@
TicketManager.T_TICKET_ADMIN)
|| title.equals(
TicketManager.T_TICKET_DETAIL)
- || title.equals("§d§l服务商面板")
- || title.equals("§6§l抢单大厅")
+ || title.equals(
+ "§d§l服务商面板")
+ || title.equals(
+ "§6§l抢单大厅")
|| title.equals(
"§b§l我处理中的工单")) {
e.setCancelled(true);
- ticket.handleClick(p, title, slot);
+ ticket.handleClick(
+ p, title, slot);
return;
}
@@ -1539,96 +1626,67 @@
ItemStack inSlot =
e.getInventory().getItem(slot);
- ItemStack onCursor = e.getCursor();
+ ItemStack cursor = e.getCursor();
- // 1. 格子里是垃圾站物品 → 取出
- if (isTrashDisplayItem(inSlot)) {
- e.setCancelled(true);
- try {
- List<String> lore =
- inSlot.getItemMeta()
- .getLore();
- String numStr =
- lore.get(0)
- .replace(
- "§7ID: #", "")
- .trim();
- int dbId = Integer
- .parseInt(numStr);
- // 先从DB删除
- if (garbage.removeItem(
- dbId)) {
- // 再给玩家(去掉ID lore)
- ItemStack give =
- inSlot.clone();
- ItemMeta gm =
- give.getItemMeta();
- if (gm != null) {
- List<String> newLore =
- new ArrayList<>();
- // 保留原始lore(跳过ID和数量行)
- if (lore.size() > 2) {
- for (int li = 2;
- li < lore
- .size();
- li++) {
- newLore.add(
- lore.get(
- li));
- }
- }
- gm.setLore(
- newLore.isEmpty()
- ? null
- : newLore);
+ // ===== 取出 =====
+ if (inSlot != null
+ && inSlot.hasItemMeta()
+ && inSlot.getItemMeta().hasLore()) {
+ List<String> lore =
+ inSlot.getItemMeta().getLore();
+ if (lore != null && !lore.isEmpty()
+ && lore.get(0)
+ .startsWith("§7ID: #")) {
+ e.setCancelled(true);
+ try {
+ String numStr = lore.get(0)
+ .replace("§7ID: #", "")
+ .trim();
+ int dbId =
+ Integer.parseInt(numStr);
+ if (garbage.removeItem(dbId)) {
+ ItemStack give =
+ inSlot.clone();
+ ItemMeta gm =
+ give.getItemMeta();
+ gm.setLore(null);
give.setItemMeta(gm);
- }
- p.getInventory()
- .addItem(give);
- // 清空格子
- e.getInventory()
- .setItem(slot,
- null);
- }
- } catch (Exception ex) {
- p.sendMessage("§c取出失败");
- }
- return;
+ p.getInventory()
+ .addItem(give);
+ e.getInventory()
+ .setItem(slot, null);
+ }
+ } catch (Exception ex) {
+ p.sendMessage("§c取出失败");
+ }
+ return;
+ }
}
- // 2. 格子空 + 手上有物品 → 放入
- if ((inSlot == null
- || inSlot.getType()
- == Material.AIR)
- && onCursor != null
- && onCursor.getType()
+ // ===== 光标存入 =====
+ if (cursor != null
+ && cursor.getType()
!= Material.AIR
- && !isMenuSnowball(
- onCursor)) {
- // 不取消!让物品自然进入格子
- Bukkit.getScheduler()
- .runTaskLater(this,
- () -> {
- ItemStack after =
- e.getInventory()
- .getItem(
- slot);
- if (after != null
- && after.getType()
- != Material.AIR) {
- garbage.saveItem(
- after
- .clone());
- e.getInventory()
- .setItem(
- slot,
- null);
- }
- }, 1L);
+ && !isMenuSnowball(cursor)) {
+ ItemStack toSave = cursor.clone();
+ getLogger().info(
+ "[垃圾站] 存入: "
+ + toSave.getType()
+ + " x" + toSave.getAmount());
+ garbage.saveItem(toSave);
+ e.setCancelled(true);
+ Bukkit.getScheduler().runTaskLater(
+ this, () -> {
+ p.getOpenInventory()
+ .setCursor(null);
+ garbage.openRecyclePage(p,
+ garbage.getRecyclePage(
+ p.getName()));
+ }, 1L);
return;
}
- // 3. 其他情况
+ // 默认取消
e.setCancelled(true);
return;
}
@@ -1806,6 +1864,7 @@
p.closeInventory();
p.sendMessage("§e请输入邀请码:");
}
+
return;
}
@@ -1828,6 +1887,7 @@
.replace("§e", "")
.replace("§a", ""));
}
+
return;
}
@@ -1856,7 +1916,18 @@
p.closeInventory();
p.sendMessage("§e输入" + tgt
+ "的临时密码:");
- } else if (slot == 16) {
+ } else if (slot == 14) {
+ Player tp = Bukkit.getPlayer(tgt);
+ if (tp != null && tp.isOnline()) {
+ forceGiveMenuSnowball(tp);
+ p.sendMessage("§a已发放雪球菜单给 "
+ + tgt);
+ } else {
+ p.sendMessage("§c玩家不在线");
+ }
+
+
+ } else if (slot == 16) {
chatInput.getState(p).type =
ChatInputManager.InputType
.ADMIN_DELETE_CONFIRM;
@@ -1866,8 +1937,11 @@
p.sendMessage(config.msg(
"admin_delete_confirm"));
}
+
return;
+
}
+
// 积分商城
if (title.startsWith("§d§l积分商城")) {
@@ -1958,43 +2032,45 @@
// 回收站拖拽
if (t.equals("§6§l垃圾回收站")) {
- for (int rawSlot : e.getRawSlots()) {
- if (rawSlot >= 49
- && rawSlot <= 53) {
+ for (int rawSlot :
+ e.getRawSlots()) {
+ if (rawSlot >= 49) {
e.setCancelled(true);
return;
}
}
- // 0-48:不取消,延迟存DB
+ // 0-48允许拖入,不取消
+ // 延迟2tick后扫描格子
+ final Player fp = p;
Bukkit.getScheduler()
- .runTaskLater(this, () -> {
- for (int rawSlot :
- e.getRawSlots()) {
- if (rawSlot < 0
- || rawSlot >= 49)
- continue;
- ItemStack item =
- e.getInventory()
- .getItem(
- rawSlot);
- if (item != null
- && item.getType()
- != Material.AIR
- && !isMenuSnowball(
- item)) {
- garbage.saveItem(
- item.clone());
- e.getInventory()
- .setItem(
- rawSlot,
- null);
- }
- }
- }, 1L);
+ .runTaskLater(this,
+ () -> {
+ Inventory top = fp
+ .getOpenInventory()
+ .getTopInventory();
+ for (int rawSlot :
+ e.getRawSlots()) {
+ if (rawSlot < 0
+ || rawSlot >= 49)
+ continue;
+ ItemStack item =
+ top.getItem(rawSlot);
+ if (item != null
+ && item.getType()
+ != Material.AIR
+ && !isMenuSnowball(
+ item)) {
+ garbage.saveItem(
+ item.clone());
+ top.setItem(
+ rawSlot, null);
+ }
+ }
+ }, 2L);
return;
}
-
+ //服务商工单系统
if (t.equals(TicketManager.T_TICKET_MAIN)
|| t.equals(
TicketManager.T_TICKET_CREATE)
@@ -2050,6 +2126,38 @@
+ count + " 个掉落物");
return true;
}
+ // /menu 开关雪球菜单
+ if (cmdName.equals("menu")) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(
+ "§c仅玩家可用");
+ return true;
+ }
+ Player p2 = (Player) sender;
+ if (args.length == 0) {
+ p2.sendMessage(
+ "§e用法: /menu <开/关>");
+ return true;
+ }
+ String arg = args[0];
+ if (arg.equals("开")) {
+ db.setField(p2.getName(),
+ "menu_snowball", 1);
+ p2.sendMessage(
+ "§a雪球菜单已开启,"
+ + "下次进入将发放");
+ } else if (arg.equals("关")) {
+ db.setField(p2.getName(),
+ "menu_snowball", 0);
+ p2.sendMessage(
+ "§c雪球菜单已关闭,"
+ + "下次进入不再发放");
+ } else {
+ p2.sendMessage(
+ "§e用法: /menu <开/关>");
+ }
+ return true;
+ }
if (cmdName.equals("reg")
|| cmdName.equals("注册")) {
@@ -2461,9 +2569,9 @@
sender.sendMessage(
"§e§l================================");
}
- }
+ }
- // pw / password
+ // pw / password
if (sub.equals("pw")
|| sub.equals("password")) {
if (!(sender instanceof Player)) {
@@ -2790,6 +2898,29 @@
}
return true;
}
+ // give - 手动发放雪球菜单
+ if (sub.equals("give")) {
+ if (args.length < 2) {
+ sender.sendMessage(
+ "§e用法: /sdf1_login give <玩家名>");
+ return true;
+ }
+ Player tp = Bukkit.getPlayer(
+ args[1]);
+ if (tp == null || !tp.isOnline()) {
+ sender.sendMessage(
+ "§c玩家不在线: "
+ + args[1]);
+ return true;
+ }
+ forceGiveMenuSnowball(tp);
+ tp.sendMessage(
+ "§a管理员已为您发放菜单雪球");
+ sender.sendMessage(
+ "§a已发放雪球菜单给 "
+ + args[1]);
+ return true;
+ }
// shopadd
if (sub.equals("shopadd")) {
@@ -3052,4 +3183,4 @@
return list;
}
-}
+}
\ No newline at end of file
Index: src/main/java/Sdf1_login/TicketManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/TicketManager.java b/src/main/java/Sdf1_login/TicketManager.java
--- a/src/main/java/Sdf1_login/TicketManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/TicketManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -1014,39 +1014,27 @@
openDetail(p, id);
return true;
}
- // 管理员:标记完结(自己在处理)
- if (adm(p) && "processing".equals(st)
+ // 管理员:标记完结(已回复状态)
+ if (adm(p) && "replied".equals(st)
&& asg.equals("admin")) {
- Map<String, Object> tk =
- plugin.getDb().getTicket(id);
- String tkType = sv(tk, "type");
- if (TYPE_DELETE.equals(tkType)) {
- approveDeleteTicket(p, id);
- return true;
- }
- plugin.getDb().completeTicket(id);
- p.sendMessage("§a工单 #" + id
- + " 已标记完结");
- openDetail(p, id);
- return true;
- }
- // 管理员:标记完结(已回复状态)
- if (adm(p) && "replied".equals(st)
- && asg.equals("admin")) {
- Map<String, Object> tk =
- plugin.getDb().getTicket(id);
- String tkType = sv(tk, "type");
- if (TYPE_DELETE.equals(tkType)) {
- approveDeleteTicket(p, id);
- return true;
- }
- plugin.getDb().completeTicket(id);
- p.sendMessage("§a工单 #" + id
- + " 已标记完结");
- openDetail(p, id);
- return true;
+
+ // 管理员:标记完结(已回复状态)
+ if (adm(p) && "replied".equals(st)
+ && asg.equals("admin")) {
+ Map<String, Object> tk =
+ plugin.getDb().getTicket(id);
+ String tkType = sv(tk, "type");
+ if (TYPE_DELETE.equals(tkType)) {
+ approveDeleteTicket(p, id);
+ return true;
+ }
+ plugin.getDb().completeTicket(id);
+ p.sendMessage("§a工单 #" + id
+ + " 已标记完结");
+ openDetail(p, id);
+ return true;
+ }
}
-
// 服务商:抢单
if (plugin.getDb()
.isServiceProvider(p.getName())
Index: src/main/resources/plugin.yml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
--- a/src/main/resources/plugin.yml (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/resources/plugin.yml (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -84,3 +84,6 @@
垃圾清理:
description: 手动清理掉落物
usage: /垃圾清理
+ menu:
+ description: 雪球菜单开关控制
+ usage: /menu <开/关>
\ No newline at end of file