2025-01-29 13:20:04 -05:00
|
|
|
package kamkeel.npcs.controllers;
|
2025-01-23 16:10:01 -05:00
|
|
|
|
2025-02-19 05:43:28 -05:00
|
|
|
import kamkeel.npcs.controllers.data.*;
|
2025-01-23 16:10:01 -05:00
|
|
|
import net.minecraft.entity.player.EntityPlayer;
|
2025-02-18 06:25:09 -05:00
|
|
|
import net.minecraft.entity.player.EntityPlayerMP;
|
2025-01-23 16:10:01 -05:00
|
|
|
import net.minecraft.nbt.CompressedStreamTools;
|
|
|
|
|
import net.minecraft.nbt.NBTTagCompound;
|
2025-02-18 06:25:09 -05:00
|
|
|
import net.minecraft.server.MinecraftServer;
|
2025-01-23 16:10:01 -05:00
|
|
|
import noppes.npcs.CustomNpcs;
|
|
|
|
|
import noppes.npcs.LogWriter;
|
2025-02-18 22:22:40 -05:00
|
|
|
import noppes.npcs.config.ConfigMain;
|
2025-02-18 06:25:09 -05:00
|
|
|
import noppes.npcs.controllers.data.PlayerData;
|
2025-01-23 16:10:01 -05:00
|
|
|
import noppes.npcs.util.CustomNPCsThreader;
|
|
|
|
|
import noppes.npcs.util.NBTJsonUtil;
|
|
|
|
|
|
2025-02-18 22:22:40 -05:00
|
|
|
import java.io.*;
|
2025-02-18 06:25:09 -05:00
|
|
|
import java.net.HttpURLConnection;
|
|
|
|
|
import java.net.URL;
|
2025-02-18 22:22:40 -05:00
|
|
|
import java.text.SimpleDateFormat;
|
2025-02-18 20:05:55 -05:00
|
|
|
import java.util.*;
|
2025-01-23 16:10:01 -05:00
|
|
|
|
|
|
|
|
public class ProfileController {
|
|
|
|
|
|
|
|
|
|
public static HashMap<String, IProfileData> profileTypes;
|
|
|
|
|
public static HashMap<UUID, Profile> activeProfiles;
|
|
|
|
|
public static String profile_directory = "profiles";
|
|
|
|
|
|
|
|
|
|
public ProfileController(){
|
|
|
|
|
profileTypes = new HashMap<>();
|
|
|
|
|
activeProfiles = new HashMap<>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
// ---------- Registration & I/O ----------
|
2025-01-23 16:10:01 -05:00
|
|
|
public static boolean registerProfileType(IProfileData type){
|
|
|
|
|
if(profileTypes.containsKey(type.getTagName()))
|
|
|
|
|
return false;
|
|
|
|
|
profileTypes.put(type.getTagName(), type);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static File getProfileDir(){
|
2025-02-18 06:25:09 -05:00
|
|
|
try {
|
2025-01-23 16:10:01 -05:00
|
|
|
File file = new File(CustomNpcs.getWorldSaveDirectory(), profile_directory);
|
|
|
|
|
if(!file.exists())
|
|
|
|
|
file.mkdir();
|
|
|
|
|
return file;
|
2025-02-18 06:25:09 -05:00
|
|
|
} catch (Exception e) {
|
2025-01-23 16:10:01 -05:00
|
|
|
e.printStackTrace();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 22:22:40 -05:00
|
|
|
// 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.
|
2025-01-23 16:10:01 -05:00
|
|
|
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 {
|
2025-02-18 06:25:09 -05:00
|
|
|
NBTTagCompound compound = load(player);
|
|
|
|
|
profile = new Profile(player, compound);
|
2025-02-18 22:22:40 -05:00
|
|
|
// 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);
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
activeProfiles.put(player.getUniqueID(), profile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static synchronized NBTTagCompound load(EntityPlayer player) {
|
|
|
|
|
File saveDir = getProfileDir();
|
2025-02-18 06:25:09 -05:00
|
|
|
String filename = player.getUniqueID().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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static synchronized NBTTagCompound load(UUID uuid) {
|
|
|
|
|
File saveDir = getProfileDir();
|
|
|
|
|
String filename = uuid.toString() + ".dat";
|
2025-01-23 16:10:01 -05:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 22:22:40 -05:00
|
|
|
// Offline save (synchronous) when no EntityPlayer is available.
|
2025-02-18 06:25:09 -05:00
|
|
|
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);
|
2025-02-18 22:22:40 -05:00
|
|
|
// Also backup if allowed.
|
|
|
|
|
backupProfile(uuid, compound);
|
2025-02-18 06:25:09 -05:00
|
|
|
} catch (Exception e) {
|
|
|
|
|
LogWriter.except(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 22:22:40 -05:00
|
|
|
// Save for online players (asynchronous).
|
2025-01-23 16:10:01 -05:00
|
|
|
public static synchronized void save(EntityPlayer player, Profile profile) {
|
2025-02-18 06:25:09 -05:00
|
|
|
profile.locked = true;
|
2025-01-23 16:10:01 -05:00
|
|
|
CustomNPCsThreader.customNPCThread.execute(() -> {
|
|
|
|
|
final NBTTagCompound compound = profile.writeToNBT();
|
|
|
|
|
final String filename = player.getUniqueID() + ".dat";
|
|
|
|
|
try {
|
|
|
|
|
File saveDir = getProfileDir();
|
2025-02-18 06:25:09 -05:00
|
|
|
File fileNew = new File(saveDir, filename + "_new");
|
|
|
|
|
File fileOld = new File(saveDir, filename);
|
|
|
|
|
CompressedStreamTools.writeCompressed(compound, new FileOutputStream(fileNew));
|
|
|
|
|
if(fileOld.exists()){
|
|
|
|
|
fileOld.delete();
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
fileNew.renameTo(fileOld);
|
2025-02-18 22:22:40 -05:00
|
|
|
// Backup if enabled.
|
|
|
|
|
if(ConfigMain.AllowProfileBackups) {
|
|
|
|
|
backupProfile(player.getUniqueID(), compound);
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
} catch (Exception e) {
|
|
|
|
|
LogWriter.except(e);
|
2025-02-18 06:25:09 -05:00
|
|
|
} finally {
|
|
|
|
|
profile.locked = false;
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 22:22:40 -05:00
|
|
|
// 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.
|
2025-02-18 06:25:09 -05:00
|
|
|
public static synchronized void logout(EntityPlayer player) {
|
|
|
|
|
if(player != null) {
|
|
|
|
|
activeProfiles.remove(player.getUniqueID());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------- Profile Retrieval Helpers ----------
|
|
|
|
|
public static Profile getProfile(EntityPlayer player) {
|
|
|
|
|
if(player == null) return null;
|
2025-01-23 16:10:01 -05:00
|
|
|
if(!activeProfiles.containsKey(player.getUniqueID()))
|
2025-02-18 06:25:09 -05:00
|
|
|
login(player);
|
|
|
|
|
return activeProfiles.get(player.getUniqueID());
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
public static Profile getProfile(UUID uuid) {
|
|
|
|
|
if(activeProfiles.containsKey(uuid))
|
|
|
|
|
return activeProfiles.get(uuid);
|
|
|
|
|
NBTTagCompound compound = load(uuid);
|
|
|
|
|
return new Profile(null, compound);
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
// ---------- 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;
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
// ---------- 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.
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
2025-02-18 20:05:55 -05:00
|
|
|
// Run verification checks on all IProfileData in priority order.
|
|
|
|
|
if(profile.player != null) {
|
|
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
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());
|
|
|
|
|
return ProfileOperation.VERIFICATION_FAILED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
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.
|
2025-02-18 22:22:40 -05:00
|
|
|
// If the new slot doesn't exist, it is created and NEW_SLOT_CREATED is returned.
|
2025-02-18 06:25:09 -05:00
|
|
|
// 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;
|
|
|
|
|
}
|
2025-02-18 22:22:40 -05:00
|
|
|
// 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.
|
2025-02-18 20:08:26 -05:00
|
|
|
if(profile.player != null) {
|
|
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
2025-02-18 22:22:40 -05:00
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
2025-02-18 20:08:26 -05:00
|
|
|
for(IProfileData pd : dataList) {
|
|
|
|
|
if(!pd.verifySwitch(profile.player)) {
|
|
|
|
|
LogWriter.error("Verification check failed for profile type: " + pd.getTagName());
|
|
|
|
|
return ProfileOperation.VERIFICATION_FAILED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(profile.player != null) {
|
2025-02-18 06:25:09 -05:00
|
|
|
saveSlotData(profile.player);
|
2025-02-18 20:08:26 -05:00
|
|
|
}
|
2025-02-18 22:22:40 -05:00
|
|
|
boolean createdNewSlot = false;
|
2025-02-18 06:25:09 -05:00
|
|
|
// 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);
|
2025-02-18 22:22:40 -05:00
|
|
|
createdNewSlot = true;
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
profile.currentID = newSlotId;
|
2025-02-18 20:08:26 -05:00
|
|
|
if(profile.player != null) {
|
2025-02-18 06:25:09 -05:00
|
|
|
loadSlotData(profile.player);
|
2025-02-18 20:08:26 -05:00
|
|
|
}
|
|
|
|
|
// Update PlayerData so that PlayerData.get(player).profileSlot matches Profile.currentID.
|
2025-02-18 06:25:09 -05:00
|
|
|
if(profile.player != null) {
|
|
|
|
|
PlayerData pdata = PlayerData.get(profile.player);
|
|
|
|
|
pdata.profileSlot = newSlotId;
|
|
|
|
|
pdata.save();
|
|
|
|
|
}
|
2025-02-18 22:22:40 -05:00
|
|
|
return createdNewSlot ? ProfileOperation.NEW_SLOT_CREATED : ProfileOperation.SUCCESS;
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------- 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);
|
2025-02-18 22:22:40 -05:00
|
|
|
if((result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) && player != null)
|
2025-02-18 06:25:09 -05:00
|
|
|
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);
|
2025-02-18 22:22:40 -05:00
|
|
|
if(result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) {
|
2025-02-18 06:25:09 -05:00
|
|
|
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);
|
2025-02-18 22:22:40 -05:00
|
|
|
if(result == ProfileOperation.SUCCESS || result == ProfileOperation.NEW_SLOT_CREATED) {
|
2025-02-18 06:25:09 -05:00
|
|
|
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) ----------
|
2025-01-23 17:45:13 -05:00
|
|
|
public static void saveSlotData(EntityPlayer player){
|
2025-02-18 06:25:09 -05:00
|
|
|
if(player == null || !activeProfiles.containsKey(player.getUniqueID()))
|
2025-01-23 16:10:01 -05:00
|
|
|
return;
|
2025-02-18 06:25:09 -05:00
|
|
|
Profile profile = activeProfiles.get(player.getUniqueID());
|
|
|
|
|
if(profile.locked) {
|
|
|
|
|
LogWriter.error("Profile is locked; cannot save slot data.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
NBTTagCompound dataCompound;
|
|
|
|
|
Slot slot;
|
|
|
|
|
if(!profile.slots.isEmpty() && profile.slots.containsKey(profile.currentID)){
|
|
|
|
|
slot = profile.slots.get(profile.currentID);
|
|
|
|
|
dataCompound = (NBTTagCompound) slot.getCompound().copy();
|
|
|
|
|
} else {
|
2025-01-23 17:45:13 -05:00
|
|
|
slot = new Slot(profile.currentID, "Slot " + profile.currentID);
|
2025-01-23 16:10:01 -05:00
|
|
|
dataCompound = new NBTTagCompound();
|
2025-01-23 17:45:13 -05:00
|
|
|
slot.setCompound(dataCompound);
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
2025-02-18 20:05:55 -05:00
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
|
|
|
|
for(IProfileData profileData : dataList){
|
2025-01-23 16:10:01 -05:00
|
|
|
NBTTagCompound cloned = (NBTTagCompound) profileData.getCurrentNBT(player).copy();
|
|
|
|
|
dataCompound.setTag(profileData.getTagName(), cloned);
|
|
|
|
|
}
|
|
|
|
|
slot.setCompound(dataCompound);
|
2025-02-19 05:43:28 -05:00
|
|
|
slot.setLastLoaded(System.currentTimeMillis());
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
public static void loadSlotData(EntityPlayer player) {
|
|
|
|
|
if(player == null || !activeProfiles.containsKey(player.getUniqueID()))
|
2025-01-23 16:10:01 -05:00
|
|
|
return;
|
|
|
|
|
Profile profile = activeProfiles.get(player.getUniqueID());
|
2025-02-18 06:25:09 -05:00
|
|
|
if(profile.slots.isEmpty() || !profile.slots.containsKey(profile.currentID))
|
|
|
|
|
return;
|
|
|
|
|
NBTTagCompound slotCompound = (NBTTagCompound) profile.slots.get(profile.currentID).getCompound().copy();
|
2025-02-18 20:05:55 -05:00
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
|
|
|
|
for(IProfileData profileData : dataList){
|
2025-02-18 06:25:09 -05:00
|
|
|
NBTTagCompound data;
|
|
|
|
|
if(slotCompound.hasKey(profileData.getTagName()))
|
|
|
|
|
data = slotCompound.getCompoundTag(profileData.getTagName());
|
|
|
|
|
else
|
|
|
|
|
data = new NBTTagCompound();
|
|
|
|
|
profileData.setNBT(player, data);
|
2025-02-18 20:05:55 -05:00
|
|
|
}
|
|
|
|
|
for(IProfileData profileData : dataList){
|
2025-01-23 16:10:01 -05:00
|
|
|
profileData.save(player);
|
|
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
2025-01-23 16:10:01 -05:00
|
|
|
|
2025-02-19 05:43:28 -05:00
|
|
|
public static List<InfoEntry> getProfileInfo(EntityPlayer player, int slotId) {
|
|
|
|
|
List<InfoEntry> infoList = new ArrayList<>();
|
2025-02-19 00:21:40 -05:00
|
|
|
Profile profile = getProfile(player);
|
|
|
|
|
if (profile == null)
|
|
|
|
|
return infoList;
|
|
|
|
|
|
|
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
|
|
|
|
|
|
|
|
|
if (slotId == profile.currentID) {
|
|
|
|
|
// Use current data (not stored slot data) for active slot.
|
|
|
|
|
for (IProfileData pd : dataList) {
|
|
|
|
|
NBTTagCompound currentNBT = pd.getCurrentNBT(player);
|
2025-02-19 05:43:28 -05:00
|
|
|
List<InfoEntry> subInfo = pd.getInfo(player, currentNBT);
|
2025-02-19 00:21:40 -05:00
|
|
|
infoList.addAll(subInfo);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Use stored data from the specified slot.
|
|
|
|
|
if (!profile.slots.containsKey(slotId))
|
|
|
|
|
return infoList;
|
|
|
|
|
NBTTagCompound slotCompound = profile.slots.get(slotId).getCompound();
|
|
|
|
|
for (IProfileData pd : dataList) {
|
|
|
|
|
if (slotCompound.hasKey(pd.getTagName())) {
|
|
|
|
|
NBTTagCompound sub = slotCompound.getCompoundTag(pd.getTagName());
|
2025-02-19 05:43:28 -05:00
|
|
|
List<InfoEntry> subInfo = pd.getInfo(player, sub);
|
2025-02-19 00:21:40 -05:00
|
|
|
infoList.addAll(subInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return infoList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
public static EntityPlayerMP getPlayer(String username){
|
|
|
|
|
return MinecraftServer.getServer().getConfigurationManager().func_152612_a(username);
|
2025-01-23 16:10:01 -05:00
|
|
|
}
|
|
|
|
|
}
|