合并多个功能为Perspective插件

This commit is contained in:
Kyoukawa Meishin 2025-10-24 18:44:44 +08:00
commit ea759874ec
25 changed files with 698 additions and 0 deletions

39
.gitignore vendored Normal file
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

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.ask.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.edit.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

12
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="data.db" uuid="9e374a52-07c5-464d-b7f9-4f210e1f7eb6">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/src/main/resources/data.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/data_source_mapping.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$PROJECT_DIR$/src/main/java/com/mmlsystem/Perspective/CheckinManager.java" value="9e374a52-07c5-464d-b7f9-4f210e1f7eb6" />
</component>
</project>

7
.idea/encodings.xml generated Normal file
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
.idea/misc.xml generated Normal file
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_24" default="true" project-jdk-name="24" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/sqldialects.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/main/java/com/mmlsystem/Perspective/Foundation.java" dialect="GenericSQL" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

54
pom.xml Normal file
View File

@ -0,0 +1,54 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mmlsystem</groupId>
<artifactId>Perspective</artifactId>
<version>1.0.2</version>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>jitpack-io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21.8-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package com.mmlsystem.Perspective;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class CheckinCommand implements CommandExecutor {
private final CheckinManager manager;
public CheckinCommand(CheckinManager manager) {
this.manager = manager;
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("§6没活可以咬打火机");
return false;
}
Player player = (Player) sender;
player.sendMessage("§b签到中请稍候...");
manager.checkinAsync(player, player::sendMessage);
return true;
}
}

View File

@ -0,0 +1,107 @@
package com.mmlsystem.Perspective;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.sql.*;
import java.time.*;
import java.util.UUID;
import java.util.function.Consumer;
public class CheckinManager {
private final Foundation plugin;
private final Economy econ;
private final DatabaseManager databaseManager;
public CheckinManager(Foundation plugin) {
this.plugin = plugin;
this.econ = Foundation.getEconomyManager().getEconomy();
this.databaseManager = Foundation.getDatabaseManager();
}
public void checkinAsync(Player player, Consumer<String> callback) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
String message;
try {
message = execCheckin(player);
} catch (Exception e) {
plugin.getLogger().severe(e.getMessage());
message = "§c未知错误。";
}
String finalMessage = message;
Bukkit.getScheduler().runTask(plugin, () -> callback.accept(finalMessage));
});
}
private String execCheckin(Player player) {
if (!player.hasPermission("com.mmlsystem.checkin")) {
return "§c拒绝访问。";
}
UUID uuid = player.getUniqueId();
LocalDate today = LocalDate.now();
try {
PreparedStatement ps = databaseManager.getConnection().prepareStatement("SELECT last_checkin, streak, total_checkin FROM checkins WHERE uuid=?");
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
if (rs.next()) {
long lastCheckin = rs.getLong("last_checkin");
int streak = rs.getInt("streak");
int total = rs.getInt("total_checkin");
LocalDate lastDate = Instant.ofEpochMilli(lastCheckin)
.atZone(ZoneId.systemDefault()).toLocalDate();
if (today.isEqual(lastDate)) {
rs.close();
ps.close();
return "§e你今天已经签到过了";
} else {
if (today.minusDays(1).isEqual(lastDate)) {
streak += 1;
} else {
streak = 1;
}
}
total += 1;
ps = databaseManager.getConnection().prepareStatement(
"UPDATE checkins SET last_checkin=?, streak=?, total_checkin=? WHERE uuid=?");
ps.setLong(1, today.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli());
ps.setInt(2, streak);
ps.setInt(3, total);
ps.setString(4, uuid.toString());
ps.executeUpdate();
double reward = getReward(streak);
econ.depositPlayer(player, reward);
ps.close();
rs.close();
return "§a签到成功已连续签到§b" + streak + "§a天获得§c$" + reward + "§a奖励";
} else {
ps = databaseManager.getConnection().prepareStatement(
"INSERT INTO checkins(last_checkin,streak,total_checkin,uuid) VALUES(?,?,?,?)");
ps.setLong(1, today.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli());
ps.setInt(2, 1);
ps.setInt(3, 1);
ps.setString(4, uuid.toString());
ps.executeUpdate();
econ.depositPlayer(player, 100.0);
ps.close();
rs.close();
return "§a首次签到成功获得§c$100§a奖励";
}
} catch (SQLException e) {
plugin.getLogger().severe(e.getMessage());
return "§c未知错误。";
}
}
public double getReward(int streak) {
if (streak >= 7) {
return 200.0;
} else {
return 25.0 * streak;
}
}
}

View File

@ -0,0 +1,20 @@
package com.mmlsystem.Perspective;
public class ConfigManager {
private static Foundation foundation;
public ConfigManager(Foundation foundation) {
ConfigManager.foundation = foundation;
}
public boolean saveConfig() {
try {
foundation.getConfig().options().copyDefaults(true);
foundation.saveConfig();
return true;
} catch (Exception e) {
foundation.getLogger().severe(e.getMessage());
return false;
}
}
}

View File

@ -0,0 +1,59 @@
package com.mmlsystem.Perspective;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class DatabaseManager {
private final Foundation foundation;
private static Connection connection;
public DatabaseManager(Foundation foundation) {
this.foundation = foundation;
}
public boolean setupDatabase() {
try {
File dbFile = new File(foundation.getDataFolder(), "data.db");
if (!foundation.getDataFolder().exists()) {
//noinspection ResultOfMethodCallIgnored
foundation.getDataFolder().mkdirs();
}
connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
Statement stmt = connection.createStatement();
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS checkins (" +
"uuid TEXT PRIMARY KEY," +
"last_checkin INTEGER," +
"streak INTEGER," +
"total_checkin INTEGER)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS playtime (" +
"uuid TEXT PRIMARY KEY, " +
"name TEXT, " +
"total BIGINT DEFAULT 0" +
")");
stmt.close();
foundation.getLogger().info("SQLite 数据库初始化成功。");
return true;
} catch (Exception e) {
foundation.getLogger().severe(e.getMessage());
return false;
}
}
public void closeDatabase() {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (Exception e) {
foundation.getLogger().severe(e.getMessage());
}
}
public Connection getConnection() {
return connection;
}
}

View File

@ -0,0 +1,30 @@
package com.mmlsystem.Perspective;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider;
public class EconomyManager {
private Economy econ;
private final Foundation foundation;
public EconomyManager(Foundation foundation) {
this.foundation = foundation;
}
public boolean setupEconomy() {
if (foundation.getServer().getPluginManager().getPlugin("Vault") == null) {
return false;
}
RegisteredServiceProvider<Economy> rsp = Bukkit.getServicesManager().getRegistration(Economy.class);
if (rsp == null) {
return false;
}
econ = rsp.getProvider();
return true;
}
public Economy getEconomy() {
return econ;
}
}

View File

@ -0,0 +1,58 @@
package com.mmlsystem.Perspective;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ExpManager {
private final Connection connection;
public final Map<UUID, Long> joinTimes = new HashMap<>();
private final Foundation plugin;
public ExpManager(Foundation plugin) {
this.plugin = plugin;
this.connection = Foundation.getDatabaseManager().getConnection();
}
public boolean savePlayer(Player player) {
long joinTime = joinTimes.getOrDefault(player.getUniqueId(), System.currentTimeMillis());
long session = System.currentTimeMillis() - joinTime;
try {
new BukkitRunnable() {
@Override
public void run() {
try {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO playtime (uuid, name, total) VALUES (?, ?, ?) " +
"ON CONFLICT(uuid) DO UPDATE SET " +
"name = excluded.name, total = total + excluded.total");
ps.setString(1, player.getUniqueId().toString());
ps.setString(2, player.getName());
ps.setLong(3, session);
ps.executeUpdate();
ps.close();
joinTimes.put(player.getUniqueId(), System.currentTimeMillis());
} catch (SQLException e) {
plugin.getLogger().severe(e.getMessage());
plugin.getLogger().severe("试图保存玩家" + player.getName() + "的在线时长时发生错误。");
}
}
}.runTaskAsynchronously(plugin);
return false;
} catch (Exception e) {
plugin.getLogger().severe(e.getMessage());
return true;
}
}
public Long getJoinTime(Player player) {
return System.currentTimeMillis() - joinTimes.getOrDefault(player.getUniqueId(), System.currentTimeMillis());
}
}

View File

@ -0,0 +1,61 @@
package com.mmlsystem.Perspective;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Objects;
public class Foundation extends JavaPlugin {
private static DatabaseManager databaseManager;
private static EconomyManager economyManager;
private static ExpManager expManager;
@Override
public void onEnable() {
getLogger().info("张华考上了北京大学");
getLogger().info("李萍进了中等技术学校");
getLogger().info("你却在这里看服务器启动日志里面有没有什么奇怪的东西");
ConfigManager configManager = new ConfigManager(this);
if (!configManager.saveConfig()) {
getLogger().severe("配置初始化失败!正在禁用插件...");
getServer().getPluginManager().disablePlugin(this);
return;
}
databaseManager = new DatabaseManager(this);
if (!databaseManager.setupDatabase()) {
getLogger().severe("数据库初始化失败!正在禁用插件...");
getServer().getPluginManager().disablePlugin(this);
return;
}
economyManager = new EconomyManager(this);
if (!economyManager.setupEconomy()) {
getLogger().severe("经济系统初始化失败!正在禁用插件...");
getServer().getPluginManager().disablePlugin(this);
return;
}
expManager = new ExpManager(this);
PermissionManager permissionManager = new PermissionManager(this);
permissionManager.setupPermission();
getServer().getPluginManager().registerEvents(new JoinListener(this), this);
Objects.requireNonNull(getCommand("checkin")).setExecutor(new CheckinCommand(new CheckinManager(this)));
getLogger().info("插件启用成功。");
}
@Override
public void onDisable() {
databaseManager.closeDatabase();
getLogger().info("插件禁用成功。");
}
public static DatabaseManager getDatabaseManager() {
return databaseManager;
}
public static EconomyManager getEconomyManager() {
return economyManager;
}
public static ExpManager getExpManager() {
return expManager;
}
}

View File

@ -0,0 +1,44 @@
package com.mmlsystem.Perspective;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Map;
import java.util.UUID;
public class JoinListener implements Listener {
private final ExpManager expManager;
private final Map<UUID, Long> joinTimes;
private final Foundation plugin;
public JoinListener(Foundation plugin) {
this.plugin = plugin;
this.expManager = Foundation.getExpManager();
this.joinTimes = expManager.joinTimes;
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
joinTimes.put(e.getPlayer().getUniqueId(), System.currentTimeMillis());
if (expManager.savePlayer(e.getPlayer())) {
plugin.getLogger().severe("无法保存" + e.getPlayer().getName() + "的在线时长记录。");
}
StatusManager statusManager = new StatusManager(plugin);
statusManager.setScoreboardUI(e.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if(expManager.savePlayer(e.getPlayer())) {
plugin.getLogger().severe("无法保存" + e.getPlayer().getName() + "的在线时长记录。");
}
});
joinTimes.remove(e.getPlayer().getUniqueId());
}
}

View File

@ -0,0 +1,23 @@
package com.mmlsystem.Perspective;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
public class PermissionManager {
private final Foundation foundation;
public static final Permission CHECKIN_PERM = new Permission(
"com.mmlsystem.checkin",
"允许玩家执行签到操作。",
PermissionDefault.TRUE
);
public PermissionManager(Foundation foundation) {
this.foundation = foundation;
}
public void setupPermission() {
PluginManager pm = foundation.getServer().getPluginManager();
pm.addPermission(CHECKIN_PERM);
}
}

View File

@ -0,0 +1,79 @@
package com.mmlsystem.Perspective;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.ScoreboardManager;
import java.time.Duration;
public class StatusManager {
private final Foundation foundation;
private final Economy econ;
private final ExpManager expManager;
public StatusManager(Foundation foundation) {
this.foundation = foundation;
this.econ = Foundation.getEconomyManager().getEconomy();
this.expManager = Foundation.getExpManager();
}
public void setScoreboardUI(Player player) {
ScoreboardManager manager = Bukkit.getScoreboardManager();
Scoreboard board = manager.getNewScoreboard();
Objective objective = board.registerNewObjective("info", "dummy", ChatColor.GOLD + "✦ 统计数据 ✦");
objective.setDisplaySlot(DisplaySlot.SIDEBAR);
objective.getScore(ChatColor.AQUA + "金币").setScore(10);
objective.getScore(ChatColor.RED + " " + econ.getBalance(player) ).setScore(9);
objective.getScore(" ").setScore(8);
objective.getScore(ChatColor.AQUA + "当前在线人数").setScore(7);
objective.getScore(ChatColor.GREEN + " " + Bukkit.getOnlinePlayers().size()).setScore(6);
objective.getScore(" ").setScore(5);
objective.getScore(ChatColor.AQUA + "在线时长").setScore(4);
objective.getScore(ChatColor.GREEN + " " + "获取中").setScore(3);
objective.getScore("").setScore(2);
objective.getScore(ChatColor.GRAY + "mymc.life").setScore(1);
player.setScoreboard(board);
new BukkitRunnable() {
@Override
public void run() {
if (!player.isOnline()) {
cancel();
return;
}
for (String entry : board.getEntries()) {
if (entry.startsWith(ChatColor.RED + " ") || entry.startsWith(ChatColor.GREEN + " ")) { // 匹配你旧的在线人数行
board.resetScores(entry);
}
}
objective.getScore(ChatColor.RED + " " + econ.getBalance(player) ).setScore(9);
objective.getScore(ChatColor.GREEN + " " + Bukkit.getOnlinePlayers().size()).setScore(6);
objective.getScore(ChatColor.GREEN + " " + getTime(player)).setScore(3);
}
}.runTaskTimer(foundation, 0L, 100L);
}
private String getTime(Player player) {
try {
long totalTime = expManager.getJoinTime(player) / 1000;
long hour;
long minute;
Duration duration = Duration.ofSeconds(totalTime);
hour = duration.toHours();
minute = duration.toMinutesPart();
return String.format("%d:%02d", hour, minute);
} catch (Exception e) {
foundation.getLogger().warning(e.getMessage());
return "未知错误";
}
}
}

View File

View File

@ -0,0 +1,11 @@
name: Perspective
main: com.mmlsystem.Perspective.Foundation
version: 1.0.2
api-version: 1.21
author: 杏川铭心
description: Perspective 服务器功能插件
depend: [Vault]
commands:
checkin:
description: 在服务器上签到并获取奖励。
usage: /checkin