blob: 3edab06fd61a7e5a1941eec444bd3f29147ebd11 [file] [log] [blame] [raw]
package net.glowstone.io.nbt;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import net.glowstone.GlowOfflinePlayer;
import net.glowstone.GlowServer;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.i18n.ConsoleMessages;
import net.glowstone.i18n.GlowstoneMessages;
import net.glowstone.io.PlayerDataService;
import net.glowstone.io.entity.EntityStorage;
import net.glowstone.util.UuidUtils;
import net.glowstone.util.nbt.CompoundTag;
import net.glowstone.util.nbt.NbtInputStream;
import net.glowstone.util.nbt.NbtOutputStream;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
/**
* 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()) {
ConsoleMessages.Warn.Io.MKDIR_FAILED.log(playerDir);
}
return new File(playerDir, UuidUtils.toString(uuid) + ".dat");
}
private void readDataImpl(GlowPlayer player, CompoundTag playerTag) {
EntityStorage.load(player, playerTag);
}
@Override
public CompletableFuture<Collection<OfflinePlayer>> getOfflinePlayers() {
// list files in directory
File[] files = playerDir.listFiles();
if (files == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<CompletableFuture<GlowOfflinePlayer>> futures = 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")) { // NON-NLS
continue;
}
// get the UUID
UUID uuid;
try {
uuid = UuidUtils.fromString(name.substring(0, 36));
} catch (IllegalArgumentException e) {
continue;
}
// creating the OfflinePlayer will read the data
futures.add(GlowOfflinePlayer.getOfflinePlayer(server, uuid));
}
CompletableFuture<Void> gotAll = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
);
return gotAll.thenApplyAsync(v ->
futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
@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(GlowstoneMessages.Kick.FILE_READ.get());
ConsoleMessages.Error.Io.PLAYER_READ.log(e, player.getName(), playerFile);
}
}
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(GlowstoneMessages.Kick.FILE_WRITE.get());
ConsoleMessages.Error.Io.PLAYER_WRITE.log(e, player.getName(), playerFile);
}
}
@SuppressWarnings("HardCodedStringLiteral")
private class NbtPlayerReader implements PlayerReader {
private CompoundTag tag = new CompoundTag();
private boolean hasPlayed;
public NbtPlayerReader(File playerFile) {
if (playerFile.exists()) {
try (NbtInputStream in = new NbtInputStream(new FileInputStream(playerFile))) {
tag = in.readCompound();
hasPlayed = true;
} catch (IOException e) {
ConsoleMessages.Error.Io.PLAYER_READ_UNKNOWN.log(e, playerFile);
}
}
}
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();
long[] out = {0};
tag.readCompound("bukkit", bukkit -> bukkit.readLong("firstPlayed", x -> out[0] = x));
return out[0];
}
@Override
public long getLastPlayed() {
checkOpen();
long[] out = {0};
// In Paper, the value is stored both in Paper->LastSeen and bukkit->lastPlayed.
if (tag.containsKey("Paper")) {
tag.readCompound("Paper", paper -> paper.readLong("LastSeen", x -> out[0] = x));
} else {
tag.readCompound("bukkit",
bukkit -> bukkit.readLong("lastPlayed", x -> out[0] = x));
}
return out[0];
}
@Override
public long getLastLogin() {
checkOpen();
long[] out = {0};
tag.readCompound("Paper", paper -> paper.readLong("LastLogin", x -> out[0] = x));
return out[0];
}
@Override
public String getLastKnownName() {
checkOpen();
String[] out = {null};
tag.readCompound("bukkit",
bukkit -> bukkit.readString("lastKnownName", x -> out[0] = x));
return out[0];
}
@Override
public void readData(GlowPlayer player) {
checkOpen();
readDataImpl(player, tag);
}
@Override
public void close() {
tag = null;
}
}
}