More Profile Handling + Login Packet

This commit is contained in:
Kam committed 2025-02-18 22:22:40 -05:00
1 parent 36181d5165
commit e8f657f7e6
17 files changed
+294 -223

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
id 'idea'
}
version = "1.9.4-beta3.2"
version = "1.9.4-beta3.3"
group= "kamkeel.customnpc-plus"
archivesBaseName = "CustomNPC-Plus"
@@ -99,7 +99,8 @@ public class CommandProfile extends CommandBase {
if(args.length <= useArgs.length + 2) {
if(args.length - 3 >= 0){
String usage = useArgs[args.length - 3];
if(usage.equals("<player>") || usage.equals("[player]")) {
if(usage.equals("<player>") || usage.equals("[player]") || usage.equals("<targetPlayer>")
|| usage.equals("<sourcePlayer>") || usage.equals("<destinationPlayer>")) {
return CommandBase.getListOfStringsMatchingLastWord(args, MinecraftServer.getServer().getAllUsernames());
}
}
@@ -4,14 +4,13 @@ import kamkeel.npcs.controllers.ProfileController;
import kamkeel.npcs.controllers.data.Profile;
import kamkeel.npcs.controllers.data.ProfileOperation;
import kamkeel.npcs.controllers.data.Slot;
import noppes.npcs.LogWriter;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import noppes.npcs.LogWriter;
import net.minecraft.nbt.NBTTagCompound;
import java.io.File;
import java.util.Map;
import java.util.UUID;
import static kamkeel.npcs.util.ColorUtil.*;
public class CommandProfileAdmin extends CommandProfileBase {
@@ -23,7 +22,7 @@ public class CommandProfileAdmin extends CommandProfileBase {
@Override
public String getDescription() {
return "Admin profile management: clone, remove, change, and list a player's slots.";
return "Admin profile management: clone, remove, change, list, rename, and rollback profiles.";
}
@Override
@@ -36,7 +35,7 @@ public class CommandProfileAdmin extends CommandProfileBase {
usage = "<sourcePlayer> <destinationPlayer> <sourceSlot> <destinationSlot> [temp]"
)
public void clone(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 4) {
if(args.length < 4) {
sendError(sender, "Usage: /profile admin clone <sourcePlayer> <destinationPlayer> <sourceSlot> <destinationSlot> [temp]");
return;
}
@@ -47,55 +46,31 @@ public class CommandProfileAdmin extends CommandProfileBase {
sourceSlot = Integer.parseInt(args[2]);
destinationSlot = Integer.parseInt(args[3]);
} catch (NumberFormatException ex) {
sendError(sender, "Source and destination slot IDs must be numbers.");
sendError(sender, "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.");
ProfileOperation result = ProfileController.cloneSlot(sourcePlayer, sourceSlot, destinationSlot, temporary);
switch(result) {
case SUCCESS:
sendResult(sender, "Successfully cloned slot %d from %s to slot %d for %s.", sourceSlot, sourcePlayer, destinationSlot, destinationPlayer);
break;
case NEW_SLOT_CREATED:
sendResult(sender, "Successfully cloned slot %d from %s to NEW slot %d for %s.", sourceSlot, sourcePlayer, destinationSlot, destinationPlayer);
break;
case LOCKED:
sendError(sender, "Profile is locked. Please try again later.");
break;
case VERIFICATION_FAILED:
sendError(sender, "Profile verification failed. Change not permitted.");
break;
case PLAYER_NOT_FOUND:
sendError(sender, "Player profile not found.");
break;
case ERROR:
default:
sendError(sender, "Error cloning slot.");
break;
}
}
@@ -104,7 +79,7 @@ public class CommandProfileAdmin extends CommandProfileBase {
usage = "<targetPlayer> <slotId>"
)
public void remove(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 2) {
if(args.length < 2) {
sendError(sender, "Usage: /profile admin remove <targetPlayer> <slotId>");
return;
}
@@ -112,29 +87,21 @@ public class CommandProfileAdmin extends CommandProfileBase {
int slotId;
try {
slotId = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
} 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) {
switch(result) {
case SUCCESS:
sendResult(sender, "Successfully removed slot %d from %s.", slotId, targetPlayer);
break;
case LOCKED:
sendError(sender, "Profile for " + targetPlayer + " is locked.");
sendError(sender, "Profile for %s is locked. Please try again later.", targetPlayer);
break;
default:
sendError(sender, "Error removing slot %d from %s.", slotId, targetPlayer);
break;
}
}
@@ -143,7 +110,7 @@ public class CommandProfileAdmin extends CommandProfileBase {
usage = "<targetPlayer> <newSlotId>"
)
public void change(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 2) {
if(args.length < 2) {
sendError(sender, "Usage: /profile admin change <targetPlayer> <newSlotId>");
return;
}
@@ -151,29 +118,29 @@ public class CommandProfileAdmin extends CommandProfileBase {
int newSlotId;
try {
newSlotId = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
} 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) {
switch(result) {
case SUCCESS:
sendResult(sender, "Successfully changed %s's active slot to %d.", targetPlayer, newSlotId);
break;
case NEW_SLOT_CREATED:
sendResult(sender, "Switched %s to NEW slot %d (new profile created).", targetPlayer, newSlotId);
break;
case ALREADY_ACTIVE:
sendError(sender, "Slot %d is already active for %s.", newSlotId, targetPlayer);
break;
case LOCKED:
sendError(sender, "Profile for " + targetPlayer + " is locked.");
sendError(sender, "Profile for %s is locked. Please try again later.", targetPlayer);
break;
case VERIFICATION_FAILED:
sendError(sender, "Profile verification failed for %s.", targetPlayer);
break;
default:
sendError(sender, "Error changing active slot for " + targetPlayer);
sendError(sender, "Error changing active slot for %s.", targetPlayer);
}
}
@@ -182,21 +149,21 @@ public class CommandProfileAdmin extends CommandProfileBase {
usage = "<targetPlayer>"
)
public void list(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 1) {
if(args.length < 1) {
sendError(sender, "Usage: /profile admin list <targetPlayer>");
return;
}
String targetPlayer = args[0];
Profile targetProfile = ProfileController.getProfile(targetPlayer);
if (targetProfile == null) {
Profile profile = ProfileController.getProfile(targetPlayer);
if(profile == null) {
sendError(sender, "Profile not found for player: " + targetPlayer);
return;
}
sendMessage(sender, "Profile Slots for " + targetPlayer + ":");
for (Map.Entry<Integer, Slot> entry : targetProfile.slots.entrySet()) {
sendMessage(sender, "Profile Slots for %s:", targetPlayer);
for(Map.Entry<Integer, Slot> entry : profile.slots.entrySet()) {
int id = entry.getKey();
String name = entry.getValue().getName();
String prefix = (id == targetProfile.currentID) ? "* " : "- ";
String prefix = (id == profile.currentID) ? "* " : "- ";
sendMessage(sender, prefix + "Slot " + id + ": " + name);
}
}
@@ -206,7 +173,7 @@ public class CommandProfileAdmin extends CommandProfileBase {
usage = "<targetPlayer> <slotId> <newName>"
)
public void rename(ICommandSender sender, String[] args) throws CommandException {
if (args.length < 3) {
if(args.length < 3) {
sendError(sender, "Usage: /profile admin rename <targetPlayer> <slotId> <newName>");
return;
}
@@ -214,50 +181,69 @@ public class CommandProfileAdmin extends CommandProfileBase {
int slotId;
try {
slotId = Integer.parseInt(args[1]);
} catch (NumberFormatException ex) {
} catch(NumberFormatException ex) {
sendError(sender, "Slot ID must be a number.");
return;
}
// Combine remaining arguments for the new name.
StringBuilder sb = new StringBuilder();
for (int i = 2; i < args.length; i++) {
if (i > 2) sb.append(" ");
for(int i = 2; i < args.length; i++){
if(i > 2) sb.append(" ");
sb.append(args[i]);
}
String newName = sb.toString().trim();
// Cap name at 20 characters.
if (newName.length() > 20) {
if(newName.length() > 20) {
newName = newName.substring(0, 20);
}
// Validate that name contains only letters and spaces.
if (!newName.matches("[A-Za-z ]+")) {
if(!newName.matches("[A-Za-z ]+")){
sendError(sender, "Invalid name. Only alphabetic characters and spaces are allowed.");
return;
}
Profile targetProfile = ProfileController.getProfile(targetPlayer);
if (targetProfile == null) {
Profile profile = ProfileController.getProfile(targetPlayer);
if(profile == null) {
sendError(sender, "Profile not found for player: " + targetPlayer);
return;
}
if (targetProfile.locked) {
sendError(sender, "Profile for " + targetPlayer + " is locked. Please try again later.");
if(profile.locked) {
sendError(sender, "Profile for %s is locked. Please try again later.", targetPlayer);
return;
}
if (!targetProfile.slots.containsKey(slotId)) {
sendError(sender, "Slot " + slotId + " not found in " + targetPlayer + "'s profile.");
if(!profile.slots.containsKey(slotId)) {
sendError(sender, "Slot %d not found in %s's profile.", slotId, targetPlayer);
return;
}
// Rename the slot.
Slot targetSlot = targetProfile.slots.get(slotId);
targetSlot.setName(newName);
// Save the updated profile.
if (targetProfile.player != null) {
ProfileController.save(targetProfile.player, targetProfile);
profile.slots.get(slotId).setName(newName);
if(profile.player != null) {
ProfileController.save(profile.player, profile);
} else {
UUID uuid = ProfileController.getUUIDFromUsername(targetPlayer);
if (uuid != null)
ProfileController.saveOffline(targetProfile, uuid);
if(uuid != null)
ProfileController.saveOffline(profile, uuid);
}
sendResult(sender, "Successfully renamed slot %d for %s to '%s'.", slotId, targetPlayer, newName);
}
@SubCommand(
desc = "Rollback a player's profile to one of their backups.",
usage = "<targetPlayer> <backupFileName>"
)
public void rollback(ICommandSender sender, String[] args) throws CommandException {
if(args.length < 2) {
sendError(sender, "Usage: /profile admin rollback <targetPlayer> <backupFileName>");
return;
}
String targetPlayer = args[0];
String backupFileName = args[1];
File backupDir = new File(ProfileController.getBackupDir(), ProfileController.getProfile(targetPlayer).player.getUniqueID().toString());
File backupFile = new File(backupDir, backupFileName);
if(!backupFile.exists()){
sendError(sender, "Backup file %s not found for player %s.", backupFileName, targetPlayer);
return;
}
boolean success = ProfileController.rollbackProfile(targetPlayer, backupFile);
if(success) {
sendResult(sender, "Successfully rolled back %s's profile to backup %s.", targetPlayer, backupFileName);
} else {
sendError(sender, "Failed to rollback %s's profile.", targetPlayer);
}
sendResult(sender, "Successfully renamed slot %s for %s to '%s'.", slotId, targetPlayer, newName);
}
}
@@ -5,7 +5,6 @@ import kamkeel.npcs.controllers.data.ProfileOperation;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendResult;
@@ -33,35 +32,41 @@ public class CommandProfileChange extends CommandProfileBase {
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (!(sender instanceof EntityPlayer)) {
if(!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by a player.");
return;
}
if (args.length < 1) {
if(args.length < 1) {
sendError(sender, "Usage: " + getUsage());
return;
}
int slotId;
try {
slotId = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) {
} catch(NumberFormatException ex) {
sendError(sender, "Slot ID must be a number: " + args[0]);
return;
}
EntityPlayer player = (EntityPlayer) sender;
EntityPlayer player = (EntityPlayer)sender;
ProfileOperation result = ProfileController.changeSlot(player, slotId);
switch(result) {
case SUCCESS:
sendResult(sender, "Successfully changed profile slot to %s.", slotId);
sendResult(sender, "Successfully changed profile slot to %d.", slotId);
break;
case NEW_SLOT_CREATED:
sendResult(sender, "Switched to new slot %d (new profile created).", slotId);
break;
case ALREADY_ACTIVE:
sendError(sender, "Slot %d is already active.", 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.");
case VERIFICATION_FAILED:
sendError(sender, "Profile verification failed. Change not permitted.");
break;
default:
sendError(sender, "Operation could not be completed.");
sendError(sender, "Error occurred while changing your profile slot.");
}
}
}
@@ -6,9 +6,7 @@ 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;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendMessage;
@@ -31,18 +29,21 @@ public class CommandProfileList extends CommandProfileBase {
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (!(sender instanceof EntityPlayer)) {
if(!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by a player.");
return;
}
EntityPlayer player = (EntityPlayer) sender;
EntityPlayer player = (EntityPlayer)sender;
Profile profile = ProfileController.getProfile(player);
if (profile == null) {
if(profile == null) {
sendError(sender, "Profile not found.");
return;
}
if(profile.slots.isEmpty()){
sendMessage(sender, "No slots found. Using default slot 0.");
}
sendMessage(sender, "Your Profile Slots:");
for (Map.Entry<Integer, Slot> entry : profile.slots.entrySet()) {
for(Map.Entry<Integer, Slot> entry : profile.slots.entrySet()) {
int id = entry.getKey();
String name = entry.getValue().getName();
String prefix = (id == profile.currentID) ? "* " : "- ";
@@ -5,7 +5,6 @@ import kamkeel.npcs.controllers.data.ProfileOperation;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendResult;
@@ -33,22 +32,22 @@ public class CommandProfileRemove extends CommandProfileBase {
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (!(sender instanceof EntityPlayer)) {
if(!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by a player.");
return;
}
if (args.length < 1) {
if(args.length < 1) {
sendError(sender, "Usage: " + getUsage());
return;
}
int slotId;
try {
slotId = Integer.parseInt(args[0]);
} catch (NumberFormatException ex) {
} catch(NumberFormatException ex) {
sendError(sender, "Slot ID must be a number: " + args[0]);
return;
}
EntityPlayer player = (EntityPlayer) sender;
EntityPlayer player = (EntityPlayer)sender;
ProfileOperation result = ProfileController.removeSlot(player, slotId);
switch(result) {
case SUCCESS:
@@ -1,12 +1,10 @@
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.UUID;
import static kamkeel.npcs.util.ColorUtil.sendError;
import static kamkeel.npcs.util.ColorUtil.sendResult;
@@ -29,52 +27,45 @@ public class CommandProfileRename extends CommandProfileBase {
@Override
public void processCommand(ICommandSender sender, String[] args) throws CommandException {
if (!(sender instanceof EntityPlayer)) {
if(!(sender instanceof EntityPlayer)) {
sendError(sender, "This command can only be used by a player.");
return;
}
if (args.length < 2) {
if(args.length < 2) {
sendError(sender, "Usage: " + getUsage());
return;
}
int slotId;
try {
slotId = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
} catch(NumberFormatException ex) {
sendError(sender, "Slot ID must be a number.");
return;
}
// Combine remaining arguments into one name string.
StringBuilder sb = new StringBuilder();
for (int i = 1; i < args.length; i++) {
if (i > 1) sb.append(" ");
for(int i = 1; i < args.length; i++){
if(i > 1) sb.append(" ");
sb.append(args[i]);
}
String newName = sb.toString().trim();
// Cap at 20 characters.
if (newName.length() > 20) {
if(newName.length() > 20) {
newName = newName.substring(0, 20);
}
// Validate that the new name contains only letters and spaces.
if (!newName.matches("[A-Za-z ]+")) {
if(!newName.matches("[A-Za-z ]+")){
sendError(sender, "Invalid name. Only alphabetic characters and spaces are allowed.");
return;
}
EntityPlayer player = (EntityPlayer) sender;
Profile profile = ProfileController.getProfile(player);
if (profile == null) {
EntityPlayer player = (EntityPlayer)sender;
if(ProfileController.getProfile(player) == null) {
sendError(sender, "Profile not found.");
return;
}
if (!profile.slots.containsKey(slotId)) {
sendError(sender, "Slot " + slotId + " not found in your profile.");
if(!ProfileController.getProfile(player).slots.containsKey(slotId)) {
sendError(sender, "Slot %d not found in your profile.", slotId);
return;
}
Slot slot = profile.slots.get(slotId);
slot.setName(newName);
// Save the profile after renaming.
ProfileController.save(player, profile);
sendResult(sender, "Successfully renamed slot %s to '%s'.", slotId, newName);
ProfileController.getProfile(player).slots.get(slotId).setName(newName);
ProfileController.save(player, ProfileController.getProfile(player));
sendResult(sender, "Successfully renamed slot %d to '%s'.", slotId, newName);
}
}
@@ -1,8 +1,8 @@
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.ProfileOperation;
import kamkeel.npcs.controllers.data.Slot;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -11,17 +11,15 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import noppes.npcs.CustomNpcs;
import noppes.npcs.LogWriter;
import noppes.npcs.config.ConfigMain;
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.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
public class ProfileController {
@@ -36,7 +34,6 @@ public class ProfileController {
}
// ---------- Registration & I/O ----------
public static boolean registerProfileType(IProfileData type){
if(profileTypes.containsKey(type.getTagName()))
return false;
@@ -56,7 +53,18 @@ public class ProfileController {
}
}
// Login for online players
// Returns the backup folder for profiles.
public static File getBackupDir() {
File base = getProfileDir();
File backup = new File(base, "backup");
if(!backup.exists()){
backup.mkdir();
}
return backup;
}
// Login for online players.
// If no slots exist, create default slot 0 and save the player's current IProfileData into it.
public static synchronized void login(EntityPlayer player){
if(player == null)
return;
@@ -67,6 +75,15 @@ public class ProfileController {
} else {
NBTTagCompound compound = load(player);
profile = new Profile(player, compound);
// If no slots exist, create default slot 0.
if(profile.slots.isEmpty()){
Slot defaultSlot = new Slot(0, "Default Slot");
defaultSlot.setCompound(new NBTTagCompound());
profile.slots.put(0, defaultSlot);
profile.currentID = 0;
saveSlotData(player);
save(player, profile);
}
activeProfiles.put(player.getUniqueID(), profile);
}
}
@@ -99,7 +116,7 @@ public class ProfileController {
return new NBTTagCompound();
}
// Offline save (synchronous) when no EntityPlayer is available
// 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";
@@ -112,12 +129,14 @@ public class ProfileController {
fileOld.delete();
}
fileNew.renameTo(fileOld);
// Also backup if allowed.
backupProfile(uuid, compound);
} catch (Exception e) {
LogWriter.except(e);
}
}
// Save for online players (asynchronous)
// Save for online players (asynchronous).
public static synchronized void save(EntityPlayer player, Profile profile) {
profile.locked = true;
CustomNPCsThreader.customNPCThread.execute(() -> {
@@ -132,6 +151,10 @@ public class ProfileController {
fileOld.delete();
}
fileNew.renameTo(fileOld);
// Backup if enabled.
if(ConfigMain.AllowProfileBackups) {
backupProfile(player.getUniqueID(), compound);
}
} catch (Exception e) {
LogWriter.except(e);
} finally {
@@ -140,7 +163,67 @@ public class ProfileController {
});
}
// Logout removes an active profile
// Backup the profile by saving a copy to profiles/backup/<uuid>/<date>.dat.
private static void backupProfile(UUID uuid, NBTTagCompound compound) {
try {
File backupDir = new File(getBackupDir(), uuid.toString());
if(!backupDir.exists()){
backupDir.mkdirs();
}
String dateStr = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File backupFile = new File(backupDir, dateStr + ".dat");
CompressedStreamTools.writeCompressed(compound, new FileOutputStream(backupFile));
// Now enforce backup amount limit.
File[] backups = backupDir.listFiles((dir, name) -> name.endsWith(".dat"));
if(backups != null && backups.length > ConfigMain.ProfileBackupAmount) {
// Sort backups by last modified ascending (oldest first)
Arrays.sort(backups, Comparator.comparingLong(File::lastModified));
for(int i = 0; i < backups.length - ConfigMain.ProfileBackupAmount; i++){
backups[i].delete();
}
}
} catch(Exception e) {
LogWriter.except(e);
}
}
// Rollback a player's profile from a backup file.
// Returns true if successful.
public static boolean rollbackProfile(String username, File backupFile) {
UUID uuid = getUUIDFromUsername(username);
if(uuid == null) return false;
try (FileInputStream fis = new FileInputStream(backupFile)) {
NBTTagCompound compound = CompressedStreamTools.readCompressed(fis);
// Overwrite the main profile file.
File saveDir = getProfileDir();
File mainFile = new File(saveDir, uuid.toString() + ".dat");
File fileNew = new File(saveDir, uuid.toString() + "_new");
CompressedStreamTools.writeCompressed(compound, new FileOutputStream(fileNew));
if(mainFile.exists()){
mainFile.delete();
}
fileNew.renameTo(mainFile);
// Update the in-memory profile.
EntityPlayerMP player = getPlayer(username);
Profile newProfile;
if(player != null) {
newProfile = new Profile(player, compound);
activeProfiles.put(uuid, newProfile);
// Load the rolled-back data into the player's IProfileData.
loadSlotData(player);
} else {
newProfile = new Profile(null, compound);
activeProfiles.put(uuid, newProfile);
}
return true;
} catch(Exception e) {
LogWriter.except(e);
return false;
}
}
// Logout removes an active profile.
public static synchronized void logout(EntityPlayer player) {
if(player != null) {
activeProfiles.remove(player.getUniqueID());
@@ -148,8 +231,6 @@ public class ProfileController {
}
// ---------- Profile Retrieval Helpers ----------
// Get profile via EntityPlayer (online)
public static Profile getProfile(EntityPlayer player) {
if(player == null) return null;
if(!activeProfiles.containsKey(player.getUniqueID()))
@@ -157,7 +238,6 @@ public class ProfileController {
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);
@@ -165,7 +245,6 @@ public class ProfileController {
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) {
@@ -178,7 +257,6 @@ public class ProfileController {
}
// ---------- Mojang API UUID Lookup ----------
public static UUID getUUIDFromUsername(String username) {
try {
URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + username);
@@ -214,7 +292,6 @@ public class ProfileController {
}
// ---------- Internal Slot Operation Helpers ----------
// Returns the next available temporary (negative) slot id (starting at -1)
private static int getNextAvailableTempSlot(Profile profile) {
int id = -1;
@@ -225,15 +302,12 @@ public class ProfileController {
}
// 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;
}
// Run verification checks on all IProfileData in priority order.
if(profile.player != null) {
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
@@ -245,7 +319,6 @@ public class ProfileController {
}
}
}
if(!profile.slots.containsKey(sourceSlotId)) {
LogWriter.error("Source slot " + sourceSlotId + " does not exist.");
return ProfileOperation.ERROR;
@@ -288,17 +361,22 @@ public class ProfileController {
}
// Change the active slot in a Profile.
// If the new slot doesn't exist, it is created and NEW_SLOT_CREATED is returned.
// 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;
}
// Run verification checks from all registered IProfileData in order of priority.
// Disallow switching to the same active slot.
if(profile.currentID == newSlotId) {
LogWriter.error("Slot " + newSlotId + " is already active.");
return ProfileOperation.ALREADY_ACTIVE;
}
// Run verification checks from all IProfileData in order of priority.
if(profile.player != null) {
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));;
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
for(IProfileData pd : dataList) {
if(!pd.verifySwitch(profile.player)) {
LogWriter.error("Verification check failed for profile type: " + pd.getTagName());
@@ -306,34 +384,31 @@ public class ProfileController {
}
}
}
if(profile.player != null) {
saveSlotData(profile.player);
}
boolean createdNewSlot = false;
// 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);
createdNewSlot = true;
}
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;
return createdNewSlot ? ProfileOperation.NEW_SLOT_CREATED : 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);
@@ -417,7 +492,7 @@ public class ProfileController {
Profile profile = getProfile(player);
if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND;
ProfileOperation result = changeSlotInternal(profile, newSlotId);
if(result == ProfileOperation.SUCCESS && player != null)
if((result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) && player != null)
save(player, profile);
return result;
}
@@ -426,7 +501,7 @@ public class ProfileController {
Profile profile = getProfile(uuid);
if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND;
ProfileOperation result = changeSlotInternal(profile, newSlotId);
if(result == ProfileOperation.SUCCESS) {
if(result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) {
if(profile.player != null)
save(profile.player, profile);
else
@@ -439,7 +514,7 @@ public class ProfileController {
Profile profile = getProfile(username);
if(profile == null) return ProfileOperation.PLAYER_NOT_FOUND;
ProfileOperation result = changeSlotInternal(profile, newSlotId);
if(result == ProfileOperation.SUCCESS) {
if(result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) {
if(profile.player != null)
save(profile.player, profile);
else {
@@ -470,7 +545,6 @@ public class ProfileController {
dataCompound = new NBTTagCompound();
slot.setCompound(dataCompound);
}
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
for(IProfileData profileData : dataList){
@@ -483,15 +557,10 @@ public class ProfileController {
public static void loadSlotData(EntityPlayer player) {
if(player == null || !activeProfiles.containsKey(player.getUniqueID()))
return;
Profile profile = activeProfiles.get(player.getUniqueID());
if(profile.slots.isEmpty() || !profile.slots.containsKey(profile.currentID))
return;
// 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.
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
for(IProfileData profileData : dataList){
@@ -500,10 +569,8 @@ public class ProfileController {
data = slotCompound.getCompoundTag(profileData.getTagName());
else
data = new NBTTagCompound();
profileData.setNBT(player, data);
}
for(IProfileData profileData : dataList){
profileData.save(player);
}
@@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import kamkeel.npcs.addon.DBCAddon;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.packets.data.LoginPacket;
import kamkeel.npcs.network.packets.data.large.SyncEffectPacket;
import kamkeel.npcs.network.packets.data.large.SyncPacket;
import kamkeel.npcs.network.enums.EnumSyncAction;
@@ -23,6 +24,10 @@ import java.util.concurrent.ConcurrentHashMap;
public class SyncController {
public static void syncPlayer(EntityPlayerMP player){
// Login Packet
PacketHandler.Instance.sendToPlayer(new LoginPacket(), player);
// 1) Factions
PacketHandler.Instance.sendToPlayer(new SyncPacket(
EnumSyncType.FACTION,
@@ -11,9 +11,9 @@ public class Profile {
public EntityPlayer player;
public int currentID;
public HashMap<Integer, Slot> slots = new HashMap<>();
public boolean locked = false; // Prevent modifications while saving
public boolean locked = false;
public Profile(EntityPlayer player, NBTTagCompound compound){
public Profile(EntityPlayer player, NBTTagCompound compound) {
this.player = player;
if(compound.hasKey("CurrentID")){
this.currentID = compound.getInteger("CurrentID");
@@ -21,9 +21,9 @@ public class Profile {
this.currentID = 0;
}
if (compound.hasKey("Slots")) {
if(compound.hasKey("Slots")) {
NBTTagCompound slotsCompound = compound.getCompoundTag("Slots");
for (String key : (Set<String>) slotsCompound.func_150296_c()) {
for(String key : (Set<String>) slotsCompound.func_150296_c()){
int slotID = Integer.parseInt(key);
NBTTagCompound slotNBT = slotsCompound.getCompoundTag(key);
Slot slot = Slot.fromNBT(slotID, slotNBT);
@@ -34,7 +34,6 @@ public class Profile {
public NBTTagCompound writeToNBT() {
NBTTagCompound fullCompound = new NBTTagCompound();
if(this.player != null)
fullCompound.setString("Name", player.getCommandSenderName());
fullCompound.setInteger("CurrentID", currentID);
@@ -44,7 +43,7 @@ public class Profile {
for (HashMap.Entry<Integer, Slot> entry : slots.entrySet()) {
Slot slot = entry.getValue();
if(slot.isTemporary())
continue; // Do not save temporary slots
continue;
int id = entry.getKey();
slot.setId(id);
NBTTagCompound slotTag = slot.toNBT();
@@ -5,6 +5,8 @@ public enum ProfileOperation {
ERROR,
LOCKED,
PLAYER_NOT_FOUND,
NEW_SLOT_CREATED,
ALREADY_ACTIVE,
VERIFICATION_FAILED
}
@@ -338,6 +338,7 @@ public class PacketHandler {
public void registerDataPackets(){
// Data Packets
DATA_PACKET.registerPacket(new LoginPacket());
DATA_PACKET.registerPacket(new AchievementPacket());
DATA_PACKET.registerPacket(new ChatAlertPacket());
DATA_PACKET.registerPacket(new ChatBubblePacket());
@@ -1,21 +1,21 @@
package kamkeel.npcs.network.packets.data;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import kamkeel.npcs.network.AbstractPacket;
import kamkeel.npcs.network.PacketChannel;
import kamkeel.npcs.network.PacketHandler;
import kamkeel.npcs.network.enums.EnumDataPacket;
import kamkeel.npcs.util.ByteBufUtils;
import net.minecraft.entity.player.EntityPlayer;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.config.ConfigMain;
import java.io.IOException;
public final class LoginPacket extends AbstractPacket {
public static final String packetName = "CNPC+|Login";
public LoginPacket() {
}
public LoginPacket() {}
@Override
public Enum getType() {
@@ -29,13 +29,14 @@ public final class LoginPacket extends AbstractPacket {
@Override
public void sendData(ByteBuf out) throws IOException {
// ByteBufUtils.writeUTF8String(out, this.data.player.getCommandSenderName());
// ByteBufUtils.writeNBT(out,this.data.saveFromNBT(new NBTTagCompound()));
out.writeBoolean(ConfigMain.PartiesEnabled);
out.writeBoolean(ConfigMain.ProfilesEnabled);
}
@SideOnly(Side.CLIENT)
@Override
public void receiveData(ByteBuf in, EntityPlayer player) throws IOException {
String playerName = ByteBufUtils.readUTF8String(in);
ClientCacheHandler.allowParties = in.readBoolean();
ClientCacheHandler.allowProfiles = in.readBoolean();
}
}
+2 -1
View File
@@ -267,7 +267,8 @@ public class CustomNpcs {
public void serverstart(FMLServerStartingEvent event) {
event.registerServerCommand(new CommandNoppes());
event.registerServerCommand(new CommandKamkeel());
event.registerServerCommand(new CommandProfile());
if(ConfigMain.ProfilesEnabled)
event.registerServerCommand(new CommandProfile());
}
private void registerNpc(Class<? extends Entity> cl, String name) {
@@ -23,6 +23,10 @@ public class ClientCacheHandler {
public static Party party;
public static boolean allowProfiles = true;
public static boolean allowParties = true;
public static ImageData getImageData(String directory) {
synchronized (imageDataCache) {
if (!imageDataCache.containsKey(directory)) {
@@ -1,10 +1,9 @@
package noppes.npcs.client.gui.player.inventory;
import cpw.mods.fml.common.Loader;
import kamkeel.npcs.addon.client.DBCClient;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.ResourceLocation;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.gui.util.GuiMenuSideButton;
import noppes.npcs.client.gui.util.GuiNPCInterface;
import noppes.npcs.config.ConfigClient;
@@ -41,13 +40,14 @@ public class GuiCNPCInventory extends GuiNPCInterface {
questsButton.renderResource = specialIcons;
addButton(questsButton);
y += 21;
GuiMenuSideButton partyButton = new GuiMenuSideButton(-101, guiLeft + xSize + 37, this.guiTop + y, 22, 22, "");
partyButton.rightSided = true;
partyButton.active = activeTab == -101;
partyButton.renderResource = specialIcons;
addButton(partyButton);
if(ClientCacheHandler.allowParties){
y += 21;
GuiMenuSideButton partyButton = new GuiMenuSideButton(-101, guiLeft + xSize + 37, this.guiTop + y, 22, 22, "");
partyButton.rightSided = true;
partyButton.active = activeTab == -101;
partyButton.renderResource = specialIcons;
addButton(partyButton);
}
if(ConfigClient.enableFactionTab){
y += 21;
GuiMenuSideButton factionButton = new GuiMenuSideButton(-102, guiLeft + xSize + 37, this.guiTop + y, 22, 22, "");
@@ -57,7 +57,7 @@ public class GuiCNPCInventory extends GuiNPCInterface {
factionButton.renderResource = specialIcons;
addButton(factionButton);
}
if(ConfigMain.EnableProfiles){
if(ClientCacheHandler.allowProfiles){
y += 21;
GuiMenuSideButton profileButton = new GuiMenuSideButton(-104, guiLeft + xSize + 37, this.guiTop + y, 22, 22, "");
profileButton.rightSided = true;
@@ -80,7 +80,13 @@ public class ConfigMain
public static Property DefaultMaxPartySizeProperty;
public static int DefaultMaxPartySize = 4;
public static boolean EnableProfiles = true;
/**
* Profile Properties
**/
public static boolean ProfilesEnabled = true;
public static boolean AllowProfileBackups = true;
public static int ProfileBackupAmount = 5;
/**
* General NPC Properties
@@ -220,7 +226,9 @@ public class ConfigMain
TrackedQuestUpdateFrequency = TrackedQuestUpdateFrequencyProperty.getInt(5);
// PROFILES
EnableProfiles = config.get(PROFILES, "Enable Profiles", true, "Allow the use of character Profiles").getBoolean(true);
ProfilesEnabled = config.get(PROFILES, "Enable Profiles", true, "Allow the use of character Profiles").getBoolean(true);
AllowProfileBackups = config.get(PROFILES, "Enable Profile Backups", true, "Will create backups of profile changes").getBoolean(true);
ProfileBackupAmount = config.get(PROFILES, "Number of Backups", 5, "How many backups per player to save").getInt(5);
// Convert to Legacy
if(CustomNpcs.legacyExist){