From fbfcd89d951f9d624ea9765266219ae3c93583a6 Mon Sep 17 00:00:00 2001 From: DarkGuardsman Date: Wed, 18 Dec 2013 07:28:00 -0500 Subject: [PATCH] added access & save api from core machine --- src/dark/api/access/AccessGroup.java | 132 ++++++++ src/dark/api/access/AccessProfile.java | 280 +++++++++++++++++ src/dark/api/access/AccessUser.java | 100 ++++++ src/dark/api/access/GroupRegistry.java | 114 +++++++ src/dark/api/access/IProfileContainer.java | 15 + src/dark/api/access/ISpecialAccess.java | 39 +++ src/dark/api/access/Nodes.java | 84 +++++ src/dark/api/save/ISaveObj.java | 12 + src/dark/api/save/IVirtualObject.java | 18 ++ src/dark/api/save/NBTFileHelper.java | 338 +++++++++++++++++++++ src/dark/api/save/SaveManager.java | 164 ++++++++++ 11 files changed, 1296 insertions(+) create mode 100644 src/dark/api/access/AccessGroup.java create mode 100644 src/dark/api/access/AccessProfile.java create mode 100644 src/dark/api/access/AccessUser.java create mode 100644 src/dark/api/access/GroupRegistry.java create mode 100644 src/dark/api/access/IProfileContainer.java create mode 100644 src/dark/api/access/ISpecialAccess.java create mode 100644 src/dark/api/access/Nodes.java create mode 100644 src/dark/api/save/ISaveObj.java create mode 100644 src/dark/api/save/IVirtualObject.java create mode 100644 src/dark/api/save/NBTFileHelper.java create mode 100644 src/dark/api/save/SaveManager.java diff --git a/src/dark/api/access/AccessGroup.java b/src/dark/api/access/AccessGroup.java new file mode 100644 index 000000000..522f7a8d2 --- /dev/null +++ b/src/dark/api/access/AccessGroup.java @@ -0,0 +1,132 @@ +package dark.api.access; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import com.builtbroken.common.Group; + +/** Used by a terminal to track what users are part of each group. As well used to setup access + * points to the terminal. + * + * @author DarkGuardsman */ +public class AccessGroup extends Group +{ + protected List nodes = new ArrayList(); + protected AccessGroup extendGroup; + + public AccessGroup(String name, AccessUser... js) + { + super(name, js); + } + + public void setToExtend(AccessGroup group) + { + this.extendGroup = group; + } + + @Override + public boolean addMemeber(AccessUser obj) + { + if (obj != null) + { + for (AccessUser user : this.memebers) + { + if (user.getName().equalsIgnoreCase(obj.getName())) + { + return false; + } + } + if (super.addMemeber(obj)) + { + obj.setGroup(this); + return true; + } + } + return false; + } + + public AccessUser getMember(String name) + { + for (AccessUser user : this.memebers) + { + if (user.getName().equalsIgnoreCase(name)) + { + return user; + } + } + return null; + } + + public NBTTagCompound save(NBTTagCompound nbt) + { + nbt.setString("groupName", this.getName()); + NBTTagList usersTag = new NBTTagList(); + for (AccessUser user : this.memebers) + { + if (!user.isTempary) + { + NBTTagCompound accessData = new NBTTagCompound(); + user.save(accessData); + usersTag.appendTag(accessData); + } + } + nbt.setTag("users", usersTag); + NBTTagList nodesTag = new NBTTagList(); + for (String str : this.nodes) + { + NBTTagCompound accessData = new NBTTagCompound(); + accessData.setString("name", str); + nodesTag.appendTag(accessData); + } + nbt.setTag("nodes", nodesTag); + return nbt; + } + + public void load(NBTTagCompound nbt) + { + this.setName(nbt.getString("groupName")); + NBTTagList userList = nbt.getTagList("users"); + for (int i = 0; i < userList.tagCount(); ++i) + { + this.addMemeber(AccessUser.loadFromNBT((NBTTagCompound) userList.tagAt(i))); + } + NBTTagList nodeList = nbt.getTagList("nodes"); + for (int i = 0; i < nodeList.tagCount(); ++i) + { + this.nodes.add(((NBTTagCompound) nodeList.tagAt(i)).getString("name")); + } + } + + public boolean hasNode(String node) + { + return this.nodes.contains(node); + } + + public void addNode(String node) + { + this.nodes.add(node); + } + + public void removeNode(String node) + { + if (this.nodes.contains(node)) + { + this.nodes.remove(node); + } + } + + public boolean isMemeber(String string) + { + for (AccessUser user : this.memebers) + { + if (user.getName().equalsIgnoreCase(string)) + { + return true; + } + } + return false; + } +} diff --git a/src/dark/api/access/AccessProfile.java b/src/dark/api/access/AccessProfile.java new file mode 100644 index 000000000..4c8922203 --- /dev/null +++ b/src/dark/api/access/AccessProfile.java @@ -0,0 +1,280 @@ +package dark.api.access; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntity; +import dark.api.save.IVirtualObject; +import dark.api.save.NBTFileHelper; +import dark.api.save.SaveManager; + +/** Designed to be used as a container for AccessGroups and AccessUser. If you plan to use this make + * sure to use it correctly. This is designed to be saved separate from the world save if marked for + * global access. Which means it can save/load at will from the world file. + * + * @author DarkGuardsman */ +public class AccessProfile implements ISpecialAccess, IVirtualObject +{ + /** A list of user access data. */ + protected List groups = new ArrayList(); + /** Display name */ + protected String profileName = ""; + /** Only used by global profiles that have no defined container. Also LocalHost means it was + * created by a tileEntity */ + protected String profileID = "LocalHost"; + /** Is this profile global */ + protected boolean global = false; + /** Save file by which this was loaded from. Mainly used to save it in the same location again. */ + protected File saveFile; + + static + { + SaveManager.registerClass("AccessProfile", AccessProfile.class); + } + + public AccessProfile() + { + if (global) + { + SaveManager.register(this); + } + } + + public AccessProfile(NBTTagCompound nbt) + { + this(nbt, false); + } + + public AccessProfile(NBTTagCompound nbt, boolean global) + { + this(); + this.load(nbt); + if (this.profileName == null || this.profileID == null) + { + if (!global) + { + this.generateNew("Default", null); + } + else + { + this.generateNew("New Group", "global"); + } + } + } + + public AccessProfile generateNew(String name, Object object) + { + GroupRegistry.loadNewGroupSet(this); + this.profileName = name; + name.replaceAll(" ", ""); + String id = null; + // Created by player for personal use + if (object instanceof EntityPlayer) + { + id = ((EntityPlayer) object).username + "_" + System.currentTimeMillis(); + }//Created by a tile + else if (object instanceof TileEntity || object == null) + { + id = "LocalHost:" + name; + }//created by the game or player for global use + else if (object instanceof String && ((String) object).equalsIgnoreCase("global")) + { + id = "P_" + name + "_" + System.currentTimeMillis(); + this.global = true; + } + this.profileID = id; + return this; + } + + public String getName() + { + return this.profileName; + } + + public String getID() + { + return this.profileID; + } + + public boolean isGlobal() + { + return this.global; + } + + @Override + public AccessUser getUserAccess(String username) + { + for (AccessGroup group : this.groups) + { + AccessUser user = group.getMember(username); + if (user != null) + { + return user; + } + } + return new AccessUser(username); + } + + @Override + public List getUsers() + { + List users = new ArrayList(); + for (AccessGroup group : this.groups) + { + users.addAll(group.getMembers()); + } + return users; + } + + @Override + public boolean setUserAccess(String player, AccessGroup g, boolean save) + { + return setUserAccess(new AccessUser(player).setTempary(save), g); + } + + @Override + public boolean setUserAccess(AccessUser user, AccessGroup group) + { + boolean bool = false; + + if (user != null && user.getName() != null) + { + bool = this.removeUserAccess(user.getName()) && group == null; + if (group != null) + { + bool = group.addMemeber(user); + } + if (bool) + { + this.onProfileUpdate(); + } + } + return bool; + } + + public boolean removeUserAccess(String player) + { + boolean re = false; + for (AccessGroup group : this.groups) + { + AccessUser user = group.getMember(player); + if (user != null && group.removeMemeber(user)) + { + re = true; + } + } + if (re) + { + this.onProfileUpdate(); + } + return re; + } + + public void onProfileUpdate() + { + + } + + @Override + public AccessGroup getGroup(String name) + { + for (AccessGroup group : this.getGroups()) + { + if (group.getName().equalsIgnoreCase(name)) + { + return group; + } + } + return null; + } + + @Override + public boolean addGroup(AccessGroup group) + { + if (!this.groups.contains(group)) + { + for (AccessGroup g : this.groups) + { + if (group.getName().equalsIgnoreCase(g.getName())) + { + return false; + } + } + if (this.groups.add(group)) + { + this.onProfileUpdate(); + return true; + } + } + return false; + } + + @Override + public AccessGroup getOwnerGroup() + { + return this.getGroup("owner"); + } + + @Override + public List getGroups() + { + if (this.groups == null || this.groups.isEmpty()) + { + GroupRegistry.loadNewGroupSet(this); + } + return this.groups; + } + + @Override + public void save(NBTTagCompound nbt) + { + this.profileName = nbt.getString("name"); + this.global = nbt.getBoolean("global"); + NBTTagList userList = nbt.getTagList("groups"); + if (userList != null && userList.tagCount() > 0) + { + this.groups.clear(); + for (int i = 0; i < userList.tagCount(); i++) + { + AccessGroup group = new AccessGroup(""); + group.load((NBTTagCompound) userList.tagAt(i)); + this.groups.add(group); + } + } + } + + @Override + public void load(NBTTagCompound nbt) + { + nbt.setString("name", this.profileName); + nbt.setBoolean("global", this.global); + NBTTagList usersTag = new NBTTagList(); + for (AccessGroup group : this.getGroups()) + { + usersTag.appendTag(group.save(new NBTTagCompound())); + } + nbt.setTag("groups", usersTag); + } + + @Override + public File getSaveFile() + { + if (this.saveFile == null) + { + this.saveFile = new File(NBTFileHelper.getWorldSaveDirectory(MinecraftServer.getServer().getFolderName()), "CoreMachine/Access/" + this.getID() + ".dat"); + } + return this.saveFile; + } + + @Override + public void setSaveFile(File file) + { + this.saveFile = file; + + } +} diff --git a/src/dark/api/access/AccessUser.java b/src/dark/api/access/AccessUser.java new file mode 100644 index 000000000..19d2f879a --- /dev/null +++ b/src/dark/api/access/AccessUser.java @@ -0,0 +1,100 @@ +package dark.api.access; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import com.builtbroken.common.User; + +/** Used to define a users access to a terminal based object. + * + * @author DarkGuardsman */ +public class AccessUser extends User +{ + protected boolean isTempary = false; + protected NBTTagCompound extraData; + protected AccessGroup group; + protected List nodes = new ArrayList(); + + public AccessUser(String username) + { + super(username); + } + + public AccessUser(EntityPlayer player) + { + super(player.username); + } + + public AccessGroup getGroup() + { + return this.group; + } + + public AccessUser setGroup(AccessGroup group) + { + this.group = group; + return this; + } + + public boolean hasNode(String node) + { + if (group != null && group.hasNode(node)) + { + return true; + } + return nodes.contains(node); + } + + public NBTTagCompound save(NBTTagCompound nbt) + { + nbt.setString("username", this.username); + nbt.setCompoundTag("extraData", this.userData()); + NBTTagList usersTag = new NBTTagList(); + for (String str : this.nodes) + { + NBTTagCompound accessData = new NBTTagCompound(); + accessData.setString("name", str); + usersTag.appendTag(accessData); + } + nbt.setTag("nodes", usersTag); + return nbt; + } + + public AccessUser load(NBTTagCompound nbt) + { + this.username = nbt.getString("username"); + this.extraData = nbt.getCompoundTag("extraData"); + NBTTagList userList = nbt.getTagList("nodes"); + for (int i = 0; i < userList.tagCount(); ++i) + { + this.nodes.add(((NBTTagCompound) userList.tagAt(i)).getString("name")); + } + return this; + } + + public static AccessUser loadFromNBT(NBTTagCompound nbt) + { + return new AccessUser("").load(nbt); + } + + public AccessUser setTempary(boolean si) + { + this.isTempary = si; + return this; + } + + /** Used to add other data to the user */ + public NBTTagCompound userData() + { + if (this.extraData == null) + { + this.extraData = new NBTTagCompound(); + } + return this.extraData; + } + +} diff --git a/src/dark/api/access/GroupRegistry.java b/src/dark/api/access/GroupRegistry.java new file mode 100644 index 000000000..1b3d217ef --- /dev/null +++ b/src/dark/api/access/GroupRegistry.java @@ -0,0 +1,114 @@ +package dark.api.access; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +/** @author DarkGuardsman */ +public class GroupRegistry +{ + public static final List nodes = new ArrayList(); + public static final HashMap> groupDefaultNodes = new HashMap(); + public static final HashMap groupDefaultExtends = new HashMap(); + + static + { + List list = new ArrayList(); + //Owner group defaults + list.add(Nodes.GROUP_OWNER_NODE); + list.add(Nodes.INV_DISABLE_NODE); + list.add(Nodes.INV_ENABLE_NODE); + createDefaultGroup("owner", "admin", list); + //Admin group defaults + List list2 = new ArrayList(); + list2.add(Nodes.GROUP_ADMIN_NODE); + list2.add(Nodes.INV_EDIT_NODE); + list2.add(Nodes.INV_LOCK_NODE); + list2.add(Nodes.INV_UNLOCK_NODE); + list2.add(Nodes.INV_CHANGE_NODE); + createDefaultGroup("admin", "user", list2); + //User group defaults + List list3 = new ArrayList(); + list3.add(Nodes.GROUP_USER_NODE); + list3.add(Nodes.INV_OPEN_NODE); + list3.add(Nodes.INV_TAKE_NODE); + list3.add(Nodes.INV_GIVE_NODE); + createDefaultGroup("user", null, list3); + } + + /** Creates a default group for all machines to use. Only add a group if there is no option to + * really manage the group's settings + * + * @param name - group name + * @param prefabGroup - group this should extend. Make sure it exists. + * @param nodes - all commands or custom nodes */ + public static void createDefaultGroup(String name, String prefabGroup, List nodes) + { + if (name != null) + { + groupDefaultNodes.put(name, nodes); + groupDefaultExtends.put(name, prefabGroup); + } + } + + /** Creates a default group for all machines to use. Only add a group if there is no option to + * really manage the group's settings + * + * @param name - group name + * @param prefabGroup - group this should extend. Make sure it exists. + * @param nodes - all commands or custom nodes */ + public static void createDefaultGroup(String name, String prefabGroup, String... nodes) + { + List nodeList = new ArrayList(); + if (nodes != null) + { + for (String node : nodes) + { + nodeList.add(node); + } + } + createDefaultGroup(name, prefabGroup, nodeList); + } + + /** Builds a new default group list for a basic machine */ + public static List getNewGroupSet() + { + List groups = new ArrayList(); + for (Entry> entry : groupDefaultNodes.entrySet()) + { + AccessGroup group = new AccessGroup(entry.getKey()); + if (entry.getValue() != null) + { + for (String string : entry.getValue()) + { + group.addNode(string); + } + } + groups.add(group); + } + return groups; + } + + /** Builds then loaded a new default group set into the terminal */ + public static void loadNewGroupSet(ISpecialAccess terminal) + { + if (terminal != null) + { + List groups = getNewGroupSet(); + for (AccessGroup group : groups) + { + terminal.addGroup(group); + } + } + } + + public static void register(String node) + { + if (!nodes.contains(node)) + { + nodes.add(node); + } + } + +} diff --git a/src/dark/api/access/IProfileContainer.java b/src/dark/api/access/IProfileContainer.java new file mode 100644 index 000000000..7a11e4461 --- /dev/null +++ b/src/dark/api/access/IProfileContainer.java @@ -0,0 +1,15 @@ +package dark.api.access; + +/** Applied to tileEntities that contain an access profile that describes how the tile interacts with + * users + * + * @author DarkGuardsman */ +public interface IProfileContainer +{ + /** Return the active profile of the machine. When calling this avoid editing the profile */ + public AccessProfile getAccessProfile(); + + /** Strait up yes or no can this user access the tile. Any future checks should be done after the + * user has accessed the machine */ + public boolean canAccess(String username); +} diff --git a/src/dark/api/access/ISpecialAccess.java b/src/dark/api/access/ISpecialAccess.java new file mode 100644 index 000000000..e5f7412ff --- /dev/null +++ b/src/dark/api/access/ISpecialAccess.java @@ -0,0 +1,39 @@ +package dark.api.access; + +import java.util.List; + +/** Used by any object that needs to restrict access to it by a set of users or groups. Make sure to + * always use the default groups(user,admin,owner) so that things work smoothly. + * + * @author DarkGuardsman */ +public interface ISpecialAccess +{ + /** Gets the user access instance */ + public AccessUser getUserAccess(String username); + + /** gets the user access list for the machine */ + public List getUsers(); + + /** sets the players access level in the access map. Make sure to remove the old user first. This + * can also be used to remove users if group is set to null. */ + public boolean setUserAccess(String username, AccessGroup group, boolean save); + + /** Sets the players access by using a completed AccessUser instance. Make sure to set its group + * if there is none. As well remove the old user first. */ + public boolean setUserAccess(AccessUser user, AccessGroup group); + + /** Get a group by name */ + public AccessGroup getGroup(String name); + + /** Get the master owner group */ + public AccessGroup getOwnerGroup(); + + /** Get all groups linked this */ + public List getGroups(); + + /** Add a group to the group list + * + * @return */ + public boolean addGroup(AccessGroup group); + +} diff --git a/src/dark/api/access/Nodes.java b/src/dark/api/access/Nodes.java new file mode 100644 index 000000000..1c6442151 --- /dev/null +++ b/src/dark/api/access/Nodes.java @@ -0,0 +1,84 @@ +package dark.api.access; + +/** Constants that represent nodes by which machines and entities used in combination with + * ISpecialAccess to limit users on what they can do. These nodes should be used in the same way by + * all machines, entities, and other mods. Too change the meaning of the node will make it difficult + * to offer universal meaning for all machines. As well would create the need to add a per node per + * machine per group access list making it more complicated for users to use. + * + * @author DarkGuardsman */ +public class Nodes +{ + /* + * Rules for using nodes with groups and your machine: + * + * Keep everything the same. + * Eg: Open should be as simple as opening the gui and no more + * + * Enable is not the same as on + * Eg: you can disable a machine preventing users from using it + * Eg: When enabled the machine can still be turned off + * + * Lock node automatically includes unlock node but not the other way around + * + * + * Machine nodes override inv node as inv nodes are only designed for containers. + * Machines nodes are global for all guis inside the machine + * + * + * Groups do not need there own nodes. Group.user, Group.owner, Group.admin are designed to flag the default groups. + * + * + * Your machine must always have a group that at least has a owner group. This group should be flagged with Group.owner but is not required. + * + * + * + */ + + //Inventory only nodes, overrided by machine nodes + public static final String INV_OPEN_NODE = "inv.open"; + public static final String INV_TAKE_NODE = "inv.take"; + public static final String INV_GIVE_NODE = "inv.give"; + public static final String INV_EDIT_NODE = "inv.edit"; + public static final String INV_CHANGE_NODE = "inv.change"; + public static final String INV_LOCK_NODE = "inv.lock"; + public static final String INV_UNLOCK_NODE = "inv.unlock"; + public static final String INV_DISABLE_NODE = "inv.disable"; + public static final String INV_ENABLE_NODE = "inv.enable"; + + //Master machines nodes, overrides all lower nodes of the same type + public static final String MACHINE_OPEN_NODE = "machine.open"; + public static final String MACHINE_LOCK_NODE = "machine.lock"; + public static final String MACHINE_UNLOCK_NODE = "machine.unlock"; + public static final String MACHINE_ENABLE_NODE = "machine.enable"; + public static final String MACHINE_DISABLE_NODE = "machine.disable"; + public static final String MACHINE_TURN_ON_NODE = "machine.on"; + public static final String MACHINE_TURN_OFF_NODE = "machine.off"; + public static final String MACHINE_CONFIG_NODE = "machine.config"; + public static final String MACHINE_UPGRADE_NODE = "machine.upgrade"; + public static final String MACHINE_DOWNGRADE_NODE = "machine.downgrade"; + + //Group nodes, these are almost always held by only admins and owners + public static final String GROUP_CREATE_NODE = "group.create"; + public static final String GROUP_DEL_NODE = "group.del"; + public static final String GROUP_EDIT_NODE = "group.edit";//Still bound by other nodes + public static final String GROUP_ADD_NODE = "group.add"; + public static final String GROUP_ADD_USER_NODE = "group.add.user"; + public static final String GROUP_ADD_ENTITY_NODE = "group.add.entity"; + public static final String GROUP_ADD_NODE_NODE = "group.add.node"; + public static final String GROUP_REMOVE_NODE = "group.remove"; + public static final String GROUP_REMOVE_USER_NODE = "group.remove.user"; + public static final String GROUP_REMOVE_ENTITY_NODE = "group.remove.entity"; + public static final String GROUP_REMOVE_NODE_NODE = "group.remove.node"; + + //Nodes for editing users inside of a group + public static final String USER_EDIT_NODE = "user.edit"; + public static final String USER_CHANGE_GROUP_NODE = "user.change.group"; + public static final String USER_ADD_NODE = "user.add.node"; + public static final String USER_REMOVE_NODE = "user.remove.node"; + + //Prefab group nodes, designed to be placed on the master group of the type + public static final String GROUP_OWNER_NODE = "group.owner"; + public static final String GROUP_ADMIN_NODE = "group.admin"; + public static final String GROUP_USER_NODE = "group.user"; +} diff --git a/src/dark/api/save/ISaveObj.java b/src/dark/api/save/ISaveObj.java new file mode 100644 index 000000000..f32bd4175 --- /dev/null +++ b/src/dark/api/save/ISaveObj.java @@ -0,0 +1,12 @@ +package dark.api.save; + +import net.minecraft.nbt.NBTTagCompound; + +public interface ISaveObj +{ + /** Saves the object to NBT */ + public void save(NBTTagCompound nbt); + + /** Load the object from NBT */ + public void load(NBTTagCompound nbt); +} diff --git a/src/dark/api/save/IVirtualObject.java b/src/dark/api/save/IVirtualObject.java new file mode 100644 index 000000000..bd1aa7cea --- /dev/null +++ b/src/dark/api/save/IVirtualObject.java @@ -0,0 +1,18 @@ +package dark.api.save; + +import java.io.File; + +/** Used in combination with the save manager and other managers to say this object needs to be save + * since its not connected with the world + * + * @author DarkGuardsman */ +public interface IVirtualObject extends ISaveObj +{ + /** File this is saved as, don't create anything here as the manager will do that for you */ + public File getSaveFile(); + + /** Will only be called after an object has been loaded. Allows the object to know were its been + * loaded from and decide if it wants to use the location as its getSaveFile return */ + public void setSaveFile(File file); + +} diff --git a/src/dark/api/save/NBTFileHelper.java b/src/dark/api/save/NBTFileHelper.java new file mode 100644 index 000000000..6d99fd2ae --- /dev/null +++ b/src/dark/api/save/NBTFileHelper.java @@ -0,0 +1,338 @@ +package dark.api.save; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagByte; +import net.minecraft.nbt.NBTTagByteArray; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagDouble; +import net.minecraft.nbt.NBTTagFloat; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagIntArray; +import net.minecraft.nbt.NBTTagLong; +import net.minecraft.nbt.NBTTagShort; +import net.minecraft.nbt.NBTTagString; +import net.minecraft.server.MinecraftServer; +import universalelectricity.core.vector.Vector2; +import universalelectricity.core.vector.Vector3; + +import com.builtbroken.common.science.units.UnitHelper; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; + +/** Helper class used to work with minecraft's NBT file system. + * + * @author DarkGuardsman */ +public class NBTFileHelper +{ + /** @param saveDirectory - file + * @param filename + * @param data + * @return */ + public static boolean saveNBTFile(File saveDirectory, String filename, NBTTagCompound data) + { + return saveNBTFile(new File(saveDirectory, filename), data); + } + + /** Saves an NBT file + * + * @param file - exact File + * @param data - nbt data + * @return */ + public static boolean saveNBTFile(File file, NBTTagCompound data) + { + if (file != null && data != null) + { + try + { + File tempFile = new File(file.getParent(), file.getName() + ".tmp"); + + CompressedStreamTools.writeCompressed(data, new FileOutputStream(tempFile)); + + if (file.exists()) + { + file.delete(); + } + + tempFile.renameTo(file); + + FMLLog.fine("Saved " + file.getName() + " NBT data file successfully."); + return true; + } + catch (Exception e) + { + System.out.println("Failed to save " + file.getName()); + e.printStackTrace(); + } + } + return false; + } + + /** Uses the default world directory to save the data to file by the given name + * + * @param filename - file name + * @param data - nbt data + * @return true if everything goes well */ + public static boolean saveNBTFile(String filename, NBTTagCompound data) + { + return saveNBTFile(getWorldSaveDirectory(MinecraftServer.getServer().getFolderName()), filename + ".dat", data); + } + + /** Reads NBT data from the world folder. + * + * @return The NBT data */ + public static NBTTagCompound loadNBTFile(File saveDirectory, String filename, boolean create) + { + if (saveDirectory != null && filename != null) + { + if (create && !saveDirectory.exists()) + { + saveDirectory.mkdirs(); + } + return loadNBTFile(new File(saveDirectory, filename + ".dat"), create); + } + return null; + } + + public static NBTTagCompound loadNBTFile(File file, boolean create) + { + if (file != null) + { + try + { + + if (file.exists()) + { + FMLLog.fine("Loaded " + file.getName() + " data."); + return CompressedStreamTools.readCompressed(new FileInputStream(file)); + } + else if (create) + { + FMLLog.fine("Created new " + file.getName() + " data."); + return new NBTTagCompound(); + } + } + catch (Exception e) + { + System.out.println("Failed to load " + file.getName() + ".dat!"); + e.printStackTrace(); + return null; + } + } + return null; + } + + /** Loads an NBT file from the current world file + * + * @param filename - name of the file + * @return NBTTagCompound that was stored in the file */ + public static NBTTagCompound loadNBTFile(String filename) + { + return loadNBTFile(getWorldSaveDirectory(MinecraftServer.getServer().getFolderName()), filename, true); + } + + public static File getWorldSaveDirectory(String worldName) + { + File parent = getBaseDirectory(); + + if (FMLCommonHandler.instance().getSide().isClient()) + { + parent = new File(getBaseDirectory(), "saves" + File.separator); + } + + return new File(parent, worldName + File.separator); + } + + public static File getBaseDirectory() + { + if (FMLCommonHandler.instance().getSide().isClient()) + { + FMLClientHandler.instance().getClient(); + return Minecraft.getMinecraft().mcDataDir; + } + else + { + return new File("."); + } + } + + /** Used to save an object without knowing what the object is exactly. Supports most + * NBTTagCompound save methods including some special cases. Which includes boolean being saves + * as a string so it can be loaded as a boolean from an object save. + * + * @param tag - NBTTagCompound to save the tag too + * @param key - name to save the object as + * @param value - the actual object + * @return the tag when done saving too i */ + public static NBTTagCompound saveObject(NBTTagCompound tag, String key, Object value) + { + if (value instanceof Float) + { + tag.setFloat(key, (Float) value); + } + else if (value instanceof Double) + { + tag.setDouble(key, (Double) value); + } + else if (value instanceof Integer) + { + tag.setInteger(key, (Integer) value); + } + else if (value instanceof String) + { + tag.setString(key, (String) value); + } + else if (value instanceof Short) + { + tag.setShort(key, (Short) value); + } + else if (value instanceof Byte) + { + tag.setByte(key, (Byte) value); + } + else if (value instanceof Long) + { + tag.setLong(key, (Long) value); + } + else if (value instanceof Boolean) + { + tag.setString(key, "NBT:SAVE:BOOLEAN:" + value); + } + else if (value instanceof NBTBase) + { + tag.setTag(key, (NBTBase) value); + } + else if (value instanceof String) + { + tag.setString(key, (String) value); + } + else if (value instanceof byte[]) + { + tag.setByteArray(key, (byte[]) value); + } + else if (value instanceof int[]) + { + tag.setIntArray(key, (int[]) value); + } + else if (value instanceof NBTTagCompound) + { + tag.setCompoundTag(key, (NBTTagCompound) value); + } + else if (value instanceof Vector2) + { + tag.setString(key, "NBT:SAVE:VECTOR:2:" + ((Vector2) value).x + ":" + ((Vector2) value).y); + } + else if (value instanceof Vector3) + { + tag.setString(key, "NBT:SAVE:VECTOR:3:" + ((Vector3) value).x + ":" + ((Vector3) value).y + ":" + ((Vector3) value).z); + } + return tag; + + } + + /** @param key + * @param value + * @return NBTTagCompound that then can be added to save file */ + public static NBTTagCompound saveObject(String key, Object value) + { + return NBTFileHelper.saveObject(new NBTTagCompound(), key, value); + } + + /** Reads an unknown object with a known name from NBT + * + * @param tag - tag to read the value from + * @param key - name of the value + * @param suggestionValue - value to return in case nothing is found + * @return object or suggestionValue if nothing is found */ + public static Object loadObject(NBTTagCompound tag, String key) + { + if (tag != null && key != null) + { + NBTBase saveTag = tag.getTag(key); + if (saveTag instanceof NBTTagFloat) + { + return tag.getFloat(key); + } + else if (saveTag instanceof NBTTagDouble) + { + return tag.getDouble(key); + } + else if (saveTag instanceof NBTTagInt) + { + return tag.getInteger(key); + } + else if (saveTag instanceof NBTTagString) + { + String str = tag.getString(key); + if (str.startsWith("NBT:SAVE:")) + { + str.replaceAll("NBT:SAVE:", ""); + if (str.startsWith("BOOLEAN:")) + { + str.replaceAll("BOOLEAN:", ""); + if (str.equalsIgnoreCase("true")) + { + return true; + } + if (str.equalsIgnoreCase("false")) + { + return false; + } + } + if (str.startsWith("VECTOR:")) + { + str.replaceAll("VECTOR:", ""); + String[] nums = str.split(":"); + if (UnitHelper.tryToParseDouble(nums[0]) == 2) + { + return new Vector2(UnitHelper.tryToParseDouble(nums[1]), UnitHelper.tryToParseDouble(nums[2])); + } + if (UnitHelper.tryToParseDouble(nums[0]) == 3) + { + return new Vector3(UnitHelper.tryToParseDouble(nums[1]), UnitHelper.tryToParseDouble(nums[2]), UnitHelper.tryToParseDouble(nums[3])); + } + } + return null; + } + return str; + } + else if (saveTag instanceof NBTTagShort) + { + return tag.getShort(key); + } + else if (saveTag instanceof NBTTagByte) + { + return tag.getByte(key); + } + else if (saveTag instanceof NBTTagLong) + { + return tag.getLong(key); + } + else if (saveTag instanceof NBTBase) + { + return tag.getTag(key); + } + else if (saveTag instanceof NBTTagByteArray) + { + return tag.getByteArray(key); + } + else if (saveTag instanceof NBTTagIntArray) + { + return tag.getIntArray(key); + } + else if (saveTag instanceof NBTTagCompound) + { + return tag.getCompoundTag(key); + } + } + return null; + } + +} diff --git a/src/dark/api/save/SaveManager.java b/src/dark/api/save/SaveManager.java new file mode 100644 index 000000000..cc4930f20 --- /dev/null +++ b/src/dark/api/save/SaveManager.java @@ -0,0 +1,164 @@ +package dark.api.save; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.event.ForgeSubscribe; +import net.minecraftforge.event.world.WorldEvent; +import cpw.mods.fml.common.FMLLog; + +public class SaveManager +{ + private static HashMap> idToClassMap = new HashMap>(); + private static HashMap, String> classToIDMap = new HashMap, String>(); + private static List saveList = new ArrayList(); + private static List objects = new ArrayList(); + private static SaveManager instance; + + public static SaveManager instance() + { + if (instance == null) + { + instance = new SaveManager(); + } + return instance; + } + + /** Called when the object wants to be save only on the next save call. Will be removed from the + * save manager after */ + public static void markNeedsSaved(Object object) + { + if (object instanceof IVirtualObject && !saveList.contains(object)) + { + saveList.add(object); + } + } + + /** Registers the object to be saved on each world save event */ + public static void register(Object object) + { + if (object instanceof IVirtualObject && !objects.contains(object)) + { + objects.add(object); + } + } + + public static void registerClass(String id, Class clazz) + { + if (id != null && clazz != null) + { + if (idToClassMap.containsKey(id) && idToClassMap.get(id) != null) + { + System.out.println("[CoreMachine]SaveManager: Something attempted to register a class with the id of another class"); + System.out.println("[CoreMachine]SaveManager: Id:" + id + " Class:" + clazz.getName()); + System.out.println("[CoreMachine]SaveManager: OtherClass:" + idToClassMap.get(id).getName()); + } + else + { + idToClassMap.put(id, clazz); + classToIDMap.put(clazz, id); + } + } + } + + public static Object createAndLoad(File file) + { + if (file.exists()) + { + Object obj = createAndLoad(NBTFileHelper.loadNBTFile(file, false)); + if (obj instanceof IVirtualObject) + { + ((IVirtualObject) obj).setSaveFile(file); + } + return obj; + } + return null; + } + + /** Creates an object from the save using its id */ + public static Object createAndLoad(NBTTagCompound par0NBTTagCompound) + { + Object obj = null; + if (par0NBTTagCompound != null && par0NBTTagCompound.hasKey("id")) + { + try + { + Class clazz = getClass(par0NBTTagCompound.getString("id")); + + if (clazz != null) + { + obj = clazz.newInstance(); + } + } + catch (Exception exception) + { + exception.printStackTrace(); + } + + if (obj instanceof IVirtualObject) + { + try + { + ((IVirtualObject) obj).load(par0NBTTagCompound); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "An object %s(%s) has thrown an exception during loading, its state cannot be restored. Report this to the mod author", par0NBTTagCompound.getString("id"), obj.getClass().getName()); + obj = null; + } + } + else + { + MinecraftServer.getServer().getLogAgent().logWarning("Skipping object with id " + par0NBTTagCompound.getString("id")); + } + + return obj; + } + return null; + } + + @ForgeSubscribe + public void worldSave(WorldEvent evt) + { + SaveManager.saveList.addAll(SaveManager.objects); + for (Object object : SaveManager.saveList) + { + if (object instanceof IVirtualObject) + { + saveObject(object); + } + } + saveList.clear(); + } + + /** Saves an object along with its ID */ + public static void saveObject(Object object) + { + if (object instanceof IVirtualObject && getID(object.getClass()) != null && ((IVirtualObject) object).getSaveFile() != null) + { + File file = ((IVirtualObject) object).getSaveFile(); + file.mkdirs(); + NBTTagCompound tag = new NBTTagCompound(); + ((IVirtualObject) object).save(tag); + tag.setString("id", getID(object.getClass())); + NBTFileHelper.saveNBTFile(file, tag); + } + } + + /** Gets the ID that the class will be saved using */ + public static String getID(Class clazz) + { + return classToIDMap.get(clazz); + } + + /** Gets the class that was registered with the ID */ + public static Class getClass(String id) + { + return idToClassMap.get(id); + } +}