Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9da8c8ec2 | ||
|
|
cf246d4076 | ||
|
|
c747dbcc76 | ||
|
|
66f1e33d5e | ||
|
|
717a7a9266 | ||
|
|
34d46a7d12 | ||
|
|
1f12d3ec30 | ||
|
|
1a58caec96 | ||
|
|
ea93ccc087 | ||
|
|
302328bc8e |
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug 反馈
|
||||||
|
about: 报告一个 Bug 以帮助改进插件
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 描述
|
||||||
|
请简明扼要地描述 Bug 是什么。
|
||||||
|
|
||||||
|
## 复现步骤
|
||||||
|
1. 进入 ...
|
||||||
|
2. 点击 ...
|
||||||
|
3. 出现错误 ...
|
||||||
|
|
||||||
|
## 期望行为
|
||||||
|
描述你期望发生的事情。
|
||||||
|
|
||||||
|
## 实际行为
|
||||||
|
描述实际发生了什么。
|
||||||
|
|
||||||
|
## 服务端信息
|
||||||
|
- 服务器类型和版本(如 Paper 1.21.4):
|
||||||
|
- 插件版本:
|
||||||
|
|
||||||
|
## 截图 / 日志
|
||||||
|
如有相关截图或报错日志,请粘贴在此处。
|
||||||
|
|
||||||
|
## 其他信息
|
||||||
|
补充任何你认为有助于解决问题的信息。
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: 问题排查指南
|
||||||
|
url: https://github.com/ypsdf1/sdf1_login
|
||||||
|
about: 在提交 Bug 前,请先查阅文档确认是否已有相关说明。
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: 功能新增
|
||||||
|
about: 提出一个新功能的需求建议
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能描述
|
||||||
|
请描述你希望添加的新功能。
|
||||||
|
|
||||||
|
## 动机
|
||||||
|
为什么需要这个功能?它能解决什么问题?
|
||||||
|
|
||||||
|
## 建议的实现方式
|
||||||
|
如果对实现方式有想法,请在此描述。
|
||||||
|
|
||||||
|
## 额外信息
|
||||||
|
补充任何你认为有助于理解需求的上下文。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: 建议提交
|
||||||
|
about: 对插件提出改进建议或反馈
|
||||||
|
title: "[Suggestion] "
|
||||||
|
labels: suggestion
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 建议内容
|
||||||
|
请描述你的建议或想法。
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
什么场景下产生的这个建议?
|
||||||
|
|
||||||
|
## 预期效果
|
||||||
|
你认为采用这个建议后能达到什么效果?
|
||||||
+2
-1
@@ -42,4 +42,5 @@ build/
|
|||||||
txt/
|
txt/
|
||||||
*.xlsx
|
*.xlsx
|
||||||
*.patch
|
*.patch
|
||||||
新建*.txt
|
新建*.txt
|
||||||
|
群戏归类.txt
|
||||||
@@ -1,112 +1,407 @@
|
|||||||
# Sdf1系列登录插件使用说明
|
# Sdf1系列登录插件使用说明
|
||||||
|
|
||||||
|
# 特别说明:本插件仅有简中语言版本,如需除简中以外的任何版本,请下载插件源码,自行修改!
|
||||||
|
|
||||||
## 1. 功能介绍
|
## 1. 功能介绍
|
||||||
|
|
||||||
| 名字 | 描述/作用 | 施工状态 |
|
| 名字 | 描述/作用 | 施工状态
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :---
|
||||||
| 登录 | 用户登录/注册 | 已完成 |
|
| 登录 | 用户登录/注册 | 已完成
|
||||||
| 世界垃圾箱 | 清理服务器凋落物 | 半成品 |
|
| 世界垃圾箱 | 清理服务器凋落物 | 已完成
|
||||||
| 工单系统 | 用户提交工单 | 已完成 |
|
| 工单系统 | 用户提交工单 | 已完成
|
||||||
| 签到系统 | 签到积分换奖励 | 已完成 |
|
| 签到系统 | 签到积分换奖励 | 已完成
|
||||||
|
| 新人礼包 | 新玩家7日礼包 | 已完成
|
||||||
|
| 挂机检测 | 挂机检测踢出 | 已完成
|
||||||
|
| 自定义菜单 | 可以自己diy指令 | 已完成
|
||||||
|
| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 已完工
|
||||||
|
|
||||||
## 2. 安装说明
|
## 2.安装说明:
|
||||||
|
|
||||||
| 前置插件 | 选装类型 |
|
| 前置插件 | 选装类型 |
|
||||||
| :--- | :--- |
|
| :--- | :---
|
||||||
| CY_beibao | 可选 |
|
| [CY_beibao](https://pan.ypshidifu.cn/s/Ygc0) | 可选
|
||||||
| EssentialsX | 可选 |
|
| EssentialsX | 可选
|
||||||
|
|
||||||
### 2.1 下载
|
### 2.1 下载
|
||||||
您可以从GitHub和Gitee下载插件,将其放入服务器plugin目录,重启服务器即可。
|
您可以从GitHub和Gitee下载到插件,将其丢入服务器plugin目录,重启服务器即可。
|
||||||
|
|
||||||
### 2.2 配置文件
|
### 2.2.2 配置文件
|
||||||
|
|
||||||
#### 2.2.1 消息.txt
|
#### 2.2.2.1 消息.txt
|
||||||
此文件控制整个插件的所有提示消息,修改后执行 /sdf1_login reload 重载插件。
|
此文件控制着整个插件的所有提示消息,您可以根据自己的需求修改,修改后需要执行 /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>`进行段落换行
|
||||||
|
|
||||||
#### 2.2.2 挂机白名单.txt
|
|
||||||
挂机踢出开启时,该配置下的玩家不受影响。
|
|
||||||
|
|
||||||
#### 2.2.3 SMTP设置.txt
|
```txt
|
||||||
控制SMTP邮件服务器配置,用于找回密码时发送邮件。
|
# ==========================================
|
||||||
|
# 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.4 插件设置.txt
|
# ---- 登录注册相关 ----
|
||||||
管控整个插件,包括账号注册限制、管理员密码等。
|
# 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.5 chat.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临时密码已发送到您的邮箱,请查收
|
||||||
|
|
||||||
## 3. 指令
|
# ---- 签到积分相关 ----
|
||||||
|
# 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} 积分
|
||||||
|
|
||||||
- Sdf1_login 主指令
|
# ---- 挂机检测相关 ----
|
||||||
- Sdf1_login reload 重载配置
|
# afk_set_enabled: 挂机检测开启
|
||||||
- reg 注册账号
|
afk_set_enabled=§a挂机检测已开启
|
||||||
- login 登录账号
|
# afk_set_disabled: 挂机检测关闭
|
||||||
- l 登录账号
|
afk_set_disabled=§c挂机检测已关闭
|
||||||
|
# afk_kick: 因挂机被踢出
|
||||||
|
afk_kick=§c因挂机过久被踢出
|
||||||
|
|
||||||
## 4. 运行截图
|
# ---- 邮箱相关 ----
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 重载广播并重新打包资源包
|
||||||
|
- menu 开/关 是否开启自定义菜单
|
||||||
|
|
||||||
|
## 4. 自定义菜单物品
|
||||||
|
- 打开主界面,选择"我的"进入<br>
|
||||||
|

|
||||||
|
- 将你的自定义物品与雪球交换<br>
|
||||||
|

|
||||||
|
- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了
|
||||||
|
|
||||||
|
## 运行截图
|
||||||
|
|
||||||
主界面
|
主界面
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
邀请数据
|
邀请数据
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
积分商店
|
积分商店
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
任务中心
|
任务中心
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
垃圾箱
|
垃圾箱
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
管理员面板
|
管理员面板
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
管理面板-用户管理
|
管理面板-用户管理
|
||||||
|

|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
工单中心
|
工单中心
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
工单管理
|
工单管理
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
我的工单
|
我的工单
|
||||||
|

|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
提交工单
|
提交工单
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
服务商列表
|
服务商列表
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
服务商抢单大厅
|
服务商抢单大厅
|
||||||
|

|
||||||

|
|
||||||
|
|
||||||
服务商-接单界面
|
服务商-接单界面
|
||||||
|

|
||||||
|
|
||||||

|
管理员-工单详情页面
|
||||||
|

|
||||||
|
|
||||||
管理员-工单详情
|
## 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)
|
||||||
|
|
||||||
## 5. 下载地址
|
|
||||||
|
|
||||||
- GitHub: https://github.com/ypsdf1/sdf1_login
|
|
||||||
- Gitee: https://gitee.com/nihaoshidifu/sdf1_login
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
@@ -37,6 +37,16 @@ public class ChatFilterManager {
|
|||||||
private boolean notifyAdmin = true;
|
private boolean notifyAdmin = true;
|
||||||
private boolean notifyAll = false;
|
private boolean notifyAll = false;
|
||||||
private int muteDuration = 300;
|
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 =
|
private static final Pattern URL_PATTERN =
|
||||||
Pattern.compile(
|
Pattern.compile(
|
||||||
@@ -106,6 +116,7 @@ public class ChatFilterManager {
|
|||||||
if (line.startsWith("\uFEFF"))
|
if (line.startsWith("\uFEFF"))
|
||||||
line = line.substring(1);
|
line = line.substring(1);
|
||||||
String t = line.trim();
|
String t = line.trim();
|
||||||
|
|
||||||
if (t.isEmpty()
|
if (t.isEmpty()
|
||||||
|| t.startsWith("#"))
|
|| t.startsWith("#"))
|
||||||
continue;
|
continue;
|
||||||
@@ -128,6 +139,12 @@ public class ChatFilterManager {
|
|||||||
String[] kv = t.split(":", 2);
|
String[] kv = t.split(":", 2);
|
||||||
String k = kv[0].trim();
|
String k = kv[0].trim();
|
||||||
String v = kv[1].trim();
|
String v = kv[1].trim();
|
||||||
|
if (equals("启用过滤")
|
||||||
|
|| equals("enabled")) {
|
||||||
|
enabled = parseBool(v);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (k.equals("通知管理员")) {
|
if (k.equals("通知管理员")) {
|
||||||
notifyAdmin = parseBool(v);
|
notifyAdmin = parseBool(v);
|
||||||
continue;
|
continue;
|
||||||
@@ -238,6 +255,7 @@ public class ChatFilterManager {
|
|||||||
new FileOutputStream(f),
|
new FileOutputStream(f),
|
||||||
StandardCharsets.UTF_8))) {
|
StandardCharsets.UTF_8))) {
|
||||||
pw.println("# Sdf1_chat 聊天过滤配置");
|
pw.println("# Sdf1_chat 聊天过滤配置");
|
||||||
|
pw.println("启用过滤: " + enabled);
|
||||||
pw.println("通知管理员: true");
|
pw.println("通知管理员: true");
|
||||||
pw.println("全服通报: false");
|
pw.println("全服通报: false");
|
||||||
pw.println("禁言时长: 300");
|
pw.println("禁言时长: 300");
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ public class ChatInputManager {
|
|||||||
TICKET_ADMIN_TYPE,
|
TICKET_ADMIN_TYPE,
|
||||||
TICKET_ADMIN_TITLE,
|
TICKET_ADMIN_TITLE,
|
||||||
TICKET_ADMIN_DESC,
|
TICKET_ADMIN_DESC,
|
||||||
TICKET_ADMIN_PROVIDER
|
TICKET_ADMIN_PROVIDER,
|
||||||
|
BACK_CHECK_ECONOMY_CONFIRM,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PlayerState {
|
public static class PlayerState {
|
||||||
@@ -63,6 +64,11 @@ public class ChatInputManager {
|
|||||||
public void reset(Player p) {
|
public void reset(Player p) {
|
||||||
states.remove(p.getName());
|
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,
|
public void handleInput(Player p, String msg,
|
||||||
Main mainPlugin) {
|
Main mainPlugin) {
|
||||||
@@ -542,24 +548,33 @@ public class ChatInputManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case BACK_CHECKIN: {
|
case BACK_CHECKIN: {
|
||||||
int cost = 20;
|
String result =
|
||||||
if (!mainPlugin.getDb()
|
mainPlugin.getCheckIn()
|
||||||
.deductPoints(
|
.backCheckIn(p, msg);
|
||||||
p.getName(), cost)) {
|
if (result == null) {
|
||||||
p.sendMessage(
|
// 等待选择(NEED_CONFIRM)
|
||||||
"§c积分不足,补签需 "
|
|
||||||
+ cost + " 积分");
|
|
||||||
reset(p);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p.sendMessage(
|
if (result.startsWith(
|
||||||
"§a补签成功!扣除 "
|
"NEED_CONFIRM:")) {
|
||||||
+ cost + " 积分。"
|
String[] parts =
|
||||||
+ "日期: " + msg);
|
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);
|
||||||
|
}
|
||||||
reset(p);
|
reset(p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case INVITE_INPUT_CODE: {
|
case INVITE_INPUT_CODE: {
|
||||||
String code = msg.trim();
|
String code = msg.trim();
|
||||||
String myName = p.getName();
|
String myName = p.getName();
|
||||||
|
|||||||
@@ -1,114 +1,286 @@
|
|||||||
package Sdf1_login;
|
package Sdf1_login;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
public class CheckInManager {
|
|
||||||
|
public class CheckInManager {
|
||||||
private final Main plugin;
|
|
||||||
|
private final Main plugin;
|
||||||
public CheckInManager(Main plugin) {
|
|
||||||
this.plugin = plugin;
|
public CheckInManager(Main plugin) {
|
||||||
}
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
public boolean isCheckedInToday(String name) {
|
|
||||||
String last = (String) plugin.getDb()
|
// ===== 今日是否已签到 =====
|
||||||
.getField(name, "last_checkin_date");
|
public boolean isCheckedInToday(
|
||||||
String today = new SimpleDateFormat("yyyy-MM-dd")
|
String name) {
|
||||||
.format(new Date());
|
String last = (String) plugin.getDb()
|
||||||
return today.equals(last);
|
.getField(name,
|
||||||
}
|
"last_checkin_date");
|
||||||
|
String today = new SimpleDateFormat(
|
||||||
public String checkIn(Player p) {
|
"yyyy-MM-dd")
|
||||||
String name = p.getName();
|
.format(new Date());
|
||||||
if (isCheckedInToday(name)) {
|
return today.equals(last);
|
||||||
return plugin.getConfig2()
|
}
|
||||||
.msg("checkin_already");
|
|
||||||
}
|
// ===== 签到 =====
|
||||||
|
public String checkIn(Player p) {
|
||||||
Object streakObj = plugin.getDb()
|
String name = p.getName();
|
||||||
.getField(name, "checkin_streak");
|
if (isCheckedInToday(name)) {
|
||||||
int streak = streakObj != null
|
return plugin.getConfig2()
|
||||||
? ((Number) streakObj).intValue() : 0;
|
.msg("checkin_already");
|
||||||
|
}
|
||||||
String lastDate = (String) plugin.getDb()
|
|
||||||
.getField(name, "last_checkin_date");
|
Object streakObj = plugin.getDb()
|
||||||
String today = new SimpleDateFormat("yyyy-MM-dd")
|
.getField(name,
|
||||||
.format(new Date());
|
"checkin_streak");
|
||||||
|
int streak = streakObj != null
|
||||||
if (lastDate != null && !lastDate.isEmpty()) {
|
? ((Number) streakObj)
|
||||||
try {
|
.intValue() : 0;
|
||||||
java.util.Date lastD = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd").parse(lastDate);
|
String lastDate = (String) plugin
|
||||||
java.util.Date todayD = new SimpleDateFormat(
|
.getDb().getField(name,
|
||||||
"yyyy-MM-dd").parse(today);
|
"last_checkin_date");
|
||||||
long diff = todayD.getTime() - lastD.getTime();
|
String today = new SimpleDateFormat(
|
||||||
if (diff > 86400000L * 2) {
|
"yyyy-MM-dd")
|
||||||
streak = 0;
|
.format(new Date());
|
||||||
} else if (diff > 86400000L) {
|
|
||||||
streak++;
|
if (lastDate != null
|
||||||
}
|
&& !lastDate.isEmpty()) {
|
||||||
} catch (Exception ignored) {
|
try {
|
||||||
}
|
java.util.Date lastD =
|
||||||
}
|
new SimpleDateFormat(
|
||||||
|
"yyyy-MM-dd")
|
||||||
if (streak == 0) streak = 1;
|
.parse(lastDate);
|
||||||
else streak++;
|
java.util.Date todayD =
|
||||||
|
new SimpleDateFormat(
|
||||||
double multi = 1.0;
|
"yyyy-MM-dd")
|
||||||
if (streak >= 21) multi = 1.5;
|
.parse(today);
|
||||||
else if (streak >= 14) multi = 1.3;
|
long diff = todayD.getTime()
|
||||||
else if (streak >= 7) multi = 1.1;
|
- lastD.getTime();
|
||||||
|
if (diff > 86400000L * 2) {
|
||||||
int points = (int) (1 * multi);
|
streak = 0;
|
||||||
plugin.getDb().addPoints(name, points);
|
} else if (diff
|
||||||
plugin.getDb().setField(
|
> 86400000L) {
|
||||||
name, "checkin_streak", streak);
|
streak++;
|
||||||
plugin.getDb().setField(
|
}
|
||||||
name, "last_checkin_date", today);
|
} catch (Exception ignored) {
|
||||||
Object totalObj = plugin.getDb()
|
}
|
||||||
.getField(name, "total_checkin_days");
|
}
|
||||||
int total = totalObj != null
|
|
||||||
? ((Number) totalObj).intValue() : 0;
|
if (streak == 0) streak = 1;
|
||||||
plugin.getDb().setField(
|
else streak++;
|
||||||
name, "total_checkin_days", total + 1);
|
|
||||||
|
double multi = 1.0;
|
||||||
return plugin.getConfig2()
|
if (streak >= 21) multi = 1.5;
|
||||||
.msg("checkin_success")
|
else if (streak >= 14) multi = 1.3;
|
||||||
.replace("{points}",
|
else if (streak >= 7) multi = 1.1;
|
||||||
String.valueOf(points))
|
|
||||||
.replace("{streak}",
|
int basePoints = plugin.getConfig2()
|
||||||
String.valueOf(streak))
|
.checkinPoints;
|
||||||
.replace("{multi}",
|
int points =
|
||||||
String.valueOf(multi));
|
(int) (basePoints * multi);
|
||||||
}
|
|
||||||
|
plugin.getDb().addPoints(
|
||||||
public String backCheckIn(Player p, String dateStr) {
|
name, points);
|
||||||
String name = p.getName();
|
plugin.getDb().setField(
|
||||||
if (!plugin.getDb().deductPoints(name, 10)) {
|
name, "checkin_streak",
|
||||||
return plugin.getConfig2()
|
streak);
|
||||||
.msg("checkin_already");
|
plugin.getDb().setField(
|
||||||
}
|
name,
|
||||||
try {
|
"last_checkin_date", today);
|
||||||
java.util.Date target = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd").parse(dateStr);
|
Object totalObj = plugin.getDb()
|
||||||
java.util.Date now = new SimpleDateFormat(
|
.getField(name,
|
||||||
"yyyy-MM-dd").parse(
|
"total_checkin_days");
|
||||||
new SimpleDateFormat("yyyy-MM-dd")
|
int total = totalObj != null
|
||||||
.format(new Date()));
|
? ((Number) totalObj)
|
||||||
long diff = now.getTime() - target.getTime();
|
.intValue() : 0;
|
||||||
if (diff < 0 || diff > 3 * 86400000L) {
|
plugin.getDb().setField(
|
||||||
plugin.getDb().addPoints(name, 10);
|
name,
|
||||||
return plugin.getConfig2()
|
"total_checkin_days",
|
||||||
.msg("checkin_already");
|
total + 1);
|
||||||
}
|
|
||||||
plugin.getDb().addPoints(name, 1);
|
return plugin.getConfig2()
|
||||||
return "§a补签成功!日期: " + dateStr;
|
.msg("checkin_success")
|
||||||
} catch (Exception e) {
|
.replace("{points}",
|
||||||
plugin.getDb().addPoints(name, 10);
|
String.valueOf(points))
|
||||||
return "§c日期格式错误,请用 yyyy-MM-dd";
|
.replace("{streak}",
|
||||||
}
|
String.valueOf(streak))
|
||||||
}
|
.replace("{multi}",
|
||||||
}
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 补签入口 =====
|
||||||
|
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;
|
||||||
|
try {
|
||||||
|
target = sdf.parse(normalized);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return "§c无效日期: " + dateStr;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getDb()
|
||||||
|
.deductPoints(name, 10)) {
|
||||||
|
return "§c积分不足,补签需 10 积分";
|
||||||
|
}
|
||||||
|
|
||||||
|
net.milkbowl.vault.economy
|
||||||
|
.Economy econ =
|
||||||
|
plugin.getEconomy();
|
||||||
|
if (econ != null) {
|
||||||
|
double bal = econ.getBalance(p);
|
||||||
|
if (bal < 10.0) {
|
||||||
|
plugin.getDb()
|
||||||
|
.addPoints(name, 10);
|
||||||
|
int mult = plugin.getConfig2()
|
||||||
|
.backCheckPointMultiplier;
|
||||||
|
int needed = 10 * mult;
|
||||||
|
p.sendMessage("§e§l[补签] "
|
||||||
|
+ "§f经济不足($"
|
||||||
|
+ String.format(
|
||||||
|
"%.2f", bal)
|
||||||
|
+ " < $10)");
|
||||||
|
p.sendMessage("§7是否用 §a§l"
|
||||||
|
+ needed + "积分 "
|
||||||
|
+ "§7代替 10经济?");
|
||||||
|
p.sendMessage("§7输入 §a1 "
|
||||||
|
+ "§7同意,"
|
||||||
|
+ "§c其他 §7取消");
|
||||||
|
return "NEED_CONFIRM:"
|
||||||
|
+ normalized
|
||||||
|
+ ":" + needed;
|
||||||
|
}
|
||||||
|
econ.withdrawPlayer(p, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
doBackCheckIn(p, normalized);
|
||||||
|
return "§a补签成功!日期: "
|
||||||
|
+ normalized
|
||||||
|
+ " §7(扣除10积分+10经济)";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ===== 执行补签 =====
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 标准化日期 =====
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 获取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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import java.io.*;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static java.lang.Double.parseDouble;
|
|
||||||
|
|
||||||
public class ConfigManager {
|
public class ConfigManager {
|
||||||
|
|
||||||
private final File dataFolder;
|
private final File dataFolder;
|
||||||
private final Map<String, String> messages = new LinkedHashMap<>();
|
private final Map<String, String> messages =
|
||||||
private final Map<String, String> smtpSettings = new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
|
private final Map<String, String> smtpSettings =
|
||||||
|
new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// ===== 公共字段 =====
|
||||||
public String adminTag = "admin";
|
public String adminTag = "admin";
|
||||||
public String adminPassword = "qweasd";
|
public String adminPassword = "qweasd";
|
||||||
public int maxAccountsPerIP = 3;
|
public int maxAccountsPerIP = 3;
|
||||||
@@ -25,7 +26,28 @@ public class ConfigManager {
|
|||||||
public long smtpVerifyExpire = 0;
|
public long smtpVerifyExpire = 0;
|
||||||
public long smtpLastSendTime = 0;
|
public long smtpLastSendTime = 0;
|
||||||
public String smtpConfiguringPlayer = "";
|
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 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;
|
||||||
|
|
||||||
public ConfigManager(File dataFolder) {
|
public ConfigManager(File dataFolder) {
|
||||||
this.dataFolder = dataFolder;
|
this.dataFolder = dataFolder;
|
||||||
@@ -35,232 +57,132 @@ public class ConfigManager {
|
|||||||
|
|
||||||
public void loadMessages() {
|
public void loadMessages() {
|
||||||
messages.clear();
|
messages.clear();
|
||||||
File file = new File(dataFolder, "消息.txt");
|
File file =
|
||||||
if (!file.exists()) createDefaultMessages(file);
|
new File(dataFolder, "消息.txt");
|
||||||
|
if (!file.exists())
|
||||||
|
createDefaultMessages(file);
|
||||||
loadMap(file, messages);
|
loadMap(file, messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String msg(String key) {
|
public String msg(String key) {
|
||||||
String val = messages.getOrDefault(key, key);
|
String val =
|
||||||
|
messages.getOrDefault(key, key);
|
||||||
val = val.replace("<b>", "&l");
|
val = val.replace("<b>", "&l");
|
||||||
val = val.replace("</b>", "&r");
|
val = val.replace("</b>", "&r");
|
||||||
val = val.replace("<br>", "\n");
|
val = val.replace("<br>", "\n");
|
||||||
val = val.replace("\\n", "\n");
|
val = val.replace("\\n", "\n");
|
||||||
// &颜色码 转换为 §颜色码
|
|
||||||
val = val.replaceAll(
|
val = val.replaceAll(
|
||||||
"&([0-9a-fk-orA-FK-OR])", "§$1");
|
"&([0-9a-fk-orA-FK-OR])", "§$1");
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void createDefaultMessages(File f) {
|
private void createDefaultMessages(File f) {
|
||||||
List<String> L = new ArrayList<>();
|
List<String> L = new ArrayList<>();
|
||||||
L.add("# ==========================================");
|
L.add("# ===== Sdf1_login 消息配置 =====");
|
||||||
L.add("# Sdf1_login 消息配置文件");
|
L.add("# 占位符用 {xxx} 表示");
|
||||||
L.add("# ==========================================");
|
L.add("# 修改后执行 /sdf1_login reload");
|
||||||
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("# ---- 登录注册 ----");
|
||||||
L.add("# ---- 登录注册相关 ----");
|
|
||||||
L.add("# login_timeout: 登录超时被踢出时的提示");
|
|
||||||
L.add("login_timeout=§c登录超时,请重新登录");
|
L.add("login_timeout=§c登录超时,请重新登录");
|
||||||
L.add("# not_registered: 未注册玩家收到的提示");
|
|
||||||
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
|
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
|
||||||
L.add("# not_logged_in: 已注册但未登录的提示");
|
|
||||||
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
|
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
|
||||||
L.add("# already_registered: 已注册玩家再次注册时的提示");
|
|
||||||
L.add("already_registered=§c您已注册过账号");
|
L.add("already_registered=§c您已注册过账号");
|
||||||
L.add("# already_logged_in: 已登录玩家再次登录时的提示");
|
|
||||||
L.add("already_logged_in=§c您已登录");
|
L.add("already_logged_in=§c您已登录");
|
||||||
L.add("# reg_success: 注册成功提示");
|
|
||||||
L.add("reg_success=§a注册成功!欢迎来到服务器");
|
L.add("reg_success=§a注册成功!欢迎来到服务器");
|
||||||
L.add("# login_success: 登录成功提示");
|
|
||||||
L.add("login_success=§a登录成功!");
|
L.add("login_success=§a登录成功!");
|
||||||
L.add("# login_failed: 密码错误提示");
|
|
||||||
L.add("login_failed=§c密码错误,请重试");
|
L.add("login_failed=§c密码错误,请重试");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 密码 ---
|
L.add("# ---- 密码 ----");
|
||||||
L.add("# ---- 密码相关 ----");
|
|
||||||
L.add("# password_wrong: 旧密码验证失败");
|
|
||||||
L.add("password_wrong=§c密码错误");
|
L.add("password_wrong=§c密码错误");
|
||||||
L.add("# password_changed: 密码修改成功");
|
|
||||||
L.add("password_changed=§a密码修改成功!");
|
L.add("password_changed=§a密码修改成功!");
|
||||||
L.add("# password_format_error: 密码格式不符合要求");
|
|
||||||
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
|
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
|
||||||
L.add("# password_same: 新密码与旧密码相同");
|
|
||||||
L.add("password_same=§c新密码不能与旧密码相同");
|
L.add("password_same=§c新密码不能与旧密码相同");
|
||||||
L.add("# reset_no_email: 找回密码时未绑定邮箱");
|
|
||||||
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
|
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
|
||||||
L.add("# reset_sent: 临时密码已发送到邮箱");
|
L.add("reset_sent=§a临时密码已发送到您的邮箱");
|
||||||
L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收");
|
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 签到积分 ---
|
L.add("# ---- 签到积分 ----");
|
||||||
L.add("# ---- 签到积分相关 ----");
|
|
||||||
L.add("# checkin_already: 今日已签到");
|
|
||||||
L.add("checkin_already=§c您今日已签到");
|
L.add("checkin_already=§c您今日已签到");
|
||||||
L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率");
|
L.add("checkin_success=§a签到成功!获得 {points} 积分");
|
||||||
L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}");
|
|
||||||
L.add("# points_insufficient: 积分不足");
|
|
||||||
L.add("points_insufficient=§c积分不足,当前: {points}");
|
L.add("points_insufficient=§c积分不足,当前: {points}");
|
||||||
L.add("# points_purchase_success: 积分购买成功");
|
|
||||||
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
|
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 挂机 ---
|
L.add("# ---- 挂机检测 ----");
|
||||||
L.add("# ---- 挂机检测相关 ----");
|
|
||||||
L.add("# afk_set_enabled: 挂机检测开启");
|
|
||||||
L.add("afk_set_enabled=§a挂机检测已开启");
|
L.add("afk_set_enabled=§a挂机检测已开启");
|
||||||
L.add("# afk_set_disabled: 挂机检测关闭");
|
|
||||||
L.add("afk_set_disabled=§c挂机检测已关闭");
|
L.add("afk_set_disabled=§c挂机检测已关闭");
|
||||||
L.add("# afk_kick: 因挂机被踢出");
|
|
||||||
L.add("afk_kick=§c因挂机过久被踢出");
|
L.add("afk_kick=§c因挂机过久被踢出");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 邮箱 ---
|
L.add("# ---- 邮箱 ----");
|
||||||
L.add("# ---- 邮箱相关 ----");
|
|
||||||
L.add("# email_set: 邮箱设置成功");
|
|
||||||
L.add("email_set=§a邮箱设置成功:{email}");
|
L.add("email_set=§a邮箱设置成功:{email}");
|
||||||
L.add("# email_format_error: 邮箱格式错误");
|
|
||||||
L.add("email_format_error=§c邮箱格式不正确");
|
L.add("email_format_error=§c邮箱格式不正确");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 邀请 ---
|
L.add("# ---- 邀请 ----");
|
||||||
L.add("# ---- 邀请相关 ----");
|
|
||||||
L.add("# invite_code_generated: 邀请码生成");
|
|
||||||
L.add("invite_code_generated=§a邀请码:{code}");
|
L.add("invite_code_generated=§a邀请码:{code}");
|
||||||
L.add("# invite_used: 使用邀请码成功");
|
|
||||||
L.add("invite_used=§a邀请成功,邀请人:{player}");
|
L.add("invite_used=§a邀请成功,邀请人:{player}");
|
||||||
L.add("# invite_self: 使用自己的邀请码");
|
|
||||||
L.add("invite_self=§c不能使用自己的邀请码");
|
L.add("invite_self=§c不能使用自己的邀请码");
|
||||||
L.add("# invite_not_found: 邀请码不存在");
|
|
||||||
L.add("invite_not_found=§c邀请码不存在");
|
L.add("invite_not_found=§c邀请码不存在");
|
||||||
L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励");
|
|
||||||
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
|
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 礼包 ---
|
L.add("# ---- 礼包 ----");
|
||||||
L.add("# ---- 新人礼包相关 ----");
|
|
||||||
L.add("# gift_not_ready: 礼包条件未满足");
|
|
||||||
L.add("gift_not_ready=§c礼包条件未满足");
|
L.add("gift_not_ready=§c礼包条件未满足");
|
||||||
L.add("# gift_claimed: 礼包领取成功");
|
|
||||||
L.add("gift_claimed=§a礼包领取成功!");
|
L.add("gift_claimed=§a礼包领取成功!");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 审批/IP ---
|
L.add("# ---- 审批与IP ----");
|
||||||
L.add("# ---- 审批与IP限制 ----");
|
|
||||||
L.add("# need_approval: 注册需审批");
|
|
||||||
L.add("need_approval=§e注册需审批 (工单#{id})");
|
L.add("need_approval=§e注册需审批 (工单#{id})");
|
||||||
L.add("# ip_max_accounts: IP注册已达上限");
|
|
||||||
L.add("ip_max_accounts=§cIP注册已达上限");
|
L.add("ip_max_accounts=§cIP注册已达上限");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 管理员 ---
|
L.add("# ---- 管理员 ----");
|
||||||
L.add("# ---- 管理员操作 ----");
|
|
||||||
L.add("# admin_delete_confirm: 管理员删除账号确认提示");
|
|
||||||
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
|
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 聊天过滤 ---
|
|
||||||
L.add("# ---- 聊天过滤 ----");
|
L.add("# ---- 聊天过滤 ----");
|
||||||
L.add("# chat_muted: 禁言玩家尝试聊天");
|
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言");
|
||||||
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天");
|
L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规链接: §e{url}");
|
||||||
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_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}");
|
||||||
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("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚");
|
||||||
L.add("");
|
L.add("");
|
||||||
|
|
||||||
// --- 通用 ---
|
|
||||||
L.add("# ---- 通用 ----");
|
L.add("# ---- 通用 ----");
|
||||||
L.add("# 未知参数: 未知命令或权限不足时的提示");
|
|
||||||
L.add("未知参数=§c未知参数或权限不足");
|
L.add("未知参数=§c未知参数或权限不足");
|
||||||
L.add("# only_player: 仅玩家可用的命令");
|
|
||||||
L.add("# only_player=§c此命令仅玩家可用");
|
|
||||||
|
|
||||||
writeLines(f, L);
|
writeLines(f, L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== SMTP ====================
|
// ==================== SMTP ====================
|
||||||
|
|
||||||
public void loadSmtp() {
|
public void loadSmtp() {
|
||||||
smtpSettings.clear();
|
smtpSettings.clear();
|
||||||
File file = new File(dataFolder, "SMTP设置.txt");
|
File file =
|
||||||
if (!file.exists()) createDefaultSmtp(file);
|
new File(dataFolder, "SMTP设置.txt");
|
||||||
|
if (!file.exists())
|
||||||
|
createDefaultSmtp(file);
|
||||||
loadMap(file, smtpSettings);
|
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) {
|
public String getSmtp(String key) {
|
||||||
return smtpSettings.getOrDefault(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) {
|
public void setSmtp(String key, String value) {
|
||||||
smtpSettings.put(key, value);
|
smtpSettings.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将当前SMTP设置写回文件 */
|
|
||||||
public void saveSmtp() {
|
public void saveSmtp() {
|
||||||
File file = new File(dataFolder, "SMTP设置.txt");
|
File file =
|
||||||
|
new File(dataFolder, "SMTP设置.txt");
|
||||||
List<String> L = new ArrayList<>();
|
List<String> L = new ArrayList<>();
|
||||||
L.add("# ===== SMTP 邮件服务器配置 =====");
|
L.add("# ===== SMTP 邮件服务器配置 =====");
|
||||||
for (Map.Entry<String, String> e : smtpSettings.entrySet()) {
|
for (Map.Entry<String, String> e
|
||||||
|
: smtpSettings.entrySet()) {
|
||||||
L.add(e.getKey() + "=" + e.getValue());
|
L.add(e.getKey() + "=" + e.getValue());
|
||||||
}
|
}
|
||||||
writeLines(file, L);
|
writeLines(file, L);
|
||||||
@@ -281,17 +203,25 @@ public class ConfigManager {
|
|||||||
// ==================== 插件设置 ====================
|
// ==================== 插件设置 ====================
|
||||||
|
|
||||||
public void loadSettings() {
|
public void loadSettings() {
|
||||||
File file = new File(dataFolder, "插件设置.txt");
|
File file =
|
||||||
if (!file.exists()) createDefaultSettings(file);
|
new File(dataFolder, "插件设置.txt");
|
||||||
Map<String, String> m = new LinkedHashMap<>();
|
if (!file.exists())
|
||||||
|
createDefaultSettings(file);
|
||||||
|
Map<String, String> m =
|
||||||
|
new LinkedHashMap<>();
|
||||||
loadMap(file, m);
|
loadMap(file, m);
|
||||||
adminTag = m.getOrDefault("管理标签", adminTag);
|
|
||||||
adminPassword = m.getOrDefault("管理密码", adminPassword);
|
adminTag = m.getOrDefault(
|
||||||
|
"管理标签", adminTag);
|
||||||
|
adminPassword = m.getOrDefault(
|
||||||
|
"管理密码", adminPassword);
|
||||||
maxAccountsPerIP = parseInt(
|
maxAccountsPerIP = parseInt(
|
||||||
m.getOrDefault("每IP最大账号数", "3"), 3);
|
m.getOrDefault("每IP最大账号数", "3"), 3);
|
||||||
approvalMode = m.getOrDefault("审批模式", approvalMode);
|
approvalMode = m.getOrDefault(
|
||||||
|
"审批模式", approvalMode);
|
||||||
autoApproveDelayMinutes = parseInt(
|
autoApproveDelayMinutes = parseInt(
|
||||||
m.getOrDefault("自动审批延迟分钟", "30"), 30);
|
m.getOrDefault("自动审批延迟分钟", "30"),
|
||||||
|
30);
|
||||||
loginTimeout = parseInt(
|
loginTimeout = parseInt(
|
||||||
m.getOrDefault("登录超时秒数", "60"), 60);
|
m.getOrDefault("登录超时秒数", "60"), 60);
|
||||||
afkEnabled = Boolean.parseBoolean(
|
afkEnabled = Boolean.parseBoolean(
|
||||||
@@ -300,60 +230,106 @@ public class ConfigManager {
|
|||||||
m.getOrDefault("挂机超时秒数", "300"), 300);
|
m.getOrDefault("挂机超时秒数", "300"), 300);
|
||||||
smtpSsl = Boolean.parseBoolean(
|
smtpSsl = Boolean.parseBoolean(
|
||||||
m.getOrDefault("smtp加密", "true"));
|
m.getOrDefault("smtp加密", "true"));
|
||||||
|
httpPort = parseInt(
|
||||||
|
m.getOrDefault("http-port", "8080"),
|
||||||
|
8080);
|
||||||
|
broadcastServerUrl = m.getOrDefault(
|
||||||
|
"resource-pack-url", "");
|
||||||
|
|
||||||
|
// 提成
|
||||||
providerPointsPerScore = parseDouble(
|
providerPointsPerScore = parseDouble(
|
||||||
m.getOrDefault("服务商积分倍率", "2.0"),
|
m.getOrDefault(
|
||||||
2.0);
|
"服务商积分倍率", "2.0"), 2.0);
|
||||||
baseEconomyReward = parseInt(
|
baseEconomyReward = parseInt(
|
||||||
m.getOrDefault("基础经济奖励", "100"),
|
m.getOrDefault(
|
||||||
100);
|
"基础经济奖励", "100"), 100);
|
||||||
providerEconomyPerScore = parseDouble(
|
providerEconomyPerScore = parseDouble(
|
||||||
m.getOrDefault("经济奖励每评分", "50.0"),
|
m.getOrDefault(
|
||||||
50.0);
|
"经济奖励每评分", "50.0"), 50.0);
|
||||||
requesterPointsReward = parseInt(
|
requesterPointsReward = parseInt(
|
||||||
m.getOrDefault("报单人积分奖励", "10"),
|
m.getOrDefault(
|
||||||
10);
|
"报单人积分奖励", "10"), 10);
|
||||||
|
|
||||||
|
// 垃圾站
|
||||||
garbageEnabled = Boolean.parseBoolean(
|
garbageEnabled = Boolean.parseBoolean(
|
||||||
m.getOrDefault("垃圾站_启用", "true"));
|
m.getOrDefault(
|
||||||
|
"垃圾站_启用", "true"));
|
||||||
garbageInterval = parseInt(
|
garbageInterval = parseInt(
|
||||||
m.getOrDefault("垃圾站_清理间隔秒",
|
m.getOrDefault(
|
||||||
"300"), 300);
|
"垃圾站_清理间隔秒", "300"), 300);
|
||||||
garbageMaxRounds = parseInt(
|
garbageMaxRounds = parseInt(
|
||||||
m.getOrDefault("垃圾站_保留轮数",
|
m.getOrDefault(
|
||||||
"1"), 1);
|
"垃圾站_保留轮数", "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);
|
||||||
|
|
||||||
|
|
||||||
|
// 自动判断模式
|
||||||
|
if (checkinRewardFixed > 0) {
|
||||||
|
checkinRewardType = "fixed";
|
||||||
|
} else if (checkinRewardMin > 0
|
||||||
|
&& checkinRewardMax > 0
|
||||||
|
&& checkinRewardMax
|
||||||
|
> checkinRewardMin) {
|
||||||
|
checkinRewardType = "range";
|
||||||
|
} else {
|
||||||
|
checkinRewardType = "none";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void saveSettings() {
|
public void saveSettings() {
|
||||||
File file = new File(dataFolder, "插件设置.txt");
|
File file =
|
||||||
|
new File(dataFolder, "插件设置.txt");
|
||||||
List<String> L = new ArrayList<>();
|
List<String> L = new ArrayList<>();
|
||||||
L.add("# ===== Sdf1_login 插件设置 =====");
|
L.add("# ===== Sdf1_login 插件设置 =====");
|
||||||
L.add("管理标签=" + adminTag);
|
L.add("管理标签=" + adminTag);
|
||||||
L.add("管理密码=" + adminPassword);
|
L.add("管理密码=" + adminPassword);
|
||||||
L.add("每IP最大账号数=" + maxAccountsPerIP);
|
L.add("每IP最大账号数=" + maxAccountsPerIP);
|
||||||
L.add("审批模式=" + approvalMode);
|
L.add("审批模式=" + approvalMode);
|
||||||
L.add("自动审批延迟分钟=" + autoApproveDelayMinutes);
|
L.add("自动审批延迟分钟="
|
||||||
|
+ autoApproveDelayMinutes);
|
||||||
L.add("登录超时秒数=" + loginTimeout);
|
L.add("登录超时秒数=" + loginTimeout);
|
||||||
L.add("挂机踢出=" + afkEnabled);
|
L.add("挂机踢出=" + afkEnabled);
|
||||||
L.add("挂机超时秒数=" + afkTimeout);
|
L.add("挂机超时秒数=" + afkTimeout);
|
||||||
// 垃圾站设置
|
L.add("# ---- 资源包 ----");
|
||||||
|
L.add("resource-pack-url="
|
||||||
|
+ broadcastServerUrl);
|
||||||
|
L.add("http-port=" + httpPort);
|
||||||
L.add("# ---- 垃圾站 ----");
|
L.add("# ---- 垃圾站 ----");
|
||||||
L.add("垃圾站_启用=" + garbageEnabled);
|
L.add("垃圾站_启用=" + garbageEnabled);
|
||||||
L.add("垃圾站_清理间隔秒=" + garbageInterval);
|
L.add("垃圾站_清理间隔秒="
|
||||||
|
+ garbageInterval);
|
||||||
L.add("垃圾站_保留轮数=" + garbageMaxRounds);
|
L.add("垃圾站_保留轮数=" + garbageMaxRounds);
|
||||||
|
L.add("# ---- 签到盲盒奖励 ----");
|
||||||
|
L.add("签到固定金额=" + checkinRewardFixed);
|
||||||
|
L.add("签到最小金额=" + checkinRewardMin);
|
||||||
|
L.add("签到最大金额=" + checkinRewardMax);
|
||||||
|
L.add("签到给积分=" + checkinGivePoints);
|
||||||
|
L.add("签到积分数量=" + checkinPoints);
|
||||||
|
L.add("补签积分倍率="
|
||||||
|
+ backCheckPointMultiplier);
|
||||||
|
|
||||||
|
|
||||||
writeLines(file, L);
|
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) {
|
private void createDefaultSettings(File f) {
|
||||||
List<String> L = new ArrayList<>();
|
List<String> L = new ArrayList<>();
|
||||||
@@ -366,97 +342,177 @@ public class ConfigManager {
|
|||||||
L.add("登录超时秒数=60");
|
L.add("登录超时秒数=60");
|
||||||
L.add("挂机踢出=false");
|
L.add("挂机踢出=false");
|
||||||
L.add("挂机超时秒数=300");
|
L.add("挂机超时秒数=300");
|
||||||
L.add("服务商积分倍率=" + providerPointsPerScore);
|
L.add("# ---- 资源包 ----");
|
||||||
L.add("基础经济奖励=" + baseEconomyReward);
|
L.add("resource-pack-url=");
|
||||||
L.add("经济奖励每评分=" + providerEconomyPerScore);
|
L.add("http-port=8080");
|
||||||
L.add("报单人积分奖励=" + requesterPointsReward);
|
|
||||||
L.add("# ---- 垃圾站 ----");
|
L.add("# ---- 垃圾站 ----");
|
||||||
L.add("垃圾站_启用=true");
|
L.add("垃圾站_启用=true");
|
||||||
L.add("垃圾站_清理间隔秒=300");
|
L.add("垃圾站_清理间隔秒=300");
|
||||||
L.add("垃圾站_保留轮数=1");
|
L.add("垃圾站_保留轮数=1");
|
||||||
|
L.add("# ---- 签到盲盒奖励 ----");
|
||||||
|
L.add("签到固定金额=0");
|
||||||
|
L.add("签到最小金额=50");
|
||||||
|
L.add("签到最大金额=500");
|
||||||
|
L.add("签到给积分=true");
|
||||||
|
L.add("签到积分数量=10");
|
||||||
|
L.add("补签积分倍率=5");
|
||||||
|
|
||||||
|
|
||||||
writeLines(f, L);
|
writeLines(f, L);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 通用读写 ====================
|
// ==================== 通用工具 ====================
|
||||||
|
|
||||||
private void loadMap(File file, Map<String, String> map) {
|
public List<String> getAfkWhitelist() {
|
||||||
try (BufferedReader r = new BufferedReader(
|
List<String> list = new ArrayList<>();
|
||||||
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
|
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();
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
try (BufferedReader r =
|
||||||
|
new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
new FileInputStream(
|
||||||
|
file),
|
||||||
|
StandardCharsets
|
||||||
|
.UTF_8))) {
|
||||||
String line;
|
String line;
|
||||||
while ((line = r.readLine()) != null) {
|
while ((line = r.readLine()) != null) {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
if (!line.isEmpty()
|
||||||
int eq = line.indexOf('=');
|
&& !line.startsWith("#")) {
|
||||||
if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim());
|
list.add(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException ignored) {}
|
||||||
System.err.println("[Sdf1_login] 读取失败: " + file.getName());
|
return list;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLines(File file, List<String> lines) {
|
|
||||||
try (BufferedWriter w = new BufferedWriter(
|
|
||||||
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
|
|
||||||
for (String l : lines) { w.write(l); w.newLine(); }
|
|
||||||
} 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() {
|
public String generateSmtpVerifyCode() {
|
||||||
String chars =
|
String chars =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
+ "abcdefghijklmnopqrstuvwxyz"
|
+ "abcdefghijklmnopqrstuvwxyz"
|
||||||
+ "0123456789";
|
+ "0123456789";
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
java.util.Random r = new java.util.Random();
|
java.util.Random r =
|
||||||
|
new java.util.Random();
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
sb.append(chars.charAt(r.nextInt(
|
sb.append(chars.charAt(
|
||||||
chars.length())));
|
r.nextInt(chars.length())));
|
||||||
}
|
}
|
||||||
smtpVerifyCode = sb.toString();
|
smtpVerifyCode = sb.toString();
|
||||||
smtpVerifyExpire =
|
smtpVerifyExpire =
|
||||||
System.currentTimeMillis() + 300000L;
|
System.currentTimeMillis()
|
||||||
|
+ 300000L;
|
||||||
return smtpVerifyCode;
|
return smtpVerifyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean checkSmtpVerifyCode(
|
||||||
* 检查验证码是否有效
|
String code) {
|
||||||
*/
|
if (System.currentTimeMillis()
|
||||||
public boolean checkSmtpVerifyCode(String code) {
|
> smtpVerifyExpire) {
|
||||||
if (System.currentTimeMillis() > smtpVerifyExpire) {
|
|
||||||
smtpVerifyCode = "";
|
smtpVerifyCode = "";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return smtpVerifyCode.equalsIgnoreCase(code);
|
return smtpVerifyCode
|
||||||
|
.equalsIgnoreCase(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除验证码
|
|
||||||
*/
|
|
||||||
public void clearSmtpVerifyCode() {
|
public void clearSmtpVerifyCode() {
|
||||||
smtpVerifyCode = "";
|
smtpVerifyCode = "";
|
||||||
smtpVerifyExpire = 0;
|
smtpVerifyExpire = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查发送间隔是否>=1分钟
|
|
||||||
*/
|
|
||||||
public boolean canSendSmtpVerify() {
|
public boolean canSendSmtpVerify() {
|
||||||
return System.currentTimeMillis()
|
return System.currentTimeMillis()
|
||||||
- smtpLastSendTime >= 60000L;
|
- smtpLastSendTime >= 60000L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recordSmtpSendTime() {
|
public void recordSmtpSendTime() {
|
||||||
smtpLastSendTime = System.currentTimeMillis();
|
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))) {
|
||||||
|
String line;
|
||||||
|
while ((line = r.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.isEmpty()
|
||||||
|
|| line.startsWith("#"))
|
||||||
|
continue;
|
||||||
|
int eq = line.indexOf('=');
|
||||||
|
if (eq > 0) {
|
||||||
|
map.put(
|
||||||
|
line.substring(0, eq)
|
||||||
|
.trim(),
|
||||||
|
line.substring(eq + 1)
|
||||||
|
.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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))) {
|
||||||
|
for (String l : lines) {
|
||||||
|
w.write(l);
|
||||||
|
w.newLine();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println(
|
||||||
|
"[Sdf1_login] 写入失败: "
|
||||||
|
+ file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,40 @@ public class DatabaseManager {
|
|||||||
+ "alert_date TEXT NOT NULL,"
|
+ "alert_date TEXT NOT NULL,"
|
||||||
+ "notified INTEGER DEFAULT 0,"
|
+ "notified INTEGER DEFAULT 0,"
|
||||||
+ "notified_time 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 "
|
st.execute("CREATE TABLE IF NOT EXISTS "
|
||||||
@@ -239,6 +273,76 @@ public class DatabaseManager {
|
|||||||
} catch (Exception ignored) {
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 用户操作 ====================
|
// ==================== 用户操作 ====================
|
||||||
|
|
||||||
@@ -747,6 +851,252 @@ public class DatabaseManager {
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== 安全报警 ====================
|
// ==================== 安全报警 ====================
|
||||||
@@ -773,6 +1123,7 @@ public class DatabaseManager {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Map<String, Object>>
|
public List<Map<String, Object>>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -230,27 +230,26 @@ public class GarbageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void saveItem(ItemStack stack) {
|
public void saveItem(ItemStack stack) {
|
||||||
{
|
try {
|
||||||
try {
|
String data = stackToBase64(stack);
|
||||||
String data =
|
if (data.isEmpty()) return;
|
||||||
stackToBase64(stack);
|
PreparedStatement ps = db.prepareStatement(
|
||||||
if (data.isEmpty()) return;
|
"INSERT INTO items "
|
||||||
PreparedStatement ps = db.prepareStatement(
|
+ "(item_data, amount, "
|
||||||
"INSERT INTO items "
|
+ "round, create_time) "
|
||||||
+ "(item_data, amount, "
|
+ "VALUES (?,?,?,?)");
|
||||||
+ "round, create_time) "
|
ps.setString(1, data);
|
||||||
+ "VALUES (?,?,?,?)");
|
ps.setInt(2, stack.getAmount());
|
||||||
ps.setString(1, data);
|
ps.setInt(3, currentRound);
|
||||||
ps.setInt(2, stack.getAmount());
|
ps.setLong(4,
|
||||||
ps.setInt(3, currentRound);
|
System.currentTimeMillis());
|
||||||
ps.setLong(4,
|
ps.executeUpdate();
|
||||||
System.currentTimeMillis());
|
ps.close();
|
||||||
ps.executeUpdate();
|
} catch (Exception e) {
|
||||||
ps.close();
|
e.printStackTrace();
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
}
|
||||||
}
|
|
||||||
}}
|
|
||||||
|
|
||||||
private int deleteOldItems(int beforeRound) {
|
private int deleteOldItems(int beforeRound) {
|
||||||
try {
|
try {
|
||||||
@@ -344,11 +343,9 @@ public class GarbageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openRecyclePage(Player p, int page) {
|
public void openRecyclePage(Player p, int page) {
|
||||||
|
|
||||||
Inventory g = Bukkit.createInventory(
|
Inventory g = Bukkit.createInventory(
|
||||||
null, 54, "§6§l垃圾回收站");
|
null, 54, "§6§l垃圾回收站");
|
||||||
|
|
||||||
// 获取全部存储物品
|
|
||||||
List<Map<String, Object>> allItems =
|
List<Map<String, Object>> allItems =
|
||||||
getStoredItems(999);
|
getStoredItems(999);
|
||||||
int total = allItems.size();
|
int total = allItems.size();
|
||||||
@@ -381,38 +378,6 @@ public class GarbageManager {
|
|||||||
.get("amount")).intValue();
|
.get("amount")).intValue();
|
||||||
ItemStack stack = base64ToStack(data);
|
ItemStack stack = base64ToStack(data);
|
||||||
if (stack != null) {
|
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);
|
stack.setAmount(amount);
|
||||||
ItemMeta im =
|
ItemMeta im =
|
||||||
stack.getItemMeta();
|
stack.getItemMeta();
|
||||||
@@ -429,10 +394,7 @@ public class GarbageManager {
|
|||||||
slot++;
|
slot++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 45-48 格:额外投放区(空位)
|
// 49 格:上一页
|
||||||
// 52-53 格:额外投放区(空位)
|
|
||||||
|
|
||||||
// 49 格:翻页-上一页
|
|
||||||
if (page > 0) {
|
if (page > 0) {
|
||||||
g.setItem(49, mk(Material.ARROW,
|
g.setItem(49, mk(Material.ARROW,
|
||||||
"§e上一页 §7("
|
"§e上一页 §7("
|
||||||
@@ -451,9 +413,8 @@ public class GarbageManager {
|
|||||||
"§7当前页: §f" + (page + 1)
|
"§7当前页: §f" + (page + 1)
|
||||||
+ "/" + totalPages));
|
+ "/" + totalPages));
|
||||||
|
|
||||||
// 51 格:翻页-下一页
|
// 51 格:下一页
|
||||||
boolean canNext = total >= pageSize
|
boolean canNext = page < totalPages - 1;
|
||||||
&& page < totalPages - 1;
|
|
||||||
if (canNext) {
|
if (canNext) {
|
||||||
g.setItem(51, mk(Material.ARROW,
|
g.setItem(51, mk(Material.ARROW,
|
||||||
"§e下一页 §7("
|
"§e下一页 §7("
|
||||||
@@ -463,13 +424,15 @@ public class GarbageManager {
|
|||||||
g.setItem(51, mk(Material.ARROW,
|
g.setItem(51, mk(Material.ARROW,
|
||||||
"§7已是最后一页"));
|
"§7已是最后一页"));
|
||||||
}
|
}
|
||||||
// 52格:返回按钮
|
|
||||||
|
// 52 格:返回
|
||||||
g.setItem(52, mk(Material.ARROW,
|
g.setItem(52, mk(Material.ARROW,
|
||||||
"§7返回",
|
"§7返回",
|
||||||
"§7返回主菜单"));
|
"§7返回主菜单"));
|
||||||
|
|
||||||
p.openInventory(g);
|
p.openInventory(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRecyclePage(String name) {
|
public int getRecyclePage(String name) {
|
||||||
return recyclePages
|
return recyclePages
|
||||||
.getOrDefault(name, 0);
|
.getOrDefault(name, 0);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class GiftManager {
|
|||||||
public GiftManager(Main plugin) {
|
public GiftManager(Main plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.giftFolder = new File(
|
this.giftFolder = new File(
|
||||||
plugin.getDataFolder(), "新人礼包");
|
plugin.getDataFolder(), "任务面板");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadStages() {
|
public void loadStages() {
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ public class LoginManager {
|
|||||||
plugin.getDb().createUser(
|
plugin.getDb().createUser(
|
||||||
name, hash, salt);
|
name, hash, salt);
|
||||||
plugin.getLoggedIn().add(name);
|
plugin.getLoggedIn().add(name);
|
||||||
|
p.setAllowFlight(false);
|
||||||
|
p.setFlying(false);
|
||||||
plugin.getDb().setLoggedIn(name, true);
|
plugin.getDb().setLoggedIn(name, true);
|
||||||
plugin.getDb().setField(name,
|
plugin.getDb().setField(name,
|
||||||
"last_login_time",
|
"last_login_time",
|
||||||
@@ -151,6 +153,8 @@ public class LoginManager {
|
|||||||
name, hash);
|
name, hash);
|
||||||
if (matchMain) {
|
if (matchMain) {
|
||||||
plugin.getLoggedIn().add(name);
|
plugin.getLoggedIn().add(name);
|
||||||
|
plugin.getDb().setLoggedIn(
|
||||||
|
name, true);
|
||||||
plugin.getDb().setLoggedIn(
|
plugin.getDb().setLoggedIn(
|
||||||
name, true);
|
name, true);
|
||||||
plugin.getDb().setField(name,
|
plugin.getDb().setField(name,
|
||||||
@@ -177,6 +181,8 @@ public class LoginManager {
|
|||||||
plugin.getLoggedIn().add(name);
|
plugin.getLoggedIn().add(name);
|
||||||
plugin.getDb().setLoggedIn(
|
plugin.getDb().setLoggedIn(
|
||||||
name, true);
|
name, true);
|
||||||
|
p.setAllowFlight(false);
|
||||||
|
p.setFlying(false);
|
||||||
plugin.getDb().setField(name,
|
plugin.getDb().setField(name,
|
||||||
"last_login_time",
|
"last_login_time",
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis());
|
||||||
|
|||||||
+2527
-943
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
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
@@ -4,115 +4,141 @@ import org.bukkit.Bukkit;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
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.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent
|
||||||
import java.util.logging.Logger;
|
.ConcurrentHashMap;
|
||||||
|
|
||||||
public class RadioDownloadListener
|
public class RadioDownloadListener
|
||||||
implements Listener {
|
implements Listener {
|
||||||
|
|
||||||
private final Logger log;
|
|
||||||
private final Main plugin;
|
private final Main plugin;
|
||||||
private final Map<UUID, Integer> retryCount =
|
private final java.util.logging.Logger log;
|
||||||
new ConcurrentHashMap<>();
|
|
||||||
private static final int MAX_RETRY = 2;
|
private final ConcurrentHashMap<UUID, Integer>
|
||||||
|
failCount = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<UUID, Boolean>
|
||||||
|
blocked = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static final int MAX_FAILED = 3;
|
||||||
|
|
||||||
public RadioDownloadListener(Main plugin) {
|
public RadioDownloadListener(Main plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.log = plugin.getLogger();
|
this.log = plugin.getLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onPlayerQuit(UUID uuid) {
|
||||||
|
failCount.remove(uuid);
|
||||||
|
blocked.remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onStatus(
|
public void onStatus(
|
||||||
PlayerResourcePackStatusEvent e) {
|
PlayerResourcePackStatusEvent e) {
|
||||||
Player p = e.getPlayer();
|
Player p = e.getPlayer();
|
||||||
UUID uuid = p.getUniqueId();
|
UUID uuid = p.getUniqueId();
|
||||||
String status = e.getStatus().name();
|
String status =
|
||||||
|
e.getStatus().name();
|
||||||
|
|
||||||
log.info("[Radio] " + p.getName()
|
log.info("[Radio] === 资源包状态 ===");
|
||||||
+ " status: " + status);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "ACCEPTED":
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "DOWNLOADED":
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "SUCCESS":
|
case "SUCCESS":
|
||||||
log.info("[Radio] " + p.getName()
|
log.info("[Radio] "
|
||||||
+ " 资源包加载成功");
|
+ p.getName()
|
||||||
retryCount.remove(uuid);
|
+ " ✓ 资源包加载成功");
|
||||||
|
failCount.remove(uuid);
|
||||||
|
blocked.remove(uuid);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "FAILED_DOWNLOAD":
|
case "ACCEPTED":
|
||||||
handleRetry(p, uuid);
|
log.info("[Radio] "
|
||||||
|
+ p.getName()
|
||||||
|
+ " 客户端已接受,"
|
||||||
|
+ "等待下载...");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "DECLINED":
|
case "DECLINED":
|
||||||
log.info("[Radio] " + p.getName()
|
log.warning("[Radio] "
|
||||||
+ " 拒绝下载");
|
+ p.getName()
|
||||||
p.sendMessage(
|
+ " ✗ 拒绝了资源包");
|
||||||
"§e[Radio] §f你拒绝了资源包");
|
log.warning("[Radio] "
|
||||||
retryCount.remove(uuid);
|
+ "可能原因: "
|
||||||
|
+ "1.客户端设置禁用资源包"
|
||||||
|
+ " 2.已缓存拒绝记录"
|
||||||
|
+ " 3.SSL证书不匹配");
|
||||||
|
handleFailure(p, uuid);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "DISCARDED":
|
case "FAILED_DOWNLOAD":
|
||||||
log.warning("[Radio] " + p.getName()
|
log.warning("[Radio] "
|
||||||
+ " DISCARDED");
|
+ p.getName()
|
||||||
p.sendMessage(
|
+ " ✗ 下载失败");
|
||||||
"§c[Radio] §f资源包下载失败"
|
log.warning("[Radio] "
|
||||||
+ ",可能原因:");
|
+ "可能原因: "
|
||||||
p.sendMessage(
|
+ "1.URL无法访问"
|
||||||
"§71. CDN/防火墙拦截");
|
+ " 2.SSL证书错误"
|
||||||
p.sendMessage(
|
+ " 3.网络超时"
|
||||||
"§72. SSL证书问题");
|
+ " 4.文件不存在(404)");
|
||||||
retryCount.remove(uuid);
|
handleFailure(p, uuid);
|
||||||
break;
|
|
||||||
|
|
||||||
case "INVALID_URL":
|
|
||||||
log.warning("[Radio] " + p.getName()
|
|
||||||
+ " URL无效");
|
|
||||||
retryCount.remove(uuid);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.info("[Radio] " + p.getName()
|
log.info("[Radio] "
|
||||||
+ " 未知: " + status);
|
+ p.getName()
|
||||||
|
+ " 未知状态: "
|
||||||
|
+ status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRetry(Player p, UUID uuid) {
|
private void handleFailure(Player p, UUID uuid) {
|
||||||
int count = retryCount
|
int failNum = failCount.getOrDefault(uuid, 0) + 1;
|
||||||
.getOrDefault(uuid, 0) + 1;
|
failCount.put(uuid, failNum);
|
||||||
retryCount.put(uuid, count);
|
|
||||||
|
|
||||||
log.warning("[Radio] " + p.getName()
|
log.warning("[Radio] " + p.getName()
|
||||||
+ " 下载失败 (" + count
|
+ " 失败次数: " + failNum + "/" + MAX_FAILED);
|
||||||
+ "/" + MAX_RETRY + ")");
|
|
||||||
|
|
||||||
if (count <= MAX_RETRY) {
|
if (failNum >= MAX_FAILED) {
|
||||||
p.sendMessage(
|
blocked.put(uuid, true);
|
||||||
"§e[Radio] §f下载失败,重试中... ("
|
log.severe("[Radio] " + p.getName()
|
||||||
+ count + "/" + MAX_RETRY + ")");
|
+ " 已达最大失败次数,停止重试,"
|
||||||
Bukkit.getScheduler().runTaskLater(
|
+ "玩家继续留在服务器");
|
||||||
plugin,
|
// 不踢出,只记录熔断,玩家正常游玩
|
||||||
() -> {
|
p.sendMessage("§e§l[Radio] 资源包加载失败"
|
||||||
if (p.isOnline()
|
+ ",跳过资源包,部分画面、音频可能出现问题"
|
||||||
&& plugin.radio != null) {
|
+ "§d§l若想继续下载,请退出重进");
|
||||||
plugin.radio
|
|
||||||
.sendResourcePack(p);
|
return;
|
||||||
}
|
|
||||||
}, 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
@@ -86,4 +86,7 @@ commands:
|
|||||||
usage: /垃圾清理
|
usage: /垃圾清理
|
||||||
menu:
|
menu:
|
||||||
description: 雪球菜单开关控制
|
description: 雪球菜单开关控制
|
||||||
usage: /menu <开/关>
|
usage: /menu <开/关>
|
||||||
|
pvp :
|
||||||
|
description: PVP榜单
|
||||||
|
usage: /pvp
|
||||||
+989
@@ -0,0 +1,989 @@
|
|||||||
|
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
|
||||||
Reference in New Issue
Block a user