commit ea759874ec5e8598d76b9e23c6c625a4de9a7091 Author: Kyoukawa Meishin Date: Fri Oct 24 18:44:44 2025 +0800 合并多个功能为Perspective插件 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..480bdf5 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..00e7407 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/src/main/resources/data.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml new file mode 100644 index 0000000..6adb8f0 --- /dev/null +++ b/.idea/data_source_mapping.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..eda147d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..24b2c2f --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c797d78 --- /dev/null +++ b/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + com.mmlsystem + Perspective + 1.0.2 + + + + central + https://repo.maven.apache.org/maven2/ + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jitpack-io + https://jitpack.io/ + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + + + + + org.spigotmc + spigot-api + 1.21.8-R0.1-SNAPSHOT + provided + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + org.jetbrains + annotations + 24.1.0 + compile + + + diff --git a/src/main/java/com/mmlsystem/Perspective/CheckinCommand.java b/src/main/java/com/mmlsystem/Perspective/CheckinCommand.java new file mode 100644 index 0000000..a091f97 --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/CheckinCommand.java @@ -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; + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/CheckinManager.java b/src/main/java/com/mmlsystem/Perspective/CheckinManager.java new file mode 100644 index 0000000..e8b4f38 --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/CheckinManager.java @@ -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 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; + } + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/ConfigManager.java b/src/main/java/com/mmlsystem/Perspective/ConfigManager.java new file mode 100644 index 0000000..90f94bf --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/ConfigManager.java @@ -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; + } + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/DatabaseManager.java b/src/main/java/com/mmlsystem/Perspective/DatabaseManager.java new file mode 100644 index 0000000..1b2a28b --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/DatabaseManager.java @@ -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; + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/EconomyManager.java b/src/main/java/com/mmlsystem/Perspective/EconomyManager.java new file mode 100644 index 0000000..ed88043 --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/EconomyManager.java @@ -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 rsp = Bukkit.getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + econ = rsp.getProvider(); + return true; + } + + public Economy getEconomy() { + return econ; + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/ExpManager.java b/src/main/java/com/mmlsystem/Perspective/ExpManager.java new file mode 100644 index 0000000..f996cde --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/ExpManager.java @@ -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 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()); + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/Foundation.java b/src/main/java/com/mmlsystem/Perspective/Foundation.java new file mode 100644 index 0000000..5cfac4a --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/Foundation.java @@ -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; + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/JoinListener.java b/src/main/java/com/mmlsystem/Perspective/JoinListener.java new file mode 100644 index 0000000..c143dee --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/JoinListener.java @@ -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 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()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/mmlsystem/Perspective/PermissionManager.java b/src/main/java/com/mmlsystem/Perspective/PermissionManager.java new file mode 100644 index 0000000..2e9f312 --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/PermissionManager.java @@ -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); + } +} diff --git a/src/main/java/com/mmlsystem/Perspective/StatusManager.java b/src/main/java/com/mmlsystem/Perspective/StatusManager.java new file mode 100644 index 0000000..85aa998 --- /dev/null +++ b/src/main/java/com/mmlsystem/Perspective/StatusManager.java @@ -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 "未知错误"; + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..3a5a10e --- /dev/null +++ b/src/main/resources/plugin.yml @@ -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 \ No newline at end of file