package net.glowstone.io.nbt;
import net.glowstone.GlowOfflinePlayer;
import net.glowstone.GlowServer;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.entity.meta.profile.ProfileCache;
import net.glowstone.io.PlayerDataService;
import net.glowstone.io.entity.EntityStorage;
import net.glowstone.util.nbt.CompoundTag;
import net.glowstone.util.nbt.NBTInputStream;
import net.glowstone.util.nbt.NBTOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
/**
* Standard NBT-based player data storage.
*/
public class NbtPlayerDataService implements PlayerDataService {
private final GlowServer server;
private final File playerDir;
public NbtPlayerDataService(GlowServer server, File playerDir) {
this.server = server;
this.playerDir = playerDir;
}
private File getPlayerFile(UUID uuid) {
if (!playerDir.isDirectory() && !playerDir.mkdirs()) {
server.getLogger().warning("Failed to create directory: " + playerDir);
}
return new File(playerDir, uuid + ".dat");
}
private void readDataImpl(GlowPlayer player, CompoundTag playerTag) {
EntityStorage.load(player, playerTag);
}
@Override
public List<OfflinePlayer> getOfflinePlayers() {
// list files in directory
File[] files = playerDir.listFiles();
if (files == null) {
return Arrays.asList();
}
List<OfflinePlayer> result = new ArrayList<>(files.length);
for (File file : files) {
// first, make sure it looks like a player file
String name = file.getName();
if (name.length() != 40 || !name.endsWith(".dat")) {
continue;
}
// get the UUID
UUID uuid;
try {
uuid = UUID.fromString(name.substring(0, 36));
} catch (IllegalArgumentException e) {
continue;
}
// creating the OfflinePlayer will read the data
result.add(new GlowOfflinePlayer(server, uuid));
}
return result;
}
@Override
public UUID lookupUUID(String name) {
// todo: caching or something
for (OfflinePlayer player : getOfflinePlayers()) {
if (name.equalsIgnoreCase(player.getName())) {
return player.getUniqueId();
}
}
// In online mode, find the Mojang UUID if possible
if (Bukkit.getServer().getOnlineMode() || ((GlowServer) Bukkit.getServer()).getProxySupport()) {
UUID uuid = ProfileCache.getUUID(name);
if (uuid != null) {
return uuid;
}
}
// Fall back to the offline-mode UUID
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8));
}
@Override
public PlayerReader beginReadingData(UUID uuid) {
return new NbtPlayerReader(getPlayerFile(uuid));
}
@Override
public void readData(GlowPlayer player) {
File playerFile = getPlayerFile(player.getUniqueId());
CompoundTag playerTag = new CompoundTag();
if (playerFile.exists()) {
try (NBTInputStream in = new NBTInputStream(new FileInputStream(playerFile))) {
playerTag = in.readCompound();
} catch (IOException e) {
player.kickPlayer("Failed to read player data!");
server.getLogger().log(Level.SEVERE, "Failed to read data for " + player.getName() + ": " + playerFile, e);
}
}
readDataImpl(player, playerTag);
}
@Override
public void writeData(GlowPlayer player) {
File playerFile = getPlayerFile(player.getUniqueId());
CompoundTag tag = new CompoundTag();
EntityStorage.save(player, tag);
try (NBTOutputStream out = new NBTOutputStream(new FileOutputStream(playerFile))) {
out.writeTag(tag);
} catch (IOException e) {
player.kickPlayer("Failed to save player data!");
server.getLogger().log(Level.SEVERE, "Failed to write data for " + player.getName() + ": " + playerFile, e);
}
}
private class NbtPlayerReader implements PlayerReader {
private CompoundTag tag = new CompoundTag();
private boolean hasPlayed = false;
public NbtPlayerReader(File playerFile) {
if (playerFile.exists()) {
try (NBTInputStream in = new NBTInputStream(new FileInputStream(playerFile))) {
tag = in.readCompound();
hasPlayed = true;
} catch (IOException e) {
server.getLogger().log(Level.SEVERE, "Failed to read data for player: " + playerFile, e);
}
}
}
private void checkOpen() {
if (tag == null) {
throw new IllegalStateException("cannot access fields after close");
}
}
@Override
public boolean hasPlayedBefore() {
return hasPlayed;
}
@Override
public Location getLocation() {
checkOpen();
World world = NbtSerialization.readWorld(server, tag);
if (world != null) {
return NbtSerialization.listTagsToLocation(world, tag);
}
return null;
}
@Override
public Location getBedSpawnLocation() {
checkOpen();
// check that all fields are present
if (!tag.isString("SpawnWorld") || !tag.isInt("SpawnX") || !tag.isInt("SpawnY") || !tag.isInt("SpawnZ")) {
return null;
}
// look up world
World world = server.getWorld(tag.getString("SpawnWorld"));
if (world == null) {
return null;
}
// return location
return new Location(world, tag.getInt("SpawnX"), tag.getInt("SpawnY"), tag.getInt("SpawnZ"));
}
@Override
public long getFirstPlayed() {
checkOpen();
if (tag.isCompound("bukkit")) {
CompoundTag bukkit = tag.getCompound("bukkit");
if (bukkit.isLong("firstPlayed")) {
return bukkit.getLong("firstPlayed");
}
}
return 0;
}
@Override
public long getLastPlayed() {
checkOpen();
if (tag.isCompound("bukkit")) {
CompoundTag bukkit = tag.getCompound("bukkit");
if (bukkit.isLong("lastPlayed")) {
return bukkit.getLong("lastPlayed");
}
}
return 0;
}
@Override
public String getLastKnownName() {
checkOpen();
if (tag.isCompound("bukkit")) {
CompoundTag bukkit = tag.getCompound("bukkit");
if (bukkit.isString("lastKnownName")) {
return bukkit.getString("lastKnownName");
}
}
return null;
}
@Override
public void readData(GlowPlayer player) {
checkOpen();
readDataImpl(player, tag);
}
@Override
public void close() {
tag = null;
}
}
}