From 3dd0e0a27d2ac2769644ffb899e490ef6a2d5a63 Mon Sep 17 00:00:00 2001 From: yhy <1763917516@qq.com> Date: Tue, 2 Jun 2026 16:34:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=A2=9E=E5=8A=A0=E5=BC=82=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yaohun/onlinereward/AuOnlineReward.java | 29 +- .../yaohun/onlinereward/config/Config.java | 1 + .../yaohun/onlinereward/data/OnlineData.java | 23 +- .../yaohun/onlinereward/data/PlayerData.java | 8 +- .../yaohun/onlinereward/gui/OnlineGui.java | 9 +- .../listener/GuiClickListener.java | 16 +- .../onlinereward/manage/PlayerManager.java | 153 +++-- .../yaohun/onlinereward/util/MessageUtil.java | 6 +- src/main/resources/plugin.yml | 4 +- 使用手册.md | 604 ++++++++++++++++++ 使用手册案例.md | 459 +++++++++++++ 11 files changed, 1221 insertions(+), 91 deletions(-) create mode 100644 使用手册.md create mode 100644 使用手册案例.md diff --git a/src/main/java/com/yaohun/onlinereward/AuOnlineReward.java b/src/main/java/com/yaohun/onlinereward/AuOnlineReward.java index 5acfe24..0de5864 100644 --- a/src/main/java/com/yaohun/onlinereward/AuOnlineReward.java +++ b/src/main/java/com/yaohun/onlinereward/AuOnlineReward.java @@ -47,18 +47,16 @@ public class AuOnlineReward extends JavaPlugin { } public void startOnlineTimeTask() { - Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> { + Bukkit.getScheduler().runTaskTimer(this, () -> { RefreshData refreshData = DataRefreshUtil.getSetting("OnlineDaily"); if(refreshData != null && !refreshData.isSameDayAsRecord()){ refreshData.setRecordTime(System.currentTimeMillis()); DataRefreshUtil.SaveRefreshDataRecord(refreshData); // 每日数据刷新处理 - getPlayerManager().clearAllPlayerData(); getPlayerManager().refreshLocalPlayerData(TimeType.DAILY); return; } if(TimeCheckUtil.isMonthlySettleAccounts()){ - getPlayerManager().clearAllPlayerData(); getPlayerManager().refreshLocalPlayerData(TimeType.MONTHLY); return; } @@ -73,12 +71,16 @@ public class AuOnlineReward extends JavaPlugin { @Override public void onDisable() { - getPlayerManager().saveAllPlayerData(); + getPlayerManager().clearAllPlayerData(); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if(args.length == 1 && args[0].equalsIgnoreCase("open")){ + if(!(sender instanceof Player)){ + sender.sendMessage("§c该命令只能由玩家执行."); + return true; + } OnlineGui.OpenGui((Player) sender); return true; } @@ -135,15 +137,28 @@ public class AuOnlineReward extends JavaPlugin { if("top".equalsIgnoreCase(args[0]) || "topDay".equalsIgnoreCase(args[0])){ HashMap hashMap = new HashMap<>(); for (PlayerData playerData : getPlayerManager().getPlayerDataMap().values()) { - hashMap.put(playerData.getPlayerName(),playerData.todayOnline); + if ("topDay".equalsIgnoreCase(args[0])) { + hashMap.put(playerData.getPlayerName(), playerData.todayOnline / 60); + } else { + hashMap.put(playerData.getPlayerName(), playerData.totalOnline / 60); + } } Ranking ranking = new Ranking(hashMap); sender.sendMessage(" "); sender.sendMessage("§e§l★ §a"+DemonAPI.getTime("yyyy-MM-dd")+"在线排行榜 §7(不定时更新数据)"); for (int rank = 1; rank < 15; rank++) { String rankingPlayer = ranking.getRankingPlayer(rank, "name"); - String onlineTime = ranking.getRankingPlayer(rank, "value"); - sender.sendMessage("§a§l★ §7第 §e" + rank + " §7名: §e" + rankingPlayer + "§7累积在线: §e"+onlineTime+"分钟"); + String valueString = ranking.getRankingPlayer(rank, "value"); + if(valueString.contains("§")){ + sender.sendMessage("§a§l★ §7第 §e" + rank + " §7名: §e" + rankingPlayer + "§7累积在线: §e" + valueString); + continue; + } + if ("topDay".equalsIgnoreCase(args[0])) { + sender.sendMessage("§a§l★ §7第 §e" + rank + " §7名: §e" + rankingPlayer + "§7累积在线: §e" + valueString + "分钟"); + } else { + int onlineTime = Integer.parseInt(valueString); + sender.sendMessage("§a§l★ §7第 §e" + rank + " §7名: §e" + rankingPlayer + "§7累积在线: §e" + (onlineTime/60) + "小时"); + } } sender.sendMessage(" "); return true; diff --git a/src/main/java/com/yaohun/onlinereward/config/Config.java b/src/main/java/com/yaohun/onlinereward/config/Config.java index 997ac64..7d6c934 100644 --- a/src/main/java/com/yaohun/onlinereward/config/Config.java +++ b/src/main/java/com/yaohun/onlinereward/config/Config.java @@ -17,6 +17,7 @@ public class Config { plugin.saveConfig(); } FileConfiguration config = plugin.getConfig(); + onlineDataMap.clear(); loadRewardData(config); } diff --git a/src/main/java/com/yaohun/onlinereward/data/OnlineData.java b/src/main/java/com/yaohun/onlinereward/data/OnlineData.java index 2f57738..0ac7444 100644 --- a/src/main/java/com/yaohun/onlinereward/data/OnlineData.java +++ b/src/main/java/com/yaohun/onlinereward/data/OnlineData.java @@ -10,6 +10,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import java.util.ArrayList; import java.util.List; public class OnlineData { @@ -52,11 +53,17 @@ public class OnlineData { } public ItemStack getItemStack(PlayerData playerData) { + if(DemonAPI.itemIsNull(this.itemStack)){ + return null; + } if(this.rewardKey.contains("MinuteReward_")) { long collectionTime = playerData.collectionTime; ItemStack stack = itemStack.clone(); - ItemMeta meta = itemStack.getItemMeta(); - List lore = meta.getLore(); + ItemMeta meta = stack.getItemMeta(); + if(meta == null || meta.getLore() == null){ + return stack; + } + List lore = new ArrayList<>(meta.getLore()); for (int i = 0; i < lore.size(); i++) { String s = lore.get(i); if (s.contains("%time%")) { @@ -88,8 +95,11 @@ public class OnlineData { return stack; } else { ItemStack stack = itemStack.clone(); - ItemMeta meta = itemStack.getItemMeta(); - List lore = meta.getLore(); + ItemMeta meta = stack.getItemMeta(); + if(meta == null || meta.getLore() == null){ + return stack; + } + List lore = new ArrayList<>(meta.getLore()); for (int i = 0; i < lore.size(); i++) { String s = lore.get(i); if(s.contains("%amounut%")){ @@ -116,7 +126,10 @@ public class OnlineData { public void carryOut(Player player){ String playerName = player.getName(); - String itemName = this.itemStack.getItemMeta().getDisplayName(); + String itemName = this.rewardKey; + if(!DemonAPI.itemIsNull(this.itemStack) && this.itemStack.getItemMeta() != null && this.itemStack.getItemMeta().hasDisplayName()){ + itemName = this.itemStack.getItemMeta().getDisplayName(); + } for (String cmd : commands) { cmd = cmd.replace("%player%", playerName); cmd = cmd.replace("%itemName%", itemName); diff --git a/src/main/java/com/yaohun/onlinereward/data/PlayerData.java b/src/main/java/com/yaohun/onlinereward/data/PlayerData.java index 1da6e9a..6e51500 100644 --- a/src/main/java/com/yaohun/onlinereward/data/PlayerData.java +++ b/src/main/java/com/yaohun/onlinereward/data/PlayerData.java @@ -117,13 +117,13 @@ public class PlayerData { } public void setCurrentReward(){ - if(this.currentReward.equalsIgnoreCase("MinuteReward_30")){ + if("MinuteReward_30".equalsIgnoreCase(this.currentReward)){ this.currentReward = "MinuteReward_60"; - } else if(this.currentReward.equalsIgnoreCase("MinuteReward_60")){ + } else if("MinuteReward_60".equalsIgnoreCase(this.currentReward)){ this.currentReward = "MinuteReward_120"; - } else if(this.currentReward.equalsIgnoreCase("MinuteReward_120")){ + } else if("MinuteReward_120".equalsIgnoreCase(this.currentReward)){ this.currentReward = "MinuteReward_240"; - } else if(this.currentReward.equalsIgnoreCase("MinuteReward_240")){ + } else if("MinuteReward_240".equalsIgnoreCase(this.currentReward)){ this.currentReward = "MinuteReward_MAX"; } } diff --git a/src/main/java/com/yaohun/onlinereward/gui/OnlineGui.java b/src/main/java/com/yaohun/onlinereward/gui/OnlineGui.java index aa9c8eb..61542e4 100644 --- a/src/main/java/com/yaohun/onlinereward/gui/OnlineGui.java +++ b/src/main/java/com/yaohun/onlinereward/gui/OnlineGui.java @@ -20,8 +20,6 @@ import java.util.HashMap; public class OnlineGui { - private static String invTitle = MessageUtil.getLanguage("Title"); - public static void OpenGui(Player player){ PlayerManager playerManager = AuOnlineReward.getPlayerManager(); RefreshData refreshData = DataRefreshUtil.getSetting("OnlineDaily"); @@ -29,7 +27,6 @@ public class OnlineGui { refreshData.setRecordTime(System.currentTimeMillis()); DataRefreshUtil.SaveRefreshDataRecord(refreshData); // 每日数据刷新处理 - playerManager.clearAllPlayerData(); playerManager.refreshLocalPlayerData(TimeType.DAILY); return; } @@ -40,12 +37,14 @@ public class OnlineGui { String playerName = player.getName(); PlayerData playerData = playerManager.getPlayerData(playerName); playerData.updataOnlineData(); - Inventory inv = Bukkit.createInventory(null, 36, invTitle); + Inventory inv = Bukkit.createInventory(null, 36, MessageUtil.getLanguage("Title")); inv.setItem(4, StackUtil.showSignStats(playerData)); HashMap dataMap = Config.getOnlineDataMap(); for (String key : dataMap.keySet()) { OnlineData data = dataMap.get(key); - inv.setItem(data.slot,data.getItemStack(playerData)); + if(data != null) { + inv.setItem(data.slot,data.getItemStack(playerData)); + } } player.openInventory(inv); } diff --git a/src/main/java/com/yaohun/onlinereward/listener/GuiClickListener.java b/src/main/java/com/yaohun/onlinereward/listener/GuiClickListener.java index f8ed39d..1732daa 100644 --- a/src/main/java/com/yaohun/onlinereward/listener/GuiClickListener.java +++ b/src/main/java/com/yaohun/onlinereward/listener/GuiClickListener.java @@ -29,15 +29,11 @@ public class GuiClickListener implements Listener { this.playerManager = playerManager; } - private static String invTitle = MessageUtil.getLanguage("Title"); - @EventHandler public void onClick(InventoryClickEvent e){ - int rawSlot = e.getRawSlot(); Player player = (Player) e.getWhoClicked(); String playerName = player.getName(); - Inventory inventory = e.getInventory(); - if(e.getView().getTitle().equalsIgnoreCase(invTitle)){ + if(e.getView().getTitle().equalsIgnoreCase(MessageUtil.getLanguage("Title"))){ e.setCancelled(true); ItemStack stack = e.getCurrentItem(); if(!DemonAPI.itemIsNull(stack)){ @@ -46,6 +42,10 @@ public class GuiClickListener implements Listener { if(nbtItem.hasKey("rewardKey")){ String onlineKey = nbtItem.getString("rewardKey"); OnlineData onlineData = Config.getOnlineData(onlineKey); + if(onlineData == null){ + DemonAPI.sendMessage(player,MessageUtil.getLanguage("Not-Available"), Sound.ENTITY_VILLAGER_NO); + return; + } // 获取玩家已累积打卡多少次 int signAmount = playerData.signAmount; // 获取领取此奖励需要累积领取奖励 @@ -82,6 +82,10 @@ public class GuiClickListener implements Listener { if(nbtItem.hasKey("onlineKey")){ String onlineKey = nbtItem.getString("onlineKey"); OnlineData onlineData = Config.getOnlineData(onlineKey); + if(onlineData == null){ + DemonAPI.sendMessage(player,MessageUtil.getLanguage("Not-Available"), Sound.ENTITY_VILLAGER_NO); + return; + } // 获取玩家是否已领取该奖励 if(playerData.isPlayerMinuteRewardExit(onlineKey)){ DemonAPI.sendMessage(player,MessageUtil.getLanguage("Already-Claimed"), Sound.ENTITY_VILLAGER_NO); @@ -109,6 +113,7 @@ public class GuiClickListener implements Listener { if(onlineTime >= needOnlineTime){ // 添加玩家领取印记 playerData.signAmount += 1; + playerData.totalSignAmount += 1; playerData.addPlayerMinuteReward(onlineKey); // 设置相关参数 playerData.setCurrentReward(); @@ -119,6 +124,7 @@ public class GuiClickListener implements Listener { } // 执行命令并重新打开Gui界面 onlineData.carryOut(player); + playerData.SavePlayerData(); OnlineGui.OpenGui(player); player.playSound(player.getLocation(),Sound.ENTITY_EXPERIENCE_ORB_PICKUP,1,1); OnlineRewardEvent onlineRewardEvent = new OnlineRewardEvent(player,onlineData); diff --git a/src/main/java/com/yaohun/onlinereward/manage/PlayerManager.java b/src/main/java/com/yaohun/onlinereward/manage/PlayerManager.java index c6d086b..d5cabf3 100644 --- a/src/main/java/com/yaohun/onlinereward/manage/PlayerManager.java +++ b/src/main/java/com/yaohun/onlinereward/manage/PlayerManager.java @@ -1,18 +1,17 @@ package com.yaohun.onlinereward.manage; +import com.yaohun.onlinereward.AuOnlineReward; import com.yaohun.onlinereward.data.PlayerData; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Arrow; import org.bukkit.entity.Player; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; +import java.util.Map; public class PlayerManager { @@ -41,6 +40,7 @@ public class PlayerManager { public void clearAllPlayerData(){ for (PlayerData playerData : playerDataMap.values()){ + playerData.updataOnlineData(); playerData.SavePlayerData(); } playerDataMap.clear(); @@ -48,12 +48,16 @@ public class PlayerManager { public void saveAllPlayerData(){ for (PlayerData playerData : playerDataMap.values()){ + playerData.updataOnlineData(); playerData.SavePlayerData(); } } public void removePlayerData(String playerName){ - getPlayerData(playerName).SavePlayerData(); + PlayerData playerData = getPlayerData(playerName); + playerData.updataOnlineData(); + playerData.setOfflineTime(System.currentTimeMillis()); + playerData.SavePlayerData(); playerDataMap.remove(playerName); } @@ -66,72 +70,24 @@ public class PlayerManager { } public void refreshLocalPlayerData(TimeType timeType){ + saveAllPlayerData(); + playerDataMap.clear(); String folderPath = "plugins/AuData/AuOnlineReward"; File folder = new File(folderPath); if (!folder.exists() || !folder.isDirectory()) { return; } - File[] files = folder.listFiles((dir, name) -> name.endsWith(".yml")); - if (files == null || files.length == 0) { - return; - } - long nowTime = System.currentTimeMillis(); - int count = 0; - // 2. 统计玩家今日在线总计时长 - HashMap integerHashMap = new HashMap<>(); - for (File file : files) { - String playerName = file.getName().replace(".yml", ""); - FileConfiguration config = YamlConfiguration.loadConfiguration(file); - if(timeType.equals(TimeType.DAILY)){ - int todayOnline = config.getInt("OnlineData.todayOnline"); - if(todayOnline >= 600){ - integerHashMap.put(playerName, todayOnline); - } - config.set("OnlineData.todayOnline", 0); - config.set("OnlineData.currentReward", "MinuteReward_30"); - config.set("OnlineData.collectionTime", nowTime); - config.set("OnlineData.receivedList", new ArrayList<>()); - try { - config.save(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - count++; - } else if(timeType.equals(TimeType.MONTHLY)){ - int monthOnline = config.getInt("OnlineData.monthOnline"); - if(monthOnline >= 600 * 180){ - integerHashMap.put(playerName, monthOnline); - } - config.set("OnlineData.monthOnline", 0); - try { - config.save(file); - } catch (IOException e) { - throw new RuntimeException(e); - } - count++; - } - } - if(timeType.equals(TimeType.DAILY)) { - Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 昨日在线时长大于1小时的玩家:"); - for (String playerName : integerHashMap.keySet()) { - int value = integerHashMap.get(playerName); - Bukkit.getConsoleSender().sendMessage(" - " + playerName + ": " + (value / 60) + "分钟"); - } - Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 每日在线数据已刷新<" + count + "名>玩家数据"); - } - if(timeType.equals(TimeType.MONTHLY)){ - Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 上月在线时长大于180小时的玩家:"); - for (String playerName : integerHashMap.keySet()) { - int value = integerHashMap.get(playerName); - Bukkit.getConsoleSender().sendMessage(" - " + playerName + ": " + (value / 600) + "小时"); - } - Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 每月在线数据已刷新<" + count + "名>玩家数据"); - } - // 重新载入在线玩家 - loadOnlineAllPlayer(); + Bukkit.getScheduler().runTaskAsynchronously(AuOnlineReward.inst(), () -> { + RefreshResult result = refreshLocalPlayerFiles(folder, timeType); + Bukkit.getScheduler().runTask(AuOnlineReward.inst(), () -> { + sendRefreshLog(timeType, result); + loadOnlineAllPlayer(); + }); + }); } public void refreshLocalPlayerData(String playerName,TimeType timeType){ + boolean cached = playerDataMap.containsKey(playerName); PlayerData playerData = getPlayerData(playerName); if(timeType.equals(TimeType.DAILY)) { playerData.refreshDailyData(); @@ -140,5 +96,78 @@ public class PlayerManager { playerData.refreshMonthlyData(); playerData.SavePlayerData(); } + if(!cached && Bukkit.getPlayerExact(playerName) == null){ + playerDataMap.remove(playerName); + } + } + + private RefreshResult refreshLocalPlayerFiles(File folder, TimeType timeType) { + File[] files = folder.listFiles((dir, name) -> name.endsWith(".yml")); + if (files == null || files.length == 0) { + return new RefreshResult(0, new HashMap<>()); + } + long nowTime = System.currentTimeMillis(); + int count = 0; + HashMap reportMap = new HashMap<>(); + for (File file : files) { + String playerName = file.getName().replace(".yml", ""); + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + if(timeType.equals(TimeType.DAILY)){ + int todayOnline = config.getInt("OnlineData.todayOnline"); + if(todayOnline >= 600){ + reportMap.put(playerName, todayOnline); + } + config.set("OnlineData.todayOnline", 0); + config.set("OnlineData.currentReward", "MinuteReward_30"); + config.set("OnlineData.collectionTime", nowTime); + config.set("OnlineData.receivedList", new ArrayList<>()); + savePlayerFile(config, file); + count++; + } else if(timeType.equals(TimeType.MONTHLY)){ + int monthOnline = config.getInt("OnlineData.monthOnline"); + if(monthOnline >= 600 * 180){ + reportMap.put(playerName, monthOnline); + } + config.set("OnlineData.monthOnline", 0); + savePlayerFile(config, file); + count++; + } + } + return new RefreshResult(count, reportMap); + } + + private void savePlayerFile(FileConfiguration config, File file) { + try { + config.save(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void sendRefreshLog(TimeType timeType, RefreshResult result) { + if(timeType.equals(TimeType.DAILY)) { + Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 昨日在线时长大于1小时的玩家:"); + for (Map.Entry entry : result.reportMap.entrySet()) { + Bukkit.getConsoleSender().sendMessage(" - " + entry.getKey() + ": " + (entry.getValue() / 60) + "分钟"); + } + Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 每日在线数据已刷新<" + result.count + "名>玩家数据"); + } + if(timeType.equals(TimeType.MONTHLY)){ + Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 上月在线时长大于180小时的玩家:"); + for (Map.Entry entry : result.reportMap.entrySet()) { + Bukkit.getConsoleSender().sendMessage(" - " + entry.getKey() + ": " + (entry.getValue() / 600) + "小时"); + } + Bukkit.getConsoleSender().sendMessage("[日志 - 在线福利] 每月在线数据已刷新<" + result.count + "名>玩家数据"); + } + } + + private static class RefreshResult { + private final int count; + private final HashMap reportMap; + + private RefreshResult(int count, HashMap reportMap) { + this.count = count; + this.reportMap = reportMap; + } } } diff --git a/src/main/java/com/yaohun/onlinereward/util/MessageUtil.java b/src/main/java/com/yaohun/onlinereward/util/MessageUtil.java index eb2c767..17e274a 100644 --- a/src/main/java/com/yaohun/onlinereward/util/MessageUtil.java +++ b/src/main/java/com/yaohun/onlinereward/util/MessageUtil.java @@ -20,6 +20,7 @@ public class MessageUtil { } public static void init(AuOnlineReward plugin) { + languageMap.clear(); File file = new File(plugin.getDataFolder(), "language.yml"); if (!file.exists()) { plugin.saveResource("language.yml", false); @@ -27,8 +28,11 @@ public class MessageUtil { FileConfiguration config = YamlConfiguration.loadConfiguration(file); for (String key : config.getKeys(false)){ ConfigurationSection section = config.getConfigurationSection(key); + if(section == null){ + continue; + } for (String key2 : section.getKeys(false)){ - String message = section.getString(key2).replace("&","§"); + String message = section.getString(key2,"").replace("&","§"); languageMap.put(key2,message); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 363fc09..a4d67c9 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: AuOnlineReward main: com.yaohun.onlinereward.AuOnlineReward -version: 1.8.0 +version: 1.8.3 depend: - DemonAPI commands: - auonline: \ No newline at end of file + auonline: diff --git a/使用手册.md b/使用手册.md new file mode 100644 index 0000000..9827765 --- /dev/null +++ b/使用手册.md @@ -0,0 +1,604 @@ +# 1.AuOnlineReward 是什么? + +AuOnlineReward 是一个 Minecraft 1.12.2 Bukkit / Spigot 在线奖励插件。 + +它用于根据玩家在线时间发放阶段奖励,并根据阶段奖励领取次数解锁累积奖励。 + +核心功能: + +- 玩家通过 GUI 查看在线信息和奖励状态 +- 支持每日在线阶段奖励 +- 支持累积领取次数奖励 +- 支持每日在线数据刷新 +- 支持月度在线数据刷新 +- 支持配置奖励物品和奖励命令 +- 支持通过 API 获取玩家在线时间 +- 支持奖励领取事件,方便其他插件监听 + +简单理解: + +```yml +AuOnlineReward = 在线时长统计 + 在线奖励 GUI + 奖励命令执行 +玩家 = 在线积累时间并领取奖励 +管理员 = 配置奖励物品、奖励条件和发奖命令 +``` + +------ + +# 2.运行环境 + +## 服务端版本 + +```yml +Minecraft: 1.12.2 +服务端: Bukkit / Spigot / Paper +Java: 8 +``` + +## 前置插件 + +AuOnlineReward 依赖 DemonAPI。 + +`plugin.yml` 中已经声明: + +```yml +depend: + - DemonAPI +``` + +服务器启动前必须确保 DemonAPI 已放入插件目录。 + +------ + +# 3.安装方式 + +## 第一步:放入插件 + +将插件 jar 放入服务器插件目录: + +```yml +plugins/AuOnlineReward.jar +``` + +同时放入前置插件: + +```yml +plugins/DemonAPI.jar +``` + +## 第二步:启动服务器 + +首次启动后会生成插件配置文件。 + +## 第三步:检查加载 + +控制台应能看到插件正常启用,没有缺少 DemonAPI 的报错。 + +------ + +# 4.目录结构 + +## 插件配置目录 + +默认配置和语言文件位于: + +```yml +plugins/AuOnlineReward/config.yml +plugins/AuOnlineReward/language.yml +``` + +## 玩家数据目录 + +玩家在线数据会保存到: + +```yml +plugins/AuData/AuOnlineReward/玩家名.yml +``` + +玩家数据示例: + +```yml +OnlineData: + todayOnline: 0 + totalOnline: 0 + monthOnline: 0 + totalSign: 0 + signAmount: 0 + currentReward: MinuteReward_30 + collectionTime: 1717200000000 + offlineTime: 1717200000000 + receivedList: [] + permanentReceived: [] +``` + +字段说明: + +```yml +todayOnline: 今日在线秒数 +totalOnline: 累积在线秒数 +monthOnline: 本月在线秒数 +totalSign: 总计领取阶段奖励次数 +signAmount: 当前轮累积领取阶段奖励次数 +currentReward: 当前可领取的阶段奖励 +collectionTime: 上次阶段奖励领取时间 +offlineTime: 上次离线时间 +receivedList: 今日已领取阶段奖励 +permanentReceived: 当前轮已领取累积奖励 +``` + +------ + +# 5.玩家命令 + +## 打开在线奖励界面 + +```yml +/auonline open +``` + +玩家执行后会打开在线福利 GUI。 + +GUI 中会显示: + +- 累积在线时间 +- 本月在线时间 +- 今日在线时间 +- 总计领取奖励次数 +- 阶段奖励状态 +- 累积奖励状态 + +注意: + +```yml +每日 00:10 前无法打开 GUI +这是为了避免跨日数据刷新期间领取状态异常 +``` + +------ + +# 6.管理员命令 + +管理员命令需要 OP 权限。 + +## 查看命令帮助 + +```yml +/auonline +``` + +## 重载配置 + +```yml +/auonline reload +``` + +重载内容: + +- 奖励配置 +- 语言配置 +- GUI 标题 +- 奖励显示文本 + +## 刷新所有玩家每日数据 + +```yml +/auonline dayrefresh +``` + +作用: + +- 清空今日在线时间 +- 重置当前阶段奖励 +- 重置今日阶段奖励领取记录 +- 重新载入在线玩家数据 + +## 刷新指定玩家每日数据 + +```yml +/auonline dayrefresh 玩家名 +``` + +只刷新指定玩家的每日在线奖励数据。 + +## 查看玩家数据 + +```yml +/auonline info 玩家名 +/auonline look 玩家名 +``` + +不填写玩家名时,默认查看执行者自己的数据。 + +## 查看今日在线排行 + +```yml +/auonline topDay +/auonline top +``` + +显示当前已缓存玩家的今日在线排行。 + +------ + +# 7.奖励类型 + +AuOnlineReward 当前包含两类奖励。 + +## 阶段奖励 + +配置节点通常以 `MinuteReward_` 开头。 + +示例: + +```yml +RewardData: + MinuteReward_30: + slot: 10 + needOnline: 1800 + needOnlineTime: 1800 + itemStack: + ==: org.bukkit.inventory.ItemStack + type: IRON_INGOT + commands: + - eco give %player% 500 +``` + +字段说明: + +```yml +slot: GUI 中的格子位置 +needOnline: 距离上次阶段奖励领取需要等待的秒数 +needOnlineTime: 今日在线秒数要求 +itemStack: GUI 中展示的奖励物品 +commands: 领取后执行的命令 +``` + +阶段奖励领取成功后: + +- 增加当前轮阶段领取次数 +- 增加总计阶段领取次数 +- 写入今日已领取记录 +- 切换到下一个阶段奖励 +- 执行奖励命令 +- 保存玩家数据 + +## 累积奖励 + +配置节点通常以 `SignReward_` 开头。 + +示例: + +```yml +RewardData: + SignReward_15: + slot: 29 + needOnline: 25 + itemStack: + ==: org.bukkit.inventory.ItemStack + type: CHEST + commands: + - eco give %player% 10000 +``` + +字段说明: + +```yml +slot: GUI 中的格子位置 +needOnline: 需要累计领取多少次阶段奖励 +itemStack: GUI 中展示的奖励物品 +commands: 领取后执行的命令 +``` + +累积奖励领取成功后会写入 `permanentReceived`。 + +当领取 `SignReward_Max` 后: + +```yml +signAmount 会重置为 0 +permanentReceived 会清空 +进入下一轮累积奖励 +``` + +------ + +# 8.奖励命令怎么写? + +奖励命令写在 `commands` 列表中。 + +示例: + +```yml +commands: + - eco give %player% 500 + - points give %player% 10 + - bcm &a玩家 &e%player% &a领取了在线奖励 +``` + +支持占位符: + +```yml +%player% 玩家名 +%itemName% 奖励物品显示名 +``` + +如果命令包含 `msg:`,会向玩家发送消息,而不是由控制台执行命令。 + +示例: + +```yml +commands: + - "msg:&a你领取了在线奖励" +``` + +颜色代码支持: + +```yml +&a &b &c &e &f +``` + +插件执行时会转换为 Minecraft 颜色符号。 + +------ + +# 9.语言文件怎么写? + +语言文件为: + +```yml +plugins/AuOnlineReward/language.yml +``` + +主要节点: + +```yml +Online-Gui: + Title: "§r日常福利 - 累积在线豪礼相赠" + Received: "§a§l★ §7领取状态: §a已领取" + Not-Started: "§c§l★ §7领取状态: §c未开始" + Pending: "§b§l★ §7领取状态: §b待领取" + Total-Online: "§7累积在线: §a%time%" + Monthly-Online: "§7本月在线: §a%time%" + Daily-Online: "§7今日在线: §a%time%" + Total-Rewards: "§7总计领取奖励: §a%amount%次" +Message: + Reward-Progress: "你还需要获取§e[%amount%次]§a阶段奖励才能领取奖励。" + Wait-Time: "您还需要等待§e[%time%秒]§a才能领取该奖励。" + Daily-Requirement: "你需要今日在线§e[%time%秒]§a才能领取该奖励。" + Already-Claimed: "你已经领取过这个累积奖励。" + Not-Available: "这个阶段奖励尚未开启,暂时无法领取。" +``` + +修改语言文件后执行: + +```yml +/auonline reload +``` + +------ + +# 10.在线时间刷新机制 + +## 在线计时 + +插件会定时统计在线玩家的在线时长。 + +统计字段: + +```yml +todayOnline: 今日在线 +monthOnline: 本月在线 +totalOnline: 累积在线 +``` + +玩家退出时会补算最后一段在线时间,并保存玩家数据。 + +## 每日刷新 + +每日刷新会处理: + +```yml +todayOnline = 0 +currentReward = MinuteReward_30 +receivedList = [] +collectionTime = 当前时间 +``` + +## 月度刷新 + +月度刷新会处理: + +```yml +monthOnline = 0 +``` + +## 刷新性能说明 + +批量刷新玩家文件时,插件会将 YAML 文件读写放到异步任务中执行。 + +刷新完成后,再回到主线程重新载入在线玩家。 + +这样可以降低大量玩家数据文件刷新时对主线程的影响。 + +------ + +# 11.API 调用 + +其他插件可以通过 `OnlineAPI` 获取玩家在线时间。 + +## 获取今日在线分钟数 + +```java +int minutes = OnlineAPI.getTime(playerName); +``` + +## 获取本月在线分钟数 + +```java +int minutes = OnlineAPI.getTimeMonth(playerName); +``` + +## 获取累积在线分钟数 + +```java +int minutes = OnlineAPI.getTimeTotal(playerName); +``` + +注意: + +```yml +API 返回单位是分钟 +底层玩家数据保存单位是秒 +``` + +------ + +# 12.事件监听 + +玩家成功领取奖励后会触发: + +```java +OnlineRewardEvent +``` + +监听示例: + +```java +@EventHandler +public void onOnlineReward(OnlineRewardEvent event) { + Player player = event.getPlayer(); + String playerName = event.getPlayerName(); + String rewardKey = event.getOnlineData().getRewardKey(); +} +``` + +事件可用于: + +- 记录领奖日志 +- 触发额外奖励 +- 接入活动系统 +- 接入统计系统 + +------ + +# 13.配置示例 + +## 阶段奖励示例 + +```yml +RewardData: + MinuteReward_30: + slot: 10 + needOnline: 1800 + needOnlineTime: 1800 + itemStack: + ==: org.bukkit.inventory.ItemStack + type: IRON_INGOT + meta: + ==: ItemMeta + meta-type: UNSPECIFIC + display-name: §6§l阶段奖励 I + lore: + - §7还差 §c%time% §7才能领取 + - §e金币 §e*500 + commands: + - eco give %player% 500 +``` + +## 累积奖励示例 + +```yml +RewardData: + SignReward_15: + slot: 29 + needOnline: 25 + itemStack: + ==: org.bukkit.inventory.ItemStack + type: CHEST + meta: + ==: ItemMeta + meta-type: TILE_ENTITY + display-name: §6§l累积打卡奖励 I + lore: + - §7还需领取 §c%amounut% §7阶段奖励 + - §e金币 §e*10000 + commands: + - eco give %player% 10000 +``` + +注意: + +```yml +阶段奖励 lore 中需要包含 %time% +累积奖励 lore 中需要包含 %amounut% +``` + +------ + +# 14.常见问题 + +## 执行 /auonline open 没反应 + +检查: + +```yml +是否在每日 00:10 之前 +DemonAPI 是否正常加载 +language.yml 中 Title 是否存在 +config.yml 中 RewardData 是否存在 +``` + +## 奖励无法领取 + +检查: + +```yml +今日在线秒数是否达到 needOnlineTime +距离上次领取是否达到 needOnline +currentReward 是否等于当前点击的阶段奖励 +该奖励是否已经在 receivedList 中 +``` + +## 累积奖励无法领取 + +检查: + +```yml +signAmount 是否达到 needOnline +该奖励是否已经在 permanentReceived 中 +``` + +## 修改配置后没有变化 + +执行: + +```yml +/auonline reload +``` + +如果删除了奖励节点,重载后旧奖励缓存会被清理。 + +## 玩家数据异常 + +可以刷新指定玩家每日数据: + +```yml +/auonline dayrefresh 玩家名 +``` + +也可以检查玩家数据文件: + +```yml +plugins/AuData/AuOnlineReward/玩家名.yml +``` + +------ + +# 15.维护建议 + +- 修改奖励配置前,先备份 `config.yml`。 +- 大量玩家数据刷新建议避开服务器高峰期。 +- 奖励命令应先在测试服验证,避免命令拼写错误导致奖励无法发放。 +- 不建议把高耗时命令放入奖励命令列表。 +- 修改语言或奖励配置后,使用 `/auonline reload` 重载。 +- 发布新版本时,应同步检查 `plugin.yml` 的版本号。 +- 每次更新后建议至少完成一次登录、打开 GUI、领取奖励、退出保存的冒烟测试。 diff --git a/使用手册案例.md b/使用手册案例.md new file mode 100644 index 0000000..56d2823 --- /dev/null +++ b/使用手册案例.md @@ -0,0 +1,459 @@ +<<<<<<< HEAD +# 1.LangUtil 是什么? + +LangUtil 是一个 Minecraft 1.12.2 Bukkit / Spigot 多语言管理插件。 + +它的作用不是替代业务插件,而是给其他插件提供统一的语言 API: + +- 每个插件维护自己的语言文件 +- 玩家可以切换自己的语言 +- 不同玩家可以看到不同语言 +- 修改语言文件后可以不重启服务器直接重载 +- 支持物品名称、物品 lore、普通消息、占位符和颜色代码 + +简单理解: + +```yml +LangUtil = 公共语言管理器 +其他插件 = 自己提供 lang 文件 +玩家 = 自己选择 zh_CN / en_US 等语言 +``` + +------ + +# 2.目录结构 + +## LangUtil 自己的数据 + +玩家语言选择会保存到: + +```yml +plugins/LangUtil/player-language.yml +``` + +格式示例: + +```yml +player-language: + 550e8400-e29b-41d4-a716-446655440000: zh_CN +``` + +## 其他插件的语言文件 + +其他插件必须把语言文件放在自己的插件目录下: + +```yml +plugins/OtherPlugin/lang/zh_CN.yml +plugins/OtherPlugin/lang/en_US.yml +``` + +注意: + +```yml +语言文件不是放在 LangUtil 的 lang 目录 +语言文件是放在调用 register 的插件自己的 lang 目录 +``` + +------ + +# 3.其他插件如何注册? + +其他插件需要依赖 LangUtil。 + +## plugin.yml + +```yml +depend: + - LangUtil +``` + +## 插件入口 + +```java +import com.io.yaohun.langutil.LangUtil; +import org.bukkit.plugin.java.JavaPlugin; + +public final class OtherPlugin extends JavaPlugin { + @Override + public void onEnable() { + LangUtil.register(this); + } +} +``` + +当执行: + +```java +LangUtil.register(this); +``` + +LangUtil 会读取: + +```yml +plugins/OtherPlugin/lang/*.yml +``` + +------ + +# 4.语言文件怎么写? + +## zh_CN.yml + +```yml +message: + reload: "&a语言文件已重载" + no-permission: "&c你没有权限使用该命令。" + +item: + flame-sword: + name: "&c烈焰长剑" + lore: + - "&7伤害: &c{damage}" + - "&7品质: &6{quality}" + - "" + - "&e右键释放火焰" +``` + +## en_US.yml + +```yml +message: + reload: "&aLanguage files reloaded" + no-permission: "&cYou do not have permission." + +item: + flame-sword: + name: "&cFlame Sword" + lore: + - "&7Damage: &c{damage}" + - "&7Quality: &6{quality}" + - "" + - "&eRight-click to release fire" +``` + +文件名就是语言 ID: + +```yml +zh_CN.yml → zh_CN +en_US.yml → en_US +``` + +------ + +# 5.玩家命令 + +## 查看可用语言 + +```yml +/lang +``` + +玩家会看到当前已经加载的语言 ID。 + +## 切换语言 + +```yml +/lang zh_CN +/lang en_US +``` + +切换后会按玩家 UUID 保存。 + +玩家退出服务器再进入,仍然保持上次选择。 + +## 重载语言文件 + +```yml +/lang reload +``` + +修改语言文件后,不需要重启服务器,执行该命令即可重新读取所有已注册插件的语言文件。 + +控制台也可以执行: + +```yml +lang reload +``` + +------ + +# 6.权限节点 + +```yml +langutil.use # 允许玩家使用 /lang 查看和切换语言 +langutil.reload # 允许使用 /lang reload +``` + +推荐配置: + +```yml +普通玩家: langutil.use +管理员: langutil.use + langutil.reload +``` + +------ + +# 7.API 调用 + +## 获取普通消息 + +```java +String message = LangUtil.get(player, this, "message.reload"); +``` + +## 发送普通消息 + +```java +LangUtil.send(player, this, "message.reload"); +``` + +## 发送消息并播放音效 + +```java +LangUtil.send(player, this, "message.reload", Sound.LEVEL_UP); +``` + +## 使用占位符 + +```java +Map placeholders = new HashMap(); +placeholders.put("damage", "12"); +placeholders.put("quality", "史诗"); + +String name = LangUtil.get(player, this, "item.flame-sword.name", placeholders); +``` + +语言文件: + +```yml +item: + flame-sword: + name: "&c烈焰长剑" +``` + +结果: + +```yml +烈焰长剑 +``` + +------ + +# 8.物品 name 和 lore 怎么做? + +物品 name 是字符串,可以用: + +```java +LangUtil.get(player, this, "item.flame-sword.name", placeholders); +``` + +物品 lore 是 List,可以用: + +```java +LangUtil.getList(player, this, "item.flame-sword.lore", placeholders); +``` + +完整示例: + +```java +Map placeholders = new HashMap(); +placeholders.put("damage", "12"); +placeholders.put("quality", "史诗"); + +ItemMeta meta = item.getItemMeta(); +meta.setDisplayName(LangUtil.get(player, this, "item.flame-sword.name", placeholders)); +meta.setLore(LangUtil.getList(player, this, "item.flame-sword.lore", placeholders)); +item.setItemMeta(meta); +``` + +------ + +# 9.推荐封装方式 + +为了让业务代码更短,其他插件可以自己封装一个 `Lang` 工具类。 + +```java +public final class Lang { + private static JavaPlugin plugin; + + private Lang() { + } + + public static void init(JavaPlugin javaPlugin) { + plugin = javaPlugin; + LangUtil.register(javaPlugin); + } + + public static void send(CommandSender sender, String path) { + LangUtil.send(sender, plugin, path); + } + + public static String get(CommandSender sender, String path) { + return LangUtil.get(sender, plugin, path); + } + + public static List getList(CommandSender sender, String path) { + return LangUtil.getList(sender, plugin, path); + } +} +``` + +插件启动时: + +```java +Lang.init(this); +``` + +业务代码: + +```java +Lang.send(player, "message.reload"); +``` + +------ + +# 10.重载机制说明 + +LangUtil 不会每次发送消息都读取本地 YAML。 + +语言文件读取时机: + +```yml +插件启动 register 时 +/lang reload 时 +再次调用 register 时 +``` + +平时调用: + +```java +LangUtil.get(...) +LangUtil.getList(...) +LangUtil.send(...) +``` + +都是从内存缓存读取。 + +这样可以避免每次发消息都读磁盘,降低主线程压力。 + +------ + +# 11.异常与回退规则 + +## 玩家没有选择语言 + +默认使用: + +```yml +zh_CN +``` + +## 玩家选择的语言文件不存在 + +回退: + +```yml +zh_CN +``` + +## zh_CN 也不存在 + +返回 path 原文: + +```java +LangUtil.get(player, this, "message.not-found"); +``` + +返回: + +```yml +message.not-found +``` + +## lore 不存在 + +返回单行 List: + +```yml +- item.xxx.lore +``` + +## YAML 格式错误 + +控制台会输出: + +```yml +插件名 +语言文件名 +错误原因 +``` + +不会因为单个语言文件错误导致服务器关闭。 + +------ + +# 12.实战使用流程 + +## 第一步:安装 LangUtil + +把插件放入服务器: + +```yml +plugins/LangUtil-1.2.0.jar +``` + +启动服务器。 + +## 第二步:业务插件依赖 LangUtil + +```yml +depend: + - LangUtil +``` + +## 第三步:业务插件注册语言文件 + +```java +LangUtil.register(this); +``` + +## 第四步:创建语言文件 + +```yml +plugins/OtherPlugin/lang/zh_CN.yml +plugins/OtherPlugin/lang/en_US.yml +``` + +## 第五步:玩家切换语言 + +```yml +/lang zh_CN +/lang en_US +``` + +## 第六步:业务插件发送消息 + +```java +LangUtil.send(player, this, "message.reload"); +``` + +## 第七步:修改语言后重载 + +```yml +/lang reload +``` + +------ + +# 13.注意事项 + +- 推荐始终使用带 `JavaPlugin plugin` 参数的 API。 +- 不推荐使用 `LangUtil.get(String path)`,因为它无法稳定判断读取哪个插件的语言。 +- 其他插件必须先调用 `LangUtil.register(this)`,否则读取不到自己的语言文件。 +- 语言文件必须放在调用注册方法的插件自己的 `lang` 目录。 +- 物品 lore 必须用 YAML List 格式编写。 +- 修改语言文件后必须执行 `/lang reload` 才会进入缓存。 +======= +# LangUtil + +>>>>>>> 12c7b3f43c6113b170b9e83dedf75650d51b9040