You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Minecraft Paper 1.20.4插件:Killstreak与Scoreboard无法保存问题求助

Minecraft Paper 1.20.4插件问题修复:击杀Streak数据保存与计分板持久化

核心问题

  • player_data.txt生成后无玩家数据
  • 玩家退出重进后,Killstreak对应的计分板无法保留

问题根源

  1. 数据保存时机单一:仅在插件禁用时保存数据,玩家中途退出服务器时,其击杀streak不会被写入文件,导致文件为空或数据不全。
  2. 登录时未加载数据:插件启动时只加载当时在线玩家的数据,新登录的玩家不会触发数据加载,重进后自然恢复不了计分板。
  3. 用Player对象做Map键的缺陷:玩家离线后,Player对象会被服务器回收,重新登录后是新对象,原Map中的数据无法关联到新玩家。

修复步骤及代码修改

1. 新增玩家登录/退出事件监听

添加PlayerJoinEvent和PlayerQuitEvent处理方法,确保玩家登录时加载数据,退出时立即保存自身数据。

2. 改用UUID存储玩家数据

将原来的Map<Player, Integer>改为Map<UUID, Integer>,用玩家唯一UUID作为键,避免名称变更或对象失效导致的数据丢失。

3. 调整数据加载与保存逻辑

  • 加载数据时,先把所有玩家的UUID和streak存入Map,玩家登录时直接从Map中读取。
  • 保存数据时,写入UUID而非玩家名称,保证数据的唯一性和可追溯性。

修改后的完整代码

package me.dev.Boost;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scoreboard.DisplaySlot;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.Score;
import org.bukkit.scoreboard.Scoreboard;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BoostPlugin extends JavaPlugin implements Listener {

    private final Map<UUID, Integer> killstreaks = new HashMap<>();
    private final Map<Player, Scoreboard> playerScoreboards = new HashMap<>();
    private File dataFile;
    private final Logger logger = Logger.getLogger("BoostPlugin");

    @Override
    public void onEnable() {
        Bukkit.getServer().getPluginManager().registerEvents(this, this);
        dataFile = new File(getDataFolder(), "player_data.txt");
        if (!dataFile.exists()) {
            try {
                dataFile.getParentFile().mkdirs();
                dataFile.createNewFile();
            } catch (IOException e) {
                logger.log(Level.SEVERE, "Failed to create data file", e);
            }
        }
        loadPlayerData();
        logger.info("BoostPlugin enabled successfully.");
    }

    @Override
    public void onDisable() {
        savePlayerData();
        logger.info("BoostPlugin disabled successfully.");
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        UUID uuid = player.getUniqueId();
        
        int streak = killstreaks.getOrDefault(uuid, 0);
        if (streak > 0) {
            updatePlayerScoreboard(player, streak);
            updatePlayerDisplayName(player, streak);
            applyEffects(player, streak);
        }
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        Player player = event.getPlayer();
        UUID uuid = player.getUniqueId();
        
        if (killstreaks.containsKey(uuid)) {
            saveSinglePlayerData(uuid, killstreaks.get(uuid));
        }
        playerScoreboards.remove(player);
        killstreaks.remove(uuid);
    }

    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        Player victim = event.getEntity();
        Player killer = victim.getKiller();

        if (killer != null) {
            handleKill(killer);
        }

        resetKillstreak(victim);
    }

    private void handleKill(Player killer) {
        UUID uuid = killer.getUniqueId();
        int currentStreak = killstreaks.getOrDefault(uuid, 0);
        int newStreak = currentStreak + 1;

        killer.getActivePotionEffects().forEach(effect -> killer.removePotionEffect(effect.getType()));
        applyEffects(killer, newStreak);

        killer.sendMessage(ChatColor.GREEN + "Killstreak: " + newStreak);
        updatePlayerScoreboard(killer, newStreak);
        updatePlayerDisplayName(killer, newStreak);

        killstreaks.put(uuid, newStreak);
    }

    private void applyEffects(Player player, int streak) {
        if (streak == 1) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, Integer.MAX_VALUE, 0, true, false));
        } else if (streak == 2) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, Integer.MAX_VALUE, 0, true, false));
        } else if (streak == 3) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.HEALTH_BOOST, Integer.MAX_VALUE, 0, true, false));
        } else if (streak == 4) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.FAST_DIGGING, Integer.MAX_VALUE, 0, true, false));
        } else if (streak >= 5) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.DOLPHINS_GRACE, Integer.MAX_VALUE, 0, true, false));
        }
    }

    private void resetKillstreak(Player player) {
        UUID uuid = player.getUniqueId();
        if (killstreaks.containsKey(uuid)) {
            killstreaks.remove(uuid);
            player.sendMessage(ChatColor.RED + "Your killstreak has been reset.");
            removePlayerScoreboard(player);
            updatePlayerDisplayName(player, 0);
            player.getActivePotionEffects().forEach(effect -> player.removePotionEffect(effect.getType()));
        }
    }

    private void updatePlayerScoreboard(Player player, int streak) {
        Scoreboard scoreboard = playerScoreboards.computeIfAbsent(player, p -> Bukkit.getScoreboardManager().getNewScoreboard());
        Objective objective = scoreboard.getObjective("killstreak");

        if (objective == null) {
            objective = scoreboard.registerNewObjective("killstreak", "dummy", ChatColor.GREEN + "Killstreak");
            objective.setDisplaySlot(DisplaySlot.BELOW_NAME);
        }

        Score score = objective.getScore(player.getName());
        score.setScore(Math.min(streak, 5));

        player.setScoreboard(scoreboard);
    }

    private void removePlayerScoreboard(Player player) {
        player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard());
        playerScoreboards.remove(player);
    }

    private void updatePlayerDisplayName(Player player, int streak) {
        String displayName = player.getName() + ChatColor.RESET;

        if (streak > 0) {
            displayName += " " + ChatColor.GREEN + "KS:" + Math.min(streak, 5);
        }

        player.setPlayerListName(displayName);
    }

    private void loadPlayerData() {
        try {
            if (!dataFile.exists()) {
                logger.info("Player data file does not exist, creating new one.");
                return;
            }

            Files.lines(dataFile.toPath()).forEach(line -> {
                String[] parts = line.split(":");
                if (parts.length == 2) {
                    try {
                        UUID uuid = UUID.fromString(parts[0]);
                        int streak = Integer.parseInt(parts[1]);
                        killstreaks.put(uuid, streak);
                    } catch (IllegalArgumentException e) {
                        logger.warning("Invalid data line: " + line);
                    }
                }
            });
            logger.info("Loaded " + killstreaks.size() + " player killstreak records.");
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Error loading player data", e);
        }
    }

    private void savePlayerData() {
        StringBuilder data = new StringBuilder();
        for (Map.Entry<UUID, Integer> entry : killstreaks.entrySet()) {
            data.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
        }
        try {
            Files.write(dataFile.toPath(), data.toString().getBytes());
            logger.info("Saved " + killstreaks.size() + " player killstreak records.");
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Error saving player data", e);
        }
    }

    private void saveSinglePlayerData(UUID uuid, int streak) {
        try {
            StringBuilder existingData = new StringBuilder();
            if (dataFile.exists()) {
                existingData.append(Files.readString(dataFile.toPath()));
            }

            String oldLine = uuid + ":" + killstreaks.getOrDefault(uuid, 0);
            int index = existingData.indexOf(oldLine);
            if (index != -1) {
                int endIndex = existingData.indexOf("\n", index);
                existingData.delete(index, endIndex != -1 ? endIndex + 1 : existingData.length());
            }

            existingData.append(uuid).append(":").append(streak).append("\n");
            Files.write(dataFile.toPath(), existingData.toString().getBytes());
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Error saving data for player " + uuid, e);
        }
    }
}

修复说明

  • 玩家退出时会立即保存自身的击杀streak数据,确保player_data.txt能实时写入数据。
  • 玩家登录时自动加载历史击杀streak,并恢复计分板和对应效果。
  • 使用UUID作为数据标识,彻底解决玩家名称变更或对象失效导致的数据丢失问题。
  • 新增效果清除逻辑,避免玩家重置streak后旧效果残留。

内容的提问来源于stack exchange,提问作者gamer boi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:32:34