初始化Sdf1_login项目

This commit is contained in:
ypsdf1
2026-05-06 17:28:50 +08:00
commit 8454df7e75
19 changed files with 4269 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+10
View File
@@ -0,0 +1,10 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# 已忽略包含查询文件的默认文件夹
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+7
View File
@@ -0,0 +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>
</project>
+14
View File
@@ -0,0 +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>
</project>
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sdf1</groupId>
<artifactId>Sdf1_login</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>Sdf1_login</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
+186
View File
@@ -0,0 +1,186 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.*;
public class AFKManager {
private final Map<UUID, Long> lastAction =
new HashMap<>();
private final Map<UUID, Integer> lastStage =
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() {
Bukkit.getScheduler().runTaskTimer(plugin,
() -> {
if (!config.afkEnabled) return;
long now = System.currentTimeMillis();
for (Player p :
Bukkit.getOnlinePlayers()) {
if (!isLoggedIn(p.getName()))
continue;
if (config.afkWhitelist
.contains(p.getName()))
continue;
UUID uuid = p.getUniqueId();
Long last = lastAction
.getOrDefault(uuid, now);
long idle = (now - last) / 1000;
int timeout = config.afkTimeout;
long remain = timeout - idle;
if (remain <= 0) {
p.kickPlayer(config.msg(
"afk_kicked"));
lastAction.remove(uuid);
lastStage.remove(uuid);
continue;
}
int stage =
lastStage.getOrDefault(uuid, 0);
if (remain <= 10) {
// 第三阶段:最后10秒
if (stage < 3) {
lastStage.put(uuid, 3);
}
// 聊天刷屏
p.sendMessage(config.msg(
"afk_warning",
"duration", fmtSec(idle),
"remain",
fmtSec(remain)));
// 红色大标题循环
p.sendTitle(
"§c§l⚠ 即将踢出 ⚠",
"§c" + fmtSec(remain)
+ "后被踢出",
5, 25, 5);
// 音效循环
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
} else if (remain <= 60) {
// 第二阶段:剩余1分钟
if (stage < 2) {
lastStage.put(uuid, 2);
// 仅一次聊天提醒
p.sendMessage(config.msg(
"afk_warning",
"duration", fmtSec(idle),
"remain",
fmtSec(remain)));
}
// 持续大标题循环
p.sendTitle(
"§e§l⚠ 挂机警告 ⚠",
"§f" + fmtSec(remain)
+ "后被踢出",
10, 40, 10);
// 音效循环
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
} else if (remain <= 180) {
// 第一阶段:剩余3分钟
if (stage < 1) {
lastStage.put(uuid, 1);
// 一次聊天提醒
p.sendMessage(config.msg(
"afk_warning",
"duration", fmtSec(idle),
"remain",
fmtSec(remain)));
// 一次大标题
p.sendTitle(
"§e§l⚠ 挂机警告 ⚠",
"§f" + fmtSec(remain)
+ "后被踢出",
10, 60, 20);
// 3次音效
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
if (p.isOnline()) {
p.playSound(
p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
p.playSound(
p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
}
}, 10L);
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
if (p.isOnline()) {
p.playSound(
p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 0.79f);
p.playSound(
p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_BASS,
1.0f, 0.79f);
}
}, 20L);
}
} else {
lastStage.remove(uuid);
}
}
}, 20L, 20L);
}
public void recordAction(UUID uuid) {
lastAction.put(uuid,
System.currentTimeMillis());
lastStage.remove(uuid);
}
public void remove(UUID uuid) {
lastAction.remove(uuid);
lastStage.remove(uuid);
}
private boolean isLoggedIn(String name) {
return ((Main) plugin)
.getLoggedIn().contains(name);
}
private String fmtSec(long sec) {
if (sec < 60) return sec + "秒";
long m = sec / 60;
long s = sec % 60;
return m + "分" + (s > 0 ? s + "秒" : "");
}
}
@@ -0,0 +1,583 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class ChatInputManager {
public enum InputType {
NONE,
CHANGE_PWD_STEP1,
CHANGE_PWD_STEP2,
CHANGE_PWD_STEP3,
SET_EMAIL,
EMAIL_VERIFY,
SMTP_STEP1,
SMTP_STEP2,
SMTP_STEP3,
SMTP_STEP4,
SMTP_STEP5,
SMTP_VERIFY,
ADMIN_AUTH,
ADMIN_SET_AFK_TIME,
ADMIN_SET_PWD,
ADMIN_SET_POINTS,
ADMIN_DELETE_CONFIRM,
INVITE_INPUT_CODE,
BACK_CHECKIN
}
public static class InputState {
public InputType type = InputType.NONE;
public String tmpStr = "";
public int tmpInt = 0;
public String targetPlayer = "";
public Map<String, String> tmpMap =
new LinkedHashMap<>();
}
private final Map<UUID, InputState> states =
new ConcurrentHashMap<>();
private final Map<String, String> verifyCodes =
new ConcurrentHashMap<>();
private final Map<String, Long> verifyExpiry =
new ConcurrentHashMap<>();
public InputState getState(Player p) {
return states.computeIfAbsent(
p.getUniqueId(), k -> new InputState());
}
public void clear(Player p) {
InputState s = states.get(p.getUniqueId());
if (s != null) {
s.type = InputType.NONE;
s.tmpMap.clear();
}
}
public void setVerifyCode(Player p, String code,
String purpose) {
String key = p.getUniqueId() + ":" + purpose;
verifyCodes.put(key, code);
verifyExpiry.put(key,
System.currentTimeMillis() + 300000L);
}
public boolean checkVerifyCode(Player p, String code,
String purpose) {
String key = p.getUniqueId() + ":" + purpose;
String stored = verifyCodes.get(key);
Long expiry = verifyExpiry.get(key);
if (stored == null || expiry == null)
return false;
if (System.currentTimeMillis() > expiry) {
verifyCodes.remove(key);
verifyExpiry.remove(key);
return false;
}
if (stored.equals(code)) {
verifyCodes.remove(key);
verifyExpiry.remove(key);
return true;
}
return false;
}
public boolean handleInput(Player p, String msg,
Main main) {
InputState s = getState(p);
if (s.type == InputType.NONE) return false;
if (msg.equalsIgnoreCase("00")) {
clear(p);
p.sendMessage(
"§e[Sdf1_login] §f操作已取消");
return true;
}
switch (s.type) {
case CHANGE_PWD_STEP1: {
String salt = (String) main.getDb()
.getField(p.getName(),
"password_salt");
if (salt == null) {
clear(p);
return true;
}
String hash =
PasswordUtils.hash(msg, salt);
if (main.getDb().checkPassword(
p.getName(), hash)) {
s.type =
InputType.CHANGE_PWD_STEP2;
p.sendMessage(
"§e[Sdf1_login] §f请输入新密码(6-20位,含大写+小写+数字)");
} else {
p.sendMessage(
"§c[Sdf1_login] §f旧密码错误!");
clear(p);
}
return true;
}
case CHANGE_PWD_STEP2: {
if (!PasswordUtils.validate(msg)) {
p.sendMessage(main.getConfig2()
.msg("password_invalid"));
return true;
}
s.tmpStr = msg;
s.type =
InputType.CHANGE_PWD_STEP3;
p.sendMessage(
"§e[Sdf1_login] §f请再次输入新密码确认");
return true;
}
case CHANGE_PWD_STEP3: {
if (!msg.equals(s.tmpStr)) {
p.sendMessage(
"§c[Sdf1_login] §f两次密码不一致");
s.type =
InputType.CHANGE_PWD_STEP2;
return true;
}
String salt =
PasswordUtils.generateSalt();
String hash =
PasswordUtils.hash(msg, salt);
main.getDb().setField(p.getName(),
"password_hash", hash);
main.getDb().setField(p.getName(),
"password_salt", salt);
main.getDb().setField(p.getName(),
"temp_password", "");
main.getDb().setField(p.getName(),
"temp_pw_used", 0);
main.getNeedsPasswordChange()
.remove(p.getName());
p.sendMessage(
"§a[Sdf1_login] §f密码修改成功!");
clear(p);
return true;
}
case SET_EMAIL: {
if (!msg.contains("@")
|| !msg.contains(".")) {
p.sendMessage(main.getConfig2()
.msg("email_invalid"));
return true;
}
String code =
PasswordUtils
.generateVerifyCode();
setVerifyCode(p, code, "email");
main.getEmail().sendVerifyCode(
msg, p.getName(), code);
s.tmpStr = msg;
s.type =
InputType.EMAIL_VERIFY;
p.sendMessage(main.getConfig2()
.msg("email_verify_sent",
"email", msg));
return true;
}
case EMAIL_VERIFY: {
if (checkVerifyCode(p, msg, "email")) {
main.getDb().setField(
p.getName(),
"email", s.tmpStr);
p.sendMessage(main.getConfig2()
.msg(
"email_verify_success"));
clear(p);
} else {
p.sendMessage(main.getConfig2()
.msg(
"email_verify_failed"));
}
return true;
}
case SMTP_STEP1: {
s.tmpMap.put("smtp地址", msg);
s.type = InputType.SMTP_STEP2;
p.sendMessage(
"§e[Sdf1_login] §f当前端口: "
+ main.getConfig2()
.getSmtp("smtp端口"));
p.sendMessage(
"§e[Sdf1_login] §f输入新端口(输入0跳过):");
return true;
}
case SMTP_STEP2: {
if (!msg.equals("0"))
s.tmpMap.put("smtp端口", msg);
s.type = InputType.SMTP_STEP3;
p.sendMessage(
"§e[Sdf1_login] §f输入发件邮箱(输入0跳过):");
return true;
}
case SMTP_STEP3: {
if (!msg.equals("0"))
s.tmpMap.put("发件邮箱", msg);
s.type = InputType.SMTP_STEP4;
p.sendMessage(
"§e[Sdf1_login] §f输入授权码(输入0跳过):");
return true;
}
case SMTP_STEP4: {
if (!msg.equals("0"))
s.tmpMap.put("授权码", msg);
s.type = InputType.SMTP_STEP5;
p.sendMessage(
"§e[Sdf1_login] §f输入测试接收邮箱(输入0跳过):");
return true;
}
case SMTP_STEP5: {
if (!msg.equals("0"))
s.tmpMap.put("测试邮箱", msg);
String testEmail =
s.tmpMap.getOrDefault(
"测试邮箱", "");
String senderEmail =
s.tmpMap.getOrDefault(
"发件邮箱", "");
String recipient =
!testEmail.isEmpty()
? testEmail : senderEmail;
if (recipient == null
|| recipient.isEmpty()) {
p.sendMessage(
"§c[Sdf1_login] §f请至少填写发件邮箱");
clear(p);
return true;
}
String code =
PasswordUtils
.generateVerifyCode();
setVerifyCode(p, code, "smtp");
main.getEmail().sendVerifyCode(
recipient,
p.getName(), code);
s.type =
InputType.SMTP_VERIFY;
p.sendMessage(main.getConfig2()
.msg("smtp_verify_sent",
"email", recipient));
return true;
}
case SMTP_VERIFY: {
if (checkVerifyCode(p, msg, "smtp")) {
String addr =
s.tmpMap.getOrDefault(
"smtp地址",
main.getConfig2()
.getSmtp("smtp地址"));
String port =
s.tmpMap.getOrDefault(
"smtp端口",
main.getConfig2()
.getSmtp("smtp端口"));
String sender =
s.tmpMap.getOrDefault(
"发件邮箱",
main.getConfig2()
.getSmtp("发件邮箱"));
String auth =
s.tmpMap.getOrDefault(
"授权码",
main.getConfig2()
.getSmtp("授权码"));
String test =
s.tmpMap.getOrDefault(
"测试邮箱",
main.getConfig2()
.getSmtp("测试邮箱"));
main.getConfig2().setSmtp(
"smtp地址", addr);
main.getConfig2().setSmtp(
"smtp端口", port);
main.getConfig2().setSmtp(
"发件邮箱", sender);
main.getConfig2().setSmtp(
"授权码", auth);
main.getConfig2().setSmtp(
"测试邮箱", test);
p.sendMessage(main.getConfig2()
.msg(
"smtp_verify_success"));
clear(p);
} else {
p.sendMessage(main.getConfig2()
.msg(
"smtp_verify_failed"));
clear(p);
}
return true;
}
case ADMIN_AUTH: {
String pass =
main.getConfig2().adminPassword;
if (msg.equals(pass)) {
if ("qweasd".equals(pass)) {
p.sendMessage(
"§c§l[警告] §f当前使用默认管理密码,请尽快修改!");
}
Bukkit.getScheduler()
.runTask(main, () -> {
main.getGui()
.openAdmin(p);
});
} else {
p.sendMessage(
"§c[Sdf1_login] §f管理密码错误!");
}
clear(p);
return true;
}
case ADMIN_SET_AFK_TIME: {
int seconds =
parseAfkTime(msg);
if (seconds > 0) {
main.getConfig2()
.afkTimeout = seconds;
int min = seconds / 60;
p.sendMessage(main.getConfig2()
.msg("afk_set_time",
"duration",
min + "分钟"));
} else {
p.sendMessage(
"§c[Sdf1_login] §f时间格式无效");
}
clear(p);
return true;
}
case ADMIN_SET_PWD: {
if (msg.equals("0")) {
p.sendMessage(
"§e[Sdf1_login] §f操作已取消");
clear(p);
return true;
}
String target = s.targetPlayer;
String salt =
PasswordUtils.generateSalt();
String hash =
PasswordUtils.hash(msg, salt);
main.getDb().setField(target,
"password_hash", hash);
main.getDb().setField(target,
"password_salt", salt);
p.sendMessage(main.getConfig2()
.msg("admin_set_password",
"user", target));
clear(p);
return true;
}
case ADMIN_SET_POINTS: {
try {
int pts =
Integer.parseInt(msg);
main.getDb().setField(
s.targetPlayer,
"points", pts);
p.sendMessage(main.getConfig2()
.msg("admin_points_set",
"user",
s.targetPlayer,
"points",
String.valueOf(pts)));
} catch (NumberFormatException e) {
p.sendMessage(
"§c[Sdf1_login] §f请输入数字");
}
clear(p);
return true;
}
case ADMIN_DELETE_CONFIRM: {
String[] parts =
msg.split("\\s+", 2);
if (parts.length < 2) {
p.sendMessage(main.getConfig2()
.msg("admin_delete_confirm"));
p.sendMessage(
"§c格式: <玩家名> <你的密码>");
return true;
}
String targetName = parts[0];
String pwd = parts[1];
String salt = (String) main.getDb()
.getField(p.getName(),
"password_salt");
if (salt == null) {
clear(p);
return true;
}
String hash =
PasswordUtils.hash(pwd, salt);
if (main.getDb().checkPassword(
p.getName(), hash)
&& targetName
.equalsIgnoreCase(
s.targetPlayer)) {
main.getDb()
.deleteUser(targetName);
p.sendMessage(main.getConfig2()
.msg("admin_delete_success",
"user",
targetName));
} else {
p.sendMessage(main.getConfig2()
.msg(
"admin_delete_failed"));
}
clear(p);
return true;
}
case BACK_CHECKIN: {
String result =
main.getCheckIn()
.backCheckIn(p, msg);
p.sendMessage(result);
clear(p);
return true;
}
case INVITE_INPUT_CODE: {
String code =
msg.trim().toUpperCase();
String inviter =
findInvokerByCode(main, code);
if (inviter != null
&& !inviter.equals(
p.getName())) {
main.getDb().setField(
p.getName(),
"invited_by", inviter);
// 更新邀请人数据
String month =
new SimpleDateFormat("yyyy-MM")
.format(new Date());
String lastMonth =
(String) main.getDb()
.getField(inviter,
"invite_month");
if (!month.equals(lastMonth)) {
main.getDb().setField(
inviter,
"invite_month", month);
main.getDb().setField(
inviter,
"monthly_invite_count",
0);
}
int cnt = ((Number) main.getDb()
.getField(inviter,
"monthly_invite_count"))
.intValue();
main.getDb().setField(
inviter,
"monthly_invite_count",
cnt + 1);
// 通知邀请人
Player inviterPlayer =
Bukkit.getPlayerExact(
inviter);
if (inviterPlayer != null
&& inviterPlayer
.isOnline()) {
inviterPlayer.sendMessage(
main.getConfig2().msg(
"invite_referral_bonus",
"points",
String.valueOf(
cnt + 1)));
}
p.sendMessage(main.getConfig2()
.msg("invite_bound",
"user", inviter));
} else {
p.sendMessage(
"§c[Sdf1_login] §f邀请码无效");
}
clear(p);
return true;
}
default:
return false;
}
}
private int parseAfkTime(String input) {
String s = input.toLowerCase().trim();
if (s.contains("小时") || s.contains("时")) {
String num =
s.replaceAll("[^0-9]", "");
if (!num.isEmpty())
return Integer.parseInt(num) * 3600;
}
if (s.contains("分钟") || s.contains("分")) {
String num =
s.replaceAll("[^0-9]", "");
if (!num.isEmpty())
return Integer.parseInt(num) * 60;
}
if (s.endsWith("h")) {
String num =
s.replaceAll("[^0-9]", "");
if (!num.isEmpty())
return Integer.parseInt(num) * 3600;
}
if (s.endsWith("m")) {
String num =
s.replaceAll("[^0-9]", "");
if (!num.isEmpty())
return Integer.parseInt(num) * 60;
}
String num =
s.replaceAll("[^0-9]", "");
if (!num.isEmpty())
return Integer.parseInt(num) * 60;
return -1;
}
private String findInvokerByCode(Main main,
String code) {
for (Map<String, Object> user
: main.getDb().getAllUsers()) {
String ic = (String) user.getOrDefault(
"invite_code", "");
if (code.equals(ic)) {
return (String) user
.get("player_name");
}
}
return null;
}
}
@@ -0,0 +1,104 @@
package Sdf1_login;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CheckInManager {
private final Main plugin;
public CheckInManager(Main plugin) {
this.plugin = plugin;
}
public boolean isCheckedInToday(String name) {
String last = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
return today.equals(last);
}
public String checkIn(Player p) {
String name = p.getName();
if (isCheckedInToday(name)) {
return plugin.getConfig2().msg("checkin_already");
}
int streak = ((Number) plugin.getDb()
.getField(name, "checkin_streak")).intValue();
String lastDate = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
// 检查是否连续
if (lastDate != null && !lastDate.isEmpty()) {
try {
java.util.Date lastD = new SimpleDateFormat(
"yyyy-MM-dd").parse(lastDate);
java.util.Date todayD = new SimpleDateFormat(
"yyyy-MM-dd").parse(today);
long diff = todayD.getTime() - lastD.getTime();
if (diff > 86400000L * 2) {
streak = 0; // 断签
} else if (diff > 86400000L) {
streak++; // 连续
}
} catch (Exception ignored) {}
}
if (streak == 0) streak = 1;
else streak++;
double multi = 1.0;
if (streak >= 21) multi = 1.5;
else if (streak >= 14) multi = 1.3;
else if (streak >= 7) multi = 1.1;
int points = (int) (1 * multi);
plugin.getDb().addPoints(name, points);
plugin.getDb().setField(
name, "checkin_streak", streak);
plugin.getDb().setField(
name, "last_checkin_date", today);
int total = ((Number) plugin.getDb()
.getField(name, "total_checkin_days"))
.intValue();
plugin.getDb().setField(
name, "total_checkin_days", total + 1);
return plugin.getConfig2().msg("checkin_success",
"points", String.valueOf(points),
"streak", String.valueOf(streak),
"multi", String.valueOf(multi));
}
public String backCheckIn(Player p, String dateStr) {
String name = p.getName();
if (!plugin.getDb().deductPoints(name, 10)) {
return plugin.getConfig2().msg("backcheckin_no_point");
}
try {
java.util.Date target = new SimpleDateFormat(
"yyyy-MM-dd").parse(dateStr);
java.util.Date now = new SimpleDateFormat(
"yyyy-MM-dd").parse(
new SimpleDateFormat("yyyy-MM-dd")
.format(new Date()));
long diff = now.getTime() - target.getTime();
if (diff < 0 || diff > 3 * 86400000L) {
plugin.getDb().addPoints(name, 10); // 退还
return plugin.getConfig2().msg(
"backcheckin_expired");
}
plugin.getDb().addPoints(name, 1); // 补签也给1分
return plugin.getConfig2().msg("backcheckin_success");
} catch (Exception e) {
plugin.getDb().addPoints(name, 10);
return "§c[Sdf1_login] §f日期格式错误,请用 yyyy-MM-dd";
}
}
}
+433
View File
@@ -0,0 +1,433 @@
package Sdf1_login;
// 配置文件
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.*;
public class ConfigManager {
private final Map<String, String> messages =
new LinkedHashMap<>();
private final Map<String, String> smtp = new LinkedHashMap<>();
private final File dataFolder;
// 配置项
public int afkTimeout = 300;
public int loginTimeout = 120;
public boolean afkEnabled = true;
public Set<String> afkWhitelist =
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
public String adminTag = "admin";
public String adminPassword = "qweasd";
public ConfigManager(File dataFolder) {
this.dataFolder = dataFolder;
}
// ===== 消息配置 =====
public void loadMessages() {
messages.clear();
File f = new File(dataFolder, "设置.txt");
if (!f.exists()) writeDefaultMessages();
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("\uFEFF"))
line = line.substring(1);
String t = line.trim();
if (t.isEmpty() || t.startsWith("#")) continue;
int eq = t.indexOf('=');
if (eq < 0) continue;
messages.put(t.substring(0, eq).trim(),
t.substring(eq + 1).trim());
}
r.close();
} catch (Exception e) {
e.printStackTrace();
}
ensureMessageDefaults();
}
private void ensureMessageDefaults() {
putDefault("not_registered",
"§c§l[Sdf1_login] §f你还没有注册,请使用 /reg <密码> 注册");
putDefault("not_logged_in",
"§c§l[Sdf1_login] §f请先使用 /login <密码> 登录");
putDefault("login_success",
"§a§l[Sdf1_login] §f登录成功!欢迎回来 {user}");
putDefault("register_success",
"§a§l[Sdf1_login] §f注册成功!请使用 /login <密码> 登录");
putDefault("login_failed",
"§c§l[Sdf1_login] §f密码错误!");
putDefault("already_registered",
"§c§l[Sdf1_login] §f你已经注册过了");
putDefault("already_logged_in",
"§a§l[Sdf1_login] §f你已经登录了");
putDefault("password_invalid",
"§c§l[Sdf1_login] §f密码不符合要求!需要6-20位,包含大写字母+小写字母+数字");
putDefault("password_format_hint",
"§7密码要求: 6-20位,至少1个大写字母+1个小写字母+1个数字");
putDefault("auto_login_ip",
"§a§l[Sdf1_login] §f检测到同IP近期登录,已自动登录!欢迎回来 {user}");
putDefault("email_verify_sent",
"§a§l[Sdf1_login] §f验证码已发送到 {email},请输入6位验证码:");
putDefault("email_verify_success",
"§a§l[Sdf1_login] §f邮箱验证成功,已绑定!");
putDefault("email_verify_failed",
"§c§l[Sdf1_login] §f验证码错误,请重试");
putDefault("smtp_verify_sent",
"§a§l[Sdf1_login] §f验证码已发送到 {email},请输入6位验证码:");
putDefault("smtp_verify_success",
"§a§l[Sdf1_login] §fSMTP配置验证成功!");
putDefault("smtp_verify_failed",
"§c§l[Sdf1_login] §f验证码错误,请重试");
putDefault("login_timeout",
"§c§l[Sdf1_login] §f登录超时,已被踢出");
putDefault("login_required",
"§c§l[Sdf1_login] §f你还没有登录,请先 /login <密码>");
putDefault("reset_no_email",
"§c§l[Sdf1_login] §f你还没有绑定邮箱,无法通过邮件重置密码");
putDefault("reset_sent",
"§a§l[Sdf1_login] §f临时密码已发送到你的邮箱,请查收");
putDefault("reset_temp_used",
"§a§l[Sdf1_login] §f临时密码已生效,旧密码已失效");
putDefault("reset_failed",
"§c§l[Sdf1_login] §f重置失败,请联系管理员");
putDefault("email_set",
"§a§l[Sdf1_login] §f邮箱已设置为: {email}");
putDefault("email_invalid",
"§c§l[Sdf1_login] §f邮箱格式不正确");
putDefault("checkin_success",
"§a§l[Sdf1_login] §f签到成功!获得 {points} 积分(连续{streak}天,倍率x{multi})");
putDefault("checkin_already",
"§c§l[Sdf1_login] §f今天已经签到过了");
putDefault("backcheckin_success",
"§a§l[Sdf1_login] §f补签成功!消耗10积分");
putDefault("backcheckin_no_point",
"§c§l[Sdf1_login] §f积分不足,补签需要10积分");
putDefault("backcheckin_expired",
"§c§l[Sdf1_login] §f只能补签3天内的签到");
putDefault("points_insufficient",
"§c§l[Sdf1_login] §f积分不足!当前: {points}");
putDefault("points_purchase_success",
"§a§l[Sdf1_login] §f兑换成功!消耗 {count} 积分");
putDefault("invite_code_generated",
"§a§l[Sdf1_login] §f你的邀请码: {code}");
putDefault("invite_bound",
"§a§l[Sdf1_login] §f你已被 {user} 邀请!");
putDefault("invite_referral_bonus",
"§a§l[Sdf1_login] §f邀请返点: +{points} 积分");
putDefault("gift_claimed",
"§a§l[Sdf1_login] §f礼包领取成功!");
putDefault("gift_not_ready",
"§c§l[Sdf1_login] §f礼包条件未满足");
putDefault("gift_all_claimed",
"§a§l[Sdf1_login] §f所有礼包已领取完毕!");
putDefault("admin_no_permission",
"§c§l[Sdf1_login] §f无权限");
putDefault("admin_set_password",
"§a§l[Sdf1_login] §f已为 {user} 设置新密码");
putDefault("admin_delete_confirm",
"§c§l[Sdf1_login] §f请输入要删除的玩家名和你的密码进行确认");
putDefault("admin_delete_success",
"§a§l[Sdf1_login] §f玩家 {user} 已删除");
putDefault("admin_delete_failed",
"§c§l[Sdf1_login] §f删除失败:验证不通过");
putDefault("admin_points_set",
"§a§l[Sdf1_login] §f已设置 {user} 积分为 {points}");
putDefault("afk_warning",
"§e§l[Sdf1_login] §f你已挂机 {duration},将在 {remain} 后被踢出");
putDefault("afk_kicked",
"§c§l[Sdf1_login] §f因挂机过久被踢出");
putDefault("afk_set_time",
"§a§l[Sdf1_login] §f挂机踢出时长已设为 {duration}");
putDefault("afk_set_enabled",
"§a§l[Sdf1_login] §f挂机踢出已开启");
putDefault("afk_set_disabled",
"§a§l[Sdf1_login] §f挂机踢出已关闭");
putDefault("afk_whitelist_added",
"§a§l[Sdf1_login] §f已将 {user} 加入防踢白名单");
putDefault("afk_whitelist_removed",
"§a§l[Sdf1_login] §f已将 {user} 移出防踢白名单");
}
private void putDefault(String key, String value) {
messages.putIfAbsent(key, value);
}
public String msg(String key, Object... kvPairs) {
String template = messages.getOrDefault(key, key);
for (int i = 0; i < kvPairs.length - 1; i += 2) {
String varName = kvPairs[i].toString();
String value = kvPairs[i + 1] != null
? kvPairs[i + 1].toString() : "";
String[] open = {"{", "[", "(", "【", "(", "《", "<"};
String[] close = {"}", "]", ")", "】", ")", "》", ">"};
for (int j = 0; j < open.length; j++) {
template = template.replace(
open[j] + varName + close[j], value);
}
}
template = template.replace("\\n", "\n");
template = template.replace("<br>", "\n");
return template;
}
private void writeDefaultMessages() {
File f = new File(dataFolder, "设置.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# ==========================================");
pw.println("# Sdf1_login 消息提示配置");
pw.println("# ==========================================");
pw.println("# 变量支持: {变量} [变量] (变量) 【变量】");
pw.println("# 可用变量: [user] [duration] [count]");
pw.println("# [code] [points] [stage]");
pw.println("# [email] [url] [reason]");
pw.println();
pw.println("# ===== 登录系统 =====");
pw.println("not_registered=§c§l[Sdf1_login] §f你还没有注册,请使用 /reg <密码> 注册");
pw.println("not_logged_in=§c§l[Sdf1_login] §f请先使用 /login <密码> 登录");
pw.println("login_success=§a§l[Sdf1_login] §f登录成功!欢迎回来 [user]");
pw.println("register_success=§a§l[Sdf1_login] §f注册成功!请使用 /login <密码> 登录");
pw.println("login_failed=§c§l[Sdf1_login] §f密码错误!");
pw.println("already_registered=§c§l[Sdf1_login] §f你已经注册过了");
pw.println("already_logged_in=§a§l[Sdf1_login] §f你已经登录了");
pw.println("password_invalid=§c§l[Sdf1_login] §f密码不符合要求!需要6-20位,包含大写字母+小写字母+数字");
pw.println("password_format_hint=§7密码要求: 6-20位,至少1个大写字母+1个小写字母+1个数字");
pw.println();
pw.println("# ===== IP自动登录 =====");
pw.println("auto_login_ip=§a§l[Sdf1_login] §f检测到同IP近期登录,已自动登录!欢迎回来 [user]");
pw.println();
pw.println("# ===== 邮箱验证 =====");
pw.println("email_verify_sent=§a§l[Sdf1_login] §f验证码已发送到 [email],请输入6位验证码:");
pw.println("email_verify_success=§a§l[Sdf1_login] §f邮箱验证成功,已绑定!");
pw.println("email_verify_failed=§c§l[Sdf1_login] §f验证码错误,请重试");
pw.println();
pw.println("# ===== SMTP验证 =====");
pw.println("smtp_verify_sent=§a§l[Sdf1_login] §f验证码已发送到 [email],请输入6位验证码:");
pw.println("smtp_verify_success=§a§l[Sdf1_login] §fSMTP配置验证成功!");
pw.println("smtp_verify_failed=§c§l[Sdf1_login] §f验证码错误,请重试");
pw.println("login_timeout=§c§l[Sdf1_login] §f登录超时,已被踢出");
pw.println("login_required=§c§l[Sdf1_login] §f你还没有登录,请先 /login <密码>");
pw.println();
pw.println("# ===== 密码重置 =====");
pw.println("reset_no_email=§c§l[Sdf1_login] §f你还没有绑定邮箱,无法通过邮件重置密码");
pw.println("reset_sent=§a§l[Sdf1_login] §f临时密码已发送到你的邮箱,请查收");
pw.println("reset_temp_used=§a§l[Sdf1_login] §f临时密码已生效,旧密码已失效");
pw.println("reset_failed=§c§l[Sdf1_login] §f重置失败,请联系管理员");
pw.println();
pw.println("# ===== 邮箱 =====");
pw.println("email_set=§a§l[Sdf1_login] §f邮箱已设置为: [email]");
pw.println("email_invalid=§c§l[Sdf1_login] §f邮箱格式不正确");
pw.println();
pw.println("# ===== 签到 =====");
pw.println("checkin_success=§a§l[Sdf1_login] §f签到成功!获得 [points] 积分(连续[streak]天,倍率x[multi])");
pw.println("checkin_already=§c§l[Sdf1_login] §f今天已经签到过了");
pw.println("backcheckin_success=§a§l[Sdf1_login] §f补签成功!消耗10积分");
pw.println("backcheckin_no_point=§c§l[Sdf1_login] §f积分不足,补签需要10积分");
pw.println("backcheckin_expired=§c§l[Sdf1_login] §f只能补签3天内的签到");
pw.println();
pw.println("# ===== 积分 =====");
pw.println("points_insufficient=§c§l[Sdf1_login] §f积分不足!当前: [points]");
pw.println("points_purchase_success=§a§l[Sdf1_login] §f兑换成功!消耗 [count] 积分");
pw.println();
pw.println("# ===== 邀请 =====");
pw.println("invite_code_generated=§a§l[Sdf1_login] §f你的邀请码: [code]");
pw.println("invite_bound=§a§l[Sdf1_login] §f你已被 [user] 邀请!");
pw.println("invite_referral_bonus=§a§l[Sdf1_login] §f邀请返点: +[points] 积分");
pw.println();
pw.println("# ===== 新人礼包 =====");
pw.println("gift_claimed=§a§l[Sdf1_login] §f礼包领取成功!");
pw.println("gift_not_ready=§c§l[Sdf1_login] §f礼包条件未满足");
pw.println("gift_all_claimed=§a§l[Sdf1_login] §f所有礼包已领取完毕!");
pw.println();
pw.println("# ===== 管理员 =====");
pw.println("admin_no_permission=§c§l[Sdf1_login] §f无权限");
pw.println("admin_set_password=§a§l[Sdf1_login] §f已为 [user] 设置新密码");
pw.println("admin_delete_confirm=§c§l[Sdf1_login] §f请输入要删除的玩家名和你的密码进行确认");
pw.println("admin_delete_success=§a§l[Sdf1_login] §f玩家 [user] 已删除");
pw.println("admin_delete_failed=§c§l[Sdf1_login] §f删除失败:验证不通过");
pw.println("admin_points_set=§a§l[Sdf1_login] §f已设置 [user] 积分为 [points]");
pw.println("管理密码=qweasd");
pw.println();
pw.println("# ===== 挂机踢出 =====");
pw.println("afk_warning=§e§l[Sdf1_login] §f你已挂机 [duration],将在 [remain] 后被踢出");
pw.println("afk_kicked=§c§l[Sdf1_login] §f因挂机过久被踢出");
pw.println("afk_set_time=§a§l[Sdf1_login] §f挂机踢出时长已设为 [duration]");
pw.println("afk_set_enabled=§a§l[Sdf1_login] §f挂机踢出已开启");
pw.println("afk_set_disabled=§a§l[Sdf1_login] §f挂机踢出已关闭");
pw.println("afk_whitelist_added=§a§l[Sdf1_login] §f已将 [user] 加入防踢白名单");
pw.println("afk_whitelist_removed=§a§l[Sdf1_login] §f已将 [user] 移出防踢白名单");
pw.println();
pw.println("# ===== 帮助 =====");
pw.println("help_header=§e[Sdf1_login] §f命令列表:");
pw.println("help_1=§a/sdf1_login §7- 打开面板");
pw.println("help_2=§a/reg <密码> §7- 注册");
pw.println("help_3=§a/login <密码> §7- 登录");
pw.println("help_4=§a/sdf1_login reset §7- 重置密码");
pw.println("help_5=§a/sdf1_login email <邮箱> §7- 绑定邮箱");
pw.println("help_6=§a/sdf1_login set <玩家> <密码> §7- [管理员] 设置密码");
pw.println("help_7=§a/sdf1_login del <玩家> §7- [管理员] 删除用户");
pw.println("help_8=§a/sdf1_login kick on/off [分钟] §7- [管理员] 挂机设置");
pw.println("help_9=§a/sdf1_login add/remove <玩家> §7- [管理员] 防踢白名单");
pw.println("help_10=§a/sdf1_login get <玩家> §7- [管理员] 查看礼包进度");
pw.println("help_11=§a/sdf1_login reload §7- [管理员] 重载配置");
} catch (IOException ignored) {}
}
// ===== SMTP配置 =====
public void loadSmtp() {
smtp.clear();
File f = new File(dataFolder, "smtp.txt");
if (!f.exists()) writeDefaultSmtp();
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("\uFEFF"))
line = line.substring(1);
String t = line.trim();
if (t.isEmpty() || t.startsWith("#")) continue;
int eq = t.indexOf('=');
if (eq < 0) continue;
smtp.put(t.substring(0, eq).trim(),
t.substring(eq + 1).trim());
}
r.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void writeDefaultSmtp() {
File f = new File(dataFolder, "smtp.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# ==========================================");
pw.println("# Sdf1_login SMTP邮件配置");
pw.println("# ==========================================");
pw.println();
pw.println("# SMTP服务器地址");
pw.println("smtp地址=smtp.qq.com");
pw.println();
pw.println("# 端口号(SSL默认465)");
pw.println("smtp端口=465");
pw.println();
pw.println("# 发件人邮箱地址");
pw.println("发件邮箱=your@email.com");
pw.println();
pw.println("# 邮箱授权码(非登录密码)");
pw.println("授权码=");
pw.println();
pw.println("# 测试接收邮箱(留空则发给发件人)");
pw.println("测试邮箱=");
} catch (IOException ignored) {}
}
public String getSmtp(String key) {
return smtp.getOrDefault(key, "");
}
public void setSmtp(String key, String value) {
smtp.put(key, value);
saveSmtp();
}
public Map<String, String> getSmtpAll() {
return new LinkedHashMap<>(smtp);
}
public void saveSettings() {
File f = new File(dataFolder, "url.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("管理标签: " + adminTag);
pw.println("管理密码: " + adminPassword);
pw.println("挂机踢出: "
+ (afkEnabled ? "true" : "false"));
pw.println("挂机时长: " + (afkTimeout / 60));
pw.println("登录超时: " + loginTimeout);
} catch (IOException ignored) {}
}
private void saveSmtp() {
File f = new File(dataFolder, "smtp.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# ==========================================");
pw.println("# Sdf1_login SMTP邮件配置");
pw.println("# ==========================================");
pw.println();
pw.println("smtp地址=" + smtp.getOrDefault("smtp地址", "smtp.qq.com"));
pw.println("smtp端口=" + smtp.getOrDefault("smtp端口", "465"));
pw.println("发件邮箱=" + smtp.getOrDefault("发件邮箱", ""));
pw.println("授权码=" + smtp.getOrDefault("授权码", ""));
pw.println("测试邮箱=" + smtp.getOrDefault("测试邮箱", ""));
} catch (IOException ignored) {}
}
// ===== 其他配置 =====
public void loadSettings() {
File f = new File(dataFolder, "url.txt");
if (!f.exists()) return;
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
while ((line = r.readLine()) != null) {
String t = line.trim();
if (t.isEmpty() || t.startsWith("#")) continue;
String[] kv = t.split(":", 2);
if (kv.length < 2) continue;
String k = kv[0].trim();
String v = kv[1].trim();
switch (k) {
case "管理标签":
adminTag = v; break;
case "管理密码":
adminPassword = v; break;
case "挂机踢出":
afkEnabled = v.equalsIgnoreCase("true")
|| v.equalsIgnoreCase("开启");
break;
case "挂机时长":
try {
afkTimeout = Integer.parseInt(v) * 60;
} catch (Exception ignored) {}
break;
case "登录超时":
try {
loginTimeout = Integer.parseInt(v);
} catch (Exception ignored) {}
break;
}
}
r.close();
} catch (Exception ignored) {}
}
}
@@ -0,0 +1,259 @@
package Sdf1_login;
import java.io.File;
import java.sql.*;
import java.util.*;
//全部数据库操作
public class DatabaseManager {
private Connection db;
private final File dataFolder;
public DatabaseManager(File dataFolder) {
this.dataFolder = dataFolder;
}
public void init() {
try {
Class.forName("org.sqlite.JDBC");
File dbFile = new File(dataFolder, "login.db");
db = DriverManager.getConnection(
"jdbc:sqlite:" + dbFile.getAbsolutePath());
Statement st = db.createStatement();
st.execute("PRAGMA journal_mode=WAL");
st.execute("CREATE TABLE IF NOT EXISTS users ("
+ "player_name TEXT PRIMARY KEY,"
+ "password_hash TEXT NOT NULL,"
+ "password_salt TEXT NOT NULL,"
+ "email TEXT DEFAULT '',"
+ "register_time INTEGER DEFAULT 0,"
+ "last_login_time INTEGER DEFAULT 0,"
+ "total_online_time INTEGER DEFAULT 0,"
+ "last_online_check INTEGER DEFAULT 0,"
+ "is_logged_in INTEGER DEFAULT 0,"
+ "blocks_broken INTEGER DEFAULT 0,"
+ "blocks_placed INTEGER DEFAULT 0,"
+ "gift_stage INTEGER DEFAULT 0,"
+ "gift_claimed TEXT DEFAULT '',"
+ "invite_code TEXT DEFAULT '',"
+ "invited_by TEXT DEFAULT '',"
+ "monthly_invite_count INTEGER DEFAULT 0,"
+ "invite_month TEXT DEFAULT '',"
+ "points INTEGER DEFAULT 0,"
+ "checkin_streak INTEGER DEFAULT 0,"
+ "last_checkin_date TEXT DEFAULT '',"
+ "total_checkin_days INTEGER DEFAULT 0,"
+ "temp_password TEXT DEFAULT '',"
+ "temp_pw_expire INTEGER DEFAULT 0,"
+ "temp_pw_used INTEGER DEFAULT 0)");
addColumnIfMissing("tasks_completed", "TEXT DEFAULT ''");
addColumnIfMissing("temp_password", "TEXT DEFAULT ''");
addColumnIfMissing("temp_pw_expire", "INTEGER DEFAULT 0");
addColumnIfMissing("temp_pw_used", "INTEGER DEFAULT 0");
st.close();
} catch (Exception e) {
throw new RuntimeException(
"[Sdf1_login] DB初始化失败: " + e.getMessage(), e);
}
}
public void close() {
try {
if (db != null && !db.isClosed()) db.close();
} catch (Exception ignored) {}
}
public Connection getDb() { return db; }
private void addColumnIfMissing(String col, String def) {
try {
db.createStatement().executeUpdate(
"ALTER TABLE users ADD COLUMN "
+ col + " " + def);
} catch (SQLException ignored) {}
}
// ===== 用户操作 =====
public boolean userExists(String name) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT 1 FROM users WHERE player_name=?");
ps.setString(1, name);
boolean r = ps.executeQuery().next();
ps.close();
return r;
} catch (SQLException e) { return false; }
}
public void createUser(String name, String hash, String salt) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT INTO users "
+ "(player_name,password_hash,"
+ "password_salt,register_time) "
+ "VALUES (?,?,?,?)");
ps.setString(1, name);
ps.setString(2, hash);
ps.setString(3, salt);
ps.setLong(4, System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Map<String, Object> getUser(String name) {
Map<String, Object> r = new LinkedHashMap<>();
try {
PreparedStatement ps = db.prepareStatement(
"SELECT * FROM users WHERE player_name=?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
for (int i = 1;
i <= rs.getMetaData().getColumnCount(); i++) {
r.put(rs.getMetaData().getColumnName(i),
rs.getObject(i));
}
}
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return r;
}
public void setField(String name, String field, Object val) {
try {
PreparedStatement ps = db.prepareStatement(
"UPDATE users SET " + field
+ "=? WHERE player_name=?");
if (val instanceof Integer)
ps.setInt(1, (Integer) val);
else if (val instanceof Long)
ps.setLong(1, (Long) val);
else
ps.setString(1, String.valueOf(val));
ps.setString(2, name);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Object getField(String name, String field) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT " + field
+ " FROM users WHERE player_name=?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
Object val = rs.next() ? rs.getObject(1) : null;
rs.close();
ps.close();
return val;
} catch (SQLException e) {
return null;
}
}
public boolean checkPassword(String name, String hash) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT password_hash FROM users "
+ "WHERE player_name=?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String stored = rs.getString("password_hash");
rs.close();
ps.close();
return stored.equals(hash);
}
rs.close();
ps.close();
return false;
} catch (SQLException e) {
return false;
}
}
public boolean checkPasswordOrTemp(
String name, String hash) {
if (checkPassword(name, hash)) return true;
Map<String, Object> user = getUser(name);
if (user.isEmpty()) return false;
String tempHash = (String) user.getOrDefault(
"temp_password", "");
long expire = ((Number) user.getOrDefault(
"temp_pw_expire", 0L)).longValue();
if (tempHash == null || tempHash.isEmpty())
return false;
if (System.currentTimeMillis() > expire)
return false;
return tempHash.equals(hash);
}
public boolean isUsingTempPassword(String name) {
String h = (String) getField(name, "temp_password");
return h != null && !h.isEmpty();
}
public void setLoggedIn(String name, boolean val) {
setField(name, "is_logged_in", val ? 1 : 0);
}
public void addPoints(String name, int amount) {
int cur = ((Number) getField(name, "points"))
.intValue();
setField(name, "points", cur + amount);
}
public boolean deductPoints(String name, int amount) {
int cur = ((Number) getField(name, "points"))
.intValue();
if (cur < amount) return false;
setField(name, "points", cur - amount);
return true;
}
public List<Map<String, Object>> getAllUsers() {
List<Map<String, Object>> list = new ArrayList<>();
try {
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(
"SELECT * FROM users");
while (rs.next()) {
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1;
i <= rs.getMetaData().getColumnCount(); i++) {
row.put(
rs.getMetaData().getColumnName(i),
rs.getObject(i));
}
list.add(row);
}
rs.close();
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
public void deleteUser(String name) {
try {
PreparedStatement ps = db.prepareStatement(
"DELETE FROM users WHERE player_name=?");
ps.setString(1, name);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
+139
View File
@@ -0,0 +1,139 @@
package Sdf1_login;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class EmailManager {
private final ConfigManager config;
public EmailManager(ConfigManager config) {
this.config = config;
}
public void sendTempPassword(String toEmail,
String playerName,
String tempPassword) {
String subject = "[Sdf1_login] 密码重置 - "
+ playerName;
String body = "玩家: " + playerName
+ "\r\n临时密码: " + tempPassword
+ "\r\n有效期: 5分钟"
+ "\r\n使用后自动失效"
+ "\r\n请尽快登录并修改密码";
sendEmail(toEmail, subject, body);
}
public void sendVerifyCode(String toEmail,
String playerName,
String code) {
String subject = "[Sdf1_login] 验证码 - "
+ playerName;
String body = "玩家: " + playerName
+ "\r\n验证码: " + code
+ "\r\n有效期: 5分钟";
sendEmail(toEmail, subject, body);
}
private void sendEmail(String toEmail,
String subject, String body) {
new Thread(() -> {
try {
String host = config.getSmtp("smtp地址");
int port = 465;
try {
port = Integer.parseInt(
config.getSmtp("smtp端口"));
} catch (Exception ignored) {}
String from = config.getSmtp("发件邮箱");
String pass = config.getSmtp("授权码");
if (host.isEmpty() || from.isEmpty()
|| pass.isEmpty()) return;
if (toEmail == null || toEmail.isEmpty())
return;
SSLSocketFactory factory =
(SSLSocketFactory) SSLSocketFactory
.getDefault();
SSLSocket socket = (SSLSocket) factory
.createSocket(host, port);
BufferedReader reader = new BufferedReader(
new InputStreamReader(
socket.getInputStream(),
StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream(),
StandardCharsets.UTF_8));
readResponse(reader);
sendCmd(writer, "EHLO localhost");
readResponse(reader);
sendCmd(writer, "AUTH LOGIN");
readResponse(reader);
sendCmd(writer, Base64.getEncoder()
.encodeToString(from.getBytes(
StandardCharsets.UTF_8)));
readResponse(reader);
sendCmd(writer, Base64.getEncoder()
.encodeToString(pass.getBytes(
StandardCharsets.UTF_8)));
readResponse(reader);
sendCmd(writer,
"MAIL FROM:<" + from + ">");
readResponse(reader);
sendCmd(writer,
"RCPT TO:<" + toEmail + ">");
readResponse(reader);
sendCmd(writer, "DATA");
readResponse(reader);
String encodedSubject = Base64.getEncoder()
.encodeToString(subject.getBytes(
StandardCharsets.UTF_8));
sendCmd(writer, "From: " + from);
sendCmd(writer, "To: " + toEmail);
sendCmd(writer, "Subject: =?UTF-8?B?"
+ encodedSubject + "?=");
sendCmd(writer, "MIME-Version: 1.0");
sendCmd(writer,
"Content-Type: text/plain;"
+ " charset=UTF-8");
sendCmd(writer, "");
sendCmd(writer, body);
sendCmd(writer, ".");
readResponse(reader);
sendCmd(writer, "QUIT");
readResponse(reader);
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
private void sendCmd(BufferedWriter w, String cmd)
throws IOException {
w.write(cmd + "\r\n");
w.flush();
}
private String readResponse(BufferedReader r)
throws IOException {
StringBuilder sb = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
sb.append(line).append("\n");
if (line.length() < 4) break;
if (line.charAt(3) == ' ') break;
}
return sb.toString();
}
}
+338
View File
@@ -0,0 +1,338 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.*;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.util.*;
public class GUIManager {
private final Main plugin;
public static final String T_MAIN =
"§6§l登录面板";
public static final String T_MY_INFO =
"§a§l我的信息";
public static final String T_ADMIN =
"§c§l管理员面板";
public static final String T_USER_MGMT =
"§e§l用户管理";
public static final String T_INVITE =
"§b§l邀请数据";
public static final String T_TASK_CENTER = "§d§l任务中心";
public static final String T_GIFT_STAGES = "§d§l新人任务";
public GUIManager(Main plugin) {
this.plugin = plugin;
}
public void openMain(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_MAIN);
fillBg(g);
g.setItem(10, mkItem(Material.PLAYER_HEAD,
"§a§l我的信息"));
g.setItem(12, mkItem(Material.PAPER,
"§b§l邀请数据"));
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
g.setItem(16, mkItem(Material.CHEST,
"§e§l新人礼包"));
if (isAdmin(p)) {
g.setItem(22, mkItem(Material.REDSTONE_BLOCK,
"§c§l管理员面板"));
}
p.openInventory(g);
}
public void openMyInfo(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_MY_INFO);
fillBg(g);
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
boolean checkedIn = plugin.getCheckIn()
.isCheckedInToday(p.getName());
Material checkMat = checkedIn
? Material.EMERALD : Material.COAL;
String checkName = checkedIn
? "§a已签到" : "§7点击签到";
int streak = ((Number) user.getOrDefault(
"checkin_streak", 0)).intValue();
int points = ((Number) user.getOrDefault(
"points", 0)).intValue();
int totalDays = ((Number) user.getOrDefault(
"total_checkin_days", 0)).intValue();
String email = (String) user.getOrDefault(
"email", "");
g.setItem(4, mkItem(Material.NAME_TAG,
"§e" + p.getName()));
g.setItem(10, mkItem(checkMat, checkName,
"§7连续签到: " + streak + "天",
"§7累计签到: " + totalDays + "天",
"§7当前积分: " + points));
g.setItem(11, mkItem(Material.PAPER,
"§e修改密码", "§7双击修改密码"));
g.setItem(12, mkItem(Material.CLOCK,
"§e补签", "§7消耗10积分补签3天内"));
g.setItem(13, mkItem(Material.ENDER_PEARL,
"§e邀请码",
"§7你的邀请码: "
+ user.getOrDefault(
"invite_code", "无"),
"§7点击生成/查看"));
g.setItem(14, mkItem(Material.BOOK,
"§e邮箱: "
+ (email.isEmpty()
? "未绑定" : email),
"§7点击绑定邮箱"));
g.setItem(15, mkItem(Material.EMERALD_BLOCK,
"§a§l积分商城",
"§7当前: " + points + "积分"));
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openAdmin(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_ADMIN);
fillBg(g);
g.setItem(4, mkItem(Material.NAME_TAG,
"§e§l管理员面板"));
g.setItem(10, mkItem(Material.REDSTONE,
"§6SMTP配置",
"§7配置邮件服务器"));
g.setItem(11, mkItem(Material.ARMOR_STAND,
"§a用户管理",
"§7管理玩家数据"));
g.setItem(12, mkItem(Material.PLAYER_HEAD,
"§a§l我的信息"));
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
boolean afkOn = plugin.getConfig2().afkEnabled;
int afkMin = plugin.getConfig2().afkTimeout / 60;
g.setItem(16, mkItem(
afkOn ? Material.REDSTONE_TORCH
: Material.TORCH,
afkOn ? "§a§l挂机踢出: 开启"
: "§c§l挂机踢出: 关闭",
"§7左键: 切换开关",
"§7右键: 设置时长",
"§7当前: " + afkMin + "分钟"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openInvite(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_INVITE);
fillBg(g);
String code = (String) plugin.getDb()
.getField(p.getName(), "invite_code");
if (code == null || code.isEmpty()) {
code = plugin.getInvite().generateCode(p);
}
int monthly = ((Number) plugin.getDb()
.getField(p.getName(),
"monthly_invite_count"))
.intValue();
g.setItem(10, mkItem(Material.PAPER,
"§b§l我的邀请码",
"§7" + code,
"",
"§e点击刷新邀请码"));
g.setItem(12, mkItem(Material.PLAYER_HEAD,
"§a本月邀请",
"§7已邀请 §e" + monthly + " §7人"));
g.setItem(16, mkItem(Material.BOOK,
"§e输入邀请码",
"§7绑定他人的邀请码",
"",
"§e点击输入"));
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
private String getInviteRate(int count) {
if (count > 15) return "60%";
if (count > 10) return "35%";
if (count > 5) return "15%";
if (count > 0) return "10%";
return "0%";
}
public void openUserManagement(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_USER_MGMT);
fillBg(g);
List<Map<String, Object>> users = plugin.getDb()
.getAllUsers();
int slot = 0;
for (Map<String, Object> user : users) {
if (slot >= 45) break;
String name = (String) user.get("player_name");
int pts = ((Number) user.getOrDefault(
"points", 0)).intValue();
int stage = ((Number) user.getOrDefault(
"gift_stage", 0)).intValue();
g.setItem(slot, mkItem(Material.PLAYER_HEAD,
"§e" + name,
"§7积分: " + pts
+ " 礼包阶段: " + stage,
"§7双击管理"));
slot++;
}
g.setItem(53, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openTaskCenter(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_TASK_CENTER);
fillBg(g);
g.setItem(10, mkItem(Material.BOOK,
"§e§l新人任务",
"§7完成新手阶段获取奖励"));
g.setItem(12, mkItem(Material.COMPASS,
"§b§l主线任务",
"§7查看主线任务列表"));
g.setItem(14, mkItem(Material.PAPER,
"§a§l支线任务",
"§7查看支线任务列表"));
g.setItem(16, mkItem(Material.EMERALD,
"§d§l每日签到",
"§7签到获取积分"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openGiftStages(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_GIFT_STAGES);
fillBg(g);
int max = plugin.getGift().getMaxStage();
for (int i = 0; i < max && i < 9; i++) {
int stage = i + 1;
boolean claimed = plugin.getDb()
.getField(p.getName(),
"gift_claimed") != null
&& ((String) plugin.getDb()
.getField(p.getName(),
"gift_claimed"))
.contains("[" + stage + "]");
boolean can = plugin.getGift()
.canClaim(p, stage);
Material mat = claimed ? Material.LIME_WOOL
: (can ? Material.YELLOW_WOOL
: Material.RED_WOOL);
String status = claimed ? "§a已领取"
: (can ? "§e可领取" : "§c未达标");
g.setItem(10 + i, mkItem(mat,
"§e第" + stage + "阶段",
"§7" + status));
}
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openTaskList(Player p, String folderName) {
File folder = new File(
plugin.getDataFolder(), folderName);
List<Map<String, Object>> tasks =
plugin.getGift()
.loadTasksFromFolder(folder);
int size = Math.max(54,
((tasks.size() / 9) + 1) * 9);
if (size > 54) size = 54;
Inventory g = Bukkit.createInventory(null, size,
"§d§l" + folderName);
fillBg(g);
for (int i = 0; i < tasks.size()
&& i < 45; i++) {
Map<String, Object> task = tasks.get(i);
String name = (String) task.get("name");
List<String> conds =
(List<String>) task.get("conditions");
List<String> rewards =
(List<String>) task.get("rewards");
List<String> lore = new ArrayList<>();
lore.add("§7───── 条件 ─────");
if (conds != null)
for (String c : conds)
lore.add("§e" + c);
lore.add("");
lore.add("§7───── 奖励 ─────");
if (rewards != null)
for (String r2 : rewards)
lore.add("§a" + r2);
g.setItem(i, mkItem(Material.BOOK,
"§e" + name,
lore.toArray(new String[0])));
}
if (tasks.isEmpty()) {
g.setItem(22, mkItem(Material.BARRIER,
"§7暂无任务"));
}
g.setItem(size - 1,
mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openUserDetail(Player p, String target) {
Inventory g = Bukkit.createInventory(
null, 27, "§e§l管理: " + target);
fillBg(g);
Map<String, Object> user = plugin.getDb()
.getUser(target);
int pts = ((Number) user.getOrDefault(
"points", 0)).intValue();
String email = (String) user.getOrDefault(
"email", "");
g.setItem(4, mkItem(Material.NAME_TAG,
"§e" + target));
g.setItem(10, mkItem(Material.EMERALD,
"§a积分: " + pts,
"§7点击设置积分"));
g.setItem(11, mkItem(Material.PAPER,
"§e邮箱: "
+ (email.isEmpty() ? "无" : email)));
g.setItem(12, mkItem(Material.BOOK,
"§e设置密码"));
g.setItem(16, mkItem(Material.BARRIER,
"§c删除用户", "§7二次验证删除"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
private boolean isAdmin(Player p) {
return p.getScoreboardTags().contains(
plugin.getConfig2().adminTag);
}
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;
}
}
+296
View File
@@ -0,0 +1,296 @@
package Sdf1_login;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class GiftManager {
private final File giftFolder;
private final Main plugin;
private final List<Map<String, Object>> stages =
new ArrayList<>();
public GiftManager(Main plugin) {
this.plugin = plugin;
this.giftFolder = new File(
plugin.getDataFolder(), "新人礼包");
}
public void loadStages() {
stages.clear();
if (!giftFolder.exists()) {
giftFolder.mkdirs();
writeDefaultStages();
}
File[] files = giftFolder.listFiles(
(dir, name) -> name.endsWith(".md"));
if (files == null) return;
Arrays.sort(files);
for (File f : files) {
stages.add(parseMarkdown(f));
}
}
private Map<String, Object> parseMarkdown(File f) {
Map<String, Object> stage = new LinkedHashMap<>();
stage.put("stage", stages.size() + 1);
stage.put("name",
f.getName().replace(".md", ""));
stage.put("rewards", new ArrayList<String>());
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
List<String> rewards = new ArrayList<>();
while ((line = r.readLine()) != null) {
if (line.contains("礼包内容")
|| line.contains("奖励")
|| line.contains("任务内容")) {
int colon = line.indexOf(':');
if (colon < 0)
colon = line.indexOf('\uff1a');
if (colon >= 0) {
rewards.add(
line.substring(colon + 1)
.trim());
}
}
}
stage.put("rewards", rewards);
r.close();
} catch (Exception e) {
e.printStackTrace();
}
return stage;
}
private void writeStage(String name, String content) {
File f = new File(giftFolder, name);
if (f.exists()) return;
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.print(content);
} catch (IOException ignored) {}
}
private void writeDefaultStages() {
writeStage("第一阶段.md",
"# 第一阶段\n\n## 领取条件\n\n- 无前级条件\n\n## 礼包内容\n\n- 皮革套装 × 1\n- 木制武器工具套装 × 1\n");
writeStage("第二阶段.md",
"# 第二阶段\n\n## 领取条件\n\n- 在线时长>1小时\n\n## 礼包内容\n\n- 500 游戏币\n");
writeStage("第三阶段.md",
"# 第三阶段\n\n## 领取条件\n\n- 破坏100个方块\n- 在线>1天\n\n## 礼包内容\n\n- 1500 游戏币\n- 铁质工具套装 × 1\n- 铁质防具 × 1\n");
writeStage("第四阶段.md",
"# 第四阶段\n\n## 领取条件\n\n- 破坏/放置300方块\n- 在线≥7天\n\n## 礼包内容\n\n- 钻石套装 × 1\n- 2000 游戏币\n");
}
/** 从任意文件夹加载任务列表 */
public List<Map<String, Object>> loadTasksFromFolder(
File folder) {
List<Map<String, Object>> tasks =
new ArrayList<>();
if (!folder.exists() || !folder.isDirectory())
return tasks;
File[] files = folder.listFiles(
(dir, name) -> name.endsWith(".md"));
if (files == null) return tasks;
Arrays.sort(files);
for (File f : files) {
Map<String, Object> task =
new LinkedHashMap<>();
task.put("name",
f.getName().replace(".md", ""));
task.put("conditions",
new ArrayList<String>());
task.put("rewards",
new ArrayList<String>());
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
List<String> current = null;
while ((line = r.readLine()) != null) {
if (line.startsWith("# ")) {
task.put("name",
line.substring(2)
.trim());
} else if (line.contains("条件")
|| line.contains("领取条件")
|| line.contains("完成条件")) {
current = (List<String>) task
.get("conditions");
} else if (line.contains("内容")
|| line.contains("奖励")
|| line.contains("任务内容")
|| line.contains("礼包内容")) {
current = (List<String>) task
.get("rewards");
} else if (line.startsWith("- ")
&& current != null) {
current.add(line.substring(2)
.trim());
}
}
r.close();
} catch (Exception e) {
e.printStackTrace();
}
tasks.add(task);
}
return tasks;
}
public boolean canClaim(Player p, int stage) {
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
if (user.isEmpty()) return false;
int curStage = ((Number) user.getOrDefault(
"gift_stage", 0)).intValue();
if (stage > curStage + 1) return false;
if (stage < 1 || stage > stages.size())
return false;
String claimed = (String) user.getOrDefault(
"gift_claimed", "");
if (claimed.contains("[" + stage + "]"))
return false;
// debug 绕过:检查 tasks_completed
String completed = (String) user.getOrDefault(
"tasks_completed", "");
if (completed.contains("[" + stage + "]"))
return true;
// 正常条件检查
long dbTime = ((Number) user.getOrDefault(
"total_online_time", 0L)).longValue();
long sessionTime = 0;
Object lastObj = plugin.getDb().getField(
p.getName(), "last_online_check");
if (lastObj != null && plugin.getLoggedIn()
.contains(p.getName())) {
long last = ((Number) lastObj).longValue();
if (last > 0) {
sessionTime =
System.currentTimeMillis() - last;
}
}
long onlineTime = dbTime + sessionTime;
int broken = ((Number) user.getOrDefault(
"blocks_broken", 0)).intValue();
int placed = ((Number) user.getOrDefault(
"blocks_placed", 0)).intValue();
switch (stage) {
case 1: return true;
case 2: return onlineTime >= 3600000;
case 3: return broken >= 100
&& onlineTime >= 86400000L;
case 4: return (broken + placed) >= 300
&& onlineTime >= 604800000L;
default: return false;
}
}
public void claimReward(Player p, int stage) {
if (!canClaim(p, stage)) return;
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
String claimed = (String) user.getOrDefault(
"gift_claimed", "");
claimed += "[" + stage + "]";
plugin.getDb().setField(
p.getName(), "gift_claimed", claimed);
plugin.getDb().setField(
p.getName(), "gift_stage",
Math.max(stage,
((Number) user.getOrDefault(
"gift_stage", 0))
.intValue()));
if (stage == 1) {
giveItem(p, Material.LEATHER_HELMET);
giveItem(p, Material.LEATHER_CHESTPLATE);
giveItem(p, Material.LEATHER_LEGGINGS);
giveItem(p, Material.LEATHER_BOOTS);
giveItem(p, Material.WOODEN_SWORD);
giveItem(p, Material.WOODEN_PICKAXE);
giveItem(p, Material.WOODEN_AXE);
giveItem(p, Material.WOODEN_SHOVEL);
} else if (stage == 2) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 500);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
} else if (stage == 3) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 1500);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
giveItem(p, Material.IRON_PICKAXE);
giveItem(p, Material.IRON_AXE);
giveItem(p, Material.IRON_SWORD);
giveItem(p, Material.IRON_HELMET);
giveItem(p, Material.IRON_CHESTPLATE);
giveItem(p, Material.IRON_LEGGINGS);
giveItem(p, Material.IRON_BOOTS);
} else if (stage == 4) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 2000);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
giveItem(p, Material.DIAMOND_HELMET);
giveItem(p, Material.DIAMOND_CHESTPLATE);
giveItem(p, Material.DIAMOND_LEGGINGS);
giveItem(p, Material.DIAMOND_BOOTS);
}
p.sendMessage(plugin.getConfig2().msg(
"gift_claimed"));
}
private void giveItem(Player p, Material mat) {
p.getInventory().addItem(new ItemStack(mat));
}
public List<Map<String, Object>> getStages() {
return stages;
}
public int getMaxStage() {
return stages.size();
}
}
@@ -0,0 +1,91 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.*;
public class InviteManager {
private final Main plugin;
public InviteManager(Main plugin) {
this.plugin = plugin;
}
public String generateCode(Player p) {
String code = PasswordUtils.generateInviteCode();
plugin.getDb().setField(
p.getName(), "invite_code", code);
return code;
}
public void onInviteeCheckIn(String inviteeName) {
String invitedBy = (String) plugin.getDb()
.getField(inviteeName, "invited_by");
if (invitedBy == null || invitedBy.isEmpty()) return;
String month = new SimpleDateFormat("yyyy-MM")
.format(new Date());
String lastMonth = (String) plugin.getDb()
.getField(invitedBy, "invite_month");
if (!month.equals(lastMonth)) {
plugin.getDb().setField(
invitedBy, "invite_month", month);
plugin.getDb().setField(
invitedBy, "monthly_invite_count", 0);
}
int count = ((Number) plugin.getDb()
.getField(invitedBy,
"monthly_invite_count")).intValue();
plugin.getDb().setField(
invitedBy, "monthly_invite_count", count + 1);
double rate = 0.10;
if (count + 1 > 15) {
rate = 0.60;
} else if (count + 1 > 10) {
rate = 0.35;
} else if (count + 1 > 5) {
rate = 0.15;
}
int bonus = (int) (1 * rate);
if (bonus > 0) {
plugin.getDb().addPoints(invitedBy, bonus);
Player inviterPlayer = Bukkit
.getPlayerExact(invitedBy);
if (inviterPlayer != null
&& inviterPlayer.isOnline()) {
inviterPlayer.sendMessage(
plugin.getConfig2().msg(
"invite_referral_bonus",
"points",
String.valueOf(bonus)));
}
}
}
public List<Map<String, Object>> getAllInviteData() {
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> user
: plugin.getDb().getAllUsers()) {
String code = (String) user.getOrDefault(
"invite_code", "");
if (!code.isEmpty()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("player",
user.get("player_name"));
row.put("code", code);
row.put("monthly",
user.getOrDefault(
"monthly_invite_count", 0));
result.add(row);
}
}
return result;
}
}
+167
View File
@@ -0,0 +1,167 @@
package Sdf1_login;
import org.bukkit.entity.Player;
public class LoginManager {
private final Main plugin;
public LoginManager(Main plugin) {
this.plugin = plugin;
}
public boolean handleRegister(Player p,
String[] args) {
String name = p.getName();
if (plugin.getDb().userExists(name)) {
p.sendMessage(plugin.getConfig2().msg(
"already_registered"));
return true;
}
if (args.length < 1) {
p.sendMessage("§c用法: /reg <密码>");
p.sendMessage(plugin.getConfig2().msg(
"password_format_hint"));
return true;
}
String pwd = args[0];
if (!PasswordUtils.validate(pwd)) {
p.sendMessage(plugin.getConfig2().msg(
"password_invalid"));
p.sendMessage(plugin.getConfig2().msg(
"password_format_hint"));
return true;
}
String salt = PasswordUtils.generateSalt();
String hash = PasswordUtils.hash(pwd, salt);
plugin.getDb().createUser(name, hash, salt);
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
p.sendMessage(plugin.getConfig2().msg(
"register_success", "user", name));
try {
org.bukkit.plugin.Plugin cy =
org.bukkit.Bukkit
.getPluginManager()
.getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
java.lang.reflect.Method m =
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class,
int.class);
m.invoke(cy, name, 9, 0);
}
} catch (Exception ignored) {}
return true;
}
public boolean handleLogin(Player p,
String[] args) {
String name = p.getName();
if (plugin.getLoggedIn().contains(name)) {
p.sendMessage(plugin.getConfig2().msg(
"already_logged_in"));
return true;
}
if (!plugin.getDb().userExists(name)) {
p.sendMessage(plugin.getConfig2().msg(
"not_registered"));
return true;
}
if (args.length < 1) {
p.sendMessage("§c用法: /login <密码>");
return true;
}
String pwd = args[0];
String salt = (String) plugin.getDb()
.getField(name, "password_salt");
if (salt == null) {
p.sendMessage(
"§c[Sdf1_login] §f数据异常,请联系管理员");
return true;
}
String hash = PasswordUtils.hash(pwd, salt);
if (plugin.getDb().checkPasswordOrTemp(
name, hash)) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
p.sendMessage(plugin.getConfig2().msg(
"login_success", "user", name));
if (!plugin.getDb().checkPassword(
name, hash)) {
plugin.getNeedsPasswordChange()
.add(name);
p.sendMessage(
"§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
p.sendMessage(
"§7用法: /sdf1_login pw <旧密码> <新密码> <确认新密码>");
}
try {
org.bukkit.plugin.Plugin cy =
org.bukkit.Bukkit
.getPluginManager()
.getPlugin(
"CY_beibao");
if (cy != null && cy.isEnabled()) {
java.lang.reflect.Method m =
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class,
int.class);
m.invoke(cy, name, 0, 0);
}
} catch (Exception ignored) {}
} else {
p.sendMessage(plugin.getConfig2().msg(
"login_failed"));
}
return true;
}
public boolean handleReset(Player p) {
String name = p.getName();
String email = (String) plugin.getDb()
.getField(name, "email");
if (email == null || email.isEmpty()) {
p.sendMessage(plugin.getConfig2().msg(
"reset_no_email"));
return true;
}
String tempPwd =
PasswordUtils.generateTempPassword();
String mainSalt = (String) plugin.getDb()
.getField(name, "password_salt");
String hash =
PasswordUtils.hash(tempPwd, mainSalt);
plugin.getDb().setField(name,
"temp_password", hash);
plugin.getDb().setField(name,
"temp_pw_expire",
System.currentTimeMillis()
+ 300000L);
plugin.getEmail().sendTempPassword(
email, name, tempPwd);
p.sendMessage(plugin.getConfig2().msg(
"reset_sent"));
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +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);
}
}
+328
View File
@@ -0,0 +1,328 @@
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.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) {}
}
// ===== CY 商品拉取 =====
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;
}
// ===== CY 商品面板 =====
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 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",
"count", "100"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"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",
"count", "50"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"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",
"count", "20"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"points",
String.valueOf(points)));
}
break;
case 31:
if (cyInstance != null) {
p.openInventory(
createCYShopGUI(p));
}
break;
}
}
// ===== CY 商品面板点击 =====
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[Sdf1_login] §f该商品已下架");
return;
}
int points = getPoints(p.getName());
if (points < price) {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"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[Sdf1_login] §f购买成功!获得 "
+ 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;
}
}
+47
View File
@@ -0,0 +1,47 @@
name: Sdf1_login
version: 1.0
main: Sdf1_login.Main
api-version: '1.21'
commands:
sdf1_login:
description: Sdf1_login主命令
usage: /sdf1_login
reg:
description: 注册
usage: /reg <密码>
login:
description: 登录
usage: /login <密码>
l:
description: 登录快捷版
usage: /l <密码>
注册:
description: 注册(中文)
usage: /注册 <密码>
登录:
description: 登录(中文)
usage: /登录 <密码>
玩家信息:
description: 打开面板(中文)
usage: /玩家信息
改密码:
description: 修改密码(中文)
usage: /改密码 <旧密码> <新密码> <确认新密码>
设置邮箱:
description: 设置邮箱(中文)
usage: /设置邮箱 <邮箱地址>
撤销:
description: 撤销密码修改(中文)
usage: /撤销 <旧密码>
删除:
description: 删除玩家(中文)
usage: /删除 <玩家> <密码>
找回密码:
description: 找回密码(中文)
usage: /找回密码
debug:
description: 调试指令
usage: /debug <玩家> take.<阶段>
签到:
description: 每日签到(中文)
usage: /签到