diff --git a/src/main/java/kamkeel/npcs/command/profile/CommandProfile.java b/src/main/java/kamkeel/npcs/command/profile/CommandProfile.java index 33d853d91..ac0c232fb 100644 --- a/src/main/java/kamkeel/npcs/command/profile/CommandProfile.java +++ b/src/main/java/kamkeel/npcs/command/profile/CommandProfile.java @@ -23,6 +23,9 @@ public class CommandProfile extends CommandBase { public CommandProfile(){ registerCommand(help); registerCommand(new CommandProfileChange()); + registerCommand(new CommandProfileRemove()); + registerCommand(new CommandProfileAdmin()); + registerCommand(new CommandProfileList()); } public void registerCommand(CommandProfileBase command){ diff --git a/src/main/java/kamkeel/npcs/command/profile/CommandProfileAdmin.java b/src/main/java/kamkeel/npcs/command/profile/CommandProfileAdmin.java new file mode 100644 index 000000000..4e6b2e6a3 --- /dev/null +++ b/src/main/java/kamkeel/npcs/command/profile/CommandProfileAdmin.java @@ -0,0 +1,207 @@ +package kamkeel.npcs.command.profile; + +import kamkeel.npcs.controllers.ProfileController; +import kamkeel.npcs.controllers.data.Profile; +import kamkeel.npcs.controllers.data.ProfileOperation; +import kamkeel.npcs.controllers.data.Slot; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import noppes.npcs.LogWriter; +import net.minecraft.nbt.NBTTagCompound; + +import java.util.Map; +import java.util.UUID; + +public class CommandProfileAdmin extends CommandProfileBase { + + @Override + public String getCommandName() { + return "admin"; + } + + @Override + public String getDescription() { + return "Admin profile management: clone, remove, change, and list a player's slots."; + } + + @Override + public String getUsage() { + return ""; + } + + @SubCommand( + desc = "Clone a slot from one player's profile to another's.", + usage = " [temp]" + ) + public void clone(ICommandSender sender, String[] args) throws CommandException { + if (args.length < 4) { + sendError(sender, "Usage: /profile admin clone [temp]"); + return; + } + String sourcePlayer = args[0]; + String destinationPlayer = args[1]; + int sourceSlot, destinationSlot; + try { + sourceSlot = Integer.parseInt(args[2]); + destinationSlot = Integer.parseInt(args[3]); + } catch (NumberFormatException ex) { + sendError(sender, "Source and destination slot IDs must be numbers."); + return; + } + boolean temporary = (args.length >= 5 && args[4].equalsIgnoreCase("temp")); + + Profile sourceProfile = ProfileController.getProfile(sourcePlayer); + if (sourceProfile == null) { + sendError(sender, "Source profile not found for player: " + sourcePlayer); + return; + } + Profile destProfile = ProfileController.getProfile(destinationPlayer); + if (destProfile == null) { + sendError(sender, "Destination profile not found for player: " + destinationPlayer); + return; + } + if (destProfile.locked) { + sendError(sender, "Destination profile is locked. Please try again later."); + return; + } + if (!sourceProfile.slots.containsKey(sourceSlot)) { + sendError(sender, "Source slot " + sourceSlot + " does not exist for player: " + sourcePlayer); + return; + } + if (destinationSlot == destProfile.currentID) { + sendError(sender, "Cannot clone into the destination's active slot."); + return; + } + try { + Slot sourceSlotObj = sourceProfile.slots.get(sourceSlot); + if (sourceSlotObj == null) { + sendError(sender, "Source slot is invalid."); + return; + } + NBTTagCompound clonedData = (NBTTagCompound) sourceSlotObj.getCompound().copy(); + Slot clonedSlot = new Slot(destinationSlot, "Admin Cloned Slot " + destinationSlot, clonedData, System.currentTimeMillis(), temporary); + destProfile.slots.put(destinationSlot, clonedSlot); + + if (destProfile.player != null) { + ProfileController.save(destProfile.player, destProfile); + } else { + UUID destUUID = ProfileController.getUUIDFromUsername(destinationPlayer); + if (destUUID != null) + ProfileController.saveOffline(destProfile, destUUID); + } + sendResult(sender, "Successfully cloned slot %d from %s to slot %d for %s%s.", + sourceSlot, sourcePlayer, destinationSlot, destinationPlayer, (temporary ? " (temporary)" : "")); + } catch (Exception e) { + LogWriter.error("Error cloning slot", e); + sendError(sender, "An error occurred while cloning slot."); + } + } + + @SubCommand( + desc = "Remove a specified slot from a player's profile.", + usage = " " + ) + public void remove(ICommandSender sender, String[] args) throws CommandException { + if (args.length < 2) { + sendError(sender, "Usage: /profile admin remove "); + return; + } + String targetPlayer = args[0]; + int slotId; + try { + slotId = Integer.parseInt(args[1]); + } catch (NumberFormatException ex) { + sendError(sender, "Slot ID must be a number."); + return; + } + Profile targetProfile = ProfileController.getProfile(targetPlayer); + if (targetProfile == null) { + sendError(sender, "Profile not found for player: " + targetPlayer); + return; + } + if (targetProfile.locked) { + sendError(sender, "Profile for " + targetPlayer + " is locked. Please try again later."); + return; + } + ProfileOperation result = ProfileController.removeSlot(targetPlayer, slotId); + switch (result) { + case SUCCESS: + sendResult(sender, "Successfully removed slot %d from %s.", slotId, targetPlayer); + break; + case LOCKED: + sendError(sender, "Profile for " + targetPlayer + " is locked."); + break; + default: + sendError(sender, "Error removing slot %d from %s.", slotId, targetPlayer); + } + } + + @SubCommand( + desc = "Change a specified player's active slot.", + usage = " " + ) + public void change(ICommandSender sender, String[] args) throws CommandException { + if (args.length < 2) { + sendError(sender, "Usage: /profile admin change "); + return; + } + String targetPlayer = args[0]; + int newSlotId; + try { + newSlotId = Integer.parseInt(args[1]); + } catch (NumberFormatException ex) { + sendError(sender, "Slot ID must be a number."); + return; + } + Profile targetProfile = ProfileController.getProfile(targetPlayer); + if (targetProfile == null) { + sendError(sender, "Profile not found for player: " + targetPlayer); + return; + } + if (targetProfile.locked) { + sendError(sender, "Profile for " + targetPlayer + " is locked. Please try again later."); + return; + } + ProfileOperation result = ProfileController.changeSlot(targetPlayer, newSlotId); + switch (result) { + case SUCCESS: + sendResult(sender, "Successfully changed %s's active slot to %d.", targetPlayer, newSlotId); + break; + case LOCKED: + sendError(sender, "Profile for " + targetPlayer + " is locked."); + break; + default: + sendError(sender, "Error changing active slot for " + targetPlayer); + } + } + + @SubCommand( + desc = "List all slots (IDs and names) for a specified player's profile.", + usage = "" + ) + public void list(ICommandSender sender, String[] args) throws CommandException { + if (args.length < 1) { + sendError(sender, "Usage: /profile admin list "); + return; + } + String targetPlayer = args[0]; + Profile targetProfile = ProfileController.getProfile(targetPlayer); + if (targetProfile == null) { + sendError(sender, "Profile not found for player: " + targetPlayer); + return; + } + StringBuilder sb = new StringBuilder(); + sb.append("Profile Slots for ").append(targetPlayer).append(":\n"); + for (Map.Entry entry : targetProfile.slots.entrySet()) { + int id = entry.getKey(); + String name = entry.getValue().getName(); + if (id == targetProfile.currentID) { + sb.append(" * "); // active slot + } else { + sb.append(" - "); + } + sb.append("Slot ").append(id).append(": ").append(name).append("\n"); + } + sendMessage(sender, sb.toString()); + } +} diff --git a/src/main/java/kamkeel/npcs/command/profile/CommandProfileChange.java b/src/main/java/kamkeel/npcs/command/profile/CommandProfileChange.java index 1e38da197..bbce07b84 100644 --- a/src/main/java/kamkeel/npcs/command/profile/CommandProfileChange.java +++ b/src/main/java/kamkeel/npcs/command/profile/CommandProfileChange.java @@ -1,61 +1,64 @@ package kamkeel.npcs.command.profile; import kamkeel.npcs.controllers.ProfileController; -import kamkeel.npcs.controllers.data.Profile; +import kamkeel.npcs.controllers.data.ProfileOperation; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; public class CommandProfileChange extends CommandProfileBase { + @Override + public String getCommandName() { + return "change"; + } - @Override - public String getCommandName() { - return "change"; - } + @Override + public String getDescription() { + return "Change your active profile slot."; + } - @Override - public String getDescription() { - return "change profiles"; - } + @Override + public String getUsage() { + return ""; + } - @Override - public String getUsage() { - return ""; - } + @Override + public boolean runSubCommands() { + return false; + } - @Override - public boolean runSubCommands(){ - return false; - } - - @Override - public void processCommand(ICommandSender sender, String[] args) throws CommandException { - if(!(sender instanceof EntityPlayer)){ - sendError(sender, "This command can only be sent by a player"); + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if (!(sender instanceof EntityPlayer)) { + sendError(sender, "This command can only be used by a player."); return; } - - int slotID; + if (args.length < 1) { + sendError(sender, "Usage: " + getUsage()); + return; + } + int slotId; try { - slotID = Integer.parseInt(args[0]); + slotId = Integer.parseInt(args[0]); } catch (NumberFormatException ex) { - sendError(sender, "Slot ID must be a number: " + args[1]); + sendError(sender, "Slot ID must be a number: " + args[0]); return; } - - // TODO: Check if the player has permission to switch to this slot - EntityPlayerMP player = (EntityPlayerMP) sender; - - Profile profile = ProfileController.activeProfiles.get(player.getUniqueID()); - if(profile == null){ - sendError(sender, "Could not find a valid profile file"); - return; + EntityPlayer player = (EntityPlayer) sender; + ProfileOperation result = ProfileController.changeSlot(player, slotId); + switch(result) { + case SUCCESS: + sendResult(sender, "Successfully changed profile slot to %s.", slotId); + break; + case LOCKED: + sendError(sender, "Profile is locked. Please try again later."); + break; + case ERROR: + sendError(sender, "Error occurred while changing your profile slot."); + break; + default: + sendError(sender, "Operation could not be completed."); } - - ProfileController.changePlayerSlot(player, slotID); - // TODO: Add Cooldown to successful operation - sendResult(sender, String.format("Switched to Slot \u00A7b%d\u00A77", slotID)); } } diff --git a/src/main/java/kamkeel/npcs/command/profile/CommandProfileList.java b/src/main/java/kamkeel/npcs/command/profile/CommandProfileList.java new file mode 100644 index 000000000..2382d14b5 --- /dev/null +++ b/src/main/java/kamkeel/npcs/command/profile/CommandProfileList.java @@ -0,0 +1,55 @@ +package kamkeel.npcs.command.profile; + +import kamkeel.npcs.controllers.ProfileController; +import kamkeel.npcs.controllers.data.Profile; +import kamkeel.npcs.controllers.data.Slot; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; + +import java.util.Map; + +public class CommandProfileList extends CommandProfileBase { + + @Override + public String getCommandName() { + return "list"; + } + + @Override + public String getDescription() { + return "List all your current profile slots (IDs and names)."; + } + + @Override + public String getUsage() { + return ""; // No extra parameters + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if (!(sender instanceof EntityPlayer)) { + sendError(sender, "This command can only be used by a player."); + return; + } + EntityPlayer player = (EntityPlayer) sender; + Profile profile = ProfileController.getProfile(player); + if (profile == null) { + sendError(sender, "Profile not found."); + return; + } + StringBuilder sb = new StringBuilder(); + sb.append("Your Profile Slots:\n"); + for (Map.Entry entry : profile.slots.entrySet()) { + int id = entry.getKey(); + String name = entry.getValue().getName(); + if (id == profile.currentID) { + sb.append(" * "); // mark active slot + } else { + sb.append(" - "); + } + sb.append("Slot ").append(id).append(": ").append(name).append("\n"); + } + sendMessage(sender, sb.toString()); + } +} diff --git a/src/main/java/kamkeel/npcs/command/profile/CommandProfileRemove.java b/src/main/java/kamkeel/npcs/command/profile/CommandProfileRemove.java new file mode 100644 index 000000000..9af22fca5 --- /dev/null +++ b/src/main/java/kamkeel/npcs/command/profile/CommandProfileRemove.java @@ -0,0 +1,64 @@ +package kamkeel.npcs.command.profile; + +import kamkeel.npcs.controllers.ProfileController; +import kamkeel.npcs.controllers.data.ProfileOperation; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; + +public class CommandProfileRemove extends CommandProfileBase { + + @Override + public String getCommandName() { + return "remove"; + } + + @Override + public String getDescription() { + return "Remove a slot from your profile. You cannot remove your active slot."; + } + + @Override + public String getUsage() { + return ""; + } + + @Override + public boolean runSubCommands() { + return false; + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if (!(sender instanceof EntityPlayer)) { + sendError(sender, "This command can only be used by a player."); + return; + } + if (args.length < 1) { + sendError(sender, "Usage: " + getUsage()); + return; + } + int slotId; + try { + slotId = Integer.parseInt(args[0]); + } catch (NumberFormatException ex) { + sendError(sender, "Slot ID must be a number: " + args[0]); + return; + } + EntityPlayer player = (EntityPlayer) sender; + ProfileOperation result = ProfileController.removeSlot(player, slotId); + switch(result) { + case SUCCESS: + sendResult(sender, "Successfully removed slot %d from your profile.", slotId); + break; + case LOCKED: + sendError(sender, "Profile is locked. Please try again later."); + break; + case ERROR: + sendError(sender, "Error removing slot. Ensure the slot exists and is not your active slot."); + break; + default: + sendError(sender, "Operation could not be completed."); + } + } +} diff --git a/src/main/java/kamkeel/npcs/controllers/ProfileController.java b/src/main/java/kamkeel/npcs/controllers/ProfileController.java index 6cde49199..96ee90145 100644 --- a/src/main/java/kamkeel/npcs/controllers/ProfileController.java +++ b/src/main/java/kamkeel/npcs/controllers/ProfileController.java @@ -1,18 +1,27 @@ package kamkeel.npcs.controllers; import kamkeel.npcs.controllers.data.IProfileData; +import kamkeel.npcs.controllers.data.ProfileOperation; import kamkeel.npcs.controllers.data.Profile; import kamkeel.npcs.controllers.data.Slot; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; import noppes.npcs.CustomNpcs; import noppes.npcs.LogWriter; +import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.util.CustomNPCsThreader; import noppes.npcs.util.NBTJsonUtil; +import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.HashMap; import java.util.UUID; @@ -20,7 +29,6 @@ public class ProfileController { public static HashMap profileTypes; public static HashMap activeProfiles; - public static String profile_directory = "profiles"; public ProfileController(){ @@ -28,52 +36,45 @@ public class ProfileController { activeProfiles = new HashMap<>(); } - /** - * Register during ServerStartEvent() - * - * @param type - IProfile Type to always Save/Load - * @return true/false if profile was successfully added - */ + // ---------- Registration & I/O ---------- + public static boolean registerProfileType(IProfileData type){ if(profileTypes.containsKey(type.getTagName())) return false; - profileTypes.put(type.getTagName(), type); return true; } public static File getProfileDir(){ - try{ + try { File file = new File(CustomNpcs.getWorldSaveDirectory(), profile_directory); if(!file.exists()) file.mkdir(); return file; - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); return null; } } + // Login for online players public static synchronized void login(EntityPlayer player){ if(player == null) return; - Profile profile; if(activeProfiles.containsKey(player.getUniqueID())){ profile = activeProfiles.get(player.getUniqueID()); profile.player = player; } else { - NBTTagCompound profileCompound = load(player); - profile = new Profile(player, profileCompound); + NBTTagCompound compound = load(player); + profile = new Profile(player, compound); activeProfiles.put(player.getUniqueID(), profile); } } public static synchronized NBTTagCompound load(EntityPlayer player) { File saveDir = getProfileDir(); - String filename = player.getUniqueID().toString(); - filename += ".dat"; + String filename = player.getUniqueID().toString() + ".dat"; try { File file = new File(saveDir, filename); if(file.exists()){ @@ -85,51 +86,354 @@ public class ProfileController { return new NBTTagCompound(); } + public static synchronized NBTTagCompound load(UUID uuid) { + File saveDir = getProfileDir(); + String filename = uuid.toString() + ".dat"; + try { + File file = new File(saveDir, filename); + if(file.exists()){ + return NBTJsonUtil.loadNBTData(file); + } + } catch (Exception e) { + LogWriter.error("Error loading profile file: " + filename, e); + } + return new NBTTagCompound(); + } + + // Offline save (synchronous) when no EntityPlayer is available + public static synchronized void saveOffline(Profile profile, UUID uuid) { + final NBTTagCompound compound = profile.writeToNBT(); + final String filename = uuid.toString() + ".dat"; + try { + File saveDir = getProfileDir(); + File fileNew = new File(saveDir, filename + "_new"); + File fileOld = new File(saveDir, filename); + CompressedStreamTools.writeCompressed(compound, new FileOutputStream(fileNew)); + if(fileOld.exists()){ + fileOld.delete(); + } + fileNew.renameTo(fileOld); + } catch (Exception e) { + LogWriter.except(e); + } + } + + // Save for online players (asynchronous) public static synchronized void save(EntityPlayer player, Profile profile) { + profile.locked = true; CustomNPCsThreader.customNPCThread.execute(() -> { final NBTTagCompound compound = profile.writeToNBT(); final String filename = player.getUniqueID() + ".dat"; try { File saveDir = getProfileDir(); - File file = new File(saveDir, filename + "_new"); - File file1 = new File(saveDir, filename); - CompressedStreamTools.writeCompressed(compound, new FileOutputStream(file)); - if(file1.exists()){ - file1.delete(); + File fileNew = new File(saveDir, filename + "_new"); + File fileOld = new File(saveDir, filename); + CompressedStreamTools.writeCompressed(compound, new FileOutputStream(fileNew)); + if(fileOld.exists()){ + fileOld.delete(); } - file.renameTo(file1); + fileNew.renameTo(fileOld); } catch (Exception e) { LogWriter.except(e); + } finally { + profile.locked = false; } }); } - public static void changeSlot(EntityPlayer player, int slotNumber){ - if(!activeProfiles.containsKey(player.getUniqueID())) - return; - - Profile profile = activeProfiles.get(player.getUniqueID()); - if(profile.currentID == slotNumber) - return; - - // Saves Current Slot Data - saveSlotData(player); - - // Sets Player to New Slot - changePlayerSlot(player, slotNumber); - - // Save Slot File - save(player, profile); + // Logout removes an active profile + public static synchronized void logout(EntityPlayer player) { + if(player != null) { + activeProfiles.remove(player.getUniqueID()); + } } - public static void saveSlotData(EntityPlayer player){ - if(!activeProfiles.containsKey(player.getUniqueID())) - return; + // ---------- Profile Retrieval Helpers ---------- + // Get profile via EntityPlayer (online) + public static Profile getProfile(EntityPlayer player) { + if(player == null) return null; + if(!activeProfiles.containsKey(player.getUniqueID())) + login(player); + return activeProfiles.get(player.getUniqueID()); + } + + // Get profile via UUID (may be offline) + public static Profile getProfile(UUID uuid) { + if(activeProfiles.containsKey(uuid)) + return activeProfiles.get(uuid); + NBTTagCompound compound = load(uuid); + return new Profile(null, compound); + } + + // Get profile via username (tries online first, then offline via Mojang API) + public static Profile getProfile(String username) { + EntityPlayer player = getPlayer(username); + if(player != null) { + return getProfile(player); + } else { + UUID uuid = getUUIDFromUsername(username); + if(uuid == null) return null; + return getProfile(uuid); + } + } + + // ---------- Mojang API UUID Lookup ---------- + + public static UUID getUUIDFromUsername(String username) { + try { + URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + username); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setConnectTimeout(5000); + con.setReadTimeout(5000); + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + StringBuilder temp = new StringBuilder(); + String line; + while((line = in.readLine()) != null){ + temp.append(line); + } + in.close(); + con.disconnect(); + String response = temp.toString(); + if(response.contains("\"id\"")) { + int idIndex = response.indexOf("\"id\""); + int colonIndex = response.indexOf(":", idIndex); + int quoteStart = response.indexOf("\"", colonIndex); + int quoteEnd = response.indexOf("\"", quoteStart + 1); + String idString = response.substring(quoteStart + 1, quoteEnd); + idString = idString.replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", + "$1-$2-$3-$4-$5" + ); + return UUID.fromString(idString); + } + } catch (IOException e) { + LogWriter.error("Error retrieving UUID for " + username, e); + } + return null; + } + + // ---------- Internal Slot Operation Helpers ---------- + + // Returns the next available temporary (negative) slot id (starting at -1) + private static int getNextAvailableTempSlot(Profile profile) { + int id = -1; + while(profile.slots.containsKey(id)) { + id--; + } + return id; + } + + // Clone a slot within a Profile. + // sourceSlotId is the slot to copy. + // destinationSlotId is the target id for non-temporary clones. + // If temporary==true, destinationSlotId is computed automatically. + private static ProfileOperation cloneSlotInternal(Profile profile, int sourceSlotId, int destinationSlotId, boolean temporary) { + if(profile.locked) { + LogWriter.error("Profile is locked; cannot clone slot."); + return ProfileOperation.LOCKED; + } + if(!profile.slots.containsKey(sourceSlotId)) { + LogWriter.error("Source slot " + sourceSlotId + " does not exist."); + return ProfileOperation.ERROR; + } + if(destinationSlotId == profile.currentID) { + LogWriter.error("Cannot clone to the current active slot (" + profile.currentID + ")."); + return ProfileOperation.ERROR; + } + if(temporary) { + destinationSlotId = getNextAvailableTempSlot(profile); + } else { + if(destinationSlotId <= 0) { + LogWriter.error("Invalid destination slot id for a non-temporary clone."); + return ProfileOperation.ERROR; + } + } + Slot sourceSlot = profile.slots.get(sourceSlotId); + NBTTagCompound clonedData = (NBTTagCompound) sourceSlot.getCompound().copy(); + Slot clonedSlot = new Slot(destinationSlotId, "Cloned Slot " + destinationSlotId, clonedData, System.currentTimeMillis(), temporary); + profile.slots.put(destinationSlotId, clonedSlot); + return ProfileOperation.SUCCESS; + } + + // Remove a slot from a Profile. + private static ProfileOperation removeSlotInternal(Profile profile, int slotId) { + if(profile.locked) { + LogWriter.error("Profile is locked; cannot remove slot."); + return ProfileOperation.LOCKED; + } + if(slotId == profile.currentID) { + LogWriter.error("Cannot remove the currently active slot (" + profile.currentID + ")."); + return ProfileOperation.ERROR; + } + if(!profile.slots.containsKey(slotId)) { + LogWriter.error("Slot " + slotId + " does not exist."); + return ProfileOperation.ERROR; + } + profile.slots.remove(slotId); + return ProfileOperation.SUCCESS; + } + + // Change the active slot in a Profile. + // Saves current slot data before switching. + private static ProfileOperation changeSlotInternal(Profile profile, int newSlotId) { + if(profile.locked) { + LogWriter.error("Profile is locked; cannot change slot."); + return ProfileOperation.LOCKED; + } + if(profile.player != null) + saveSlotData(profile.player); + // If the new slot doesn't exist, create it. + if(!profile.slots.containsKey(newSlotId)) { + Slot newSlot = new Slot(newSlotId, "Slot " + newSlotId); + newSlot.setCompound(new NBTTagCompound()); + profile.slots.put(newSlotId, newSlot); + } + + profile.currentID = newSlotId; + if(profile.player != null) + loadSlotData(profile.player); + + // Update PlayerData so that PlayerData.get(player).profileSlot matches Profile.currentID + if(profile.player != null) { + PlayerData pdata = PlayerData.get(profile.player); + pdata.profileSlot = newSlotId; + pdata.save(); + } + return ProfileOperation.SUCCESS; + } + + // ---------- Public Operations (Overloaded for EntityPlayer, UUID, or username) ---------- + + // ----- Clone Slot ----- + public static ProfileOperation cloneSlot(EntityPlayer player, int sourceSlotId, int destinationSlotId, boolean temporary) { + Profile profile = getProfile(player); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary); + if(result == ProfileOperation.SUCCESS && player != null) + save(player, profile); + return result; + } + + public static ProfileOperation cloneSlot(UUID uuid, int sourceSlotId, int destinationSlotId, boolean temporary) { + Profile profile = getProfile(uuid); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else + saveOffline(profile, uuid); + } + return result; + } + + public static ProfileOperation cloneSlot(String username, int sourceSlotId, int destinationSlotId, boolean temporary) { + Profile profile = getProfile(username); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else { + UUID uuid = getUUIDFromUsername(username); + if(uuid != null) + saveOffline(profile, uuid); + } + } + return result; + } + + // ----- Remove Slot ----- + public static ProfileOperation removeSlot(EntityPlayer player, int slotId) { + Profile profile = getProfile(player); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = removeSlotInternal(profile, slotId); + if(result == ProfileOperation.SUCCESS && player != null) + save(player, profile); + return result; + } + + public static ProfileOperation removeSlot(UUID uuid, int slotId) { + Profile profile = getProfile(uuid); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = removeSlotInternal(profile, slotId); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else + saveOffline(profile, uuid); + } + return result; + } + + public static ProfileOperation removeSlot(String username, int slotId) { + Profile profile = getProfile(username); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = removeSlotInternal(profile, slotId); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else { + UUID uuid = getUUIDFromUsername(username); + if(uuid != null) + saveOffline(profile, uuid); + } + } + return result; + } + + // ----- Change Slot ----- + public static ProfileOperation changeSlot(EntityPlayer player, int newSlotId) { + Profile profile = getProfile(player); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = changeSlotInternal(profile, newSlotId); + if(result == ProfileOperation.SUCCESS && player != null) + save(player, profile); + return result; + } + + public static ProfileOperation changeSlot(UUID uuid, int newSlotId) { + Profile profile = getProfile(uuid); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = changeSlotInternal(profile, newSlotId); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else + saveOffline(profile, uuid); + } + return result; + } + + public static ProfileOperation changeSlot(String username, int newSlotId) { + Profile profile = getProfile(username); + if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND; + ProfileOperation result = changeSlotInternal(profile, newSlotId); + if(result == ProfileOperation.SUCCESS) { + if(profile.player != null) + save(profile.player, profile); + else { + UUID uuid = getUUIDFromUsername(username); + if(uuid != null) + saveOffline(profile, uuid); + } + } + return result; + } + + // ---------- Saving current slot data (used before switching) ---------- + public static void saveSlotData(EntityPlayer player){ + if(player == null || !activeProfiles.containsKey(player.getUniqueID())) + return; + Profile profile = activeProfiles.get(player.getUniqueID()); + if(profile.locked) { + LogWriter.error("Profile is locked; cannot save slot data."); + return; + } NBTTagCompound dataCompound; Slot slot; - - Profile profile = activeProfiles.get(player.getUniqueID()); if(!profile.slots.isEmpty() && profile.slots.containsKey(profile.currentID)){ slot = profile.slots.get(profile.currentID); dataCompound = (NBTTagCompound) slot.getCompound().copy(); @@ -138,39 +442,39 @@ public class ProfileController { dataCompound = new NBTTagCompound(); slot.setCompound(dataCompound); } - for(IProfileData profileData : profileTypes.values()){ NBTTagCompound cloned = (NBTTagCompound) profileData.getCurrentNBT(player).copy(); dataCompound.setTag(profileData.getTagName(), cloned); } - slot.setCompound(dataCompound); } - public static void changePlayerSlot(EntityPlayer player, int id){ - if(!activeProfiles.containsKey(player.getUniqueID())) + // TODO: + public static void loadSlotData(EntityPlayer player) { + if(player == null || !activeProfiles.containsKey(player.getUniqueID())) return; - NBTTagCompound slotCompound; - Slot slot; Profile profile = activeProfiles.get(player.getUniqueID()); - if(!profile.slots.isEmpty() && profile.slots.containsKey(id)){ - slot = profile.slots.get(id); - slotCompound = (NBTTagCompound) slot.getCompound().copy(); - } else { - slot = new Slot(id, "Slot " + id); - slotCompound = new NBTTagCompound(); - slot.setCompound(slotCompound); - } + if(profile.slots.isEmpty() || !profile.slots.containsKey(profile.currentID)) + return; - slot.setLastLoaded(System.currentTimeMillis()); - profile.slots.put(id, slot); + // Get a copy of the active slot's NBT compound. + NBTTagCompound slotCompound = (NBTTagCompound) profile.slots.get(profile.currentID).getCompound().copy(); + // For each registered profile data type, load its saved compound onto the player. for(IProfileData profileData : profileTypes.values()){ - profileData.setNBT(player, slotCompound.getCompoundTag(profileData.getTagName())); + NBTTagCompound data; + if(slotCompound.hasKey(profileData.getTagName())) + data = slotCompound.getCompoundTag(profileData.getTagName()); + else + data = new NBTTagCompound(); + + profileData.setNBT(player, data); profileData.save(player); } + } - profile.currentID = id; + public static EntityPlayerMP getPlayer(String username){ + return MinecraftServer.getServer().getConfigurationManager().func_152612_a(username); } } diff --git a/src/main/java/kamkeel/npcs/controllers/data/CNPCData.java b/src/main/java/kamkeel/npcs/controllers/data/CNPCData.java index 9b0b0dece..d4e61530f 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/CNPCData.java +++ b/src/main/java/kamkeel/npcs/controllers/data/CNPCData.java @@ -1,6 +1,8 @@ package kamkeel.npcs.controllers.data; +import kamkeel.npcs.controllers.SyncController; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.controllers.data.PlayerData; @@ -21,6 +23,7 @@ public class CNPCData implements IProfileData { public void save(EntityPlayer player) { PlayerData customNPCData = PlayerData.get(player); customNPCData.save(); + SyncController.syncPlayerData((EntityPlayerMP) player, false); } @Override diff --git a/src/main/java/kamkeel/npcs/controllers/data/Profile.java b/src/main/java/kamkeel/npcs/controllers/data/Profile.java index 01002a470..502bea91a 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/Profile.java +++ b/src/main/java/kamkeel/npcs/controllers/data/Profile.java @@ -11,17 +11,17 @@ public class Profile { public EntityPlayer player; public int currentID; public HashMap slots = new HashMap<>(); + public boolean locked = false; // Prevent modifications while saving public Profile(EntityPlayer player, NBTTagCompound compound){ this.player = player; if(compound.hasKey("CurrentID")){ this.currentID = compound.getInteger("CurrentID"); } else { - this.currentID = 1; + this.currentID = 0; } if (compound.hasKey("Slots")) { - // Load Slots NBTTagCompound slotsCompound = compound.getCompoundTag("Slots"); for (String key : (Set) slotsCompound.func_150296_c()) { int slotID = Integer.parseInt(key); @@ -42,14 +42,14 @@ public class Profile { if(!slots.isEmpty()){ NBTTagCompound slotsCompound = new NBTTagCompound(); for (HashMap.Entry entry : slots.entrySet()) { - int id = entry.getKey(); Slot slot = entry.getValue(); + if(slot.isTemporary()) + continue; // Do not save temporary slots + int id = entry.getKey(); slot.setId(id); - NBTTagCompound slotTag = slot.toNBT(); slotsCompound.setTag(String.valueOf(id), slotTag); } - fullCompound.setTag("Slots", slotsCompound); } return fullCompound; diff --git a/src/main/java/kamkeel/npcs/controllers/data/ProfileOperation.java b/src/main/java/kamkeel/npcs/controllers/data/ProfileOperation.java new file mode 100644 index 000000000..62ec9508a --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ProfileOperation.java @@ -0,0 +1,8 @@ +package kamkeel.npcs.controllers.data; + +public enum ProfileOperation { + SUCCESS, + ERROR, + LOCKED, + PLAYER_NOT_FOUND +} diff --git a/src/main/java/kamkeel/npcs/controllers/data/Slot.java b/src/main/java/kamkeel/npcs/controllers/data/Slot.java index 5e6f86869..9b3c5dd97 100644 --- a/src/main/java/kamkeel/npcs/controllers/data/Slot.java +++ b/src/main/java/kamkeel/npcs/controllers/data/Slot.java @@ -8,56 +8,59 @@ public class Slot { private String name; private long lastLoaded; private NBTTagCompound compound; + private boolean temporary = false; public Slot(int id, String name) { this.id = id; this.name = name; } - public Slot(int id, String name, NBTTagCompound compound, long last) { + public Slot(int id, String name, NBTTagCompound compound, long last, boolean temporary) { this.id = id; this.name = name; this.compound = compound; this.lastLoaded = last; + this.temporary = temporary; } + // Getters and setters public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public long getLastLoaded() { return lastLoaded; } - public void setLastLoaded(long lastLoaded) { this.lastLoaded = lastLoaded; } - public NBTTagCompound getCompound() { return compound; } - public void setCompound(NBTTagCompound compound) { this.compound = compound; } + public boolean isTemporary() { + return temporary; + } + public void setTemporary(boolean temporary) { + this.temporary = temporary; + } public NBTTagCompound toNBT() { NBTTagCompound slotNBT = new NBTTagCompound(); slotNBT.setString("Name", name); slotNBT.setLong("LastLoaded", lastLoaded); slotNBT.setTag("Data", compound); + slotNBT.setBoolean("Temporary", temporary); return slotNBT; } @@ -65,6 +68,7 @@ public class Slot { String name = slotNBT.getString("Name"); long lastLoaded = slotNBT.getLong("LastLoaded"); NBTTagCompound compound = slotNBT.getCompoundTag("Data"); - return new Slot(id, name, compound, lastLoaded); + boolean temporary = slotNBT.hasKey("Temporary") && slotNBT.getBoolean("Temporary"); + return new Slot(id, name, compound, lastLoaded, temporary); } } diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerData.java b/src/main/java/noppes/npcs/controllers/data/PlayerData.java index 1afc9e2a8..4f8731c96 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerData.java @@ -68,6 +68,8 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData { public ScreenSize screenSize = new ScreenSize(-1,-1); + public int profileSlot = 0; + public void onLogin() { //Continue playing animation for self when re-logging AnimationData animationData = this.animationData; @@ -113,6 +115,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData { uuid = data.getString("UUID"); } companionID = data.getInteger("PlayerCompanionId"); + profileSlot = data.getInteger("ProfileSlot"); if(data.hasKey("PlayerCompanion") && !hasCompanion()){ EntityCustomNpc npc = new EntityCustomNpc(player.worldObj); npc.readEntityFromNBT(data.getCompoundTag("PlayerCompanion")); @@ -148,6 +151,7 @@ public class PlayerData implements IExtendedEntityProperties, IPlayerData { compound.setString("UUID", uuid); compound.setInteger("PlayerCompanionId", companionID); compound.setBoolean("isGUIOpen",isGUIOpen); + compound.setInteger("ProfileSlot", profileSlot); if(hasCompanion()){ NBTTagCompound nbt = new NBTTagCompound();