我为Bungeecord创建了自己的小平面文件库存网桥。当玩家离开时,他的数据被保存,并且当他加入时,他的数据被加载。
我遇到的问题是,当播放器切换服务器时,在保存数据之前先加载数据。因此,我增加了加载数据的延迟,并且它可以正常工作。
但这是不可靠的,所以有人说我应该使用“同步块锁定到同一对象上,以加载和保存播放器数据”。
这是我的代码:
private Player player;
public DataManager(Player player) {
this.player = player;
}
public synchronized void saveData() throws IOException {
ConfigManager configmanager = new ConfigManager(player.getUniqueId());
FileConfiguration config = configmanager.getConfig();
config.set("Contents", player.getInventory().getContents());
config.set("Enderchest", player.getEnderChest().getContents());
config.set("Armor", player.getInventory().getArmorContents());
config.set("Effects", player.getActivePotionEffects());
config.set("Health", player.getHealth());
config.set("Experience", player.getExp());
config.set("Food", player.getFoodLevel());
configmanager.saveConfig();
}
@SuppressWarnings("unchecked")
public synchronized void loadData() {
FileConfiguration config = new ConfigManager(player.getUniqueId()).getConfig();
player.getInventory().setContents(((List<ItemStack>) config.get("Contents")).toArray(new ItemStack[0]));
player.getEnderChest().setContents(((List<ItemStack>) config.get("Enderchest")).toArray(new ItemStack[0]));
player.getInventory().setArmorContents(((List<ItemStack>) config.get("Armor")).toArray(new ItemStack[0]));
player.addPotionEffects((Collection<PotionEffect>) config.get("Effects"));
player.setHealth(config.getDouble("Health"));
player.setExp((float) config.getDouble("Experience"));
player.setFoodLevel(config.getInt("Food"));
}
玩家加入时:
@EventHandler
public void onJoin(PlayerJoinEvent event) {
TimerTask task = new TimerTask() {
@Override
public void run() {
new DataManager(event.getPlayer()).loadData();
}
};
new Timer().schedule(task, 5000);
}
玩家退出时:
@EventHandler
public void onQuit(PlayerQuitEvent event) throws IOException {
new DataManager(event.getPlayer()).saveData();
}
我不完全知道这个人的意思。 我该怎么办?
答案 0 :(得分:0)
我真的不知道这个人是什么意思,但是我为您提供了可靠的解决方案。
首先,我将通知您artifactSourceId=xyz
发生时的情况:
基本上,Java会存储new DataManager(event.getPlayer())
的副本并将其存储在RAM中,然后执行您的任务。由于这在您仅将新实例用于一项任务(例如保存或加载数据)时是无用的,因此您可以创建静态函数并异步运行它们,这样它们就不会阻塞bukkit的主线程。这个想法是只要写入数据就创建一个public class DataManager
文件,并且仅在删除.lock
文件后读取数据。
.lock
将public static void saveData(Player player) throws IOException {
File locker = new File(YOUR_PATH_TO_CONFIG + player.getUniqueId() + ".lock");
locker.createNewFile(); //create locker-file
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
try {
ConfigManager configmanager = new ConfigManager(player.getUniqueId());
FileConfiguration config = configmanager.getConfig();
config.set("Contents", player.getInventory().getContents());
config.set("Enderchest", player.getEnderChest().getContents());
config.set("Armor", player.getInventory().getArmorContents());
config.set("Effects", player.getActivePotionEffects());
config.set("Health", player.getHealth());
config.set("Experience", player.getExp());
config.set("Food", player.getFoodLevel());
configmanager.saveConfig();
} catch (IOException ex) {
// TODO: handle exception
}
locker.delete(); //delete the locker-file so the new server can access the playerdata
});
}
public static void loadData(Player player) {
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { //run async to not block the main thread
File f = new File(YOUR_PATH_TO_CONFIG + player.getUniqueId() + ".lock");
while (f.exists()) { //as long as the locker-file exists we want to wait
try {
Thread.sleep(50); //wait one minecraft-tick
} catch (InterruptedException ignored) {}
}
Bukkit.getScheduler().runTask(plugin, () -> { //run sync
FileConfiguration config = new ConfigManager(player.getUniqueId()).getConfig();
player.getInventory().setContents(Arrays.asList((List<ItemStack>) config.get("Contents")).toArray(new ItemStack[0]));
player.getEnderChest().setContents(((List<ItemStack>) config.get("Enderchest")).toArray(new ItemStack[0]));
player.getInventory().setArmorContents(((List<ItemStack>) config.get("Armor")).toArray(new ItemStack[0]));
player.addPotionEffects((Collection<PotionEffect>) config.get("Effects"));
player.setHealth(config.getDouble("Health"));
player.setExp((float) config.getDouble("Experience"));
player.setFoodLevel(config.getInt("Food"));
});
});
}
替换为存储播放器配置的位置。
现在您应该将事件更改为此:
YOUR_PATH_TO_CONFIG
我将QuitEvent的优先级设置为LOWEST,以便首先调用它((服务器加入新服务器之前的事件),将JoinEvent设置为MONITOR,以便在播放器已经存在时最后被调用加入。
我没有多次对此进行全面测试,并且服务器延迟,但是到目前为止它仍然有效。如果它不起作用,您应该考虑使用Bungeecords @EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
DataManager.loadData(event.getPlayer());
}
@EventHandler(priority = EventPriority.LOWEST)
public void onQuit(PlayerQuitEvent event) throws IOException {
DataManager.saveData(event.getPlayer());
}
。 https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/在那里,您可以通过插件消息将完整清单发送到BungeeCord,然后将其发送到播放器加入的下一台服务器。只要确保BungeeCord服务器完全收到插件消息后,播放器就可以加入新服务器。但这是真正的编程经验,如果您刚刚开始编程,我不会推荐给您,因为这会给您带来一些麻烦(只需询问stackoverflow即可)
也许对您的并发性问题更简单,更可靠的解决办法是,让播放器在5秒钟之内不让玩家加入另一台服务器,以便在保存数据期间他停留在大厅中
这些全都只是要求,我希望我在此处发布的第一个代码段能够正常工作。如果某事不起作用,请亲自问我。我会尽力为您提供帮助。