feat: 新增优惠券、订单管理等功能
This commit is contained in:
Generated
+10
-10
@@ -1,10 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
|
||||
Generated
+6
-6
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+13
-13
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-5
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
|
||||
#################################################################################
|
||||
# WARNING: Do not store sensitive information in this file, #
|
||||
# as its contents will be included in the Qodana report. #
|
||||
#################################################################################
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
#exclude:
|
||||
# - name: <SomeDisabledInspectionId>
|
||||
# paths:
|
||||
# - <path/where/not/run/inspection>
|
||||
|
||||
projectJDK: "21" #(Applied in CI/CD pipeline)
|
||||
|
||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||
#bootstrap: sh ./prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||
#plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
|
||||
# severityThresholds - configures maximum thresholds for different problem severities
|
||||
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
|
||||
# Code Coverage is available in Ultimate and Ultimate Plus plans
|
||||
#failureConditions:
|
||||
# severityThresholds:
|
||||
# any: 15
|
||||
# critical: 5
|
||||
# testCoverageThresholds:
|
||||
# fresh: 70
|
||||
# total: 50
|
||||
|
||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||
linter: jetbrains/qodana-jvm:2026.1
|
||||
@@ -1,197 +1,197 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AFKManager {
|
||||
|
||||
// 玩家最后活动时间(空闲才开始计时)
|
||||
private final Map<UUID, Long> lastAction =
|
||||
new HashMap<>();
|
||||
// 每个玩家当前的警告阶段
|
||||
private final Map<UUID, Integer> lastStage =
|
||||
new HashMap<>();
|
||||
// 是否已进入挂机检测(false=还在活动,不计时)
|
||||
private final Map<UUID, Boolean> afkTracking =
|
||||
new HashMap<>();
|
||||
|
||||
private final ConfigManager config;
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
public AFKManager(ConfigManager config,
|
||||
JavaPlugin plugin) {
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void startCheck() {
|
||||
new BukkitRunnable() {
|
||||
public void run() {
|
||||
tick();
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 20L);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
if (!config.afkEnabled) return;
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
String name = p.getName();
|
||||
UUID uuid = p.getUniqueId();
|
||||
|
||||
if (!isLoggedIn(name)) continue;
|
||||
if (config.getAfkWhitelist().contains(name))
|
||||
continue;
|
||||
|
||||
// 确保有记录
|
||||
if (!lastAction.containsKey(uuid)) {
|
||||
lastAction.put(uuid, now);
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
long last = lastAction.get(uuid);
|
||||
long idle = (now - last) / 1000;
|
||||
int timeout = config.afkTimeout;
|
||||
|
||||
// 还在活动范围内:不进入挂机检测
|
||||
if (idle < 60) {
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 超过60秒无活动:开始挂机检测
|
||||
afkTracking.put(uuid, true);
|
||||
long remain = timeout - idle;
|
||||
|
||||
// 已超时:踢出
|
||||
if (remain <= 0) {
|
||||
p.kickPlayer("§c§l你已挂机 "
|
||||
+ fmtSec(idle) + ",被踢出");
|
||||
lastAction.remove(uuid);
|
||||
lastStage.remove(uuid);
|
||||
afkTracking.remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
int stage = lastStage.getOrDefault(uuid, 0);
|
||||
|
||||
// 阶段3:最后10秒
|
||||
if (remain <= 10) {
|
||||
if (stage < 3) {
|
||||
lastStage.put(uuid, 3);
|
||||
}
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§c还剩 " + remain + " 秒踢出",
|
||||
0, 22, 3);
|
||||
playSound(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 阶段2:1分钟~10秒
|
||||
if (remain <= 60) {
|
||||
if (stage < 2) {
|
||||
lastStage.put(uuid, 2);
|
||||
p.sendMessage(
|
||||
"§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c"
|
||||
+ fmtSec(idle)
|
||||
+ "§f,还剩 §c"
|
||||
+ fmtSec(remain)
|
||||
+ "§f踢出");
|
||||
}
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§c还剩 " + fmtSec(remain) + " 踢出",
|
||||
5, 25, 5);
|
||||
playSound(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 阶段1:3分钟提醒
|
||||
if (remain <= 180 && stage < 1) {
|
||||
lastStage.put(uuid, 1);
|
||||
p.sendMessage(
|
||||
"§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c"
|
||||
+ fmtSec(idle)
|
||||
+ "§f,还剩 §c"
|
||||
+ fmtSec(remain)
|
||||
+ "§f踢出");
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§f还剩 " + fmtSec(remain) + " 踢出",
|
||||
10, 60, 20);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final int delay = i * 10;
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
plugin, () -> {
|
||||
if (p.isOnline()) playSound(p);
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (remain > 180) {
|
||||
lastStage.remove(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家有活动时调用(移动/交互/放置/挖掘等)
|
||||
* 重置计时,退出挂机检测
|
||||
*/
|
||||
public void recordAction(UUID uuid) {
|
||||
lastAction.put(uuid, System.currentTimeMillis());
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家下线时调用
|
||||
* 彻底停止计时,清理所有数据
|
||||
*/
|
||||
public void remove(UUID uuid) {
|
||||
lastAction.remove(uuid);
|
||||
lastStage.remove(uuid);
|
||||
afkTracking.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家当前是否在挂机检测中
|
||||
*/
|
||||
public boolean isAfkTracking(UUID uuid) {
|
||||
return afkTracking.getOrDefault(uuid, false);
|
||||
}
|
||||
|
||||
private boolean isLoggedIn(String name) {
|
||||
return ((Main) plugin).getLoggedIn()
|
||||
.contains(name);
|
||||
}
|
||||
|
||||
private void playSound(Player p) {
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_CHIME,
|
||||
1.0f, 0.79f);
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_BASS,
|
||||
1.0f, 0.79f);
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_PLING,
|
||||
1.0f, 0.79f);
|
||||
}
|
||||
|
||||
private String fmtSec(long sec) {
|
||||
if (sec < 60) return sec + "秒";
|
||||
long m = sec / 60;
|
||||
long s = sec % 60;
|
||||
return m + "分" + (s > 0 ? s + "秒" : "");
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AFKManager {
|
||||
|
||||
// 玩家最后活动时间(空闲才开始计时)
|
||||
private final Map<UUID, Long> lastAction =
|
||||
new HashMap<>();
|
||||
// 每个玩家当前的警告阶段
|
||||
private final Map<UUID, Integer> lastStage =
|
||||
new HashMap<>();
|
||||
// 是否已进入挂机检测(false=还在活动,不计时)
|
||||
private final Map<UUID, Boolean> afkTracking =
|
||||
new HashMap<>();
|
||||
|
||||
private final ConfigManager config;
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
public AFKManager(ConfigManager config,
|
||||
JavaPlugin plugin) {
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void startCheck() {
|
||||
new BukkitRunnable() {
|
||||
public void run() {
|
||||
tick();
|
||||
}
|
||||
}.runTaskTimer(plugin, 20L, 20L);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
if (!config.afkEnabled) return;
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
String name = p.getName();
|
||||
UUID uuid = p.getUniqueId();
|
||||
|
||||
if (!isLoggedIn(name)) continue;
|
||||
if (config.getAfkWhitelist().contains(name))
|
||||
continue;
|
||||
|
||||
// 确保有记录
|
||||
if (!lastAction.containsKey(uuid)) {
|
||||
lastAction.put(uuid, now);
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
long last = lastAction.get(uuid);
|
||||
long idle = (now - last) / 1000;
|
||||
int timeout = config.afkTimeout;
|
||||
|
||||
// 还在活动范围内:不进入挂机检测
|
||||
if (idle < 60) {
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 超过60秒无活动:开始挂机检测
|
||||
afkTracking.put(uuid, true);
|
||||
long remain = timeout - idle;
|
||||
|
||||
// 已超时:踢出
|
||||
if (remain <= 0) {
|
||||
p.kickPlayer("§c§l你已挂机 "
|
||||
+ fmtSec(idle) + ",被踢出");
|
||||
lastAction.remove(uuid);
|
||||
lastStage.remove(uuid);
|
||||
afkTracking.remove(uuid);
|
||||
continue;
|
||||
}
|
||||
|
||||
int stage = lastStage.getOrDefault(uuid, 0);
|
||||
|
||||
// 阶段3:最后10秒
|
||||
if (remain <= 10) {
|
||||
if (stage < 3) {
|
||||
lastStage.put(uuid, 3);
|
||||
}
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§c还剩 " + remain + " 秒踢出",
|
||||
0, 22, 3);
|
||||
playSound(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 阶段2:1分钟~10秒
|
||||
if (remain <= 60) {
|
||||
if (stage < 2) {
|
||||
lastStage.put(uuid, 2);
|
||||
p.sendMessage(
|
||||
"§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c"
|
||||
+ fmtSec(idle)
|
||||
+ "§f,还剩 §c"
|
||||
+ fmtSec(remain)
|
||||
+ "§f踢出");
|
||||
}
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§c还剩 " + fmtSec(remain) + " 踢出",
|
||||
5, 25, 5);
|
||||
playSound(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 阶段1:3分钟提醒
|
||||
if (remain <= 180 && stage < 1) {
|
||||
lastStage.put(uuid, 1);
|
||||
p.sendMessage(
|
||||
"§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c"
|
||||
+ fmtSec(idle)
|
||||
+ "§f,还剩 §c"
|
||||
+ fmtSec(remain)
|
||||
+ "§f踢出");
|
||||
p.sendTitle(
|
||||
"§e§l挂机了 " + fmtSec(idle),
|
||||
"§f还剩 " + fmtSec(remain) + " 踢出",
|
||||
10, 60, 20);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final int delay = i * 10;
|
||||
Bukkit.getScheduler().runTaskLater(
|
||||
plugin, () -> {
|
||||
if (p.isOnline()) playSound(p);
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (remain > 180) {
|
||||
lastStage.remove(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家有活动时调用(移动/交互/放置/挖掘等)
|
||||
* 重置计时,退出挂机检测
|
||||
*/
|
||||
public void recordAction(UUID uuid) {
|
||||
lastAction.put(uuid, System.currentTimeMillis());
|
||||
afkTracking.put(uuid, false);
|
||||
lastStage.put(uuid, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家下线时调用
|
||||
* 彻底停止计时,清理所有数据
|
||||
*/
|
||||
public void remove(UUID uuid) {
|
||||
lastAction.remove(uuid);
|
||||
lastStage.remove(uuid);
|
||||
afkTracking.remove(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家当前是否在挂机检测中
|
||||
*/
|
||||
public boolean isAfkTracking(UUID uuid) {
|
||||
return afkTracking.getOrDefault(uuid, false);
|
||||
}
|
||||
|
||||
private boolean isLoggedIn(String name) {
|
||||
return ((Main) plugin).getLoggedIn()
|
||||
.contains(name);
|
||||
}
|
||||
|
||||
private void playSound(Player p) {
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_CHIME,
|
||||
1.0f, 0.79f);
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_BASS,
|
||||
1.0f, 0.79f);
|
||||
p.playSound(p.getLocation(),
|
||||
Sound.BLOCK_NOTE_BLOCK_PLING,
|
||||
1.0f, 0.79f);
|
||||
}
|
||||
|
||||
private String fmtSec(long sec) {
|
||||
if (sec < 60) return sec + "秒";
|
||||
long m = sec / 60;
|
||||
long s = sec % 60;
|
||||
return m + "分" + (s > 0 ? s + "秒" : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AccountRequestManager {
|
||||
|
||||
public enum ApprovalMode { AUTO, MANUAL, HYBRID }
|
||||
|
||||
public static class Request {
|
||||
public final int id;
|
||||
public final String requester;
|
||||
public final String target;
|
||||
public final String ip;
|
||||
public final long time;
|
||||
public String status;
|
||||
public String denyReason;
|
||||
|
||||
public Request(int id, String requester, String target, String ip) {
|
||||
this.id = id;
|
||||
this.requester = requester;
|
||||
this.target = target;
|
||||
this.ip = ip;
|
||||
this.time = System.currentTimeMillis();
|
||||
this.status = "PENDING";
|
||||
this.denyReason = "";
|
||||
}
|
||||
}
|
||||
|
||||
private final Main plugin;
|
||||
private final Map<Integer, Request> requests = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> pendingByPlayer = new ConcurrentHashMap<>();
|
||||
private int nextId = 1;
|
||||
private ApprovalMode mode = ApprovalMode.MANUAL;
|
||||
private int autoDelayMinutes = 60;
|
||||
|
||||
public AccountRequestManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public ApprovalMode getMode() { return mode; }
|
||||
|
||||
public void setMode(String m) {
|
||||
try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); }
|
||||
catch (Exception e) { this.mode = ApprovalMode.MANUAL; }
|
||||
}
|
||||
|
||||
public void setAutoDelayMinutes(int min) {
|
||||
this.autoDelayMinutes = min;
|
||||
}
|
||||
|
||||
public Request createRequest(String requester, String target, String ip) {
|
||||
int id = nextId++;
|
||||
Request req = new Request(id, requester, target, ip);
|
||||
requests.put(id, req);
|
||||
pendingByPlayer.put(requester, id);
|
||||
|
||||
if (mode == ApprovalMode.AUTO) {
|
||||
scheduleAutoApprove(id);
|
||||
} else if (mode == ApprovalMode.HYBRID) {
|
||||
String targetIp = (String) plugin.getDb().getField(target, "ip_address");
|
||||
if (ip != null && ip.equals(targetIp)) {
|
||||
scheduleAutoApprove(id);
|
||||
}
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
private void scheduleAutoApprove(int id) {
|
||||
long delayTicks = (long) autoDelayMinutes * 60L * 20L;
|
||||
Request req = requests.get(id);
|
||||
if (req == null) return;
|
||||
String requesterName = req.requester;
|
||||
String targetName = req.target;
|
||||
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过");
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
Request r = requests.get(id);
|
||||
if (r != null && "PENDING".equals(r.status)) {
|
||||
approveRequest(id);
|
||||
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过");
|
||||
Player requester = Bukkit.getPlayer(requesterName);
|
||||
if (requester != null && requester.isOnline()) {
|
||||
requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!");
|
||||
}
|
||||
}
|
||||
}, delayTicks);
|
||||
}
|
||||
|
||||
public boolean approveRequest(int id) {
|
||||
Request req = requests.get(id);
|
||||
if (req == null || !"PENDING".equals(req.status)) return false;
|
||||
req.status = "APPROVED";
|
||||
plugin.getDb().deleteUser(req.target);
|
||||
pendingByPlayer.remove(req.requester);
|
||||
// 清背包+传送出生点+踢出
|
||||
Player target = Bukkit.getPlayer(req.target);
|
||||
if (target != null && target.isOnline()) {
|
||||
target.getInventory().clear();
|
||||
target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]);
|
||||
target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]);
|
||||
target.teleport(target.getWorld().getSpawnLocation());
|
||||
target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean denyRequest(int id, String reason) {
|
||||
Request req = requests.get(id);
|
||||
if (req == null || !"PENDING".equals(req.status)) return false;
|
||||
req.status = "DENIED";
|
||||
req.denyReason = reason;
|
||||
pendingByPlayer.remove(req.requester);
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<Request> getPendingRequests() {
|
||||
List<Request> list = new ArrayList<>();
|
||||
for (Request r : requests.values()) {
|
||||
if ("PENDING".equals(r.status)) list.add(r);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Request getRequest(int id) {
|
||||
return requests.get(id);
|
||||
}
|
||||
|
||||
public boolean hasPending(String player) {
|
||||
return pendingByPlayer.containsKey(player);
|
||||
}
|
||||
|
||||
public void cancelPending(String player) {
|
||||
Integer id = pendingByPlayer.remove(player);
|
||||
if (id != null) {
|
||||
Request r = requests.get(id);
|
||||
if (r != null) r.status = "CANCELLED";
|
||||
}
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AccountRequestManager {
|
||||
|
||||
public enum ApprovalMode { AUTO, MANUAL, HYBRID }
|
||||
|
||||
public static class Request {
|
||||
public final int id;
|
||||
public final String requester;
|
||||
public final String target;
|
||||
public final String ip;
|
||||
public final long time;
|
||||
public String status;
|
||||
public String denyReason;
|
||||
|
||||
public Request(int id, String requester, String target, String ip) {
|
||||
this.id = id;
|
||||
this.requester = requester;
|
||||
this.target = target;
|
||||
this.ip = ip;
|
||||
this.time = System.currentTimeMillis();
|
||||
this.status = "PENDING";
|
||||
this.denyReason = "";
|
||||
}
|
||||
}
|
||||
|
||||
private final Main plugin;
|
||||
private final Map<Integer, Request> requests = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> pendingByPlayer = new ConcurrentHashMap<>();
|
||||
private int nextId = 1;
|
||||
private ApprovalMode mode = ApprovalMode.MANUAL;
|
||||
private int autoDelayMinutes = 60;
|
||||
|
||||
public AccountRequestManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public ApprovalMode getMode() { return mode; }
|
||||
|
||||
public void setMode(String m) {
|
||||
try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); }
|
||||
catch (Exception e) { this.mode = ApprovalMode.MANUAL; }
|
||||
}
|
||||
|
||||
public void setAutoDelayMinutes(int min) {
|
||||
this.autoDelayMinutes = min;
|
||||
}
|
||||
|
||||
public Request createRequest(String requester, String target, String ip) {
|
||||
int id = nextId++;
|
||||
Request req = new Request(id, requester, target, ip);
|
||||
requests.put(id, req);
|
||||
pendingByPlayer.put(requester, id);
|
||||
|
||||
if (mode == ApprovalMode.AUTO) {
|
||||
scheduleAutoApprove(id);
|
||||
} else if (mode == ApprovalMode.HYBRID) {
|
||||
String targetIp = (String) plugin.getDb().getField(target, "ip_address");
|
||||
if (ip != null && ip.equals(targetIp)) {
|
||||
scheduleAutoApprove(id);
|
||||
}
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
private void scheduleAutoApprove(int id) {
|
||||
long delayTicks = (long) autoDelayMinutes * 60L * 20L;
|
||||
Request req = requests.get(id);
|
||||
if (req == null) return;
|
||||
String requesterName = req.requester;
|
||||
String targetName = req.target;
|
||||
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过");
|
||||
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
Request r = requests.get(id);
|
||||
if (r != null && "PENDING".equals(r.status)) {
|
||||
approveRequest(id);
|
||||
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过");
|
||||
Player requester = Bukkit.getPlayer(requesterName);
|
||||
if (requester != null && requester.isOnline()) {
|
||||
requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!");
|
||||
}
|
||||
}
|
||||
}, delayTicks);
|
||||
}
|
||||
|
||||
public boolean approveRequest(int id) {
|
||||
Request req = requests.get(id);
|
||||
if (req == null || !"PENDING".equals(req.status)) return false;
|
||||
req.status = "APPROVED";
|
||||
plugin.getDb().deleteUser(req.target);
|
||||
pendingByPlayer.remove(req.requester);
|
||||
// 清背包+传送出生点+踢出
|
||||
Player target = Bukkit.getPlayer(req.target);
|
||||
if (target != null && target.isOnline()) {
|
||||
target.getInventory().clear();
|
||||
target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]);
|
||||
target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]);
|
||||
target.teleport(target.getWorld().getSpawnLocation());
|
||||
target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean denyRequest(int id, String reason) {
|
||||
Request req = requests.get(id);
|
||||
if (req == null || !"PENDING".equals(req.status)) return false;
|
||||
req.status = "DENIED";
|
||||
req.denyReason = reason;
|
||||
pendingByPlayer.remove(req.requester);
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<Request> getPendingRequests() {
|
||||
List<Request> list = new ArrayList<>();
|
||||
for (Request r : requests.values()) {
|
||||
if ("PENDING".equals(r.status)) list.add(r);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public Request getRequest(int id) {
|
||||
return requests.get(id);
|
||||
}
|
||||
|
||||
public boolean hasPending(String player) {
|
||||
return pendingByPlayer.containsKey(player);
|
||||
}
|
||||
|
||||
public void cancelPending(String player) {
|
||||
Integer id = pendingByPlayer.remove(player);
|
||||
if (id != null) {
|
||||
Request r = requests.get(id);
|
||||
if (r != null) r.status = "CANCELLED";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,23 @@ public class BondManager {
|
||||
} catch (SQLException e) { return 0; }
|
||||
}
|
||||
|
||||
/** 该玩家是否在 bond 表中有真实记录 */
|
||||
public boolean hasBondAccount(String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"SELECT 1 FROM bonds "
|
||||
+ "WHERE player_name=? "
|
||||
+ "AND amount > 0");
|
||||
ps.setString(1, player);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
boolean exists = rs.next();
|
||||
rs.close(); ps.close();
|
||||
return exists;
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 增加债券 ========================
|
||||
|
||||
public int addBonds(String player, int amount) {
|
||||
@@ -272,6 +289,53 @@ public class BondManager {
|
||||
int balanceBefore,
|
||||
int balanceAfter) {
|
||||
try {
|
||||
// 合并模式:shop_buy 30秒内同商品合并
|
||||
if ("shop_buy".equals(type)
|
||||
&& reason != null
|
||||
&& !reason.isEmpty()) {
|
||||
long mergeWindow =
|
||||
System.currentTimeMillis() - 30000L;
|
||||
PreparedStatement chk =
|
||||
db.prepareStatement(
|
||||
"SELECT id, amount "
|
||||
+ "FROM bond_transaction "
|
||||
+ "WHERE player_name=? "
|
||||
+ "AND type=? "
|
||||
+ "AND reason=? "
|
||||
+ "AND time > ? "
|
||||
+ "ORDER BY time DESC "
|
||||
+ "LIMIT 1");
|
||||
chk.setString(1, player);
|
||||
chk.setString(2, type);
|
||||
chk.setString(3, reason);
|
||||
chk.setLong(4, mergeWindow);
|
||||
ResultSet rs = chk.executeQuery();
|
||||
if (rs.next()) {
|
||||
int existId = rs.getInt("id");
|
||||
int existAmt = rs.getInt("amount");
|
||||
rs.close();
|
||||
chk.close();
|
||||
PreparedStatement upd =
|
||||
db.prepareStatement(
|
||||
"UPDATE bond_transaction "
|
||||
+ "SET amount=?, "
|
||||
+ "balance_after=?, "
|
||||
+ "time=? "
|
||||
+ "WHERE id=?");
|
||||
upd.setInt(1, existAmt + amount);
|
||||
upd.setInt(2, balanceAfter);
|
||||
upd.setLong(3,
|
||||
System.currentTimeMillis());
|
||||
upd.setInt(4, existId);
|
||||
upd.executeUpdate();
|
||||
upd.close();
|
||||
return;
|
||||
}
|
||||
rs.close();
|
||||
chk.close();
|
||||
}
|
||||
|
||||
// 原始插入
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bond_transaction "
|
||||
+ "(player_name, type, amount,"
|
||||
@@ -283,16 +347,14 @@ public class BondManager {
|
||||
ps.setString(2, type);
|
||||
ps.setInt(3, amount);
|
||||
ps.setString(4,
|
||||
targetPlayer != null
|
||||
? targetPlayer : "");
|
||||
targetPlayer != null ? targetPlayer : "");
|
||||
ps.setString(5,
|
||||
operator != null ? operator : "");
|
||||
ps.setString(6,
|
||||
reason != null ? reason : "");
|
||||
ps.setInt(7, balanceBefore);
|
||||
ps.setInt(8, balanceAfter);
|
||||
ps.setLong(9,
|
||||
System.currentTimeMillis());
|
||||
ps.setLong(9, System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -314,6 +376,47 @@ public class BondManager {
|
||||
type, targetPlayer, operator, reason);
|
||||
}
|
||||
|
||||
public boolean checkoutCart(String player,
|
||||
String[] itemNames,
|
||||
int[] subtotals) {
|
||||
int total = 0;
|
||||
for (int s : subtotals) total += s;
|
||||
if (isFrozen(player)) return false;
|
||||
|
||||
int before = getBonds(player);
|
||||
if (before < total) return false;
|
||||
int after = before - total;
|
||||
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"INSERT INTO bonds "
|
||||
+ "(player_name,amount,status) "
|
||||
+ "VALUES (?,?,'normal') "
|
||||
+ "ON CONFLICT(player_name) "
|
||||
+ "DO UPDATE SET amount=?");
|
||||
ps.setString(1, player);
|
||||
ps.setInt(2, after);
|
||||
ps.setInt(3, after);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
int running = before;
|
||||
for (int i = 0; i < itemNames.length; i++) {
|
||||
int next = running - subtotals[i];
|
||||
logTransaction(player, "shop_buy", subtotals[i],
|
||||
"", "商店系统",
|
||||
"购物车购买-" + itemNames[i],
|
||||
running, next);
|
||||
running = next;
|
||||
}
|
||||
|
||||
logLegacy(player, -total);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setBonds(String player, int amount) {
|
||||
ensureAccount(player);
|
||||
|
||||
@@ -9,12 +9,7 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
public class BondPrinter {
|
||||
|
||||
@@ -38,9 +33,11 @@ public class BondPrinter {
|
||||
TYPE_NAMES.put("transfer_in", "转入");
|
||||
TYPE_NAMES.put("redeem", "CDK兑换");
|
||||
TYPE_NAMES.put("shop_buy", "商城购买");
|
||||
TYPE_NAMES.put("shop_sell", "商城出售"); // ← 补上
|
||||
TYPE_NAMES.put("daily_sign", "签到奖励");
|
||||
TYPE_NAMES.put("freeze", "冻结");
|
||||
TYPE_NAMES.put("unfreeze", "解冻");
|
||||
|
||||
}
|
||||
|
||||
public BondPrinter(Main plugin) {
|
||||
@@ -151,8 +148,7 @@ public class BondPrinter {
|
||||
.getAllPlayerNames();
|
||||
if (players.isEmpty()) {
|
||||
if (sender != null) {
|
||||
sender.sendMessage(
|
||||
"§c没有流水数据");
|
||||
sender.sendMessage("§c没有流水数据");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -162,59 +158,84 @@ public class BondPrinter {
|
||||
+ ".xls";
|
||||
File file = new File(outputDir, fileName);
|
||||
|
||||
// ===== 收集所有流水 =====
|
||||
// key = 时间戳+玩家+金额,用于去重
|
||||
// value = 一行数据
|
||||
List<String[]> allRows = new ArrayList<>();
|
||||
Set<String> seenKeys = new HashSet<>();
|
||||
|
||||
for (String player : players) {
|
||||
List<Map<String, Object>> txs =
|
||||
plugin.getBonds()
|
||||
.getAllTransactions(player);
|
||||
for (Map<String, Object> tx : txs) {
|
||||
int txId = ((Number) tx.get("id"))
|
||||
.intValue();
|
||||
String dedupKey =
|
||||
String.valueOf(txId);
|
||||
if (!seenKeys.add(dedupKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String timeStr = txTime(tx);
|
||||
long timeMs = 0;
|
||||
Object t = tx.get("time");
|
||||
if (t != null) {
|
||||
timeMs =
|
||||
((Number) t).longValue();
|
||||
}
|
||||
|
||||
allRows.add(new String[]{
|
||||
player,
|
||||
timeStr,
|
||||
txTypeName(tx),
|
||||
String.valueOf(
|
||||
txInt(tx, "amount")),
|
||||
txStr(tx, "target_player"),
|
||||
txStr(tx, "operator"),
|
||||
txStr(tx, "reason"),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_before")),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_after")),
|
||||
// 额外存时间戳用于排序
|
||||
String.valueOf(timeMs)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 按时间戳排序 =====
|
||||
allRows.sort((a, b) -> {
|
||||
long ta = Long.parseLong(
|
||||
a[a.length - 1]);
|
||||
long tb = Long.parseLong(
|
||||
b[b.length - 1]);
|
||||
return Long.compare(ta, tb);
|
||||
});
|
||||
|
||||
// ===== 写入文件 =====
|
||||
PrintWriter pw = new PrintWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(file),
|
||||
StandardCharsets.UTF_8));
|
||||
try {
|
||||
writeXmlHeader(pw, "全服流水");
|
||||
writeXmlRow(pw,
|
||||
new String[]{
|
||||
"玩家", "时间",
|
||||
"类型", "金额",
|
||||
"对象", "操作者",
|
||||
"理由",
|
||||
"操作前余额",
|
||||
"操作后余额"});
|
||||
writeXmlRow(pw, new String[]{
|
||||
"玩家", "时间", "类型",
|
||||
"金额", "对象", "操作者",
|
||||
"理由", "操作前余额",
|
||||
"操作后余额"
|
||||
});
|
||||
|
||||
Set<String> written = new HashSet<>();
|
||||
int totalRows = 0;
|
||||
|
||||
for (String player : players) {
|
||||
List<Map<String, Object>> txs =
|
||||
plugin.getBonds()
|
||||
.getAllTransactions(
|
||||
player);
|
||||
for (Map<String, Object> tx : txs) {
|
||||
int txId = ((Number) tx
|
||||
.get("id"))
|
||||
.intValue();
|
||||
if (!written.add(
|
||||
String.valueOf(
|
||||
txId))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeXmlRow(pw, new String[]{
|
||||
player,
|
||||
txTime(tx),
|
||||
txTypeName(tx),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"amount")),
|
||||
txStr(tx,
|
||||
"target_player"),
|
||||
txStr(tx, "operator"),
|
||||
txStr(tx, "reason"),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_before")),
|
||||
String.valueOf(
|
||||
txInt(tx,
|
||||
"balance_after"))
|
||||
});
|
||||
totalRows++;
|
||||
}
|
||||
for (String[] row : allRows) {
|
||||
// 去掉最后的时间戳列
|
||||
String[] cells =
|
||||
new String[row.length - 1];
|
||||
System.arraycopy(row, 0,
|
||||
cells, 0, cells.length);
|
||||
writeXmlRow(pw, cells);
|
||||
}
|
||||
|
||||
writeXmlFooter(pw);
|
||||
@@ -222,14 +243,15 @@ public class BondPrinter {
|
||||
|
||||
if (sender != null) {
|
||||
sender.sendMessage(
|
||||
"§a[打印] §f全服流水"
|
||||
+ " §a已导出");
|
||||
"§a[打印] §f全服流水 §a已导出");
|
||||
sender.sendMessage("§7文件: §f"
|
||||
+ fileName);
|
||||
sender.sendMessage("§7共 §f"
|
||||
+ players.size()
|
||||
+ " 名玩家,§f"
|
||||
+ totalRows + " §7条");
|
||||
+ allRows.size() + " §7条");
|
||||
sender.sendMessage(
|
||||
"§7按时间从早到晚排序");
|
||||
sender.sendMessage(
|
||||
"§724小时后自动删除");
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scoreboard.Scoreboard;
|
||||
|
||||
import java.io.*;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class CDKManager {
|
||||
|
||||
private final Main plugin;
|
||||
@@ -41,198 +41,18 @@ public class CDKManager {
|
||||
st.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"[CDK] DB init failed: " + e.getMessage(), e);
|
||||
"[CDK] DB init failed: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCDKsFromDir() {
|
||||
File dir = new File(
|
||||
plugin.getDataFolder(), "cdk");
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 已创建 cdk/ 目录");
|
||||
return;
|
||||
}
|
||||
File[] files = dir.listFiles(
|
||||
(d, n) -> n.endsWith(".txt"));
|
||||
if (files == null || files.length == 0) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] cdk/ 目录下"
|
||||
+ "无 .txt 文件");
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (File f : files) {
|
||||
int loaded = loadCDKFile(f);
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 文件: " + f.getName()
|
||||
+ " 加载 " + loaded + " 条");
|
||||
count += loaded;
|
||||
}
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 共导入 " + count + " 条");
|
||||
}
|
||||
// ===== CDK兑换(供SDF1调用) =====
|
||||
|
||||
/** 单文件加载,返回成功数 */
|
||||
private int loadCDKFile(File f) {
|
||||
int count = 0;
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(f),
|
||||
StandardCharsets.UTF_8));
|
||||
String line;
|
||||
int lineNum = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
lineNum++;
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
if (line.startsWith("#")) continue;
|
||||
if (line.startsWith("//")) continue;
|
||||
|
||||
// 支持格式:
|
||||
// CODE 100
|
||||
// CODE,100
|
||||
// CODE 100 bond
|
||||
// CODE,100,bond
|
||||
String[] parts = line
|
||||
.split("[,\\s]+");
|
||||
if (parts.length < 2) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 格式错误: "
|
||||
+ line);
|
||||
continue;
|
||||
}
|
||||
|
||||
String code = parts[0].trim();
|
||||
if (code.isEmpty()) continue;
|
||||
|
||||
int amount;
|
||||
try {
|
||||
amount = Integer.parseInt(
|
||||
parts[1].trim());
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 金额格式错误: "
|
||||
+ parts[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
String type = parts.length >= 3
|
||||
? parts[2].trim() : "bond";
|
||||
|
||||
boolean ok = addCDK(code, amount,
|
||||
type);
|
||||
if (ok) {
|
||||
count++;
|
||||
} else {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] " + f.getName()
|
||||
+ " L" + lineNum
|
||||
+ " 重复跳过: "
|
||||
+ code);
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] 读取失败: " + f.getName()
|
||||
+ " " + e.getMessage());
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public int importFromFile(String fileName) {
|
||||
File dir = new File(
|
||||
plugin.getDataFolder(), "cdk");
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 先在 cdk/ 目录下找
|
||||
File f = new File(dir, fileName);
|
||||
if (!f.exists()) {
|
||||
// 再在插件根目录找
|
||||
f = new File(
|
||||
plugin.getDataFolder(), fileName);
|
||||
}
|
||||
if (!f.exists()) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] 文件不存在: "
|
||||
+ fileName);
|
||||
return -1;
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 开始导入: "
|
||||
+ f.getAbsolutePath()
|
||||
+ " 大小:" + f.length()
|
||||
+ " bytes");
|
||||
|
||||
int count = loadCDKFile(f);
|
||||
plugin.getLogger().info(
|
||||
"[CDK] 导入完成: " + count + " 条");
|
||||
return count;
|
||||
}
|
||||
|
||||
public int importFromSdf1(String fileName) {
|
||||
int count = 0;
|
||||
Scoreboard board = Bukkit.getScoreboardManager()
|
||||
.getMainScoreboard();
|
||||
// 尝试从sdf1的计分板读取口令
|
||||
// 这部分依赖sdf1插件的配置
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
private boolean addCDK(String code, int amount,
|
||||
String type) {
|
||||
try {
|
||||
// 先检查是否已存在
|
||||
PreparedStatement check =
|
||||
db.prepareStatement(
|
||||
"SELECT code FROM cdk "
|
||||
+ "WHERE code=?");
|
||||
check.setString(1, code);
|
||||
ResultSet rs = check.executeQuery();
|
||||
if (rs.next()) {
|
||||
rs.close();
|
||||
check.close();
|
||||
return false;
|
||||
}
|
||||
rs.close();
|
||||
check.close();
|
||||
|
||||
PreparedStatement ps =
|
||||
db.prepareStatement(
|
||||
"INSERT INTO cdk "
|
||||
+ "(code, amount, type,"
|
||||
+ " created_time)"
|
||||
+ " VALUES (?,?,?,?)");
|
||||
ps.setString(1, code);
|
||||
ps.setInt(2, amount);
|
||||
ps.setString(3,
|
||||
type != null ? type : "bond");
|
||||
ps.setLong(4,
|
||||
System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().warning(
|
||||
"[CDK] addCDK失败: " + code
|
||||
+ " " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 替换整个 redeem 方法 =====
|
||||
/**
|
||||
* 兑换CDK,返回结果字符串
|
||||
* @return "success:金额:余额前:余额后"
|
||||
* 或 "fail:原因"
|
||||
*/
|
||||
public String redeem(String code, String player) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
@@ -242,45 +62,54 @@ public class CDKManager {
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (!rs.next()) {
|
||||
rs.close(); ps.close();
|
||||
return null;
|
||||
return "fail:not_found";
|
||||
}
|
||||
int amount = rs.getInt("amount");
|
||||
String type = rs.getString("type");
|
||||
int used = rs.getInt("used");
|
||||
rs.close(); ps.close();
|
||||
|
||||
if (used == 1) return "该兑换码已被使用";
|
||||
if (used == 1)
|
||||
return "fail:already_used";
|
||||
|
||||
// [新增] 冻结检查
|
||||
if (plugin.getBonds().isFrozen(player)) {
|
||||
return "账户已被冻结,无法兑换";
|
||||
}
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(player))
|
||||
return "fail:frozen";
|
||||
|
||||
PreparedStatement up = db.prepareStatement(
|
||||
"UPDATE cdk SET used=1,"
|
||||
+ "used_by=?,used_time=? "
|
||||
+ "WHERE code=?");
|
||||
up.setString(1, player);
|
||||
up.setLong(2, System.currentTimeMillis());
|
||||
up.setLong(2,
|
||||
System.currentTimeMillis());
|
||||
up.setString(3, code);
|
||||
up.executeUpdate(); up.close();
|
||||
|
||||
if ("bond".equals(type)) {
|
||||
// [修改] 使用带流水的重载方法
|
||||
plugin.getBonds().addBonds(player, amount,
|
||||
BondManager.TX_REDEEM, "",
|
||||
"system", "CDK兑换: " + code);
|
||||
return "兑换成功!获得 " + amount + " 债券";
|
||||
int bef = plugin.getBonds()
|
||||
.getBonds(player);
|
||||
plugin.getBonds().addBonds(player,
|
||||
amount,
|
||||
BondManager.TX_REDEEM,
|
||||
"", "CDK系统",
|
||||
"CDK兑换: " + code);
|
||||
int aft = plugin.getBonds()
|
||||
.getBonds(player);
|
||||
return "success:" + amount
|
||||
+ ":" + bef + ":" + aft;
|
||||
}
|
||||
return null;
|
||||
return "fail:unknown_type";
|
||||
} catch (SQLException e) {
|
||||
return "兑换失败";
|
||||
return "fail:" + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 聊天输入系统(主插件转发调用)=====
|
||||
// ===== 聊天输入系统 =====
|
||||
|
||||
public void requestInput(Player p, String type, String context) {
|
||||
public void requestInput(Player p,
|
||||
String type,
|
||||
String context) {
|
||||
CDKInput input = new CDKInput();
|
||||
input.type = type;
|
||||
input.context = context;
|
||||
@@ -289,129 +118,133 @@ public class CDKManager {
|
||||
}
|
||||
|
||||
public boolean isListening(String playerName) {
|
||||
return pendingInputs.containsKey(playerName);
|
||||
return pendingInputs
|
||||
.containsKey(playerName);
|
||||
}
|
||||
|
||||
public void onChat(Player p, String msg) {
|
||||
CDKInput input = pendingInputs.remove(p.getName());
|
||||
CDKInput input = pendingInputs
|
||||
.remove(p.getName());
|
||||
if (input == null) return;
|
||||
|
||||
if (System.currentTimeMillis() - input.time > 60000) {
|
||||
p.sendMessage("§c输入超时,请重新操作");
|
||||
if (System.currentTimeMillis()
|
||||
- input.time > 60000) {
|
||||
p.sendMessage("§c输入超时");
|
||||
return;
|
||||
}
|
||||
|
||||
if ("cdk".equals(input.type)) {
|
||||
String result = redeem(msg.trim(), p.getName());
|
||||
p.sendMessage(result != null
|
||||
? "§e§l[CDK] §f" + result
|
||||
: "§c§l[CDK] §f无效的兑换码");
|
||||
String result = redeem(
|
||||
msg.trim(), p.getName());
|
||||
if (result != null
|
||||
&& result.startsWith("success:")) {
|
||||
String[] parts = result.split(":");
|
||||
int amt = Integer.parseInt(parts[1]);
|
||||
int bef = Integer.parseInt(parts[2]);
|
||||
int aft = Integer.parseInt(parts[3]);
|
||||
p.sendMessage("§e§l[CDK] §f兑换成功!"
|
||||
+ "获得 §c" + amt + "§f债券");
|
||||
p.sendMessage("§7余额: §e" + bef
|
||||
+ " §7-> §a" + aft);
|
||||
} else {
|
||||
p.sendMessage("§c§l[CDK] §f"
|
||||
+ (result != null
|
||||
? result.replace("fail:", "")
|
||||
: "无效的兑换码"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ("econ".equals(input.type)) {
|
||||
handleEconInput(p, msg.trim(), input.context);
|
||||
handleEconInput(p, msg.trim(),
|
||||
input.context);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("bond".equals(input.type)) {
|
||||
handleBondInput(p, msg.trim(), input.context);
|
||||
handleBondInput(p, msg.trim(),
|
||||
input.context);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEconInput(Player admin, String msg, String target) {
|
||||
net.milkbowl.vault.economy.Economy econ = null;
|
||||
private void handleEconInput(Player admin,
|
||||
String msg,
|
||||
String target) {
|
||||
net.milkbowl.vault.economy.Economy econ
|
||||
= null;
|
||||
try {
|
||||
var reg = plugin.getServer().getServicesManager()
|
||||
.getRegistration(net.milkbowl.vault.economy.Economy.class);
|
||||
if (reg != null) econ = reg.getProvider();
|
||||
var reg = plugin.getServer()
|
||||
.getServicesManager()
|
||||
.getRegistration(
|
||||
net.milkbowl.vault
|
||||
.economy
|
||||
.Economy.class);
|
||||
if (reg != null)
|
||||
econ = reg.getProvider();
|
||||
} catch (Exception ignored) {}
|
||||
if (econ == null) {
|
||||
admin.sendMessage("§c经济系统不可用");
|
||||
return;
|
||||
}
|
||||
if (!msg.startsWith("+") && !msg.startsWith("-")) {
|
||||
admin.sendMessage("§c格式: §a+100 §7给钱 / §c-50 §7扣钱");
|
||||
if (!msg.startsWith("+")
|
||||
&& !msg.startsWith("-")) {
|
||||
admin.sendMessage("§c格式: §a+100"
|
||||
+ " §7或 §c-50");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String numStr = msg.substring(1).trim();
|
||||
double amount = Double.parseDouble(numStr);
|
||||
String numStr =
|
||||
msg.substring(1).trim();
|
||||
double amount =
|
||||
Double.parseDouble(numStr);
|
||||
if (amount <= 0) {
|
||||
admin.sendMessage("§c金额必须大于0");
|
||||
admin.sendMessage("§c金额必须>0");
|
||||
return;
|
||||
}
|
||||
if (msg.startsWith("-")) {
|
||||
Player tp = plugin.getServer().getPlayerExact(target);
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp == null) {
|
||||
admin.sendMessage("§c" + target + " 不在线");
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 不在线");
|
||||
return;
|
||||
}
|
||||
if (!econ.has(tp, amount)) {
|
||||
admin.sendMessage("§c" + target + " 余额不足");
|
||||
admin.sendMessage("§c余额不足");
|
||||
return;
|
||||
}
|
||||
double oldBal = econ.getBalance(tp);
|
||||
econ.withdrawPlayer(tp, amount);
|
||||
double newBal = econ.getBalance(tp);
|
||||
admin.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
admin.sendMessage("§7目标: §e" + target);
|
||||
admin.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
admin.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
admin.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
tp.sendMessage("§c扣款: §e-$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §c" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7操作者: §e" + admin.getName());
|
||||
tp.sendMessage("§7============== §c余额扣款 §7==============");
|
||||
admin.sendMessage("§a扣款成功");
|
||||
} else {
|
||||
double oldBal = econ.getBalance(target);
|
||||
econ.depositPlayer(target, amount);
|
||||
double newBal = econ.getBalance(target);
|
||||
Player tp = plugin.getServer().getPlayerExact(target);
|
||||
admin.sendMessage("§7============== §6余额转账 §7==============");
|
||||
admin.sendMessage("§7目标: §e" + target);
|
||||
admin.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
admin.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
admin.sendMessage("§7============== §6余额转账 §7==============");
|
||||
if (tp != null) {
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
tp.sendMessage("§a到账: §e$" + String.format("%.2f", amount));
|
||||
tp.sendMessage("§7余额: §e" + String.format("%.2f", oldBal) + " → §a" + String.format("%.2f", newBal));
|
||||
tp.sendMessage("§7来自: §e" + admin.getName());
|
||||
tp.sendMessage("§7============== §a收到转账 §7==============");
|
||||
}
|
||||
admin.sendMessage("§a到账成功");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
admin.sendMessage("§c格式错误,请输入 §a+数字 §7或 §c-数字");
|
||||
admin.sendMessage("§c格式错误");
|
||||
}
|
||||
}
|
||||
// ===== 替换整个 handleBondInput 方法 =====
|
||||
|
||||
private void handleBondInput(Player admin,
|
||||
String msg,
|
||||
String target) {
|
||||
if (!msg.startsWith("+")
|
||||
&& !msg.startsWith("-")) {
|
||||
admin.sendMessage(
|
||||
"§c格式: §a+100 §7给债券"
|
||||
+ " / §c-50 §7扣债券");
|
||||
admin.sendMessage(
|
||||
"§7可附加理由: §a+100 活动奖励");
|
||||
admin.sendMessage("§c格式: §a+100"
|
||||
+ " §7或 §c-50");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String sign = msg.substring(0, 1);
|
||||
String rest = msg.substring(1).trim();
|
||||
|
||||
// 拆分金额和理由
|
||||
String[] parts = rest.split("\\s+", 2);
|
||||
String rest =
|
||||
msg.substring(1).trim();
|
||||
String[] parts =
|
||||
rest.split("\\s+", 2);
|
||||
int amount = Integer.parseInt(parts[0]);
|
||||
String reason = parts.length >= 2
|
||||
? parts[1].trim()
|
||||
: "管理员手动调整";
|
||||
|
||||
if (amount <= 0) {
|
||||
admin.sendMessage("§c金额必须大于0");
|
||||
admin.sendMessage("§c金额必须>0");
|
||||
return;
|
||||
}
|
||||
String opName = admin.getName();
|
||||
@@ -422,123 +255,32 @@ public class CDKManager {
|
||||
if ("-".equals(sign)) {
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(target)) {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 账户已被冻结");
|
||||
admin.sendMessage("§c账户冻结");
|
||||
return;
|
||||
}
|
||||
int oldBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
if (plugin.getBonds().deductBonds(
|
||||
target, amount, txType,
|
||||
"", opName, reason)) {
|
||||
int newBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
admin.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
admin.sendMessage(
|
||||
"§7目标: §e" + target);
|
||||
admin.sendMessage("§c扣款: §c-"
|
||||
+ amount + " §6枚");
|
||||
admin.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
admin.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §c"
|
||||
+ newBal);
|
||||
admin.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null) {
|
||||
tp.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
tp.sendMessage("§c扣款: §c-"
|
||||
+ amount + " §6枚");
|
||||
tp.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
tp.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §c"
|
||||
+ newBal);
|
||||
tp.sendMessage("§7操作者: §e"
|
||||
+ opName);
|
||||
tp.sendMessage(
|
||||
"§7============== §c"
|
||||
+ "债券扣款 §7"
|
||||
+ "=============");
|
||||
}
|
||||
admin.sendMessage("§a扣除成功");
|
||||
} else {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 债券不足(当前: "
|
||||
+ oldBal + ")");
|
||||
admin.sendMessage("§c债券不足");
|
||||
}
|
||||
} else {
|
||||
if (plugin.getBonds()
|
||||
.isFrozen(target)) {
|
||||
admin.sendMessage("§c" + target
|
||||
+ " 账户已被冻结");
|
||||
admin.sendMessage("§c账户冻结");
|
||||
return;
|
||||
}
|
||||
int oldBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
plugin.getBonds().addBonds(target,
|
||||
amount, txType,
|
||||
"", opName, reason);
|
||||
int newBal = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
admin.sendMessage(
|
||||
"§7============== §6"
|
||||
+ "债券转账 §7"
|
||||
+ "=============");
|
||||
admin.sendMessage(
|
||||
"§7目标: §e" + target);
|
||||
admin.sendMessage("§a到账: §a+"
|
||||
+ amount + " §6枚");
|
||||
admin.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
admin.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §a"
|
||||
+ newBal);
|
||||
admin.sendMessage(
|
||||
"§7============== §6"
|
||||
+ "债券转账 §7"
|
||||
+ "=============");
|
||||
Player tp = plugin.getServer()
|
||||
.getPlayerExact(target);
|
||||
if (tp != null) {
|
||||
tp.sendMessage(
|
||||
"§7============== §a"
|
||||
+ "收到债券 §7"
|
||||
+ "=============");
|
||||
tp.sendMessage("§a到账: §a+"
|
||||
+ amount + " §6枚");
|
||||
tp.sendMessage("§7理由: §f"
|
||||
+ reason);
|
||||
tp.sendMessage("§7债券: §e"
|
||||
+ oldBal + " → §a"
|
||||
+ newBal);
|
||||
tp.sendMessage("§7来自: §e"
|
||||
+ opName);
|
||||
tp.sendMessage(
|
||||
"§7============== §a"
|
||||
+ "收到债券 §7"
|
||||
+ "=============");
|
||||
}
|
||||
admin.sendMessage("§a发放成功");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
admin.sendMessage("§c格式错误,请输入 "
|
||||
+ "§a+数字 §7或 §c-数字");
|
||||
admin.sendMessage("§c格式错误");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void cancelInput(String player) {
|
||||
pendingInputs.remove(player);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class CheckInManager {
|
||||
return today.equals(last);
|
||||
}
|
||||
|
||||
// ===== 签到 =====
|
||||
// ===== 签到 =====
|
||||
public String checkIn(Player p) {
|
||||
String name = p.getName();
|
||||
@@ -53,7 +54,8 @@ public class CheckInManager {
|
||||
java.util.Date todayD =
|
||||
new SimpleDateFormat("yyyy-MM-dd")
|
||||
.parse(today);
|
||||
long diff = todayD.getTime() - lastD.getTime();
|
||||
long diff = todayD.getTime()
|
||||
- lastD.getTime();
|
||||
if (diff > 86400000L * 2) {
|
||||
streak = 0;
|
||||
} else if (diff > 86400000L) {
|
||||
@@ -71,35 +73,49 @@ public class CheckInManager {
|
||||
else if (streak >= 14) multi = 1.3;
|
||||
else if (streak >= 7) multi = 1.1;
|
||||
|
||||
int basePoints = plugin.getConfig2().checkinPoints;
|
||||
int basePoints =
|
||||
plugin.getConfig2().checkinPoints;
|
||||
int points = (int) (basePoints * multi);
|
||||
|
||||
plugin.getDb().addPoints(name, points);
|
||||
plugin.getDb().setField(name, "checkin_streak", streak);
|
||||
plugin.getDb().setField(name, "last_checkin_date", today);
|
||||
plugin.getDb().setField(name,
|
||||
"checkin_streak", streak);
|
||||
plugin.getDb().setField(name,
|
||||
"last_checkin_date", today);
|
||||
|
||||
Object totalObj = plugin.getDb()
|
||||
.getField(name, "total_checkin_days");
|
||||
int total = totalObj != null
|
||||
? ((Number) totalObj).intValue()
|
||||
: 0;
|
||||
plugin.getDb().setField(name, "total_checkin_days",
|
||||
total + 1);
|
||||
plugin.getDb().setField(name,
|
||||
"total_checkin_days", total + 1);
|
||||
|
||||
int bondReward = 1;
|
||||
if (streak >= 21) bondReward = 3;
|
||||
else if (streak >= 14) bondReward = 2;
|
||||
else if (streak >= 7) bondReward = 2;
|
||||
plugin.getBonds().addBonds(name, bondReward);
|
||||
// ★ 债券盲盒奖励(6参数版,写入流水)★
|
||||
int bondMin =
|
||||
plugin.getConfig2().checkinBondMin;
|
||||
int bondMax =
|
||||
plugin.getConfig2().checkinBondMax;
|
||||
if (bondMin <= 0) bondMin = 1;
|
||||
if (bondMax < bondMin) bondMax = bondMin;
|
||||
int bondReward = bondMin
|
||||
+ new java.util.Random()
|
||||
.nextInt(bondMax - bondMin + 1);
|
||||
plugin.getBonds().addBonds(name, bondReward,
|
||||
"daily_sign", "", "签到系统",
|
||||
"每日签到连续" + streak + "天");
|
||||
|
||||
return plugin.getConfig2().msg("checkin_success")
|
||||
.replace("{points}", String.valueOf(points))
|
||||
.replace("{streak}", String.valueOf(streak))
|
||||
.replace("{multi}", String.valueOf(multi))
|
||||
return plugin.getConfig2()
|
||||
.msg("checkin_success")
|
||||
.replace("{points}",
|
||||
String.valueOf(points))
|
||||
.replace("{streak}",
|
||||
String.valueOf(streak))
|
||||
.replace("{multi}",
|
||||
String.valueOf(multi))
|
||||
+ " §e+" + bondReward + "债券";
|
||||
}
|
||||
|
||||
|
||||
// ===== 获取连续天数 =====
|
||||
public int getStreak(String name) {
|
||||
Object val = plugin.getDb()
|
||||
@@ -111,7 +127,16 @@ public class CheckInManager {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 标准化日期
|
||||
private String normalizeDate(String input) {
|
||||
String s = input.trim().replace("/", "-");
|
||||
if (s.matches("\\d{8}")) {
|
||||
return s.substring(0, 4) + "-"
|
||||
+ s.substring(4, 6) + "-"
|
||||
+ s.substring(6, 8);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
// ===== 补签入口 =====
|
||||
public String backCheckIn(
|
||||
Player p, String dateStr) {
|
||||
@@ -130,14 +155,12 @@ public class CheckInManager {
|
||||
} catch (Exception ex) {
|
||||
return "§c无效日期: " + dateStr;
|
||||
}
|
||||
String reformat =
|
||||
sdf.format(target);
|
||||
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今天不需要补签";
|
||||
}
|
||||
@@ -146,19 +169,16 @@ public class CheckInManager {
|
||||
if (normalized.compareTo(
|
||||
threeDaysAgo) < 0) {
|
||||
return "§c只能补签3天内"
|
||||
+ "(最多到 "
|
||||
+ threeDaysAgo + ")";
|
||||
+ "(最多到 " + threeDaysAgo + ")";
|
||||
}
|
||||
|
||||
Object val = plugin.getDb()
|
||||
.getField(name,
|
||||
"backcheck_dates");
|
||||
.getField(name, "backcheck_dates");
|
||||
String existing = val != null
|
||||
? val.toString() : "";
|
||||
boolean alreadyBacked = false;
|
||||
if (!existing.isEmpty()) {
|
||||
for (String d
|
||||
: existing.split(",")) {
|
||||
for (String d : existing.split(",")) {
|
||||
if (normalizeDate(d.trim())
|
||||
.equals(normalized)) {
|
||||
alreadyBacked = true;
|
||||
@@ -167,71 +187,94 @@ public class CheckInManager {
|
||||
}
|
||||
}
|
||||
if (alreadyBacked) {
|
||||
return "§c该日期已补签: "
|
||||
+ normalized;
|
||||
return "§c该日期已补签: " + normalized;
|
||||
}
|
||||
|
||||
Object normalCheck =
|
||||
plugin.getDb().getField(
|
||||
name,
|
||||
"last_checkin_date");
|
||||
String lastCheckin = normalCheck
|
||||
!= null
|
||||
? normalCheck.toString()
|
||||
: "";
|
||||
name, "last_checkin_date");
|
||||
String lastCheckin = normalCheck != null
|
||||
? normalCheck.toString() : "";
|
||||
if (lastCheckin.equals(normalized)) {
|
||||
return "§c该日期已签到: "
|
||||
+ normalized;
|
||||
return "§c该日期已签到: " + normalized;
|
||||
}
|
||||
|
||||
if (!plugin.getDb()
|
||||
.deductPoints(name, 10)) {
|
||||
return "§c积分不足,补签需 10 积分";
|
||||
}
|
||||
// ★ 补签消耗:3债券 + 10积分
|
||||
// 积分不够时:用 9债券(3倍)代替 ★
|
||||
int bondCost = 3;
|
||||
int pointCost = 10;
|
||||
int bondAltCost = 9; // 3倍债券替代
|
||||
|
||||
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;
|
||||
int myBonds = plugin.getBonds()
|
||||
.getBonds(name);
|
||||
Object ptsObj = plugin.getDb()
|
||||
.getField(name, "points");
|
||||
int myPoints = ptsObj != null
|
||||
? ((Number) ptsObj).intValue()
|
||||
: 0;
|
||||
|
||||
|
||||
// 方案A:3债券 + 10积分
|
||||
boolean canA = myBonds >= bondCost
|
||||
&& myPoints >= pointCost;
|
||||
// 方案B:9债券(积分不够时)
|
||||
boolean canB = myBonds >= bondAltCost
|
||||
&& myPoints < pointCost;
|
||||
|
||||
if (!canA && !canB) {
|
||||
if (myPoints < pointCost
|
||||
&& myBonds < bondAltCost) {
|
||||
return "§c补签费用不足"
|
||||
+ "(需3债券+10积分,"
|
||||
+ "或9债券代替)"
|
||||
+ " 当前: "
|
||||
+ myBonds + "债券 "
|
||||
+ myPoints + "积分";
|
||||
}
|
||||
econ.withdrawPlayer(p, 10);
|
||||
if (myBonds < bondCost) {
|
||||
return "§c债券不足(需"
|
||||
+ bondCost + "枚)";
|
||||
}
|
||||
return "§c积分不足(需"
|
||||
+ pointCost + "分)";
|
||||
}
|
||||
|
||||
// 执行扣款
|
||||
if (canA) {
|
||||
// 方案A:3债券 + 10积分
|
||||
plugin.getBonds().deductBonds(
|
||||
name, bondCost,
|
||||
"admin_deduct", "",
|
||||
"补签系统", "补签扣款");
|
||||
plugin.getDb().deductPoints(
|
||||
name, pointCost);
|
||||
} else {
|
||||
// 方案B:9债券代替
|
||||
plugin.getBonds().deductBonds(
|
||||
name, bondAltCost,
|
||||
"admin_deduct", "",
|
||||
"补签系统", "补签扣款(债券替代)");
|
||||
}
|
||||
|
||||
doBackCheckIn(p, normalized);
|
||||
|
||||
String costMsg = canA
|
||||
? bondCost + "债券+" + pointCost + "积分"
|
||||
: bondAltCost + "债券(替代)";
|
||||
return "§a补签成功!日期: "
|
||||
+ normalized
|
||||
+ " §7(扣除10积分+10经济)";
|
||||
+ " §7消耗: " + costMsg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===== 执行补签 =====
|
||||
// ===== 执行补签 =====
|
||||
public void doBackCheckIn(
|
||||
Player p, String dateStr) {
|
||||
String name = p.getName();
|
||||
Object val = plugin.getDb()
|
||||
.getField(name,
|
||||
"backcheck_dates");
|
||||
.getField(name, "backcheck_dates");
|
||||
String existing = val != null
|
||||
? val.toString() : "";
|
||||
String updated = existing.isEmpty()
|
||||
@@ -241,25 +284,8 @@ public class CheckInManager {
|
||||
"backcheck_dates", updated);
|
||||
int streak = getStreak(name);
|
||||
plugin.getDb().setField(name,
|
||||
"checkin_streak",
|
||||
streak + 1);
|
||||
int bondReward = 1;
|
||||
if (streak >= 21) bondReward = 3;
|
||||
else if (streak >= 14) bondReward = 2;
|
||||
else if (streak >= 7) bondReward = 2;
|
||||
plugin.getBonds().addBonds(name, bondReward);
|
||||
}
|
||||
// ===== 标准化日期 =====
|
||||
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;
|
||||
"checkin_streak", streak + 1);
|
||||
// ★ 补签不发放奖励,只记录
|
||||
}
|
||||
|
||||
// ===== 获取N天前日期 =====
|
||||
|
||||
@@ -7,10 +7,10 @@ import java.util.*;
|
||||
public class ConfigManager {
|
||||
|
||||
private final File dataFolder;
|
||||
private final Map<String, String> messages =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<String, String> smtpSettings =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<String, String> messages
|
||||
= new LinkedHashMap<>();
|
||||
private final Map<String, String> smtpSettings
|
||||
= new LinkedHashMap<>();
|
||||
|
||||
// ===== 公共字段 =====
|
||||
public String adminTag = "admin";
|
||||
@@ -30,7 +30,6 @@ public class ConfigManager {
|
||||
public int httpPort = 8080;
|
||||
public String garbageTag = "admin";
|
||||
|
||||
// 签到经济奖励
|
||||
public double checkinRewardFixed = 0;
|
||||
public double checkinRewardMin = 0;
|
||||
public double checkinRewardMax = 0;
|
||||
@@ -38,35 +37,36 @@ public class ConfigManager {
|
||||
public int checkinPoints = 10;
|
||||
public String checkinRewardType = "none";
|
||||
public int backCheckPointMultiplier = 5;
|
||||
// 提成/工单
|
||||
public int checkinBondMin = 1;
|
||||
public int checkinBondMax = 3;
|
||||
|
||||
public double providerPointsPerScore = 2.0;
|
||||
public int baseEconomyReward = 100;
|
||||
public double providerEconomyPerScore = 50.0;
|
||||
public int requesterPointsReward = 10;
|
||||
|
||||
// 垃圾站
|
||||
public boolean garbageEnabled = true;
|
||||
public int garbageInterval = 300;
|
||||
public int garbageMaxRounds = 1;
|
||||
|
||||
// ★ 构造函数接收 File,和 Main.java 调用一致 ★
|
||||
public ConfigManager(File dataFolder) {
|
||||
this.dataFolder = dataFolder;
|
||||
}
|
||||
public String rewardChannel = "债券"; // bonds = 债券,economy = Vault经济
|
||||
|
||||
|
||||
// ==================== 消息 ====================
|
||||
|
||||
public void loadMessages() {
|
||||
messages.clear();
|
||||
File file =
|
||||
new File(dataFolder, "消息.txt");
|
||||
if (!file.exists())
|
||||
createDefaultMessages(file);
|
||||
File file = new File(dataFolder, "消息.txt");
|
||||
if (!file.exists()) createDefaultMessages(file);
|
||||
loadMap(file, messages);
|
||||
}
|
||||
|
||||
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>", "&r");
|
||||
val = val.replace("<br>", "\n");
|
||||
@@ -79,81 +79,22 @@ public class ConfigManager {
|
||||
private void createDefaultMessages(File f) {
|
||||
List<String> L = new ArrayList<>();
|
||||
L.add("# ===== Sdf1_login 消息配置 =====");
|
||||
L.add("# 占位符用 {xxx} 表示");
|
||||
L.add("# 修改后执行 /sdf1_login reload");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 登录注册 ----");
|
||||
L.add("login_timeout=§c登录超时,请重新登录");
|
||||
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
|
||||
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
|
||||
L.add("already_registered=§c您已注册过账号");
|
||||
L.add("login_timeout=§c登录超时");
|
||||
L.add("not_registered=§e您尚未注册");
|
||||
L.add("not_logged_in=§e您尚未登录");
|
||||
L.add("already_registered=§c您已注册过");
|
||||
L.add("already_logged_in=§c您已登录");
|
||||
L.add("reg_success=§a注册成功!欢迎来到服务器");
|
||||
L.add("reg_success=§a注册成功!");
|
||||
L.add("login_success=§a登录成功!");
|
||||
L.add("login_failed=§c密码错误,请重试");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 密码 ----");
|
||||
L.add("login_failed=§c密码错误");
|
||||
L.add("password_wrong=§c密码错误");
|
||||
L.add("password_changed=§a密码修改成功!");
|
||||
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
|
||||
L.add("password_format_error=§c密码需6位以上含大小写和数字");
|
||||
L.add("password_same=§c新密码不能与旧密码相同");
|
||||
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
|
||||
L.add("reset_sent=§a临时密码已发送到您的邮箱");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 签到积分 ----");
|
||||
L.add("checkin_already=§c您今日已签到");
|
||||
L.add("checkin_success=§a签到成功!获得 {points} 积分");
|
||||
L.add("points_insufficient=§c积分不足,当前: {points}");
|
||||
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 挂机检测 ----");
|
||||
L.add("afk_set_enabled=§a挂机检测已开启");
|
||||
L.add("afk_set_disabled=§c挂机检测已关闭");
|
||||
L.add("afk_kick=§c因挂机过久被踢出");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 邮箱 ----");
|
||||
L.add("email_set=§a邮箱设置成功:{email}");
|
||||
L.add("email_format_error=§c邮箱格式不正确");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 邀请 ----");
|
||||
L.add("invite_code_generated=§a邀请码:{code}");
|
||||
L.add("invite_used=§a邀请成功,邀请人:{player}");
|
||||
L.add("invite_self=§c不能使用自己的邀请码");
|
||||
L.add("invite_not_found=§c邀请码不存在");
|
||||
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 礼包 ----");
|
||||
L.add("gift_not_ready=§c礼包条件未满足");
|
||||
L.add("gift_claimed=§a礼包领取成功!");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 审批与IP ----");
|
||||
L.add("need_approval=§e注册需审批 (工单#{id})");
|
||||
L.add("ip_max_accounts=§cIP注册已达上限");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 管理员 ----");
|
||||
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 聊天过滤 ----");
|
||||
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_violation=§7当前违规次数: §e{count}");
|
||||
L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url}");
|
||||
L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚");
|
||||
L.add("");
|
||||
|
||||
L.add("# ---- 通用 ----");
|
||||
L.add("chat_muted=§c你已被禁言");
|
||||
L.add("未知参数=§c未知参数或权限不足");
|
||||
|
||||
writeLines(f, L);
|
||||
}
|
||||
|
||||
@@ -161,10 +102,8 @@ public class ConfigManager {
|
||||
|
||||
public void loadSmtp() {
|
||||
smtpSettings.clear();
|
||||
File file =
|
||||
new File(dataFolder, "SMTP设置.txt");
|
||||
if (!file.exists())
|
||||
createDefaultSmtp(file);
|
||||
File file = new File(dataFolder, "SMTP设置.txt");
|
||||
if (!file.exists()) createDefaultSmtp(file);
|
||||
loadMap(file, smtpSettings);
|
||||
}
|
||||
|
||||
@@ -177,10 +116,9 @@ public class ConfigManager {
|
||||
}
|
||||
|
||||
public void saveSmtp() {
|
||||
File file =
|
||||
new File(dataFolder, "SMTP设置.txt");
|
||||
File file = new File(dataFolder, "SMTP设置.txt");
|
||||
List<String> L = new ArrayList<>();
|
||||
L.add("# ===== SMTP 邮件服务器配置 =====");
|
||||
L.add("# ===== SMTP 配置 =====");
|
||||
for (Map.Entry<String, String> e
|
||||
: smtpSettings.entrySet()) {
|
||||
L.add(e.getKey() + "=" + e.getValue());
|
||||
@@ -190,10 +128,10 @@ public class ConfigManager {
|
||||
|
||||
private void createDefaultSmtp(File f) {
|
||||
List<String> L = new ArrayList<>();
|
||||
L.add("# ===== SMTP 邮件服务器配置 =====");
|
||||
L.add("# ===== SMTP 配置 =====");
|
||||
L.add("smtp地址=smtp.example.com");
|
||||
L.add("smtp端口=465");
|
||||
L.add("smtp账号=your_email@example.com");
|
||||
L.add("smtp账号=");
|
||||
L.add("smtp密码=");
|
||||
L.add("发件人名称=Sdf1_login");
|
||||
L.add("smtp加密=true");
|
||||
@@ -203,131 +141,134 @@ public class ConfigManager {
|
||||
// ==================== 插件设置 ====================
|
||||
|
||||
public void loadSettings() {
|
||||
File file =
|
||||
new File(dataFolder, "插件设置.txt");
|
||||
if (!file.exists())
|
||||
createDefaultSettings(file);
|
||||
Map<String, String> m =
|
||||
new LinkedHashMap<>();
|
||||
File file = new File(dataFolder, "插件设置.txt");
|
||||
if (!file.exists()) createDefaultSettings(file);
|
||||
Map<String, String> m = new LinkedHashMap<>();
|
||||
loadMap(file, m);
|
||||
|
||||
adminTag = m.getOrDefault(
|
||||
"管理标签", adminTag);
|
||||
adminPassword = m.getOrDefault(
|
||||
"管理密码", adminPassword);
|
||||
maxAccountsPerIP = parseInt(
|
||||
m.getOrDefault("每IP最大账号数", "3"), 3);
|
||||
approvalMode = m.getOrDefault(
|
||||
"审批模式", approvalMode);
|
||||
autoApproveDelayMinutes = parseInt(
|
||||
m.getOrDefault("自动审批延迟分钟", "30"),
|
||||
30);
|
||||
loginTimeout = parseInt(
|
||||
m.getOrDefault("登录超时秒数", "60"), 60);
|
||||
afkEnabled = Boolean.parseBoolean(
|
||||
m.getOrDefault("挂机踢出", "false"));
|
||||
afkTimeout = parseInt(
|
||||
m.getOrDefault("挂机超时秒数", "300"), 300);
|
||||
smtpSsl = Boolean.parseBoolean(
|
||||
m.getOrDefault("smtp加密", "true"));
|
||||
httpPort = parseInt(
|
||||
m.getOrDefault("http-port", "8080"),
|
||||
8080);
|
||||
broadcastServerUrl = m.getOrDefault(
|
||||
"resource-pack-url", "");
|
||||
adminTag = m.getOrDefault("管理标签", adminTag);
|
||||
adminPassword = m.getOrDefault("管理密码", adminPassword);
|
||||
maxAccountsPerIP = parseInt(m.getOrDefault("每IP最大账号数", "3"), 3);
|
||||
approvalMode = m.getOrDefault("审批模式", approvalMode);
|
||||
autoApproveDelayMinutes = parseInt(m.getOrDefault("自动审批延迟分钟", "30"), 30);
|
||||
loginTimeout = parseInt(m.getOrDefault("登录超时秒数", "60"), 60);
|
||||
afkEnabled = Boolean.parseBoolean(m.getOrDefault("挂机踢出", "false"));
|
||||
afkTimeout = parseInt(m.getOrDefault("挂机超时秒数", "300"), 300);
|
||||
smtpSsl = Boolean.parseBoolean(m.getOrDefault("smtp加密", "true"));
|
||||
httpPort = parseInt(m.getOrDefault("http-port", "8080"), 8080);
|
||||
broadcastServerUrl = m.getOrDefault("resource-pack-url", "");
|
||||
|
||||
// 提成
|
||||
providerPointsPerScore = parseDouble(
|
||||
m.getOrDefault(
|
||||
"服务商积分倍率", "2.0"), 2.0);
|
||||
baseEconomyReward = parseInt(
|
||||
m.getOrDefault(
|
||||
"基础经济奖励", "100"), 100);
|
||||
providerEconomyPerScore = parseDouble(
|
||||
m.getOrDefault(
|
||||
"经济奖励每评分", "50.0"), 50.0);
|
||||
requesterPointsReward = parseInt(
|
||||
m.getOrDefault(
|
||||
"报单人积分奖励", "10"), 10);
|
||||
providerPointsPerScore = parseDouble(m.getOrDefault("服务商积分倍率", "2.0"), 2.0);
|
||||
baseEconomyReward = parseInt(m.getOrDefault("基础经济奖励", "100"), 100);
|
||||
providerEconomyPerScore = parseDouble(m.getOrDefault("经济奖励每评分", "50.0"), 50.0);
|
||||
requesterPointsReward = parseInt(m.getOrDefault("报单人积分奖励", "10"), 10);
|
||||
|
||||
// 垃圾站
|
||||
garbageEnabled = Boolean.parseBoolean(
|
||||
m.getOrDefault(
|
||||
"垃圾站_启用", "true"));
|
||||
garbageInterval = parseInt(
|
||||
m.getOrDefault(
|
||||
"垃圾站_清理间隔秒", "300"), 300);
|
||||
garbageMaxRounds = parseInt(
|
||||
m.getOrDefault(
|
||||
"垃圾站_保留轮数", "1"), 1);
|
||||
garbageEnabled = Boolean.parseBoolean(m.getOrDefault("垃圾站_启用", "true"));
|
||||
garbageInterval = parseInt(m.getOrDefault("垃圾站_清理间隔秒", "300"), 300);
|
||||
garbageMaxRounds = parseInt(m.getOrDefault("垃圾站_保留轮数", "1"), 1);
|
||||
|
||||
// 签到
|
||||
checkinRewardFixed = parseDouble(
|
||||
m.getOrDefault(
|
||||
"签到固定金额", "0"), 0);
|
||||
checkinRewardMin = parseDouble(
|
||||
m.getOrDefault(
|
||||
"签到最小金额", "0"), 0);
|
||||
checkinRewardMax = parseDouble(
|
||||
m.getOrDefault(
|
||||
"签到最大金额", "0"), 0);
|
||||
checkinGivePoints = Boolean.parseBoolean(
|
||||
m.getOrDefault(
|
||||
"签到给积分", "true"));
|
||||
checkinPoints = parseInt(
|
||||
m.getOrDefault(
|
||||
"签到积分数量", "10"), 10);
|
||||
backCheckPointMultiplier = parseInt(
|
||||
m.getOrDefault(
|
||||
"补签积分倍率", "5"), 5);
|
||||
checkinRewardFixed = parseDouble(m.getOrDefault("签到固定金额", "0"), 0);
|
||||
checkinRewardMin = parseDouble(m.getOrDefault("签到最小金额", "0"), 0);
|
||||
checkinRewardMax = parseDouble(m.getOrDefault("签到最大金额", "0"), 0);
|
||||
checkinGivePoints = Boolean.parseBoolean(m.getOrDefault("签到给积分", "true"));
|
||||
checkinPoints = parseInt(m.getOrDefault("签到积分数量", "10"), 10);
|
||||
backCheckPointMultiplier = parseInt(m.getOrDefault("补签积分倍率", "5"), 5);
|
||||
checkinBondMin = parseInt(m.getOrDefault("签到债券最小", "1"), 1);
|
||||
checkinBondMax = parseInt(m.getOrDefault("签到债券最大", "3"), 3);
|
||||
if (checkinBondMax < checkinBondMin) checkinBondMax = checkinBondMin;
|
||||
|
||||
if (checkinRewardFixed > 0) checkinRewardType = "fixed";
|
||||
else if (checkinRewardMin > 0 && checkinRewardMax > checkinRewardMin) checkinRewardType = "range";
|
||||
else checkinRewardType = "none";
|
||||
rewardChannel = m.getOrDefault("奖励发放方式", "债券");
|
||||
rewardChannel = normalizeRewardChannel(rewardChannel);
|
||||
|
||||
// 自动判断模式
|
||||
if (checkinRewardFixed > 0) {
|
||||
checkinRewardType = "fixed";
|
||||
} else if (checkinRewardMin > 0
|
||||
&& checkinRewardMax > 0
|
||||
&& checkinRewardMax
|
||||
> checkinRewardMin) {
|
||||
checkinRewardType = "range";
|
||||
} else {
|
||||
checkinRewardType = "none";
|
||||
// 验证合法值
|
||||
if (!"economy".equalsIgnoreCase(rewardChannel)
|
||||
&& !"bonds".equalsIgnoreCase(rewardChannel)) {
|
||||
rewardChannel = "bonds";
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ★ 自动补充新配置项 ★
|
||||
boolean needAppend = false;
|
||||
List<String> newKeys = new ArrayList<>();
|
||||
if (!m.containsKey("签到债券最小")) { needAppend = true; newKeys.add("签到债券最小=" + checkinBondMin); }
|
||||
if (!m.containsKey("签到债券最大")) { needAppend = true; newKeys.add("签到债券最大=" + checkinBondMax); }
|
||||
if (!m.containsKey("奖励发放方式")) {
|
||||
needAppend = true;
|
||||
newKeys.add("奖励发放方式=债券");
|
||||
}
|
||||
if (needAppend) {
|
||||
try {
|
||||
FileWriter fw = new FileWriter(file, true);
|
||||
BufferedWriter bw = new BufferedWriter(fw);
|
||||
bw.newLine();
|
||||
for (String key : newKeys) { bw.write(key); bw.newLine(); }
|
||||
bw.flush(); bw.close();
|
||||
System.out.println("[Config] 已补充签到债券配置");
|
||||
|
||||
} catch (IOException ignored) {}
|
||||
if (!m.containsKey("奖励发放方式")) {
|
||||
try {
|
||||
FileWriter fw = new FileWriter(file, true);
|
||||
BufferedWriter bw = new BufferedWriter(fw);
|
||||
bw.newLine();
|
||||
bw.write("奖励发放方式=bonds");
|
||||
bw.newLine();
|
||||
bw.flush(); bw.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 奖励方式标准化:中英文全兼容
|
||||
* "债券" "bonds" "Bond" "BONDS" → "bonds"
|
||||
* "经济" "economy" "Economy" "ECONOMY" → "economy"
|
||||
* 其他任何值 → "bonds"(默认债券)
|
||||
*/
|
||||
private String normalizeRewardChannel(String raw) {
|
||||
if (raw == null) return "bonds";
|
||||
String s = raw.trim().toLowerCase();
|
||||
// 中文
|
||||
if (s.contains("债券")) return "bonds";
|
||||
if (s.contains("经济")) return "economy";
|
||||
// 英文
|
||||
if (s.contains("bond")) return "bonds";
|
||||
if (s.contains("econ")) return "economy";
|
||||
if (s.contains("Vault")) return "economy";
|
||||
// 兜底
|
||||
return "bonds";
|
||||
}
|
||||
|
||||
public void saveSettings() {
|
||||
File file =
|
||||
new File(dataFolder, "插件设置.txt");
|
||||
File file = new File(dataFolder, "插件设置.txt");
|
||||
List<String> L = new ArrayList<>();
|
||||
L.add("# ===== Sdf1_login 插件设置 =====");
|
||||
L.add("管理标签=" + adminTag);
|
||||
L.add("管理密码=" + adminPassword);
|
||||
L.add("每IP最大账号数=" + maxAccountsPerIP);
|
||||
L.add("审批模式=" + approvalMode);
|
||||
L.add("自动审批延迟分钟="
|
||||
+ autoApproveDelayMinutes);
|
||||
L.add("自动审批延迟分钟=" + autoApproveDelayMinutes);
|
||||
L.add("登录超时秒数=" + loginTimeout);
|
||||
L.add("挂机踢出=" + afkEnabled);
|
||||
L.add("挂机超时秒数=" + afkTimeout);
|
||||
L.add("# ---- 资源包 ----");
|
||||
L.add("resource-pack-url="
|
||||
+ broadcastServerUrl);
|
||||
L.add("resource-pack-url=" + broadcastServerUrl);
|
||||
L.add("http-port=" + httpPort);
|
||||
L.add("# ---- 垃圾站 ----");
|
||||
L.add("垃圾站_启用=" + garbageEnabled);
|
||||
L.add("垃圾站_清理间隔秒="
|
||||
+ garbageInterval);
|
||||
L.add("垃圾站_清理间隔秒=" + garbageInterval);
|
||||
L.add("垃圾站_保留轮数=" + garbageMaxRounds);
|
||||
L.add("# ---- 签到盲盒奖励 ----");
|
||||
L.add("签到固定金额=" + checkinRewardFixed);
|
||||
L.add("签到最小金额=" + checkinRewardMin);
|
||||
L.add("签到最大金额=" + checkinRewardMax);
|
||||
L.add("签到给积分=" + checkinGivePoints);
|
||||
L.add("签到积分数量=" + checkinPoints);
|
||||
L.add("补签积分倍率="
|
||||
+ backCheckPointMultiplier);
|
||||
|
||||
|
||||
L.add("签到债券最小=" + checkinBondMin);
|
||||
L.add("签到债券最大=" + checkinBondMax);
|
||||
L.add("补签积分倍率=" + backCheckPointMultiplier);
|
||||
L.add("奖励发放方式=债券");
|
||||
writeLines(file, L);
|
||||
}
|
||||
|
||||
@@ -342,177 +283,90 @@ public class ConfigManager {
|
||||
L.add("登录超时秒数=60");
|
||||
L.add("挂机踢出=false");
|
||||
L.add("挂机超时秒数=300");
|
||||
L.add("# ---- 资源包 ----");
|
||||
L.add("resource-pack-url=");
|
||||
L.add("http-port=8080");
|
||||
L.add("# ---- 垃圾站 ----");
|
||||
L.add("垃圾站_启用=true");
|
||||
L.add("垃圾站_清理间隔秒=300");
|
||||
L.add("垃圾站_保留轮数=1");
|
||||
L.add("# ---- 签到盲盒奖励 ----");
|
||||
L.add("签到固定金额=0");
|
||||
L.add("签到最小金额=50");
|
||||
L.add("签到最大金额=500");
|
||||
L.add("签到给积分=true");
|
||||
L.add("签到积分数量=10");
|
||||
L.add("签到债券最小=1");
|
||||
L.add("签到债券最大=3");
|
||||
L.add("补签积分倍率=5");
|
||||
|
||||
|
||||
L.add("奖励发放方式=债券");
|
||||
writeLines(f, L);
|
||||
}
|
||||
|
||||
// ==================== 通用工具 ====================
|
||||
|
||||
public List<String> getAfkWhitelist() {
|
||||
List<String> list = new ArrayList<>();
|
||||
File file = new File(
|
||||
dataFolder, "挂机白名单.txt");
|
||||
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();
|
||||
}
|
||||
FileWriter fw = new FileWriter(file);
|
||||
fw.write("# 每行一个玩家名\n");
|
||||
fw.flush();
|
||||
fw.close();
|
||||
} catch (IOException ignored) {}
|
||||
return list;
|
||||
}
|
||||
try (BufferedReader r =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(
|
||||
file),
|
||||
StandardCharsets
|
||||
.UTF_8))) {
|
||||
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("#")) {
|
||||
if (!line.isEmpty() && !line.startsWith("#"))
|
||||
list.add(line);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
return list;
|
||||
}
|
||||
|
||||
// ==================== 其他 ====================
|
||||
|
||||
public String generateSmtpVerifyCode() {
|
||||
String chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
+ "abcdefghijklmnopqrstuvwxyz"
|
||||
+ "0123456789";
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
java.util.Random r =
|
||||
new java.util.Random();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
sb.append(chars.charAt(
|
||||
r.nextInt(chars.length())));
|
||||
}
|
||||
java.util.Random r = new java.util.Random();
|
||||
for (int i = 0; i < 6; i++) sb.append(chars.charAt(r.nextInt(chars.length())));
|
||||
smtpVerifyCode = sb.toString();
|
||||
smtpVerifyExpire =
|
||||
System.currentTimeMillis()
|
||||
+ 300000L;
|
||||
smtpVerifyExpire = System.currentTimeMillis() + 300000L;
|
||||
return smtpVerifyCode;
|
||||
}
|
||||
|
||||
public boolean checkSmtpVerifyCode(
|
||||
String code) {
|
||||
if (System.currentTimeMillis()
|
||||
> smtpVerifyExpire) {
|
||||
smtpVerifyCode = "";
|
||||
return false;
|
||||
}
|
||||
return smtpVerifyCode
|
||||
.equalsIgnoreCase(code);
|
||||
public boolean checkSmtpVerifyCode(String code) {
|
||||
if (System.currentTimeMillis() > smtpVerifyExpire) { smtpVerifyCode = ""; return false; }
|
||||
return smtpVerifyCode.equalsIgnoreCase(code);
|
||||
}
|
||||
|
||||
public void clearSmtpVerifyCode() {
|
||||
smtpVerifyCode = "";
|
||||
smtpVerifyExpire = 0;
|
||||
}
|
||||
public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; }
|
||||
public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; }
|
||||
public void recordSmtpSendTime() { smtpLastSendTime = System.currentTimeMillis(); }
|
||||
|
||||
public boolean canSendSmtpVerify() {
|
||||
return System.currentTimeMillis()
|
||||
- smtpLastSendTime >= 60000L;
|
||||
}
|
||||
// ==================== 文件工具 ====================
|
||||
|
||||
public void recordSmtpSendTime() {
|
||||
smtpLastSendTime =
|
||||
System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// ==================== 文件读写 ====================
|
||||
|
||||
private void loadMap(File file,
|
||||
Map<String, String> map) {
|
||||
try (BufferedReader r =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(
|
||||
file),
|
||||
StandardCharsets
|
||||
.UTF_8))) {
|
||||
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;
|
||||
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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
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 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 ignored) {}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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; } }
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class CypayCommand
|
||||
}
|
||||
|
||||
// ===== /cypay import =====
|
||||
if (args.length == 2
|
||||
/* if (args.length == 2
|
||||
&& "import".equalsIgnoreCase(
|
||||
args[0])) {
|
||||
if (!hasAdminPerm(sender)) {
|
||||
@@ -93,7 +93,7 @@ public class CypayCommand
|
||||
sender.sendMessage("§a导入完成: "
|
||||
+ result + " 条");
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
|
||||
// ===== /cypay freeze =====
|
||||
if (args.length == 2
|
||||
@@ -368,6 +368,14 @@ public class CypayCommand
|
||||
// ===== /cypay <player> — 查指定玩家 =====
|
||||
if (args.length == 1) {
|
||||
String target = args[0];
|
||||
// ★ 验证玩家在 login.db 中存在 ★
|
||||
if (!plugin.getDb().userExists(target)) {
|
||||
sender.sendMessage("§c玩家 " + target
|
||||
+ " 不存在于服务器");
|
||||
return true;
|
||||
}
|
||||
|
||||
// String target = args[0];
|
||||
if (hasAdminPerm(sender)) {
|
||||
int bonds = plugin.getBonds()
|
||||
.getBonds(target);
|
||||
|
||||
@@ -60,9 +60,11 @@ public class DatabaseManager {
|
||||
+ "tasks_completed TEXT DEFAULT '',"
|
||||
+ "premium_uuid TEXT DEFAULT '',"
|
||||
+ "register_ip TEXT DEFAULT '',"
|
||||
+ "register_type TEXT DEFAULT 'manual'"
|
||||
+ "register_type TEXT DEFAULT 'manual',"
|
||||
+ "backcheck_dates TEXT DEFAULT ''"
|
||||
+ ")");
|
||||
|
||||
|
||||
// 安全添加 users 缺失列
|
||||
safeAdd(st, "tasks_completed",
|
||||
"TEXT DEFAULT ''");
|
||||
|
||||
@@ -1,275 +1,284 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.Base64;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class EmailManager {
|
||||
|
||||
private final ConfigManager config;
|
||||
private final Logger logger;
|
||||
|
||||
public EmailManager(ConfigManager config) {
|
||||
this.config = config;
|
||||
this.logger = Logger.getLogger("Sdf1_login");
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
String host = config.getSmtp("smtp地址");
|
||||
String user = config.getSmtp("smtp账号");
|
||||
String pass = config.getSmtp("smtp密码");
|
||||
return host != null && !host.isEmpty()
|
||||
&& user != null && !user.isEmpty()
|
||||
&& pass != null && !pass.isEmpty();
|
||||
}
|
||||
|
||||
public boolean sendVerifyCode(String to,
|
||||
String code) {
|
||||
if (!isConfigured()) {
|
||||
logger.warning(
|
||||
"[Sdf1_login] SMTP未配置,"
|
||||
+ "无法发送验证码");
|
||||
return false;
|
||||
}
|
||||
logger.info("[Sdf1_login] 发送验证码到 " + to);
|
||||
return sendEmailSync(to,
|
||||
"[Sdf1_login] 验证码",
|
||||
"您的验证码: " + code
|
||||
+ "\n5分钟内有效。\n"
|
||||
+ "如非本人操作请忽略。");
|
||||
}
|
||||
|
||||
public boolean sendTempPassword(String to,
|
||||
String player, String tempPwd) {
|
||||
if (!isConfigured()) {
|
||||
logger.warning(
|
||||
"[Sdf1_login] SMTP未配置");
|
||||
return false;
|
||||
}
|
||||
return sendEmailSync(to,
|
||||
"[Sdf1_login] 临时密码",
|
||||
"玩家 " + player
|
||||
+ " 的临时密码: " + tempPwd
|
||||
+ "\n请尽快登录并修改密码!");
|
||||
}
|
||||
|
||||
private boolean sendEmailSync(String to,
|
||||
String subject, String body) {
|
||||
final boolean[] result = {false};
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
result[0] = sendEmail(
|
||||
to, subject, body);
|
||||
} catch (Exception e) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 邮件线程异常: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}, "Sdf1_login-SMTP");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
try {
|
||||
t.join(15000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
if (t.isAlive()) {
|
||||
t.interrupt();
|
||||
logger.severe(
|
||||
"[Sdf1_login] SMTP超时(15秒)");
|
||||
return false;
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private boolean sendEmail(String to,
|
||||
String subject, String body) {
|
||||
String host = config.getSmtp("smtp地址");
|
||||
String portStr = config.getSmtp("smtp端口");
|
||||
String user = config.getSmtp("smtp账号");
|
||||
String pass = config.getSmtp("smtp密码");
|
||||
String fromName = config.getSmtp(
|
||||
"发件人名称");
|
||||
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(portStr);
|
||||
} catch (Exception e) {
|
||||
port = 465;
|
||||
}
|
||||
|
||||
try {
|
||||
String encSubject = "=?UTF-8?B?"
|
||||
+ Base64.getEncoder()
|
||||
.encodeToString(
|
||||
subject.getBytes("UTF-8"))
|
||||
+ "?=";
|
||||
String encBody = Base64.getEncoder()
|
||||
.encodeToString(
|
||||
body.getBytes("UTF-8"));
|
||||
|
||||
if (port == 465) {
|
||||
SSLSocketFactory factory =
|
||||
(SSLSocketFactory)
|
||||
SSLSocketFactory
|
||||
.getDefault();
|
||||
SSLSocket socket = (SSLSocket)
|
||||
factory.createSocket(
|
||||
host, port);
|
||||
socket.setSoTimeout(10000);
|
||||
doSmtp(socket, host, user, pass,
|
||||
to, encSubject, encBody,
|
||||
fromName);
|
||||
socket.close();
|
||||
} else {
|
||||
Socket plainSocket =
|
||||
new Socket(host, port);
|
||||
plainSocket.setSoTimeout(10000);
|
||||
BufferedReader preReader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
plainSocket
|
||||
.getInputStream(),
|
||||
"UTF-8"));
|
||||
PrintWriter preWriter =
|
||||
new PrintWriter(
|
||||
plainSocket
|
||||
.getOutputStream(),
|
||||
true);
|
||||
checkResponse(preReader, "220");
|
||||
sendLine(preWriter,
|
||||
"EHLO " + host);
|
||||
checkResponse(preReader, "250");
|
||||
sendLine(preWriter, "STARTTLS");
|
||||
checkResponse(preReader, "220");
|
||||
SSLSocketFactory factory =
|
||||
(SSLSocketFactory)
|
||||
SSLSocketFactory
|
||||
.getDefault();
|
||||
SSLSocket sslSocket = (SSLSocket)
|
||||
factory.createSocket(
|
||||
plainSocket, host,
|
||||
port, true);
|
||||
sslSocket.startHandshake();
|
||||
doSmtp(sslSocket, host, user, pass,
|
||||
to, encSubject, encBody,
|
||||
fromName);
|
||||
sslSocket.close();
|
||||
}
|
||||
logger.info(
|
||||
"[Sdf1_login] 邮件发送成功 -> "
|
||||
+ to);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 邮件异常: "
|
||||
+ e.getClass()
|
||||
.getSimpleName()
|
||||
+ ": " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 根因: "
|
||||
+ e.getCause()
|
||||
.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doSmtp(SSLSocket socket,
|
||||
String host, String user, String pass,
|
||||
String to, String subject, String body,
|
||||
String fromName) throws Exception {
|
||||
BufferedReader reader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
socket.getInputStream(),
|
||||
"UTF-8"));
|
||||
OutputStream rawOut =
|
||||
socket.getOutputStream();
|
||||
PrintWriter writer =
|
||||
new PrintWriter(
|
||||
new java.io.BufferedOutputStream(
|
||||
rawOut), true);
|
||||
|
||||
checkResponse(reader, "220");
|
||||
sendLine(writer, "EHLO " + host);
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "AUTH LOGIN");
|
||||
checkResponse(reader, "334");
|
||||
sendLine(writer, Base64.getEncoder()
|
||||
.encodeToString(
|
||||
user.getBytes("UTF-8")));
|
||||
checkResponse(reader, "334");
|
||||
sendLine(writer, Base64.getEncoder()
|
||||
.encodeToString(
|
||||
pass.getBytes("UTF-8")));
|
||||
checkResponse(reader, "235");
|
||||
sendLine(writer,
|
||||
"MAIL FROM:<" + user + ">");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer,
|
||||
"RCPT TO:<" + to + ">");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "DATA");
|
||||
checkResponse(reader, "354");
|
||||
sendLine(writer,
|
||||
"From: " + fromName
|
||||
+ " <" + user + ">");
|
||||
sendLine(writer,
|
||||
"To: <" + to + ">");
|
||||
sendLine(writer,
|
||||
"Reply-To: <" + user + ">");
|
||||
sendLine(writer,
|
||||
"Subject: " + subject);
|
||||
sendLine(writer, "MIME-Version: 1.0");
|
||||
sendLine(writer,
|
||||
"Content-Type: text/plain; "
|
||||
+ "charset=UTF-8");
|
||||
sendLine(writer,
|
||||
"Content-Transfer-Encoding: "
|
||||
+ "base64");
|
||||
sendLine(writer, "");
|
||||
sendLine(writer, body);
|
||||
sendLine(writer, ".");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "QUIT");
|
||||
}
|
||||
|
||||
private void sendLine(PrintWriter writer,
|
||||
String line) {
|
||||
writer.print(line + "\r\n");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
private void checkResponse(
|
||||
BufferedReader reader,
|
||||
String expected) throws Exception {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
if (line.length() >= 4
|
||||
&& Character.isDigit(
|
||||
line.charAt(0))
|
||||
&& line.charAt(3) == ' ')
|
||||
break;
|
||||
}
|
||||
String resp = sb.toString().trim();
|
||||
if (!resp.startsWith(expected)) {
|
||||
if (resp.startsWith("535"))
|
||||
logger.severe(
|
||||
"[Sdf1_login] SMTP 535 "
|
||||
+ "认证失败");
|
||||
throw new Exception(
|
||||
"SMTP响应异常: 期望 "
|
||||
+ expected
|
||||
+ ",实际: " + resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.util.Base64;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class EmailManager {
|
||||
|
||||
private final ConfigManager config;
|
||||
private final Logger logger;
|
||||
|
||||
public EmailManager(ConfigManager config) {
|
||||
this.config = config;
|
||||
this.logger = Logger.getLogger("Sdf1_login");
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
String host = config.getSmtp("smtp地址");
|
||||
String user = config.getSmtp("smtp账号");
|
||||
String pass = config.getSmtp("smtp密码");
|
||||
return host != null && !host.isEmpty()
|
||||
&& user != null && !user.isEmpty()
|
||||
&& pass != null && !pass.isEmpty();
|
||||
}
|
||||
|
||||
public boolean sendVerifyCode(String to,
|
||||
String code) {
|
||||
if (!isConfigured()) {
|
||||
logger.warning(
|
||||
"[Sdf1_login] SMTP未配置,"
|
||||
+ "无法发送验证码");
|
||||
return false;
|
||||
}
|
||||
logger.info("[Sdf1_login] 发送验证码到 " + to);
|
||||
return sendEmailSync(to,
|
||||
"[Sdf1_login] 验证码",
|
||||
"您的验证码: " + code
|
||||
+ "\n5分钟内有效。\n"
|
||||
+ "如非本人操作请忽略。");
|
||||
}
|
||||
|
||||
public boolean sendTempPassword(String to,
|
||||
String player, String tempPwd) {
|
||||
if (!isConfigured()) {
|
||||
logger.warning(
|
||||
"[Sdf1_login] SMTP未配置");
|
||||
return false;
|
||||
}
|
||||
return sendEmailSync(to,
|
||||
"[Sdf1_login] 临时密码",
|
||||
"玩家 " + player
|
||||
+ " 的临时密码: " + tempPwd
|
||||
+ "\n请尽快登录并修改密码!");
|
||||
}
|
||||
|
||||
public boolean sendBody(String to, String subject, String body) {
|
||||
if (!isConfigured()) {
|
||||
logger.warning("[Sdf1_login] SMTP未配置");
|
||||
return false;
|
||||
}
|
||||
return sendEmailSync(to, subject, body);
|
||||
}
|
||||
|
||||
|
||||
private boolean sendEmailSync(String to,
|
||||
String subject, String body) {
|
||||
final boolean[] result = {false};
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
result[0] = sendEmail(
|
||||
to, subject, body);
|
||||
} catch (Exception e) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 邮件线程异常: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}, "Sdf1_login-SMTP");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
try {
|
||||
t.join(15000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
if (t.isAlive()) {
|
||||
t.interrupt();
|
||||
logger.severe(
|
||||
"[Sdf1_login] SMTP超时(15秒)");
|
||||
return false;
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private boolean sendEmail(String to,
|
||||
String subject, String body) {
|
||||
String host = config.getSmtp("smtp地址");
|
||||
String portStr = config.getSmtp("smtp端口");
|
||||
String user = config.getSmtp("smtp账号");
|
||||
String pass = config.getSmtp("smtp密码");
|
||||
String fromName = config.getSmtp(
|
||||
"发件人名称");
|
||||
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(portStr);
|
||||
} catch (Exception e) {
|
||||
port = 465;
|
||||
}
|
||||
|
||||
try {
|
||||
String encSubject = "=?UTF-8?B?"
|
||||
+ Base64.getEncoder()
|
||||
.encodeToString(
|
||||
subject.getBytes("UTF-8"))
|
||||
+ "?=";
|
||||
String encBody = Base64.getEncoder()
|
||||
.encodeToString(
|
||||
body.getBytes("UTF-8"));
|
||||
|
||||
if (port == 465) {
|
||||
SSLSocketFactory factory =
|
||||
(SSLSocketFactory)
|
||||
SSLSocketFactory
|
||||
.getDefault();
|
||||
SSLSocket socket = (SSLSocket)
|
||||
factory.createSocket(
|
||||
host, port);
|
||||
socket.setSoTimeout(10000);
|
||||
doSmtp(socket, host, user, pass,
|
||||
to, encSubject, encBody,
|
||||
fromName);
|
||||
socket.close();
|
||||
} else {
|
||||
Socket plainSocket =
|
||||
new Socket(host, port);
|
||||
plainSocket.setSoTimeout(10000);
|
||||
BufferedReader preReader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
plainSocket
|
||||
.getInputStream(),
|
||||
"UTF-8"));
|
||||
PrintWriter preWriter =
|
||||
new PrintWriter(
|
||||
plainSocket
|
||||
.getOutputStream(),
|
||||
true);
|
||||
checkResponse(preReader, "220");
|
||||
sendLine(preWriter,
|
||||
"EHLO " + host);
|
||||
checkResponse(preReader, "250");
|
||||
sendLine(preWriter, "STARTTLS");
|
||||
checkResponse(preReader, "220");
|
||||
SSLSocketFactory factory =
|
||||
(SSLSocketFactory)
|
||||
SSLSocketFactory
|
||||
.getDefault();
|
||||
SSLSocket sslSocket = (SSLSocket)
|
||||
factory.createSocket(
|
||||
plainSocket, host,
|
||||
port, true);
|
||||
sslSocket.startHandshake();
|
||||
doSmtp(sslSocket, host, user, pass,
|
||||
to, encSubject, encBody,
|
||||
fromName);
|
||||
sslSocket.close();
|
||||
}
|
||||
logger.info(
|
||||
"[Sdf1_login] 邮件发送成功 -> "
|
||||
+ to);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 邮件异常: "
|
||||
+ e.getClass()
|
||||
.getSimpleName()
|
||||
+ ": " + e.getMessage());
|
||||
if (e.getCause() != null) {
|
||||
logger.severe(
|
||||
"[Sdf1_login] 根因: "
|
||||
+ e.getCause()
|
||||
.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doSmtp(SSLSocket socket,
|
||||
String host, String user, String pass,
|
||||
String to, String subject, String body,
|
||||
String fromName) throws Exception {
|
||||
BufferedReader reader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
socket.getInputStream(),
|
||||
"UTF-8"));
|
||||
OutputStream rawOut =
|
||||
socket.getOutputStream();
|
||||
PrintWriter writer =
|
||||
new PrintWriter(
|
||||
new java.io.BufferedOutputStream(
|
||||
rawOut), true);
|
||||
|
||||
checkResponse(reader, "220");
|
||||
sendLine(writer, "EHLO " + host);
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "AUTH LOGIN");
|
||||
checkResponse(reader, "334");
|
||||
sendLine(writer, Base64.getEncoder()
|
||||
.encodeToString(
|
||||
user.getBytes("UTF-8")));
|
||||
checkResponse(reader, "334");
|
||||
sendLine(writer, Base64.getEncoder()
|
||||
.encodeToString(
|
||||
pass.getBytes("UTF-8")));
|
||||
checkResponse(reader, "235");
|
||||
sendLine(writer,
|
||||
"MAIL FROM:<" + user + ">");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer,
|
||||
"RCPT TO:<" + to + ">");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "DATA");
|
||||
checkResponse(reader, "354");
|
||||
sendLine(writer,
|
||||
"From: " + fromName
|
||||
+ " <" + user + ">");
|
||||
sendLine(writer,
|
||||
"To: <" + to + ">");
|
||||
sendLine(writer,
|
||||
"Reply-To: <" + user + ">");
|
||||
sendLine(writer,
|
||||
"Subject: " + subject);
|
||||
sendLine(writer, "MIME-Version: 1.0");
|
||||
sendLine(writer,
|
||||
"Content-Type: text/plain; "
|
||||
+ "charset=UTF-8");
|
||||
sendLine(writer,
|
||||
"Content-Transfer-Encoding: "
|
||||
+ "base64");
|
||||
sendLine(writer, "");
|
||||
sendLine(writer, body);
|
||||
sendLine(writer, ".");
|
||||
checkResponse(reader, "250");
|
||||
sendLine(writer, "QUIT");
|
||||
}
|
||||
|
||||
private void sendLine(PrintWriter writer,
|
||||
String line) {
|
||||
writer.print(line + "\r\n");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
private void checkResponse(
|
||||
BufferedReader reader,
|
||||
String expected) throws Exception {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
if (line.length() >= 4
|
||||
&& Character.isDigit(
|
||||
line.charAt(0))
|
||||
&& line.charAt(3) == ' ')
|
||||
break;
|
||||
}
|
||||
String resp = sb.toString().trim();
|
||||
if (!resp.startsWith(expected)) {
|
||||
if (resp.startsWith("535"))
|
||||
logger.severe(
|
||||
"[Sdf1_login] SMTP 535 "
|
||||
+ "认证失败");
|
||||
throw new Exception(
|
||||
"SMTP响应异常: 期望 "
|
||||
+ expected
|
||||
+ ",实际: " + resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -163,8 +163,15 @@ public class GUIManager implements Listener {
|
||||
"§6§l垃圾回收站",
|
||||
"§7查看和取回清理的物品"));
|
||||
|
||||
|
||||
}
|
||||
// ===== Row3(27-35): 管理功能 =====
|
||||
// ★ 商店 - 所有人都能看 ★
|
||||
g.setItem(27, mkItem(Material.CHEST,
|
||||
"§6§l商店",
|
||||
"§7浏览、购买、出售物品",
|
||||
"§7货币: 债券"));
|
||||
|
||||
if (isAdmin(p)) {
|
||||
g.setItem(29, mkItem(
|
||||
Material.REDSTONE_BLOCK,
|
||||
@@ -301,6 +308,10 @@ public class GUIManager implements Listener {
|
||||
|
||||
g.setItem(22, mkItem(Material.LIME_WOOL,
|
||||
"§a§l添加菜单项"));
|
||||
g.setItem(27, mkItem(Material.ENDER_CHEST,
|
||||
"§6§l商店",
|
||||
"§7浏览、购买、出售物品",
|
||||
"§7货币: 债券"));
|
||||
|
||||
g.setItem(26, mkItem(Material.ARROW,
|
||||
"§7返回"));
|
||||
@@ -342,7 +353,7 @@ public class GUIManager implements Listener {
|
||||
|
||||
public void openShop(Player p) {
|
||||
p.closeInventory();
|
||||
p.performCommand("sdf1_login shop");
|
||||
plugin.getShopManager().openShopMain(p);
|
||||
}
|
||||
|
||||
public void openTicket(Player p) {
|
||||
@@ -351,11 +362,16 @@ public class GUIManager implements Listener {
|
||||
}
|
||||
@EventHandler
|
||||
public void onInvClose(
|
||||
|
||||
InventoryCloseEvent e) {
|
||||
if (!(e.getPlayer() instanceof Player))
|
||||
return;
|
||||
if (!(e.getPlayer() instanceof Player)) return;
|
||||
Player p = (Player) e.getPlayer();
|
||||
Player p3 = (Player) e.getPlayer();
|
||||
String t = e.getView().getTitle();
|
||||
if (!(e.getPlayer() instanceof Player)) return;
|
||||
Player p2 = (Player) e.getPlayer();
|
||||
|
||||
if (!t.equals(T_EDITOR)) return;
|
||||
|
||||
@@ -718,13 +734,39 @@ public class GUIManager implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onInvDrag(
|
||||
InventoryDragEvent e) {
|
||||
if (!(e.getWhoClicked()
|
||||
instanceof Player)) return;
|
||||
Player p = (Player) e.getWhoClicked();
|
||||
if (!T_EDITOR.equals(
|
||||
e.getView().getTitle())) return;
|
||||
public void onInvDrag(InventoryDragEvent e) {
|
||||
if (!(e.getWhoClicked() instanceof Player)) return;
|
||||
String title = e.getView().getTitle();
|
||||
plugin.getLogger().info("[GUI] title检查点1: [" + title + "]");
|
||||
|
||||
// 购物车/退款/商店/流水记录页面:禁止一切拖拽
|
||||
if ("§a§l购物车".equals(title)
|
||||
|| "§c§l退款中心".equals(title)
|
||||
|| "§6§l商店".equals(title)
|
||||
|| title.startsWith("§e§l流水记录")
|
||||
|| title.startsWith("§e§l转账记录")
|
||||
|| title.startsWith("§d§l余额操作")) {
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 商店分类页:禁止拖拽
|
||||
if (title.startsWith("§6§l") && !title.equals(T_MAIN)
|
||||
&& !title.equals(T_MY_INFO)
|
||||
&& !title.equals(T_ADMIN)
|
||||
&& !title.equals(T_MORE)
|
||||
&& !title.equals(T_EDITOR)
|
||||
&& !title.equals(T_BIOME)
|
||||
&& !title.equals(T_GIFT_STAGES)
|
||||
&& !title.equals(T_INVITE)
|
||||
&& !title.equals(T_USER_MGMT)
|
||||
&& !title.equals(T_TASK_CENTER)) {
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑器:图标拖拽替换
|
||||
if (!T_EDITOR.equals(title)) return;
|
||||
if (e.getRawSlots().contains(12)) {
|
||||
List<MenuManager.MenuItem> list =
|
||||
plugin.getMenu().getItems();
|
||||
@@ -734,69 +776,195 @@ public class GUIManager implements Listener {
|
||||
e.getOldCursor().clone();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onInvClick(InventoryClickEvent event) {
|
||||
if (!(event.getWhoClicked() instanceof Player)) return;
|
||||
if (!(event.getWhoClicked() instanceof Player))
|
||||
return;
|
||||
Player p = (Player) event.getWhoClicked();
|
||||
String title = event.getView().getTitle();
|
||||
int raw = event.getRawSlot();
|
||||
|
||||
// 我的信息 — CDK兑换 (slot 16)
|
||||
// ★ 模糊匹配所有订单相关
|
||||
if (title.contains("订单")
|
||||
|| title.contains("购买记录")) {
|
||||
event.setCancelled(true);
|
||||
if (title.contains("小票配置")) {
|
||||
plugin.getOrderManager()
|
||||
.handleReceiptConfigClick(p, raw);
|
||||
} else if (title.contains("编辑:")) {
|
||||
plugin.getOrderManager()
|
||||
.handleReceiptBuilderClick(p, raw);
|
||||
} else if (title.contains("购买记录")) {
|
||||
plugin.getOrderManager()
|
||||
.handleMyOrdersClick(
|
||||
p, raw, event.isLeftClick(),
|
||||
event.isShiftClick());
|
||||
} else if (title.contains("管理员订单面板")) {
|
||||
plugin.getOrderManager()
|
||||
.handleAdminClick(p, raw);
|
||||
} else if (title.contains("订单管理")) {
|
||||
plugin.getOrderManager()
|
||||
.handleAdminOrdersClick(
|
||||
p, raw, event.isLeftClick(),
|
||||
event.isShiftClick());
|
||||
} else if (title.contains("订单中心")) {
|
||||
plugin.getOrderManager()
|
||||
.handleCenterClick(p, raw);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 我的信息 — CDK兑换 =====
|
||||
if (T_MY_INFO.equals(title) && raw == 16) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[CDK] §f请输入兑换码(聊天输入):");
|
||||
plugin.getCDK().requestInput(p, "cdk", "");
|
||||
return;
|
||||
if (handleCDKClick(p, event)) return;
|
||||
}
|
||||
if (T_MAIN.equals(title) && raw == 32) {
|
||||
event.setCancelled(true);
|
||||
plugin.openMyWallet(p);
|
||||
return;
|
||||
}
|
||||
// 余额操作界面
|
||||
// 余额操作界面
|
||||
if ("§d§l余额操作".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 49) {
|
||||
openAdmin(p);
|
||||
|
||||
// ===== 主菜单 =====
|
||||
if (T_MAIN.equals(title)) {
|
||||
if (raw == 32) {
|
||||
event.setCancelled(true);
|
||||
plugin.openMyWallet(p);
|
||||
return;
|
||||
}
|
||||
if (raw == 27) {
|
||||
event.setCancelled(true);
|
||||
plugin.getShopManager().openShopMain(p);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 我的钱包 =====
|
||||
if ("§6§l我的钱包".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 22) { openMain(p); return; }
|
||||
if (raw == 12) { plugin.openWalletTransactions(p, 7); return; }
|
||||
if (raw == 13) { plugin.openWalletTransactions(p, 14); return; }
|
||||
if (raw == 14) { plugin.openWalletTransfers(p); return; }
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 流水/转账记录 =====
|
||||
if (title.startsWith("§e§l流水记录")
|
||||
|| title.startsWith("§e§l转账记录")) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 53) { plugin.openMyWallet(p); return; }
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 余额操作 =====
|
||||
if ("§d§l余额操作".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
if (raw == 49) { openAdmin(p); return; }
|
||||
if (raw < 0 || raw >= 45) return;
|
||||
ItemStack item = event.getView().getTopInventory().getItem(raw);
|
||||
ItemStack item = event.getView()
|
||||
.getTopInventory().getItem(raw);
|
||||
if (item == null) return;
|
||||
Material mat = item.getType();
|
||||
if (mat != Material.PLAYER_HEAD && mat != Material.EMERALD_BLOCK) return;
|
||||
if (mat != Material.PLAYER_HEAD
|
||||
&& mat != Material.EMERALD_BLOCK) return;
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im == null || im.getDisplayName() == null) return;
|
||||
String targetName = im.getDisplayName().replaceAll("§[0-9a-fk-orA-FK-OR]", "");
|
||||
Player target = plugin.getServer().getPlayerExact(targetName);
|
||||
if (im == null
|
||||
|| im.getDisplayName() == null) return;
|
||||
String targetName = im.getDisplayName()
|
||||
.replaceAll("§[0-9a-fk-orA-FK-OR]", "");
|
||||
Player target = plugin.getServer()
|
||||
.getPlayerExact(targetName);
|
||||
if (target == null) return;
|
||||
String adminTag = plugin.getConfig2().adminTag;
|
||||
boolean isAdmin = p.isOp() || (!adminTag.isEmpty() && p.getScoreboardTags().contains(adminTag));
|
||||
String adminTag =
|
||||
plugin.getConfig2().adminTag;
|
||||
boolean isAdmin = p.isOp()
|
||||
|| (!adminTag.isEmpty()
|
||||
&& p.getScoreboardTags()
|
||||
.contains(adminTag));
|
||||
if (!isAdmin) return;
|
||||
|
||||
boolean leftOnly = event.isLeftClick() && !event.isRightClick();
|
||||
boolean rightOnly = event.isRightClick() && !event.isLeftClick();
|
||||
|
||||
if (leftOnly) {
|
||||
event.setCancelled(true);
|
||||
if (event.isLeftClick()
|
||||
&& !event.isRightClick()) {
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[经济操作] §f对 §a" + targetName + " §f进行经济操作");
|
||||
p.sendMessage("§e§l[经济操作] §f对 §a"
|
||||
+ targetName + " §f进行经济操作");
|
||||
p.sendMessage("§7输入 §a+数字 §7给钱,§c-数字 §7扣钱");
|
||||
plugin.getCDK().requestInput(p, "econ", targetName);
|
||||
} else if (rightOnly) {
|
||||
event.setCancelled(true);
|
||||
} else if (event.isRightClick()
|
||||
&& !event.isLeftClick()) {
|
||||
p.closeInventory();
|
||||
p.sendMessage("§e§l[债券操作] §f对 §a" + targetName + " §f进行债券操作");
|
||||
p.sendMessage("§e§l[债券操作] §f对 §a"
|
||||
+ targetName + " §f进行债券操作");
|
||||
p.sendMessage("§7输入 §a+数字 §7给债券,§c-数字 §7扣债券");
|
||||
plugin.getCDK().requestInput(p, "bond", targetName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 商店主界面 =====
|
||||
if ("§6§l商店".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getShopManager().handleMainShopClick(p, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 退款中心 → 重定向订单中心 =====
|
||||
if ("§c§l退款中心".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getOrderManager().openOrderCenter(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 购物车页面 =====
|
||||
if ("§a§l购物车".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getShopManager().handleCartClick(
|
||||
p, raw, event.isLeftClick(),
|
||||
event.isRightClick());
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 打包确认 =====
|
||||
if ("§6§l选择包装方式".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getShopManager().handlePackingConfirm(p, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 自选颜色 =====
|
||||
if ("§b§l选择潜影盒颜色".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getShopManager().handleColorSelect(p, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 我的订单 =====
|
||||
if ("§6§l我的订单".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getOrderManager().handleMyOrdersClick(
|
||||
p, raw, event.isLeftClick(),
|
||||
event.isShiftClick());
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 管理员订单面板 =====
|
||||
if ("§c§l管理员订单面板".equals(title)) {
|
||||
event.setCancelled(true);
|
||||
plugin.getOrderManager().handleAdminClick(p, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== 商店分类页 =====
|
||||
ShopManager sm = plugin.getShopManager();
|
||||
boolean handled = sm.handleCategoryClick(
|
||||
p, title, raw,
|
||||
event.isLeftClick(),
|
||||
event.isShiftClick());
|
||||
if (handled) {
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void openEditor(
|
||||
|
||||
public void openEditor(
|
||||
Player p, int idx) {
|
||||
List<MenuManager.MenuItem> list =
|
||||
plugin.getMenu().getItems();
|
||||
@@ -1026,27 +1194,51 @@ public class GUIManager implements Listener {
|
||||
* 处理"我的信息"GUI中CDK兑换按钮的点击
|
||||
* 返回 true 表示已处理
|
||||
*/
|
||||
public boolean handleCDKClick(Player p,
|
||||
InventoryClickEvent event) {
|
||||
public boolean handleCDKClick(Player p, InventoryClickEvent event) {
|
||||
String title = event.getView().getTitle();
|
||||
int raw = event.getRawSlot();
|
||||
|
||||
if (!T_MY_INFO.equals(title)) return false;
|
||||
|
||||
// CDK兑换按钮 (slot 16)
|
||||
if (raw == 16) {
|
||||
event.setCancelled(true);
|
||||
p.closeInventory();
|
||||
p.sendMessage(
|
||||
"§e§l[CDK] §f请输入兑换码"
|
||||
+ "(直接输入聊天即可):");
|
||||
plugin.getCDK().requestInput(
|
||||
p, "cdk", "");
|
||||
|
||||
org.bukkit.plugin.Plugin sdf1 =
|
||||
org.bukkit.Bukkit.getPluginManager().getPlugin("Sdf1");
|
||||
|
||||
// 情况1:插件未安装
|
||||
if (sdf1 == null) {
|
||||
p.sendMessage("§c§l[CDK] §f口令兑换系统尚未安装,请联系管理员");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 情况2:插件已安装但未启用
|
||||
if (!sdf1.isEnabled()) {
|
||||
p.sendMessage("§c§l[CDK] §f口令兑换系统未启用,请联系管理员");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 情况3:正常接入 — 反射调用SDF1的startListening
|
||||
try {
|
||||
Class<?> mainClass = sdf1.getClass();
|
||||
java.lang.reflect.Method method =
|
||||
mainClass.getDeclaredMethod("startListening", Player.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(sdf1, p);
|
||||
// startListening内部已发送 "已开启口令监听 (15秒)" 提示
|
||||
} catch (NoSuchMethodException e) {
|
||||
p.sendMessage("§c§l[CDK] §f口令系统版本过低,请联系管理员更新");
|
||||
} catch (Exception e) {
|
||||
p.sendMessage("§c§l[CDK] §f兑换系统异常,请稍后再试");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ===== 放入自定义图标 =====
|
||||
private void doPlaceIcon(Player p,
|
||||
Inventory topInv,
|
||||
|
||||
@@ -1,67 +1,110 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IPGroupManager {
|
||||
|
||||
private final Main plugin;
|
||||
private final int maxAccounts;
|
||||
|
||||
public IPGroupManager(Main plugin, int maxAccounts) {
|
||||
this.plugin = plugin;
|
||||
this.maxAccounts = maxAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查该IP是否还能注册新账号
|
||||
*/
|
||||
public boolean canRegister(String ip) {
|
||||
if (maxAccounts <= 0) return true;
|
||||
if (ip == null || ip.isEmpty()) return true;
|
||||
return queryAccountsByIP(ip).size() < maxAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该IP下已注册的账号数量
|
||||
*/
|
||||
public int getAccountCount(String ip) {
|
||||
if (ip == null || ip.isEmpty()) return 0;
|
||||
return queryAccountsByIP(ip).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该IP下所有账号名
|
||||
*/
|
||||
public List<String> getAccounts(String ip) {
|
||||
return queryAccountsByIP(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接通过数据库连接查询指定IP下的所有账号
|
||||
*/
|
||||
private List<String> queryAccountsByIP(String ip) {
|
||||
List<String> accounts = new ArrayList<>();
|
||||
Connection conn = plugin.getDb().getDb();
|
||||
if (conn == null) return accounts;
|
||||
try {
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"SELECT player_name FROM users "
|
||||
+ "WHERE ip_address = ?");
|
||||
ps.setString(1, ip);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
accounts.add(
|
||||
rs.getString("player_name"));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IPGroupManager {
|
||||
|
||||
private final Main plugin;
|
||||
private final int maxAccounts;
|
||||
|
||||
public IPGroupManager(Main plugin, int maxAccounts) {
|
||||
this.plugin = plugin;
|
||||
this.maxAccounts = maxAccounts;
|
||||
}
|
||||
|
||||
public boolean canRegister(String ip) {
|
||||
if (maxAccounts <= 0) return true;
|
||||
if (ip == null || ip.isEmpty()) return true;
|
||||
int count = getAccountCount(ip);
|
||||
boolean can = count < maxAccounts;
|
||||
plugin.getLogger().info(
|
||||
"[IP限制] IP=" + ip
|
||||
+ " 已注册=" + count
|
||||
+ " 上限=" + maxAccounts
|
||||
+ " 允许=" + can);
|
||||
return can;
|
||||
}
|
||||
|
||||
public int getAccountCount(String ip) {
|
||||
if (ip == null || ip.isEmpty()) return 0;
|
||||
return queryAccountsByIP(ip).size();
|
||||
}
|
||||
|
||||
public List<String> getAccounts(String ip) {
|
||||
return queryAccountsByIP(ip);
|
||||
}
|
||||
|
||||
private List<String> queryAccountsByIP(String ip) {
|
||||
List<String> accounts = new ArrayList<>();
|
||||
Connection conn = plugin.getDb().getDb();
|
||||
if (conn == null) return accounts;
|
||||
try {
|
||||
// 主查询:ip_address 列
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"SELECT player_name FROM users "
|
||||
+ "WHERE ip_address = ?");
|
||||
ps.setString(1, ip);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
accounts.add(
|
||||
rs.getString("player_name"));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
|
||||
// 兜底:如果主列没数据,查 last_login_ip
|
||||
if (accounts.isEmpty()) {
|
||||
PreparedStatement ps2 = conn.prepareStatement(
|
||||
"SELECT player_name FROM users "
|
||||
+ "WHERE last_login_ip = ? "
|
||||
+ "AND (ip_address IS NULL "
|
||||
+ "OR ip_address = '' "
|
||||
+ "OR ip_address != ?)");
|
||||
ps2.setString(1, ip);
|
||||
ps2.setString(2, ip);
|
||||
ResultSet rs2 = ps2.executeQuery();
|
||||
while (rs2.next()) {
|
||||
String name = rs2.getString("player_name");
|
||||
if (!accounts.contains(name))
|
||||
accounts.add(name);
|
||||
}
|
||||
rs2.close();
|
||||
ps2.close();
|
||||
}
|
||||
|
||||
// 兜底2:查 register_ip
|
||||
if (accounts.isEmpty()) {
|
||||
PreparedStatement ps3 = conn.prepareStatement(
|
||||
"SELECT player_name FROM users "
|
||||
+ "WHERE register_ip = ? "
|
||||
+ "AND (ip_address IS NULL "
|
||||
+ "OR ip_address = '') "
|
||||
+ "AND (last_login_ip IS NULL "
|
||||
+ "OR last_login_ip = '' "
|
||||
+ "OR last_login_ip != ?)");
|
||||
ps3.setString(1, ip);
|
||||
ps3.setString(2, ip);
|
||||
ResultSet rs3 = ps3.executeQuery();
|
||||
while (rs3.next()) {
|
||||
String name = rs3.getString("player_name");
|
||||
if (!accounts.contains(name))
|
||||
accounts.add(name);
|
||||
}
|
||||
rs3.close();
|
||||
ps3.close();
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[IP限制] 查询IP=" + ip
|
||||
+ " 结果数=" + accounts.size()
|
||||
+ " 账号=" + accounts);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class InventorySerializer {
|
||||
|
||||
public static String toBase64(ItemStack[] items) throws IOException {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeInt(items.length);
|
||||
for (ItemStack item : items) {
|
||||
dataOutput.writeObject(item);
|
||||
}
|
||||
dataOutput.close();
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to serialize item stacks.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack[] fromBase64(String data) throws IOException {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return new ItemStack[0];
|
||||
}
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
ItemStack[] items = new ItemStack[dataInput.readInt()];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = (ItemStack) dataInput.readObject();
|
||||
}
|
||||
dataInput.close();
|
||||
return items;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException("Unable to deserialize item stacks.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class InventorySerializer {
|
||||
|
||||
public static String toBase64(ItemStack[] items) throws IOException {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
|
||||
dataOutput.writeInt(items.length);
|
||||
for (ItemStack item : items) {
|
||||
dataOutput.writeObject(item);
|
||||
}
|
||||
dataOutput.close();
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to serialize item stacks.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack[] fromBase64(String data) throws IOException {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return new ItemStack[0];
|
||||
}
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
|
||||
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
|
||||
ItemStack[] items = new ItemStack[dataInput.readInt()];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
items[i] = (ItemStack) dataInput.readObject();
|
||||
}
|
||||
dataInput.close();
|
||||
return items;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IOException("Unable to deserialize item stacks.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,16 @@ public class LoginManager {
|
||||
|
||||
public boolean handleLogin(Player p,
|
||||
String[] args) {
|
||||
|
||||
String name = p.getName();
|
||||
// ★ 在 handleLogin 方法开头
|
||||
if (plugin.getNeedsPasswordChange().contains(name)
|
||||
&& plugin.getLoggedIn().contains(name)) {
|
||||
// 已登录但还没改密码,只允许执行 /sdf1_login pw
|
||||
p.sendMessage("§c请先修改密码: /sdf1_login pw <旧密码> <新密码>");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (plugin.getLoggedIn().contains(name)) {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
@@ -179,27 +188,21 @@ public class LoginManager {
|
||||
name, hash);
|
||||
if (matchTemp) {
|
||||
plugin.getLoggedIn().add(name);
|
||||
plugin.getDb().setLoggedIn(
|
||||
name, true);
|
||||
plugin.getDb().setLoggedIn(name, true);
|
||||
p.setAllowFlight(false);
|
||||
p.setFlying(false);
|
||||
plugin.getDb().setField(name,
|
||||
"last_login_time",
|
||||
plugin.getDb().setField(name, "last_login_time",
|
||||
System.currentTimeMillis());
|
||||
plugin.getDb().setField(name,
|
||||
"last_online_check",
|
||||
plugin.getDb().setField(name, "last_online_check",
|
||||
System.currentTimeMillis());
|
||||
plugin.restoreInventory(p);
|
||||
plugin.recordIPLogin(p);
|
||||
plugin.giveMenuSnowball(p);
|
||||
plugin.getNeedsPasswordChange()
|
||||
.add(name);
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("login_success"));
|
||||
p.sendMessage(
|
||||
"§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
|
||||
p.sendMessage(
|
||||
"§7用法: /sdf1_login pw");
|
||||
// ★ 确保这行存在
|
||||
plugin.getNeedsPasswordChange().add(name);
|
||||
p.sendMessage(plugin.getConfig2().msg("login_success"));
|
||||
p.sendMessage("§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
|
||||
p.sendMessage("§7用法: /sdf1_login pw");
|
||||
activateCY(p, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.bukkit.event.inventory.*;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -17,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import java.util.Date;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
@@ -33,8 +35,6 @@ import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
@@ -52,7 +52,6 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.inventory.CraftItemEvent;
|
||||
import org.bukkit.event.player.PlayerFishEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -120,6 +119,7 @@ public class Main extends JavaPlugin
|
||||
private final Map<String, Long> ipAutoLogin =
|
||||
new HashMap<>();
|
||||
private static final long IP_SKIP_MS = 300000L;
|
||||
private SalesStatsManager salesStats;
|
||||
|
||||
private final Map<UUID, Long> lastActivity =
|
||||
new ConcurrentHashMap<>();
|
||||
@@ -138,6 +138,11 @@ public class Main extends JavaPlugin
|
||||
// 在 Main.java 字段声明区域(bondManager 附近)添加:
|
||||
// 在 bondManager 字段附近添加:
|
||||
private BondPrinter bondPrinter;
|
||||
private OrderManager orderManager;
|
||||
|
||||
public OrderManager getOrderManager() {
|
||||
return orderManager;
|
||||
}
|
||||
|
||||
// 钱包流水查看目标(玩家名 → 查看目标)
|
||||
private final Map<String, String> walletViewTarget =
|
||||
@@ -148,7 +153,7 @@ public class Main extends JavaPlugin
|
||||
TX_SDF = new java.text.SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
|
||||
private ShopManager shopManager; //商店
|
||||
|
||||
|
||||
|
||||
@@ -275,17 +280,23 @@ private PVPManager pvpManager;
|
||||
public Set<String> getNeedsPasswordChange() {
|
||||
return needsPasswordChange;
|
||||
}
|
||||
|
||||
|
||||
// public RadioManager radio;
|
||||
@Override
|
||||
public void onEnable() {
|
||||
getDataFolder().mkdirs();
|
||||
salesStats = new SalesStatsManager(this);
|
||||
|
||||
// ===== 1. 基础配置 =====
|
||||
config = new ConfigManager(getDataFolder());
|
||||
config.loadMessages();
|
||||
config.loadSmtp();
|
||||
config.loadSettings();
|
||||
|
||||
configMgr = new ConfigManager(getDataFolder());
|
||||
configMgr.loadSettings();
|
||||
configMgr.loadMessages();
|
||||
orderManager = new OrderManager(this);
|
||||
// ===== 2. 数据库 =====
|
||||
db = new DatabaseManager(getDataFolder());
|
||||
db.init();
|
||||
@@ -299,6 +310,10 @@ private PVPManager pvpManager;
|
||||
|
||||
// ===== 4. GUI(依赖 menu) =====
|
||||
gui = new GUIManager(this);
|
||||
shopManager = new ShopManager(this);
|
||||
Bukkit.getPluginManager().registerEvents(shopManager, this);
|
||||
shopManager.loadCategories();
|
||||
|
||||
getServer().getPluginManager()
|
||||
.registerEvents(gui, this);
|
||||
|
||||
@@ -371,6 +386,10 @@ private PVPManager pvpManager;
|
||||
getCommand("menu").setExecutor(this);
|
||||
if (getCommand("printer") != null) {
|
||||
getCommand("printer").setExecutor(this);
|
||||
getCommand("shop").setExecutor(this);
|
||||
getCommand("商店").setExecutor(this);
|
||||
// getCommand("testorder").setExecutor(this);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -421,22 +440,46 @@ private PVPManager pvpManager;
|
||||
cdkManager = new CDKManager(this);
|
||||
userGroupManager = new UserGroupManager(this);
|
||||
cypayCommand = new CypayCommand(this);
|
||||
// 加载CDK文件
|
||||
cdkManager.loadCDKsFromDir();
|
||||
/*加载CDK文件
|
||||
cdkManager.loadCDKsFromDir();*/
|
||||
// cypay 命令
|
||||
if (getCommand("cypay") != null) {
|
||||
getCommand("cypay").setExecutor(cypayCommand);
|
||||
getCommand("cypay").setTabCompleter(cypayCommand);
|
||||
}
|
||||
bondPrinter = new BondPrinter(this);
|
||||
cdkManager.loadCDKsFromDir();
|
||||
// cdkManager.loadCDKsFromDir();
|
||||
//14 ====商店====
|
||||
// ★ 只重载数据,不创建新实例 ★
|
||||
if (shopManager != null) {
|
||||
shopManager.loadCategories();
|
||||
}
|
||||
|
||||
configMgr = new ConfigManager(getDataFolder());
|
||||
configMgr.loadSettings();
|
||||
orderManager = new OrderManager(this);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
getLogger().info("Sdf1_login v1.0 | 就绪");
|
||||
getLogger().info("[SDF1] orderManager=" + (orderManager != null ? "OK" : "NULL"));
|
||||
|
||||
}
|
||||
public BondPrinter getBondPrinter() {
|
||||
return bondPrinter;
|
||||
}
|
||||
public ShopManager getShopManager() {
|
||||
return shopManager;
|
||||
}
|
||||
|
||||
private ConfigManager configMgr;
|
||||
|
||||
public ConfigManager getConfigMgr() {
|
||||
return configMgr;
|
||||
}
|
||||
|
||||
|
||||
private void setupPVPTab() {
|
||||
if (getCommand("pvp") != null) {
|
||||
@@ -494,6 +537,7 @@ private PVPManager pvpManager;
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (shopManager != null) shopManager.saveAll();
|
||||
// 取消未执行的删除任务
|
||||
if (pendingDeleteTask != null) {
|
||||
pendingDeleteTask.cancel();
|
||||
@@ -2282,10 +2326,14 @@ private PVPManager pvpManager;
|
||||
long time = ((Number) tx.get("time"))
|
||||
.longValue();
|
||||
|
||||
boolean pos = amount > 0;
|
||||
String sign = pos ? "+" : "";
|
||||
String color = pos ? "§a" : "§c";
|
||||
String typeName = walletFormatType(type);
|
||||
boolean isExpense = "shop_buy".equals(type)
|
||||
|| "shop_packing".equals(type);
|
||||
boolean pos = isExpense ? false : (amount > 0);
|
||||
String sign = pos ? "+" : (isExpense ? "-" : "");
|
||||
String color = pos ? "§a" : (isExpense ? "§c" : (amount < 0 ? "§c" : ""));
|
||||
// 金额取绝对值显示
|
||||
int displayAmount = Math.abs(amount);
|
||||
|
||||
|
||||
// 用 if-else 替代 switch 避免编译问题
|
||||
Material mat = Material.PAPER;
|
||||
@@ -2317,11 +2365,13 @@ private PVPManager pvpManager;
|
||||
} else {
|
||||
mat = Material.RED_WOOL;
|
||||
}
|
||||
String typeName = walletFormatType(type);
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§7类型: " + typeName);
|
||||
lore.add("§7金额: " + color + sign + amount
|
||||
lore.add("§7金额: " + color + sign + displayAmount
|
||||
+ " §6枚");
|
||||
|
||||
if (tgt != null && !tgt.isEmpty())
|
||||
lore.add("§7对象: §e" + tgt);
|
||||
if (reason != null && !reason.isEmpty())
|
||||
@@ -2333,8 +2383,9 @@ private PVPManager pvpManager;
|
||||
ItemStack item = new ItemStack(mat);
|
||||
ItemMeta im = item.getItemMeta();
|
||||
if (im != null) {
|
||||
im.setDisplayName(color + sign + amount
|
||||
im.setDisplayName(color + sign + displayAmount
|
||||
+ " §6枚 - " + typeName);
|
||||
|
||||
im.setLore(lore);
|
||||
item.setItemMeta(im);
|
||||
}
|
||||
@@ -3587,6 +3638,18 @@ private PVPManager pvpManager;
|
||||
e.setCancelled(true);
|
||||
}
|
||||
}
|
||||
/** 验证玩家是否在主库中真实存在 */
|
||||
public boolean isRealPlayer(String name) {
|
||||
if (name == null || name.isEmpty()) return false;
|
||||
try {
|
||||
Map<String, Object> user =
|
||||
getDb().getUser(name);
|
||||
return user != null && !user.isEmpty()
|
||||
&& user.containsKey("player_name");
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Commands =====
|
||||
@Override
|
||||
@@ -3595,6 +3658,136 @@ private PVPManager pvpManager;
|
||||
String[] args) {
|
||||
|
||||
String cmdName = cmd.getName().toLowerCase();
|
||||
if (cmd.getName().equalsIgnoreCase("testorder")
|
||||
&& sender instanceof Player) {
|
||||
Player p = (Player) sender;
|
||||
p.sendMessage("§e=== 测试订单中心 ===");
|
||||
p.sendMessage("§7当前界面: §f"
|
||||
+ p.getOpenInventory().getTitle());
|
||||
this.getOrderManager().openOrderCenter(p);
|
||||
// 延迟1秒检查实际打开了什么
|
||||
Bukkit.getScheduler().runTaskLater(this, () -> {
|
||||
if (p.isOnline()) {
|
||||
String opened = p.getOpenInventory()
|
||||
.getTitle();
|
||||
p.sendMessage("§7打开后界面: §f" + opened);
|
||||
this.getLogger().info("[TEST] 打开后: "
|
||||
+ opened);
|
||||
}
|
||||
}, 20L);
|
||||
return true;
|
||||
}
|
||||
if (cmd.getName().equalsIgnoreCase("shop")
|
||||
|| cmd.getName().equals("商店")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可使用");
|
||||
return true;
|
||||
}
|
||||
if (shopManager != null) {
|
||||
shopManager.openShopMain((Player) sender);
|
||||
} else {
|
||||
sender.sendMessage("§c商店系统未加载");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== 在 onCommand 方法体内,最开头加: =====
|
||||
if (cmd.getName().equalsIgnoreCase("sdf1_login")
|
||||
&& args.length > 0
|
||||
&& args[0].equalsIgnoreCase("shop")) {
|
||||
if (shopManager == null) {
|
||||
sender.sendMessage("§c商店未加载");
|
||||
return true;
|
||||
}
|
||||
return shopManager.handleCommand(sender, args);
|
||||
}
|
||||
// set
|
||||
String sub = args[0].toLowerCase();
|
||||
if (sub.equals("set")) {
|
||||
if (!isAdmin(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
sender.sendMessage("§e用法: /sdf1_login set <玩家> <临时密码>");
|
||||
return true;
|
||||
}
|
||||
String tgt = args[1];
|
||||
String tempPwd = args[2];
|
||||
if (!db.userExists(tgt)) {
|
||||
sender.sendMessage("§c玩家不存在");
|
||||
return true;
|
||||
}
|
||||
String salt = (String) db.getField(tgt, "password_salt");
|
||||
String hash = PasswordUtils.hash(tempPwd, salt);
|
||||
db.setField(tgt, "temp_password", hash);
|
||||
db.setField(tgt, "temp_pw_expire",
|
||||
System.currentTimeMillis() + 300000L);
|
||||
db.setField(tgt, "temp_pw_used", 0);
|
||||
|
||||
// ★ 立即发邮件(踢人之前)
|
||||
String emailAddr = (String) db.getField(tgt, "email");
|
||||
if (emailAddr != null && !emailAddr.isEmpty()) {
|
||||
// 获取最近登录信息
|
||||
Object lastLoginObj = db.getField(tgt, "last_login_time");
|
||||
long lastLogin = lastLoginObj != null
|
||||
? ((Number) lastLoginObj).longValue() : 0;
|
||||
String lastIP = (String) db.getField(tgt, "last_ip");
|
||||
if (lastIP == null || lastIP.isEmpty())
|
||||
lastIP = "未知";
|
||||
|
||||
java.text.SimpleDateFormat sdf =
|
||||
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String loginTimeStr = lastLogin > 0
|
||||
? sdf.format(new java.util.Date(lastLogin))
|
||||
: "未知时间";
|
||||
|
||||
final String to = emailAddr;
|
||||
final String fName = tgt;
|
||||
final String pwd = tempPwd;
|
||||
final String ipInfo = lastIP;
|
||||
final String timeInfo = loginTimeStr;
|
||||
final Main self = this;
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this,
|
||||
() -> {
|
||||
// ★ 发送临时密码邮件(带登录信息)
|
||||
boolean sent = email.sendTempPassword(
|
||||
to, fName, pwd);
|
||||
// ★ 额外发送安全通知邮件
|
||||
if (sent) {
|
||||
String body = "玩家 " + fName
|
||||
+ ":\n\n"
|
||||
+ "管理员已为您的账号设置了临时密码。\n\n"
|
||||
+ "临时密码: " + pwd + "\n"
|
||||
+ "过期时间: 5分钟内\n\n"
|
||||
+ "最近登录记录:\n"
|
||||
+ " 时间: " + timeInfo + "\n"
|
||||
+ " IP地址: " + ipInfo + "\n\n"
|
||||
+ "如果您本人未操作,请忽略此邮件。\n"
|
||||
+ "如果您怀疑账号被盗,请立即修改密码。";
|
||||
email.sendBody(to,
|
||||
"【安全通知】账号临时密码已设置", body);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(self, () -> {
|
||||
if (sent) {
|
||||
sender.sendMessage("§a临时密码已发送至 "
|
||||
+ tgt + " 的邮箱");
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage("§7该玩家未绑定邮箱,跳过邮件通知");
|
||||
}
|
||||
|
||||
// 踢人
|
||||
Player tp = Bukkit.getPlayer(tgt);
|
||||
if (tp != null && tp.isOnline())
|
||||
tp.kickPlayer("§c管理员已设置了临时密码,请用新密码登录");
|
||||
sender.sendMessage("§a已为 " + tgt + " 设置临时密码");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (label.equalsIgnoreCase("pvp")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
@@ -3604,10 +3797,22 @@ private PVPManager pvpManager;
|
||||
Player p = (Player) sender;
|
||||
return pvpManager.onCommand(p, args);
|
||||
}
|
||||
|
||||
if (cmdName.equals("cypay")) {
|
||||
return cypayCommand.onCommand(sender, cmd, label, args);
|
||||
if (args.length > 0) {
|
||||
String target = args[0];
|
||||
if (!getDb().userExists(target)) {
|
||||
sender.sendMessage("§c玩家 " + target
|
||||
+ " 不存在于服务器");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return cypayCommand.onCommand(
|
||||
sender, cmd, label, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===== /printer [玩家名] =====
|
||||
if (label.equalsIgnoreCase("printer")) {
|
||||
if (bondPrinter == null) {
|
||||
@@ -4018,19 +4223,6 @@ private PVPManager pvpManager;
|
||||
gui.openMain((Player) sender);
|
||||
return true;
|
||||
}
|
||||
// String cmdName = cmd.getName().toLowerCase();
|
||||
if (!cmdName.equals("sdf1_login")) return false;
|
||||
|
||||
if (args.length == 0) {
|
||||
if (sender instanceof Player) {
|
||||
gui.openMain((Player) sender);
|
||||
} else {
|
||||
sender.sendMessage("§e用法: /sdf1_login <子命令>");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String sub = args[0].toLowerCase();
|
||||
|
||||
// ===== del / confirm / cancel(最先处理) =====
|
||||
if (sub.equals("del")) {
|
||||
@@ -4179,7 +4371,10 @@ private PVPManager pvpManager;
|
||||
if (getMenu() != null) {
|
||||
getMenu().loadMenu();
|
||||
}
|
||||
|
||||
if (shopManager != null) {
|
||||
shopManager.loadCategories();
|
||||
}
|
||||
if (shopManager != null) shopManager.loadCategories();
|
||||
sender.sendMessage("§a配置已重载!");
|
||||
return true;
|
||||
}
|
||||
@@ -4303,8 +4498,12 @@ private PVPManager pvpManager;
|
||||
db.setField(p2.getName(), "password_salt", newSalt);
|
||||
if (!mainOk && tempOk)
|
||||
db.clearTempPassword(p2.getName());
|
||||
// pw 命令改密码成功后
|
||||
needsPasswordChange.remove(p2.getName());
|
||||
p2.sendMessage(config.msg("password_changed"));
|
||||
// 清除临时密码
|
||||
if (!mainOk && tempOk)
|
||||
db.clearTempPassword(p2.getName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4785,62 +4984,6 @@ private PVPManager pvpManager;
|
||||
return true;
|
||||
}
|
||||
|
||||
// set
|
||||
if (sub.equals("set")) {
|
||||
if (!isAdmin(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
sender.sendMessage(
|
||||
"§e用法: /sdf1_login set <玩家> <临时密码>");
|
||||
return true;
|
||||
}
|
||||
String tgt = args[1];
|
||||
String tempPwd = args[2];
|
||||
if (!PasswordUtils.validate(tempPwd)) {
|
||||
sender.sendMessage("§c密码格式不符合要求");
|
||||
return true;
|
||||
}
|
||||
if (!db.userExists(tgt)) {
|
||||
sender.sendMessage("§c玩家不存在");
|
||||
return true;
|
||||
}
|
||||
String salt = (String) db.getField(
|
||||
tgt, "password_salt");
|
||||
String hash = PasswordUtils.hash(
|
||||
tempPwd, salt);
|
||||
db.setField(tgt, "temp_password", hash);
|
||||
db.setField(tgt, "temp_pw_expire",
|
||||
System.currentTimeMillis()
|
||||
+ 300000L);
|
||||
|
||||
db.setField(tgt, "temp_pw_used", 0);
|
||||
sender.sendMessage("§a已为 " + tgt
|
||||
+ " 设置临时密码");
|
||||
Player tp = Bukkit.getPlayer(tgt);
|
||||
if (tp != null && tp.isOnline())
|
||||
tp.kickPlayer(
|
||||
"§c管理员已设置临时密码");
|
||||
// 邮件通知
|
||||
String emailAddr = (String) db.getField(
|
||||
tgt, "email");
|
||||
if (emailAddr != null
|
||||
&& !emailAddr.isEmpty()) {
|
||||
String body = "玩家 " + tgt
|
||||
+ ":\n您的临时密码已由管理员设置。\n"
|
||||
+ "临时密码将在5分钟后过期,请尽快登录并修改密码。";
|
||||
final String to = emailAddr;
|
||||
final String finalBody = body;
|
||||
Bukkit.getScheduler()
|
||||
.runTaskAsynchronously(this,
|
||||
() -> email
|
||||
.sendTempPassword(
|
||||
to, tgt,
|
||||
finalBody));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// give - 手动发放雪球菜单
|
||||
if (sub.equals("give")) {
|
||||
@@ -5032,6 +5175,21 @@ private PVPManager pvpManager;
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* 供SDF1反射调用的CDK兑换
|
||||
* 返回: "success:金额:余额前:余额后"
|
||||
* 或 "fail:原因"
|
||||
*/
|
||||
public String redeemBondForExternal(
|
||||
String playerName, String code) {
|
||||
if (cdkManager == null)
|
||||
return "fail:CDK未初始化";
|
||||
return cdkManager.redeem(code, playerName);
|
||||
}
|
||||
// 旧的(可能缺失或拼写错误)
|
||||
public SalesStatsManager getSalesStats() {
|
||||
return salesStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是垃圾站展示物品(带ID lore)
|
||||
@@ -5096,7 +5254,7 @@ private PVPManager pvpManager;
|
||||
"get", "del", "set",
|
||||
"take", "add", "kick",
|
||||
"ticket", "oa", "stop",
|
||||
"shopadd", "shopdel", "back", "radio", "back"));
|
||||
"shopadd", "shopdel", "back", "radio", "back", "shop"));
|
||||
} else {
|
||||
list.addAll(Arrays.asList(
|
||||
"pw", "email", "sign",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,72 +1,72 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class PasswordUtils {
|
||||
|
||||
private static final SecureRandom RANDOM =
|
||||
new SecureRandom();
|
||||
|
||||
public static String generateSalt() {
|
||||
byte[] salt = new byte[16];
|
||||
RANDOM.nextBytes(salt);
|
||||
return Base64.getEncoder().encodeToString(salt);
|
||||
}
|
||||
|
||||
public static String hash(String password, String salt) {
|
||||
try {
|
||||
MessageDigest md =
|
||||
MessageDigest.getInstance("SHA-256");
|
||||
md.update(Base64.getDecoder().decode(salt));
|
||||
byte[] hash =
|
||||
md.digest(password.getBytes("UTF-8"));
|
||||
return Base64.getEncoder().encodeToString(hash);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean validate(String password) {
|
||||
if (password == null) return false;
|
||||
if (password.length() < 6 || password.length() > 20)
|
||||
return false;
|
||||
boolean hasUpper = false;
|
||||
boolean hasLower = false;
|
||||
boolean hasDigit = false;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) hasUpper = true;
|
||||
if (Character.isLowerCase(c)) hasLower = true;
|
||||
if (Character.isDigit(c)) hasDigit = true;
|
||||
}
|
||||
return hasUpper && hasLower && hasDigit;
|
||||
}
|
||||
|
||||
public static String generateTempPassword() {
|
||||
String chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(chars.charAt(
|
||||
RANDOM.nextInt(chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String generateInviteCode() {
|
||||
String chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
StringBuilder sb = new StringBuilder(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
sb.append(chars.charAt(
|
||||
RANDOM.nextInt(chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String generateVerifyCode() {
|
||||
int code = 100000 + RANDOM.nextInt(900000);
|
||||
return String.valueOf(code);
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
public class PasswordUtils {
|
||||
|
||||
private static final SecureRandom RANDOM =
|
||||
new SecureRandom();
|
||||
|
||||
public static String generateSalt() {
|
||||
byte[] salt = new byte[16];
|
||||
RANDOM.nextBytes(salt);
|
||||
return Base64.getEncoder().encodeToString(salt);
|
||||
}
|
||||
|
||||
public static String hash(String password, String salt) {
|
||||
try {
|
||||
MessageDigest md =
|
||||
MessageDigest.getInstance("SHA-256");
|
||||
md.update(Base64.getDecoder().decode(salt));
|
||||
byte[] hash =
|
||||
md.digest(password.getBytes("UTF-8"));
|
||||
return Base64.getEncoder().encodeToString(hash);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean validate(String password) {
|
||||
if (password == null) return false;
|
||||
if (password.length() < 6 || password.length() > 20)
|
||||
return false;
|
||||
boolean hasUpper = false;
|
||||
boolean hasLower = false;
|
||||
boolean hasDigit = false;
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) hasUpper = true;
|
||||
if (Character.isLowerCase(c)) hasLower = true;
|
||||
if (Character.isDigit(c)) hasDigit = true;
|
||||
}
|
||||
return hasUpper && hasLower && hasDigit;
|
||||
}
|
||||
|
||||
public static String generateTempPassword() {
|
||||
String chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(chars.charAt(
|
||||
RANDOM.nextInt(chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String generateInviteCode() {
|
||||
String chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
StringBuilder sb = new StringBuilder(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
sb.append(chars.charAt(
|
||||
RANDOM.nextInt(chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String generateVerifyCode() {
|
||||
int code = 100000 + RANDOM.nextInt(900000);
|
||||
return String.valueOf(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,374 +1,374 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
public class PointsManager {
|
||||
|
||||
private final Main plugin;
|
||||
private Object cyInstance = null;
|
||||
private Method cyActivationMethod = null;
|
||||
private Method cyGetShopItemsMethod = null;
|
||||
|
||||
private final List<Map<String, Object>> cyItems =
|
||||
new ArrayList<>();
|
||||
|
||||
public PointsManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
discoverCY();
|
||||
}
|
||||
|
||||
private void discoverCY() {
|
||||
try {
|
||||
org.bukkit.plugin.Plugin p = Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("CY_beibao");
|
||||
if (p != null && p.isEnabled()) {
|
||||
cyInstance = p;
|
||||
cyGetShopItemsMethod = p.getClass()
|
||||
.getMethod("onSdf1GetShopItems");
|
||||
cyActivationMethod = p.getClass()
|
||||
.getMethod("onSdf1Activation",
|
||||
String.class,
|
||||
int.class, int.class);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] CY_beibao 联控已连接");
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCYItems() {
|
||||
cyItems.clear();
|
||||
if (cyInstance == null
|
||||
|| cyGetShopItemsMethod == null) return;
|
||||
try {
|
||||
Object result =
|
||||
cyGetShopItemsMethod.invoke(cyInstance);
|
||||
if (result == null) return;
|
||||
String data = result.toString();
|
||||
if (data.isEmpty()) return;
|
||||
String[] items = data.split(";");
|
||||
for (String itemStr : items) {
|
||||
String[] p = itemStr.split("\\|");
|
||||
if (p.length < 8) continue;
|
||||
Map<String, Object> item =
|
||||
new LinkedHashMap<>();
|
||||
item.put("id", p[0]);
|
||||
item.put("name", p[1]);
|
||||
item.put("days",
|
||||
Integer.parseInt(p[2]));
|
||||
item.put("slots",
|
||||
Integer.parseInt(p[3]));
|
||||
item.put("price",
|
||||
Integer.parseInt(p[4]));
|
||||
item.put("stock",
|
||||
Integer.parseInt(p[5]));
|
||||
item.put("icon", p[6]);
|
||||
item.put("lifetime",
|
||||
"1".equals(p[7]));
|
||||
cyItems.add(item);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 拉取CY商品失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void activateCY(String name,
|
||||
int slots, int days) {
|
||||
try {
|
||||
if (cyInstance == null
|
||||
|| cyActivationMethod == null) return;
|
||||
cyActivationMethod.invoke(
|
||||
cyInstance, name, slots, days);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] CY激活失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getPoints(String name) {
|
||||
return ((Number) plugin.getDb()
|
||||
.getField(name, "points")).intValue();
|
||||
}
|
||||
|
||||
public Inventory createShopGUI(Player p) {
|
||||
int points = getPoints(p.getName());
|
||||
Inventory inv = Bukkit.createInventory(
|
||||
null, 54,
|
||||
"§d§l积分商城 | 当前积分: " + points);
|
||||
fillBg(inv);
|
||||
inv.setItem(10, mkItem(Material.EMERALD,
|
||||
"§a游戏币充值",
|
||||
"§7消耗积分兑换游戏币",
|
||||
"§7100积分 = $1000",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
inv.setItem(12, mkItem(Material.ENDER_CHEST,
|
||||
"§b背包空间",
|
||||
"§7消耗积分兑换背包格子",
|
||||
"§750积分 = 9格永久空间",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE,
|
||||
"§e经验瓶",
|
||||
"§7消耗积分兑换经验",
|
||||
"§720积分 = 10级经验",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
if (cyInstance != null) {
|
||||
inv.setItem(31, mkItem(Material.CHEST,
|
||||
"§6背包商城",
|
||||
"§7来自CY_beibao的商品",
|
||||
"",
|
||||
"§e点击查看"));
|
||||
}
|
||||
inv.setItem(49, mkItem(Material.ARROW, "§7返回"));
|
||||
return inv;
|
||||
}
|
||||
|
||||
public Inventory createCYShopGUI(Player p) {
|
||||
refreshCYItems();
|
||||
int points = getPoints(p.getName());
|
||||
int size = Math.max(54,
|
||||
((cyItems.size() / 9) + 1) * 9);
|
||||
if (size > 54) size = 54;
|
||||
Inventory inv = Bukkit.createInventory(
|
||||
null, size,
|
||||
"§d§lCY背包商城 | 当前积分: " + points);
|
||||
fillBg(inv);
|
||||
for (int i = 0; i < cyItems.size()
|
||||
&& i < 45; i++) {
|
||||
Map<String, Object> item = cyItems.get(i);
|
||||
String name = (String) item.get("name");
|
||||
int price = ((Number) item.get("price"))
|
||||
.intValue();
|
||||
int stock = ((Number) item.get("stock"))
|
||||
.intValue();
|
||||
int days = ((Number) item.get("days"))
|
||||
.intValue();
|
||||
int slots = ((Number) item.get("slots"))
|
||||
.intValue();
|
||||
boolean lifetime = (boolean) item
|
||||
.get("lifetime");
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§7价格: §e" + price + " 积分");
|
||||
if (lifetime) {
|
||||
lore.add("§7格子: §e终身 +" + slots);
|
||||
} else {
|
||||
lore.add("§7格子: §e+" + slots);
|
||||
lore.add("§7时间: §e" + days + "天");
|
||||
}
|
||||
if (stock == 0) {
|
||||
lore.add("§7库存: §a无限");
|
||||
} else if (stock == -1) {
|
||||
lore.add("§7库存: §c已下架");
|
||||
} else {
|
||||
lore.add("§7库存: §e" + stock);
|
||||
}
|
||||
lore.add("");
|
||||
if (points >= price && stock != -1) {
|
||||
lore.add("§e点击购买");
|
||||
} else if (stock == -1) {
|
||||
lore.add("§c已下架");
|
||||
} else {
|
||||
lore.add("§c积分不足");
|
||||
}
|
||||
inv.setItem(i, mkItem(Material.CHEST,
|
||||
"§a" + name,
|
||||
lore.toArray(new String[0])));
|
||||
}
|
||||
inv.setItem(size - 1,
|
||||
mkItem(Material.ARROW, "§7返回"));
|
||||
return inv;
|
||||
}
|
||||
|
||||
public void addItem(String name,
|
||||
int price, String cmd2) {
|
||||
// 追加到商品配置文件
|
||||
File file = new File(
|
||||
plugin.getDataFolder(),
|
||||
"ShopItems.txt");
|
||||
try {
|
||||
java.io.FileWriter fw =
|
||||
new java.io.FileWriter(file, true);
|
||||
fw.write(name + ":" + price
|
||||
+ ":" + cmd2 + "\n");
|
||||
fw.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeItem(int index) {
|
||||
File file = new File(
|
||||
plugin.getDataFolder(),
|
||||
"ShopItems.txt");
|
||||
try {
|
||||
java.util.List<String> lines =
|
||||
new java.util.ArrayList<>();
|
||||
java.io.BufferedReader br =
|
||||
new java.io.BufferedReader(
|
||||
new java.io.FileReader(
|
||||
file));
|
||||
String line;
|
||||
int i = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.trim().isEmpty()
|
||||
|| line.startsWith("#")) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
if (i != index) {
|
||||
lines.add(line);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
br.close();
|
||||
java.io.PrintWriter pw =
|
||||
new java.io.PrintWriter(file);
|
||||
for (String l : lines) pw.println(l);
|
||||
pw.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleClick(Player p, int slot) {
|
||||
int points = getPoints(p.getName());
|
||||
switch (slot) {
|
||||
case 10:
|
||||
if (points >= 100) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 100);
|
||||
if (plugin.getEconomy() != null) {
|
||||
plugin.getEconomy()
|
||||
.depositPlayer(p, 1000);
|
||||
}
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "100"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if (points >= 50) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 50);
|
||||
activateCY(p.getName(), 9, 0);
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "50"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
if (points >= 20) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 20);
|
||||
p.setLevel(p.getLevel() + 10);
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "20"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 31:
|
||||
if (cyInstance != null) {
|
||||
p.openInventory(
|
||||
createCYShopGUI(p));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCYClick(Player p, int slot) {
|
||||
if (slot < 0 || slot >= cyItems.size()
|
||||
|| slot >= 45) return;
|
||||
Map<String, Object> item = cyItems.get(slot);
|
||||
int price = ((Number) item.get("price"))
|
||||
.intValue();
|
||||
int stock = ((Number) item.get("stock"))
|
||||
.intValue();
|
||||
int days = ((Number) item.get("days"))
|
||||
.intValue();
|
||||
int slots = ((Number) item.get("slots"))
|
||||
.intValue();
|
||||
boolean lifetime = (boolean) item
|
||||
.get("lifetime");
|
||||
|
||||
if (stock == -1) {
|
||||
p.sendMessage("§c该商品已下架");
|
||||
return;
|
||||
}
|
||||
int points = getPoints(p.getName());
|
||||
if (points < price) {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getDb().deductPoints(p.getName(), price);
|
||||
int actDays = lifetime ? 0 : days;
|
||||
activateCY(p.getName(), slots, actDays);
|
||||
|
||||
String tl = lifetime
|
||||
? "终身" : (days + "天");
|
||||
p.sendMessage("§a购买成功!获得 "
|
||||
+ tl + " +" + slots + "格 背包空间");
|
||||
}
|
||||
|
||||
private void fillBg(Inventory inv) {
|
||||
ItemStack gl = mkItem(
|
||||
Material.GRAY_STAINED_GLASS_PANE, " ");
|
||||
for (int i = 0; i < inv.getSize(); i++)
|
||||
inv.setItem(i, gl);
|
||||
}
|
||||
|
||||
private ItemStack mkItem(Material mat, String name,
|
||||
String... lore) {
|
||||
ItemStack it = new ItemStack(mat);
|
||||
ItemMeta im = it.getItemMeta();
|
||||
if (im != null) {
|
||||
im.setDisplayName(name);
|
||||
if (lore.length > 0)
|
||||
im.setLore(Arrays.asList(lore));
|
||||
it.setItemMeta(im);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
public class PointsManager {
|
||||
|
||||
private final Main plugin;
|
||||
private Object cyInstance = null;
|
||||
private Method cyActivationMethod = null;
|
||||
private Method cyGetShopItemsMethod = null;
|
||||
|
||||
private final List<Map<String, Object>> cyItems =
|
||||
new ArrayList<>();
|
||||
|
||||
public PointsManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
discoverCY();
|
||||
}
|
||||
|
||||
private void discoverCY() {
|
||||
try {
|
||||
org.bukkit.plugin.Plugin p = Bukkit
|
||||
.getPluginManager()
|
||||
.getPlugin("CY_beibao");
|
||||
if (p != null && p.isEnabled()) {
|
||||
cyInstance = p;
|
||||
cyGetShopItemsMethod = p.getClass()
|
||||
.getMethod("onSdf1GetShopItems");
|
||||
cyActivationMethod = p.getClass()
|
||||
.getMethod("onSdf1Activation",
|
||||
String.class,
|
||||
int.class, int.class);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] CY_beibao 联控已连接");
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCYItems() {
|
||||
cyItems.clear();
|
||||
if (cyInstance == null
|
||||
|| cyGetShopItemsMethod == null) return;
|
||||
try {
|
||||
Object result =
|
||||
cyGetShopItemsMethod.invoke(cyInstance);
|
||||
if (result == null) return;
|
||||
String data = result.toString();
|
||||
if (data.isEmpty()) return;
|
||||
String[] items = data.split(";");
|
||||
for (String itemStr : items) {
|
||||
String[] p = itemStr.split("\\|");
|
||||
if (p.length < 8) continue;
|
||||
Map<String, Object> item =
|
||||
new LinkedHashMap<>();
|
||||
item.put("id", p[0]);
|
||||
item.put("name", p[1]);
|
||||
item.put("days",
|
||||
Integer.parseInt(p[2]));
|
||||
item.put("slots",
|
||||
Integer.parseInt(p[3]));
|
||||
item.put("price",
|
||||
Integer.parseInt(p[4]));
|
||||
item.put("stock",
|
||||
Integer.parseInt(p[5]));
|
||||
item.put("icon", p[6]);
|
||||
item.put("lifetime",
|
||||
"1".equals(p[7]));
|
||||
cyItems.add(item);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 拉取CY商品失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void activateCY(String name,
|
||||
int slots, int days) {
|
||||
try {
|
||||
if (cyInstance == null
|
||||
|| cyActivationMethod == null) return;
|
||||
cyActivationMethod.invoke(
|
||||
cyInstance, name, slots, days);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] CY激活失败: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getPoints(String name) {
|
||||
return ((Number) plugin.getDb()
|
||||
.getField(name, "points")).intValue();
|
||||
}
|
||||
|
||||
public Inventory createShopGUI(Player p) {
|
||||
int points = getPoints(p.getName());
|
||||
Inventory inv = Bukkit.createInventory(
|
||||
null, 54,
|
||||
"§d§l积分商城 | 当前积分: " + points);
|
||||
fillBg(inv);
|
||||
inv.setItem(10, mkItem(Material.EMERALD,
|
||||
"§a游戏币充值",
|
||||
"§7消耗积分兑换游戏币",
|
||||
"§7100积分 = $1000",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
inv.setItem(12, mkItem(Material.ENDER_CHEST,
|
||||
"§b背包空间",
|
||||
"§7消耗积分兑换背包格子",
|
||||
"§750积分 = 9格永久空间",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE,
|
||||
"§e经验瓶",
|
||||
"§7消耗积分兑换经验",
|
||||
"§720积分 = 10级经验",
|
||||
"",
|
||||
"§e点击兑换"));
|
||||
if (cyInstance != null) {
|
||||
inv.setItem(31, mkItem(Material.CHEST,
|
||||
"§6背包商城",
|
||||
"§7来自CY_beibao的商品",
|
||||
"",
|
||||
"§e点击查看"));
|
||||
}
|
||||
inv.setItem(49, mkItem(Material.ARROW, "§7返回"));
|
||||
return inv;
|
||||
}
|
||||
|
||||
public Inventory createCYShopGUI(Player p) {
|
||||
refreshCYItems();
|
||||
int points = getPoints(p.getName());
|
||||
int size = Math.max(54,
|
||||
((cyItems.size() / 9) + 1) * 9);
|
||||
if (size > 54) size = 54;
|
||||
Inventory inv = Bukkit.createInventory(
|
||||
null, size,
|
||||
"§d§lCY背包商城 | 当前积分: " + points);
|
||||
fillBg(inv);
|
||||
for (int i = 0; i < cyItems.size()
|
||||
&& i < 45; i++) {
|
||||
Map<String, Object> item = cyItems.get(i);
|
||||
String name = (String) item.get("name");
|
||||
int price = ((Number) item.get("price"))
|
||||
.intValue();
|
||||
int stock = ((Number) item.get("stock"))
|
||||
.intValue();
|
||||
int days = ((Number) item.get("days"))
|
||||
.intValue();
|
||||
int slots = ((Number) item.get("slots"))
|
||||
.intValue();
|
||||
boolean lifetime = (boolean) item
|
||||
.get("lifetime");
|
||||
|
||||
List<String> lore = new ArrayList<>();
|
||||
lore.add("§7价格: §e" + price + " 积分");
|
||||
if (lifetime) {
|
||||
lore.add("§7格子: §e终身 +" + slots);
|
||||
} else {
|
||||
lore.add("§7格子: §e+" + slots);
|
||||
lore.add("§7时间: §e" + days + "天");
|
||||
}
|
||||
if (stock == 0) {
|
||||
lore.add("§7库存: §a无限");
|
||||
} else if (stock == -1) {
|
||||
lore.add("§7库存: §c已下架");
|
||||
} else {
|
||||
lore.add("§7库存: §e" + stock);
|
||||
}
|
||||
lore.add("");
|
||||
if (points >= price && stock != -1) {
|
||||
lore.add("§e点击购买");
|
||||
} else if (stock == -1) {
|
||||
lore.add("§c已下架");
|
||||
} else {
|
||||
lore.add("§c积分不足");
|
||||
}
|
||||
inv.setItem(i, mkItem(Material.CHEST,
|
||||
"§a" + name,
|
||||
lore.toArray(new String[0])));
|
||||
}
|
||||
inv.setItem(size - 1,
|
||||
mkItem(Material.ARROW, "§7返回"));
|
||||
return inv;
|
||||
}
|
||||
|
||||
public void addItem(String name,
|
||||
int price, String cmd2) {
|
||||
// 追加到商品配置文件
|
||||
File file = new File(
|
||||
plugin.getDataFolder(),
|
||||
"ShopItems.txt");
|
||||
try {
|
||||
java.io.FileWriter fw =
|
||||
new java.io.FileWriter(file, true);
|
||||
fw.write(name + ":" + price
|
||||
+ ":" + cmd2 + "\n");
|
||||
fw.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeItem(int index) {
|
||||
File file = new File(
|
||||
plugin.getDataFolder(),
|
||||
"ShopItems.txt");
|
||||
try {
|
||||
java.util.List<String> lines =
|
||||
new java.util.ArrayList<>();
|
||||
java.io.BufferedReader br =
|
||||
new java.io.BufferedReader(
|
||||
new java.io.FileReader(
|
||||
file));
|
||||
String line;
|
||||
int i = 0;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.trim().isEmpty()
|
||||
|| line.startsWith("#")) {
|
||||
lines.add(line);
|
||||
continue;
|
||||
}
|
||||
if (i != index) {
|
||||
lines.add(line);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
br.close();
|
||||
java.io.PrintWriter pw =
|
||||
new java.io.PrintWriter(file);
|
||||
for (String l : lines) pw.println(l);
|
||||
pw.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handleClick(Player p, int slot) {
|
||||
int points = getPoints(p.getName());
|
||||
switch (slot) {
|
||||
case 10:
|
||||
if (points >= 100) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 100);
|
||||
if (plugin.getEconomy() != null) {
|
||||
plugin.getEconomy()
|
||||
.depositPlayer(p, 1000);
|
||||
}
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "100"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if (points >= 50) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 50);
|
||||
activateCY(p.getName(), 9, 0);
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "50"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
if (points >= 20) {
|
||||
plugin.getDb().deductPoints(
|
||||
p.getName(), 20);
|
||||
p.setLevel(p.getLevel() + 10);
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_purchase_success")
|
||||
.replace("{count}", "20"));
|
||||
} else {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
}
|
||||
break;
|
||||
case 31:
|
||||
if (cyInstance != null) {
|
||||
p.openInventory(
|
||||
createCYShopGUI(p));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCYClick(Player p, int slot) {
|
||||
if (slot < 0 || slot >= cyItems.size()
|
||||
|| slot >= 45) return;
|
||||
Map<String, Object> item = cyItems.get(slot);
|
||||
int price = ((Number) item.get("price"))
|
||||
.intValue();
|
||||
int stock = ((Number) item.get("stock"))
|
||||
.intValue();
|
||||
int days = ((Number) item.get("days"))
|
||||
.intValue();
|
||||
int slots = ((Number) item.get("slots"))
|
||||
.intValue();
|
||||
boolean lifetime = (boolean) item
|
||||
.get("lifetime");
|
||||
|
||||
if (stock == -1) {
|
||||
p.sendMessage("§c该商品已下架");
|
||||
return;
|
||||
}
|
||||
int points = getPoints(p.getName());
|
||||
if (points < price) {
|
||||
p.sendMessage(plugin.getConfig2()
|
||||
.msg("points_insufficient")
|
||||
.replace("{points}",
|
||||
String.valueOf(points)));
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getDb().deductPoints(p.getName(), price);
|
||||
int actDays = lifetime ? 0 : days;
|
||||
activateCY(p.getName(), slots, actDays);
|
||||
|
||||
String tl = lifetime
|
||||
? "终身" : (days + "天");
|
||||
p.sendMessage("§a购买成功!获得 "
|
||||
+ tl + " +" + slots + "格 背包空间");
|
||||
}
|
||||
|
||||
private void fillBg(Inventory inv) {
|
||||
ItemStack gl = mkItem(
|
||||
Material.GRAY_STAINED_GLASS_PANE, " ");
|
||||
for (int i = 0; i < inv.getSize(); i++)
|
||||
inv.setItem(i, gl);
|
||||
}
|
||||
|
||||
private ItemStack mkItem(Material mat, String name,
|
||||
String... lore) {
|
||||
ItemStack it = new ItemStack(mat);
|
||||
ItemMeta im = it.getItemMeta();
|
||||
if (im != null) {
|
||||
im.setDisplayName(name);
|
||||
if (lore.length > 0)
|
||||
im.setLore(Arrays.asList(lore));
|
||||
it.setItemMeta(im);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
// 如果 Bonds 在 Sdf1_login 包下:
|
||||
import Sdf1_login.BondManager;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -892,7 +894,7 @@ public class QuestTracker {
|
||||
|
||||
/**
|
||||
* 发放阶段奖励
|
||||
* 含经济内容的走 Vault API
|
||||
* 含经济内容的走 Vault API或债券
|
||||
*/
|
||||
public void claimRewards(
|
||||
Player player,
|
||||
@@ -900,13 +902,20 @@ public class QuestTracker {
|
||||
if (qf == null
|
||||
|| qf.rewards == null
|
||||
|| qf.rewards.isEmpty()) {
|
||||
player.sendMessage(
|
||||
"§c无可领取的奖励");
|
||||
player.sendMessage("§c无可领取的奖励");
|
||||
return;
|
||||
}
|
||||
|
||||
net.milkbowl.vault.economy.Economy econ =
|
||||
((Main) plugin).getEconomy();
|
||||
Main main = (Main) plugin;
|
||||
// ★ 读取配置:economy = Vault,bonds = 债券(默认)
|
||||
boolean useEconomy = "economy".equalsIgnoreCase(
|
||||
main.getConfigMgr().rewardChannel);
|
||||
|
||||
// Vault 经济引用(仅 useEconomy 时使用)
|
||||
net.milkbowl.vault.economy.Economy econ = null;
|
||||
if (useEconomy) {
|
||||
econ = main.getEconomy();
|
||||
}
|
||||
|
||||
int sentCount = 0;
|
||||
|
||||
@@ -916,52 +925,70 @@ public class QuestTracker {
|
||||
|
||||
// 检查可选奖励标记
|
||||
if (hasOptionalMarker(rt)) {
|
||||
if (!areAllConditionsMet(player.getName(), qf))
|
||||
if (!areAllConditionsMet(player.getName(), qf)) {
|
||||
player.sendMessage(
|
||||
"§7[可选奖励] "
|
||||
+ "需完成所有可选条件: "
|
||||
+ rt);
|
||||
"§7[可选奖励] 需完成所有可选条件: " + rt);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 按 + 分割子项
|
||||
String[] parts =
|
||||
rt.split("[+,]");
|
||||
String[] parts = rt.split("[+,]");
|
||||
for (String part : parts) {
|
||||
part = part.trim();
|
||||
if (part.isEmpty()) continue;
|
||||
|
||||
// 尝试发经济
|
||||
double amt =
|
||||
extractEconomyAmount(part);
|
||||
if (amt > 0 && econ != null) {
|
||||
double before =
|
||||
econ.getBalance(player);
|
||||
econ.depositPlayer(
|
||||
player, amt);
|
||||
player.sendMessage(
|
||||
"§a§l[任务奖励] §f"
|
||||
+ part
|
||||
+ " §7(余额: §e"
|
||||
+ String.format(
|
||||
"%.2f", before)
|
||||
+ " §7→ §a"
|
||||
+ String.format(
|
||||
"%.2f",
|
||||
econ.getBalance(
|
||||
player))
|
||||
+ "§7)");
|
||||
// 通知提成系统
|
||||
if (plugin instanceof Main) {
|
||||
((Main) plugin)
|
||||
.getCommission()
|
||||
.payCommission(
|
||||
player
|
||||
.getName(),
|
||||
amt);
|
||||
// 尝试识别经济金额
|
||||
double amt = extractEconomyAmount(part);
|
||||
if (amt > 0) {
|
||||
// ★ 根据配置分发
|
||||
if (useEconomy && econ != null) {
|
||||
// —— Vault 经济 ——
|
||||
double before =
|
||||
econ.getBalance(player);
|
||||
econ.depositPlayer(player, amt);
|
||||
double after =
|
||||
econ.getBalance(player);
|
||||
player.sendMessage(
|
||||
"§a§l[任务奖励] §f" + part
|
||||
+ " §7(余额: §e"
|
||||
+ String.format("%.2f", before)
|
||||
+ " §7→ §a"
|
||||
+ String.format("%.2f", after)
|
||||
+ "§7)");
|
||||
if (plugin instanceof Main) {
|
||||
((Main) plugin).getCommission()
|
||||
.payCommission(
|
||||
player.getName(), amt);
|
||||
}
|
||||
} else {
|
||||
// —— 债券(默认) ——
|
||||
int bondAmt =
|
||||
(int) Math.round(amt);
|
||||
BondManager bonds = main.getBonds();
|
||||
if (bonds != null) {
|
||||
int before = bonds.getBonds(
|
||||
player.getName());
|
||||
bonds.addBonds(
|
||||
player.getName(),
|
||||
bondAmt,
|
||||
"quest_reward",
|
||||
qf.displayName,
|
||||
"任务系统",
|
||||
"任务奖励:" + qf.displayName);
|
||||
int after = bonds.getBonds(
|
||||
player.getName());
|
||||
player.sendMessage(
|
||||
"§a§l[任务奖励] §f" + part
|
||||
+ " §7(债券: §e"
|
||||
+ before
|
||||
+ " §7→ §a"
|
||||
+ after + "§7)");
|
||||
} else {
|
||||
player.sendMessage(
|
||||
"§c债券系统不可用");
|
||||
}
|
||||
}
|
||||
|
||||
sentCount++;
|
||||
continue;
|
||||
}
|
||||
@@ -971,32 +998,27 @@ public class QuestTracker {
|
||||
parseRewardItems(part);
|
||||
if (!items.isEmpty()) {
|
||||
for (ItemStack is : items) {
|
||||
player.getInventory()
|
||||
.addItem(is);
|
||||
player.getInventory().addItem(is);
|
||||
}
|
||||
player.sendMessage(
|
||||
"§a§l[任务奖励] §f"
|
||||
+ part);
|
||||
"§a§l[任务奖励] §f" + part);
|
||||
sentCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 非经济非物品 → 仅消息
|
||||
player.sendMessage(
|
||||
"§a§l[任务奖励] §f"
|
||||
+ part);
|
||||
"§a§l[任务奖励] §f" + part);
|
||||
sentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[QuestTracker] "
|
||||
+ player.getName()
|
||||
+ " 领取了 \""
|
||||
+ qf.displayName
|
||||
+ "\" 共 " + sentCount
|
||||
+ " 项奖励");
|
||||
"[QuestTracker] " + player.getName()
|
||||
+ " 预取了 \"" + qf.displayName
|
||||
+ "\" 共 " + sentCount + "项奖励");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查奖励文本是否含可选标记
|
||||
*/
|
||||
@@ -1043,7 +1065,9 @@ public class QuestTracker {
|
||||
|| t.contains("$")
|
||||
|| t.contains("经济")
|
||||
|| t.contains("余额")
|
||||
|| t.contains("发钱");
|
||||
|| t.contains("发钱")
|
||||
|| t.contains("债券"); // ★ 新增
|
||||
|
||||
|
||||
if (!hasEcon) return 0;
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RefundRecord {
|
||||
public final UUID playerUUID;
|
||||
public final String playerName;
|
||||
public final String itemId;
|
||||
public final String itemName;
|
||||
public final Material material;
|
||||
public final Material shulkerMaterial;
|
||||
public int amount;
|
||||
public int totalPaid;
|
||||
public final long timestamp;
|
||||
|
||||
// 普通购买
|
||||
public RefundRecord(UUID uuid, String name,
|
||||
String itemId, String itemName,
|
||||
Material material, int amount,
|
||||
int totalPaid, long timestamp) {
|
||||
this.playerUUID = uuid;
|
||||
this.playerName = name;
|
||||
this.itemId = itemId;
|
||||
this.itemName = itemName;
|
||||
this.material = material;
|
||||
this.shulkerMaterial = null;
|
||||
this.amount = amount;
|
||||
this.totalPaid = totalPaid;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
// 盒装购买
|
||||
public RefundRecord(UUID uuid, String name,
|
||||
String itemId, String itemName,
|
||||
Material material,
|
||||
Material shulkerMat,
|
||||
int amount, int totalPaid,
|
||||
long timestamp) {
|
||||
this.playerUUID = uuid;
|
||||
this.playerName = name;
|
||||
this.itemId = itemId;
|
||||
this.itemName = itemName;
|
||||
this.material = material;
|
||||
this.shulkerMaterial = shulkerMat;
|
||||
this.amount = amount;
|
||||
this.totalPaid = totalPaid;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis()
|
||||
- timestamp > 300000L;
|
||||
}
|
||||
|
||||
public int getSecondsLeft() {
|
||||
long left = 300000L
|
||||
- (System.currentTimeMillis()
|
||||
- timestamp);
|
||||
return (int) Math.max(0, left / 1000);
|
||||
}
|
||||
}
|
||||
@@ -1,248 +1,248 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 风控管理器:检测异常登录行为,记录尝试次数,触发安全报警
|
||||
*/
|
||||
public class RiskControlManager {
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
// 每个玩家名 -> 连续失败次数
|
||||
private final Map<String, Integer> failAttempts =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 每个IP -> 关联的可疑玩家列表
|
||||
private final Map<String, Set<String>> ipSuspicious =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 每个玩家 -> 上次验证时间(用于冷却)
|
||||
private final Map<String, Long> verifyCooldown =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 配置项
|
||||
private static final int MAX_FAIL_ATTEMPTS = 5;
|
||||
private static final int MAX_ACCOUNTS_PER_IP = 5;
|
||||
private static final long COOLDOWN_MS = 60000L;
|
||||
|
||||
public RiskControlManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次失败的登录尝试
|
||||
*
|
||||
* @param playerName 玩家名
|
||||
* @return 剩余尝试次数
|
||||
*/
|
||||
public int recordFailAttempt(String playerName) {
|
||||
int current = failAttempts
|
||||
.getOrDefault(playerName, 0) + 1;
|
||||
failAttempts.put(playerName, current);
|
||||
return Math.max(0, MAX_FAIL_ATTEMPTS - current);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定玩家的失败计数(登录成功时调用)
|
||||
*/
|
||||
public void resetFailAttempts(String playerName) {
|
||||
failAttempts.remove(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家的失败次数
|
||||
*/
|
||||
public int getFailAttempts(String playerName) {
|
||||
return failAttempts
|
||||
.getOrDefault(playerName, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断玩家是否已被锁定(失败次数达到上限)
|
||||
*/
|
||||
public boolean isLocked(String playerName) {
|
||||
return failAttempts
|
||||
.getOrDefault(playerName, 0)
|
||||
>= MAX_FAIL_ATTEMPTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查IP是否达到注册上限
|
||||
* 直接查数据库,不依赖 DatabaseManager 的方法
|
||||
*/
|
||||
public boolean canRegisterFromIP(String ip) {
|
||||
if (ip == null || ip.isEmpty()) return true;
|
||||
try {
|
||||
java.sql.Connection conn =
|
||||
plugin.getDb().getDb();
|
||||
if (conn == null) return true;
|
||||
java.sql.PreparedStatement ps =
|
||||
conn.prepareStatement(
|
||||
"SELECT COUNT(*) FROM users "
|
||||
+ "WHERE ip_address = ?");
|
||||
ps.setString(1, ip);
|
||||
java.sql.ResultSet rs = ps.executeQuery();
|
||||
int count = 0;
|
||||
if (rs.next()) count = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return count < 5;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录IP关联的可疑行为
|
||||
*/
|
||||
public void recordSuspiciousIP(String ip,
|
||||
String playerName) {
|
||||
if (ip == null || ip.isEmpty()) return;
|
||||
ipSuspicious
|
||||
.computeIfAbsent(ip,
|
||||
k -> ConcurrentHashMap.newKeySet())
|
||||
.add(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某IP关联的所有可疑玩家
|
||||
*/
|
||||
public Set<String> getSuspiciousAccounts(String ip) {
|
||||
return ipSuspicious
|
||||
.getOrDefault(ip, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在验证冷却期内
|
||||
*/
|
||||
public boolean isOnCooldown(String playerName) {
|
||||
Long last = verifyCooldown.get(playerName);
|
||||
if (last == null) return false;
|
||||
return System.currentTimeMillis() - last
|
||||
< COOLDOWN_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证冷却时间
|
||||
*/
|
||||
public void setCooldown(String playerName) {
|
||||
verifyCooldown.put(playerName,
|
||||
System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当检测到可疑登录时触发报警
|
||||
* - 全服广播
|
||||
* - 有邮箱则发邮件
|
||||
* - 记录到数据库
|
||||
*/
|
||||
public void triggerAlert(Player player,
|
||||
String suspiciousIP) {
|
||||
String name = player.getName();
|
||||
|
||||
// 记录到数据库
|
||||
plugin.getDb().recordSecurityAlert(
|
||||
name, suspiciousIP);
|
||||
|
||||
// 记录IP可疑行为
|
||||
recordSuspiciousIP(suspiciousIP, name);
|
||||
|
||||
// 全服广播
|
||||
String serverMsg = "§c§l[安全警告] §e" + name
|
||||
+ " §f的账号疑似遭到 §c" + suspiciousIP
|
||||
+ " §f的盗号尝试, 请各位玩家注意账号安全!";
|
||||
Bukkit.broadcastMessage(serverMsg);
|
||||
|
||||
// 有邮箱则发邮件
|
||||
String emailAddr = (String) plugin.getDb()
|
||||
.getField(name, "email");
|
||||
if (emailAddr != null
|
||||
&& !emailAddr.isEmpty()) {
|
||||
String subject =
|
||||
"[安全警告] 您的账号疑似遭到盗号尝试";
|
||||
String body = "尊敬的玩家 " + name
|
||||
+ ":\n\n"
|
||||
+ "您的账号在 "
|
||||
+ new java.text.SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss")
|
||||
.format(new java.util.Date())
|
||||
+ " 疑似遭到 IP: " + suspiciousIP
|
||||
+ " 的盗号尝试。\n\n"
|
||||
+ "建议您:\n"
|
||||
+ "1. 立即修改密码"
|
||||
+ "(使用 /sdf1_login pw)\n"
|
||||
+ "2. 如非本人操作,"
|
||||
+ "请联系管理员处理\n\n"
|
||||
+ "--Sdf1_login 安全系统";
|
||||
plugin.getEmail().sendTempPassword(
|
||||
emailAddr, name, body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录失败时的风控逻辑
|
||||
*
|
||||
* @param player 玩家
|
||||
* @param suspiciousIP 可疑来源IP
|
||||
* @return true=已被锁定(应踢出), false=仍可继续尝试
|
||||
*/
|
||||
public boolean handleLoginFailure(Player player,
|
||||
String suspiciousIP) {
|
||||
String name = player.getName();
|
||||
int remaining = recordFailAttempt(name);
|
||||
|
||||
if (remaining <= 0) {
|
||||
// 达到上限,触发报警
|
||||
triggerAlert(player, suspiciousIP);
|
||||
player.kickPlayer(
|
||||
"§c§l[风控] §f登录失败次数过多,"
|
||||
+ " 账号已被临时锁定, 请联系管理员");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 未达上限,提示剩余次数
|
||||
player.sendMessage(
|
||||
"§c§l[风控] §f密码错误! "
|
||||
+ "剩余" + remaining + "次机会");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录成功后重置风控状态
|
||||
*/
|
||||
public void onLoginSuccess(String playerName) {
|
||||
resetFailAttempts(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的冷却记录(可由定时任务调用)
|
||||
*/
|
||||
public void cleanup() {
|
||||
long now = System.currentTimeMillis();
|
||||
verifyCooldown.entrySet().removeIf(
|
||||
e -> now - e.getValue() > COOLDOWN_MS * 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有玩家的失败尝试记录(调试用)
|
||||
*/
|
||||
public Map<String, Integer> getAllFailAttempts() {
|
||||
return Collections.unmodifiableMap(failAttempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有风控数据(管理员调试用)
|
||||
*/
|
||||
public void clearAll() {
|
||||
failAttempts.clear();
|
||||
ipSuspicious.clear();
|
||||
verifyCooldown.clear();
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 风控管理器:检测异常登录行为,记录尝试次数,触发安全报警
|
||||
*/
|
||||
public class RiskControlManager {
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
// 每个玩家名 -> 连续失败次数
|
||||
private final Map<String, Integer> failAttempts =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 每个IP -> 关联的可疑玩家列表
|
||||
private final Map<String, Set<String>> ipSuspicious =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 每个玩家 -> 上次验证时间(用于冷却)
|
||||
private final Map<String, Long> verifyCooldown =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
// 配置项
|
||||
private static final int MAX_FAIL_ATTEMPTS = 5;
|
||||
private static final int MAX_ACCOUNTS_PER_IP = 5;
|
||||
private static final long COOLDOWN_MS = 60000L;
|
||||
|
||||
public RiskControlManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次失败的登录尝试
|
||||
*
|
||||
* @param playerName 玩家名
|
||||
* @return 剩余尝试次数
|
||||
*/
|
||||
public int recordFailAttempt(String playerName) {
|
||||
int current = failAttempts
|
||||
.getOrDefault(playerName, 0) + 1;
|
||||
failAttempts.put(playerName, current);
|
||||
return Math.max(0, MAX_FAIL_ATTEMPTS - current);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定玩家的失败计数(登录成功时调用)
|
||||
*/
|
||||
public void resetFailAttempts(String playerName) {
|
||||
failAttempts.remove(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家的失败次数
|
||||
*/
|
||||
public int getFailAttempts(String playerName) {
|
||||
return failAttempts
|
||||
.getOrDefault(playerName, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断玩家是否已被锁定(失败次数达到上限)
|
||||
*/
|
||||
public boolean isLocked(String playerName) {
|
||||
return failAttempts
|
||||
.getOrDefault(playerName, 0)
|
||||
>= MAX_FAIL_ATTEMPTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查IP是否达到注册上限
|
||||
* 直接查数据库,不依赖 DatabaseManager 的方法
|
||||
*/
|
||||
public boolean canRegisterFromIP(String ip) {
|
||||
if (ip == null || ip.isEmpty()) return true;
|
||||
try {
|
||||
java.sql.Connection conn =
|
||||
plugin.getDb().getDb();
|
||||
if (conn == null) return true;
|
||||
java.sql.PreparedStatement ps =
|
||||
conn.prepareStatement(
|
||||
"SELECT COUNT(*) FROM users "
|
||||
+ "WHERE ip_address = ?");
|
||||
ps.setString(1, ip);
|
||||
java.sql.ResultSet rs = ps.executeQuery();
|
||||
int count = 0;
|
||||
if (rs.next()) count = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return count < 5;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 记录IP关联的可疑行为
|
||||
*/
|
||||
public void recordSuspiciousIP(String ip,
|
||||
String playerName) {
|
||||
if (ip == null || ip.isEmpty()) return;
|
||||
ipSuspicious
|
||||
.computeIfAbsent(ip,
|
||||
k -> ConcurrentHashMap.newKeySet())
|
||||
.add(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某IP关联的所有可疑玩家
|
||||
*/
|
||||
public Set<String> getSuspiciousAccounts(String ip) {
|
||||
return ipSuspicious
|
||||
.getOrDefault(ip, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在验证冷却期内
|
||||
*/
|
||||
public boolean isOnCooldown(String playerName) {
|
||||
Long last = verifyCooldown.get(playerName);
|
||||
if (last == null) return false;
|
||||
return System.currentTimeMillis() - last
|
||||
< COOLDOWN_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证冷却时间
|
||||
*/
|
||||
public void setCooldown(String playerName) {
|
||||
verifyCooldown.put(playerName,
|
||||
System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当检测到可疑登录时触发报警
|
||||
* - 全服广播
|
||||
* - 有邮箱则发邮件
|
||||
* - 记录到数据库
|
||||
*/
|
||||
public void triggerAlert(Player player,
|
||||
String suspiciousIP) {
|
||||
String name = player.getName();
|
||||
|
||||
// 记录到数据库
|
||||
plugin.getDb().recordSecurityAlert(
|
||||
name, suspiciousIP);
|
||||
|
||||
// 记录IP可疑行为
|
||||
recordSuspiciousIP(suspiciousIP, name);
|
||||
|
||||
// 全服广播
|
||||
String serverMsg = "§c§l[安全警告] §e" + name
|
||||
+ " §f的账号疑似遭到 §c" + suspiciousIP
|
||||
+ " §f的盗号尝试, 请各位玩家注意账号安全!";
|
||||
Bukkit.broadcastMessage(serverMsg);
|
||||
|
||||
// 有邮箱则发邮件
|
||||
String emailAddr = (String) plugin.getDb()
|
||||
.getField(name, "email");
|
||||
if (emailAddr != null
|
||||
&& !emailAddr.isEmpty()) {
|
||||
String subject =
|
||||
"[安全警告] 您的账号疑似遭到盗号尝试";
|
||||
String body = "尊敬的玩家 " + name
|
||||
+ ":\n\n"
|
||||
+ "您的账号在 "
|
||||
+ new java.text.SimpleDateFormat(
|
||||
"yyyy-MM-dd HH:mm:ss")
|
||||
.format(new java.util.Date())
|
||||
+ " 疑似遭到 IP: " + suspiciousIP
|
||||
+ " 的盗号尝试。\n\n"
|
||||
+ "建议您:\n"
|
||||
+ "1. 立即修改密码"
|
||||
+ "(使用 /sdf1_login pw)\n"
|
||||
+ "2. 如非本人操作,"
|
||||
+ "请联系管理员处理\n\n"
|
||||
+ "--Sdf1_login 安全系统";
|
||||
plugin.getEmail().sendTempPassword(
|
||||
emailAddr, name, body);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录失败时的风控逻辑
|
||||
*
|
||||
* @param player 玩家
|
||||
* @param suspiciousIP 可疑来源IP
|
||||
* @return true=已被锁定(应踢出), false=仍可继续尝试
|
||||
*/
|
||||
public boolean handleLoginFailure(Player player,
|
||||
String suspiciousIP) {
|
||||
String name = player.getName();
|
||||
int remaining = recordFailAttempt(name);
|
||||
|
||||
if (remaining <= 0) {
|
||||
// 达到上限,触发报警
|
||||
triggerAlert(player, suspiciousIP);
|
||||
player.kickPlayer(
|
||||
"§c§l[风控] §f登录失败次数过多,"
|
||||
+ " 账号已被临时锁定, 请联系管理员");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 未达上限,提示剩余次数
|
||||
player.sendMessage(
|
||||
"§c§l[风控] §f密码错误! "
|
||||
+ "剩余" + remaining + "次机会");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录成功后重置风控状态
|
||||
*/
|
||||
public void onLoginSuccess(String playerName) {
|
||||
resetFailAttempts(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的冷却记录(可由定时任务调用)
|
||||
*/
|
||||
public void cleanup() {
|
||||
long now = System.currentTimeMillis();
|
||||
verifyCooldown.entrySet().removeIf(
|
||||
e -> now - e.getValue() > COOLDOWN_MS * 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有玩家的失败尝试记录(调试用)
|
||||
*/
|
||||
public Map<String, Integer> getAllFailAttempts() {
|
||||
return Collections.unmodifiableMap(failAttempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有风控数据(管理员调试用)
|
||||
*/
|
||||
public void clearAll() {
|
||||
failAttempts.clear();
|
||||
ipSuspicious.clear();
|
||||
verifyCooldown.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class SalesStatsManager {
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
private final Map<String, Integer> sessionSales =
|
||||
new ConcurrentHashMap<>();
|
||||
private int sessionRevenue = 0;
|
||||
private int sessionOrders = 0;
|
||||
private int sessionItems = 0;
|
||||
private int sessionRefunds = 0;
|
||||
private int sessionRefundAmount = 0;
|
||||
private long sessionStart = System.currentTimeMillis();
|
||||
|
||||
// 状态
|
||||
private boolean waitingForEmpty = false;
|
||||
private boolean hasReported = false;
|
||||
private BukkitRunnable waitTask = null;
|
||||
|
||||
public SalesStatsManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
startMonitor();
|
||||
}
|
||||
|
||||
// ★ 每10秒检测一次
|
||||
private void startMonitor() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int online = Bukkit.getOnlinePlayers().size();
|
||||
|
||||
if (online > 0) {
|
||||
// 有玩家在线 → 重置等待状态
|
||||
if (waitingForEmpty) {
|
||||
// 刚有人进来了,清除已汇报标记
|
||||
// 下次没人时会重新汇报
|
||||
}
|
||||
waitingForEmpty = false;
|
||||
hasReported = false;
|
||||
cancelWaitTask();
|
||||
} else if (!waitingForEmpty && !hasReported) {
|
||||
// 没人了,且还没汇报过 → 开始1分钟倒计时
|
||||
waitingForEmpty = true;
|
||||
startWaitCountdown();
|
||||
}
|
||||
}
|
||||
}.runTaskTimer(plugin, 200L, 200L);
|
||||
}
|
||||
|
||||
// ★ 1分钟等待
|
||||
private void startWaitCountdown() {
|
||||
cancelWaitTask();
|
||||
waitTask = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int online = Bukkit.getOnlinePlayers().size();
|
||||
|
||||
if (online > 0) {
|
||||
// 等待期间有人进来 → 取消,等下次没人
|
||||
waitingForEmpty = false;
|
||||
hasReported = false;
|
||||
waitTask = null;
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 1分钟没人 → 汇报一次
|
||||
if (!hasReported) {
|
||||
hasReported = true;
|
||||
report();
|
||||
}
|
||||
waitingForEmpty = false;
|
||||
waitTask = null;
|
||||
cancel();
|
||||
}
|
||||
};
|
||||
waitTask.runTaskLater(plugin, 1200L); // 60秒
|
||||
}
|
||||
|
||||
private void cancelWaitTask() {
|
||||
if (waitTask != null) {
|
||||
waitTask.cancel();
|
||||
waitTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 记录 =====
|
||||
|
||||
public void recordSale(String itemName, int price) {
|
||||
sessionSales.merge(itemName, 1, Integer::sum);
|
||||
sessionRevenue += price;
|
||||
sessionOrders++;
|
||||
sessionItems++;
|
||||
}
|
||||
|
||||
public void recordRefund(int amount) {
|
||||
sessionRefunds++;
|
||||
sessionRefundAmount += amount;
|
||||
}
|
||||
|
||||
// ===== 汇报 =====
|
||||
|
||||
public void report() {
|
||||
if (sessionItems == 0 && sessionRefunds == 0) {
|
||||
plugin.getLogger().info("[Sales] 无数据,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
long minutes =
|
||||
(System.currentTimeMillis() - sessionStart)
|
||||
/ 60000;
|
||||
if (minutes < 1) minutes = 1;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("§6§l========== 销量汇总 ==========");
|
||||
sb.append("\n§7时长: §f").append(minutes).append("分钟");
|
||||
sb.append("\n§7订单: §f").append(sessionOrders).append("笔");
|
||||
sb.append("\n§7商品: §f").append(sessionItems).append("件");
|
||||
sb.append("\n§7成交额: §6").append(sessionRevenue).append("枚");
|
||||
sb.append("\n§7退款: §c").append(sessionRefunds)
|
||||
.append("次(§c").append(sessionRefundAmount)
|
||||
.append("枚§7)");
|
||||
sb.append("\n§6§l------------------------------");
|
||||
|
||||
if (!sessionSales.isEmpty()) {
|
||||
sb.append("\n§7销量排行:");
|
||||
List<Map.Entry<String, Integer>> sorted =
|
||||
new ArrayList<>(sessionSales.entrySet());
|
||||
sorted.sort((a, b) ->
|
||||
b.getValue() - a.getValue());
|
||||
int rank = 1;
|
||||
for (Map.Entry<String, Integer> e : sorted) {
|
||||
if (rank > 10) break;
|
||||
sb.append("\n§e").append(rank).append(". ")
|
||||
.append(e.getKey())
|
||||
.append(" §7x").append(e.getValue());
|
||||
rank++;
|
||||
}
|
||||
}
|
||||
sb.append("\n§6§l==============================");
|
||||
|
||||
String msg = sb.toString();
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (p.isOp()) {
|
||||
for (String line : msg.split("\n"))
|
||||
p.sendMessage(line);
|
||||
}
|
||||
}
|
||||
plugin.getLogger().info("[Sales] 汇报完成");
|
||||
resetSession();
|
||||
}
|
||||
|
||||
public void resetSession() {
|
||||
sessionSales.clear();
|
||||
sessionRevenue = 0;
|
||||
sessionOrders = 0;
|
||||
sessionItems = 0;
|
||||
sessionRefunds = 0;
|
||||
sessionRefundAmount = 0;
|
||||
sessionStart = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShopCategory {
|
||||
private final String name;
|
||||
private final String fileName;
|
||||
private final List<ShopItem> items;
|
||||
|
||||
public ShopCategory(String name, String fileName) {
|
||||
this.name = name;
|
||||
this.fileName = fileName;
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getFileName() { return fileName; }
|
||||
public List<ShopItem> getItems() { return items; }
|
||||
|
||||
public ShopItem getItem(String id) {
|
||||
for (ShopItem item : items) {
|
||||
if (item.getId().equalsIgnoreCase(id))
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addItem(ShopItem item) {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
public class ShopItem {
|
||||
private final String id;
|
||||
private final String displayName;
|
||||
private final Material material;
|
||||
private final int buyPrice;
|
||||
private final int sellPrice;
|
||||
private int stock;
|
||||
private int hourlySales;
|
||||
private int totalSales;
|
||||
//打包了 直接测试替换就好了。
|
||||
public ShopItem(String id, String displayName,
|
||||
Material material, int buyPrice,
|
||||
int sellPrice, int stock,
|
||||
int hourlySales, int totalSales) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
this.material = material;
|
||||
this.buyPrice = buyPrice;
|
||||
this.sellPrice = sellPrice;
|
||||
this.stock = stock;
|
||||
this.hourlySales = hourlySales;
|
||||
this.totalSales = totalSales;
|
||||
}
|
||||
|
||||
public int getEffectiveSellPrice() {
|
||||
if (sellPrice <= 0) return 0;
|
||||
return sellPrice;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getDisplayName() { return displayName; }
|
||||
public Material getMaterial() { return material; }
|
||||
public int getBuyPrice() { return buyPrice; }
|
||||
public int getSellPrice() { return sellPrice; }
|
||||
public int getStock() { return stock; }
|
||||
public int getHourlySales() { return hourlySales; }
|
||||
public int getTotalSales() { return totalSales; }
|
||||
|
||||
public void setStock(int s) { this.stock = s; }
|
||||
public void setHourlySales(int s) { this.hourlySales = s; }
|
||||
public void setTotalSales(int s) { this.totalSales = s; }
|
||||
public void addSales(int n) {
|
||||
this.hourlySales += n;
|
||||
this.totalSales += n;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,158 +1,158 @@
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VerificationManager {
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
public VerificationManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public boolean isBedrockPlayer(Player p) {
|
||||
String uuid = p.getUniqueId().toString();
|
||||
return uuid.contains("f851ba4")
|
||||
|| uuid.startsWith("00000000-0000-0000");
|
||||
}
|
||||
|
||||
public boolean isOnlineMode() {
|
||||
return Bukkit.getServer().getOnlineMode();
|
||||
}
|
||||
|
||||
public void verifyPremiumAsync(Player player,
|
||||
Consumer<Boolean> callback) {
|
||||
Thread t = new Thread(() -> doVerify(player, callback),
|
||||
"Sdf1_login-PremiumVerify");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void doVerify(Player player,
|
||||
Consumer<Boolean> callback) {
|
||||
String name = player.getName();
|
||||
String uuid = player.getUniqueId().toString();
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证开始 ======");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 玩家: " + name);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 当前UUID: " + uuid);
|
||||
|
||||
try {
|
||||
UUID offlineUuid = UUID.nameUUIDFromBytes(
|
||||
("OfflinePlayer:" + name)
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
String offlineStr = offlineUuid.toString();
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 离线UUID: " + offlineStr);
|
||||
|
||||
boolean isOfflineUuid =
|
||||
player.getUniqueId().equals(offlineUuid);
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] UUID匹配离线格式: "
|
||||
+ isOfflineUuid);
|
||||
|
||||
if (!isOfflineUuid) {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 正版玩家");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证结束 ======");
|
||||
callback.accept(true);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] UUID为离线格式,"
|
||||
+ "查询Mojang API...");
|
||||
UUID mojangUuid = fetchMojangUuid(name);
|
||||
|
||||
if (mojangUuid != null) {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang返回UUID: "
|
||||
+ mojangUuid);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 盗版客户端"
|
||||
+ "使用了正版名字");
|
||||
} else {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang无此名字");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 纯离线玩家");
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证结束 ======");
|
||||
callback.accept(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 验证异常: "
|
||||
+ e.getMessage());
|
||||
callback.accept(false);
|
||||
}
|
||||
}
|
||||
|
||||
private UUID fetchMojangUuid(String name) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(
|
||||
"https://api.mojang.com/users/profiles/minecraft/"
|
||||
+ name);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
conn.setRequestMethod("GET");
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang API响应: " + code);
|
||||
|
||||
if (code == 200) {
|
||||
BufferedReader reader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
conn.getInputStream(),
|
||||
StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String json = sb.toString();
|
||||
String uuidStr = json.replaceAll(
|
||||
".*\"id\"\\s*:\\s*\"([^\"]+)\".*",
|
||||
"$1");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang原始UUID: "
|
||||
+ uuidStr);
|
||||
|
||||
String formatted = uuidStr.replaceFirst(
|
||||
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
|
||||
"$1-$2-$3-$4-$5");
|
||||
return UUID.fromString(formatted);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] Mojang查询异常: "
|
||||
+ e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) conn.disconnect();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
package Sdf1_login;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VerificationManager {
|
||||
|
||||
private final Main plugin;
|
||||
|
||||
public VerificationManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public boolean isBedrockPlayer(Player p) {
|
||||
String uuid = p.getUniqueId().toString();
|
||||
return uuid.contains("f851ba4")
|
||||
|| uuid.startsWith("00000000-0000-0000");
|
||||
}
|
||||
|
||||
public boolean isOnlineMode() {
|
||||
return Bukkit.getServer().getOnlineMode();
|
||||
}
|
||||
|
||||
public void verifyPremiumAsync(Player player,
|
||||
Consumer<Boolean> callback) {
|
||||
Thread t = new Thread(() -> doVerify(player, callback),
|
||||
"Sdf1_login-PremiumVerify");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void doVerify(Player player,
|
||||
Consumer<Boolean> callback) {
|
||||
String name = player.getName();
|
||||
String uuid = player.getUniqueId().toString();
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证开始 ======");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 玩家: " + name);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 当前UUID: " + uuid);
|
||||
|
||||
try {
|
||||
UUID offlineUuid = UUID.nameUUIDFromBytes(
|
||||
("OfflinePlayer:" + name)
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
String offlineStr = offlineUuid.toString();
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 离线UUID: " + offlineStr);
|
||||
|
||||
boolean isOfflineUuid =
|
||||
player.getUniqueId().equals(offlineUuid);
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] UUID匹配离线格式: "
|
||||
+ isOfflineUuid);
|
||||
|
||||
if (!isOfflineUuid) {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 正版玩家");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证结束 ======");
|
||||
callback.accept(true);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] UUID为离线格式,"
|
||||
+ "查询Mojang API...");
|
||||
UUID mojangUuid = fetchMojangUuid(name);
|
||||
|
||||
if (mojangUuid != null) {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang返回UUID: "
|
||||
+ mojangUuid);
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 盗版客户端"
|
||||
+ "使用了正版名字");
|
||||
} else {
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang无此名字");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] 结论: 纯离线玩家");
|
||||
}
|
||||
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] ====== 验证结束 ======");
|
||||
callback.accept(false);
|
||||
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 验证异常: "
|
||||
+ e.getMessage());
|
||||
callback.accept(false);
|
||||
}
|
||||
}
|
||||
|
||||
private UUID fetchMojangUuid(String name) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(
|
||||
"https://api.mojang.com/users/profiles/minecraft/"
|
||||
+ name);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
conn.setRequestMethod("GET");
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang API响应: " + code);
|
||||
|
||||
if (code == 200) {
|
||||
BufferedReader reader =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(
|
||||
conn.getInputStream(),
|
||||
StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
String json = sb.toString();
|
||||
String uuidStr = json.replaceAll(
|
||||
".*\"id\"\\s*:\\s*\"([^\"]+)\".*",
|
||||
"$1");
|
||||
plugin.getLogger().info(
|
||||
"[Sdf1_login] Mojang原始UUID: "
|
||||
+ uuidStr);
|
||||
|
||||
String formatted = uuidStr.replaceFirst(
|
||||
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
|
||||
"$1-$2-$3-$4-$5");
|
||||
return UUID.fromString(formatted);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] Mojang查询异常: "
|
||||
+ e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) conn.disconnect();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,18 +60,12 @@ commands:
|
||||
deny:
|
||||
description: 拒绝OA请求
|
||||
usage: /deny <编号> [理由]
|
||||
#sdf1debug:
|
||||
# description: 调试指令
|
||||
# usage: /sdf1debug <玩家> take.<阶段>
|
||||
email:
|
||||
description: 绑定邮箱
|
||||
usage: /email <邮箱>
|
||||
绑定:
|
||||
description: 绑定邮箱(中文)
|
||||
usage: /绑定 <邮箱>
|
||||
调试:
|
||||
description: 调试模式
|
||||
usage: /调试
|
||||
绑定邮箱:
|
||||
description: 绑定邮箱
|
||||
usage: /绑定邮箱
|
||||
@@ -95,4 +89,13 @@ commands:
|
||||
usage: /cypay
|
||||
printer :
|
||||
description: 打印债券流水
|
||||
usage: /printer
|
||||
usage: /printer
|
||||
shop :
|
||||
description: 游戏商人
|
||||
usage: /shop
|
||||
商店:
|
||||
description: 打开商店
|
||||
usage: /商店
|
||||
#testorder:
|
||||
# description: 测试订单中心
|
||||
# usage: /testorder
|
||||
|
||||
Reference in New Issue
Block a user