package openblocks.common;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.authlib.GameProfile;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import openblocks.Config;
import openblocks.api.InventoryEvent;
import openblocks.api.InventoryEvent.SubInventory;
import openmods.Log;
import openmods.inventory.GenericInventory;
import openmods.utils.ItemUtils;
import openmods.utils.TagUtils;
import org.apache.commons.lang3.StringUtils;
public class PlayerInventoryStore {
public static final String TAG_PLAYER_UUID = "PlayerUUID";
public static final String TAG_PLAYER_NAME = "PlayerName";
private static final String TAG_LOCATION = "Location";
private static final String TAG_INVENTORY = "Inventory";
private static final String TAG_SUB_INVENTORIES = "SubInventories";
private static final String TAG_SLOT = "Slot";
private final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
private static final Pattern SAFE_CHARS = Pattern.compile("[^A-Za-z0-9_-]");
private static final String PREFIX = "inventory-";
private PlayerInventoryStore() {}
public static final PlayerInventoryStore instance = new PlayerInventoryStore();
public interface ExtrasFiller {
public void addExtras(NBTTagCompound meta);
}
public static class LoadedInventories {
public final IInventory mainInventory;
public Map<String, SubInventory> subInventories;
private LoadedInventories(IInventory mainInventory, Map<String, SubInventory> subInventories) {
this.mainInventory = mainInventory;
this.subInventories = subInventories;
}
}
private synchronized File getNewDumpFile(Date date, String player, World world, String type) {
String dateStr = formatter.format(date);
int id = 0;
while (true) {
String filename = String.format(PREFIX + "%s-%s-%s-%d", player, dateStr, type, id);
File file = world.getSaveHandler().getMapFileFromName(filename);
if (!file.exists()) return file;
id++;
}
}
private static String stripFilename(String name) {
return StringUtils.removeEndIgnoreCase(StringUtils.removeStartIgnoreCase(name, PREFIX), ".dat");
}
public File storePlayerInventory(final EntityPlayer player, String type) {
final InventoryEvent.Store evt = new InventoryEvent.Store(player);
MinecraftForge.EVENT_BUS.post(evt);
final GameProfile profile = player.getGameProfile();
return storeInventory(player.inventory, profile.getName(), type, player.worldObj,
createExtrasFiller(profile, player.posX, player.posY, player.posZ, evt.getSubInventories()));
}
private static ExtrasFiller createExtrasFiller(final GameProfile profile, final double x, final double y, final double z, final Map<String, SubInventory> subs) {
return new ExtrasFiller() {
@Override
public void addExtras(NBTTagCompound meta) {
meta.setString(TAG_PLAYER_NAME, profile.getName());
meta.setString(TAG_PLAYER_UUID, profile.getId().toString());
meta.setTag(TAG_LOCATION, TagUtils.store(x, y, z));
NBTTagCompound subInventories = new NBTTagCompound();
for (Map.Entry<String, SubInventory> e : subs.entrySet()) {
NBTTagList subInventory = new NBTTagList();
for (Map.Entry<Integer, ItemStack> ie : e.getValue().asMap().entrySet()) {
ItemStack stack = ie.getValue();
if (stack != null) {
NBTTagCompound stacktag = ItemUtils.writeStack(stack);
stacktag.setInteger(TAG_SLOT, ie.getKey());
subInventory.appendTag(stacktag);
}
}
subInventories.setTag(e.getKey(), subInventory);
}
meta.setTag(TAG_SUB_INVENTORIES, subInventories);
}
};
}
public File storeInventory(IInventory inventory, String name, String type, World world, ExtrasFiller filler) {
GenericInventory copy = new GenericInventory("tmp", false, inventory.getSizeInventory());
copy.copyFrom(inventory);
Date now = new Date();
Matcher matcher = SAFE_CHARS.matcher(name);
String playerName = matcher.replaceAll("_");
File dumpFile = getNewDumpFile(now, playerName, world, type);
NBTTagCompound root = new NBTTagCompound();
{
final NBTTagCompound invData = new NBTTagCompound();
copy.writeToNBT(invData);
root.setTag(TAG_INVENTORY, invData);
}
root.setLong("Created", now.getTime());
root.setString("Type", type);
filler.addExtras(root);
try {
OutputStream stream = new FileOutputStream(dumpFile);
try {
CompressedStreamTools.writeCompressed(root, stream);
} finally {
stream.close();
}
} catch (IOException e) {
Log.warn(e, "Failed to dump data for player %s, file %s", name, dumpFile.getAbsoluteFile());
}
return dumpFile;
}
private static IInventory loadInventory(NBTTagCompound rootTag) {
if (!rootTag.hasKey(TAG_INVENTORY, Constants.NBT.TAG_COMPOUND)) {
Log.debug("No main inventory found");
return null;
}
NBTTagCompound invTag = rootTag.getCompoundTag(TAG_INVENTORY);
GenericInventory result = new GenericInventory("tmp", false, 0);
result.readFromNBT(invTag);
return result;
}
private static SubInventory loadSubInventory(NBTTagList subTag) {
SubInventory result = new SubInventory();
for (int i = 0; i < subTag.tagCount(); i++) {
NBTTagCompound itemTag = subTag.getCompoundTagAt(i);
if (!itemTag.hasNoTags()) {
int slot = itemTag.getInteger(TAG_SLOT);
ItemStack stack = ItemUtils.readStack(itemTag);
if (stack != null) result.addItemStack(slot, stack);
}
}
return result;
}
private static Map<String, SubInventory> loadSubInventories(NBTTagCompound subsTag) {
Map<String, SubInventory> result = Maps.newHashMap();
@SuppressWarnings("unchecked")
final Set<String> keys = subsTag.func_150296_c();
for (String key : keys) {
NBTTagList subTag = subsTag.getTagList(key, Constants.NBT.TAG_COMPOUND);
final SubInventory sub = loadSubInventory(subTag);
result.put(key, sub);
}
return result;
}
private static NBTTagCompound loadInventoryTag(World world, String fileId) {
File file = world.getSaveHandler().getMapFileFromName(PREFIX + stripFilename(fileId));
try {
InputStream stream = new FileInputStream(file);
try {
return CompressedStreamTools.readCompressed(stream);
} finally {
stream.close();
}
} catch (IOException e) {
Log.warn(e, "Failed to read data from file %s", file.getAbsoluteFile());
return null;
}
}
public List<String> getMatchedDumps(World world, String prefix) {
File saveFolder = getSaveFolder(world);
final String actualPrefix = StringUtils.startsWithIgnoreCase(prefix, PREFIX)? prefix : PREFIX + prefix;
File[] files = saveFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(actualPrefix);
}
});
List<String> result = Lists.newArrayList();
int toCut = PREFIX.length();
for (File f : files) {
String name = f.getName();
result.add(name.substring(toCut, name.length() - 4));
}
return result;
}
public static File getSaveFolder(World world) {
File dummy = world.getSaveHandler().getMapFileFromName("dummy");
return dummy.getParentFile();
}
public LoadedInventories loadInventories(World world, String fileId) {
final NBTTagCompound rootTag = loadInventoryTag(world, fileId);
if (rootTag == null) return null;
IInventory mainInventory = loadInventory(rootTag);
final Map<String, SubInventory> subInventories;
if (rootTag.hasKey(TAG_SUB_INVENTORIES, Constants.NBT.TAG_COMPOUND)) {
NBTTagCompound subsTag = rootTag.getCompoundTag(TAG_SUB_INVENTORIES);
subInventories = loadSubInventories(subsTag);
} else {
subInventories = Maps.newHashMap();
}
return new LoadedInventories(mainInventory, subInventories);
}
public boolean restoreInventory(EntityPlayer player, String fileId) {
final LoadedInventories inventories = loadInventories(player.worldObj, fileId);
if (inventories == null) return false;
final IInventory main = inventories.mainInventory;
if (main != null) {
InventoryPlayer current = player.inventory;
final int targetInventorySize = current.getSizeInventory();
final int sourceInventorySize = main.getSizeInventory();
for (int i = 0; i < sourceInventorySize; i++) {
final ItemStack stack = main.getStackInSlot(i);
if (i < targetInventorySize) current.setInventorySlotContents(i, stack);
else player.dropPlayerItemWithRandomChoice(stack, false);
}
}
final Map<String, SubInventory> subs = inventories.subInventories;
if (subs != null) {
MinecraftForge.EVENT_BUS.post(new InventoryEvent.Load(player, subs));
}
return true;
}
@SubscribeEvent(priority = EventPriority.HIGH)
public void onPlayerDeath(LivingDeathEvent event) {
if (Config.dumpStiffsStuff && (event.entity instanceof EntityPlayerMP) && !(event.entity instanceof FakePlayer)) {
EntityPlayer player = (EntityPlayer)event.entity;
final String playerName = player.getDisplayName();
try {
File file = storePlayerInventory(player, "death");
Log.info("Storing post-mortem inventory into %s. It can be restored with command '/ob_inventory restore %s %s'",
file.getAbsolutePath(), playerName, stripFilename(file.getName()));
} catch (Exception e) {
Log.severe(e, "Failed to store inventory for player %s", playerName);
}
}
}
}