Files
CustomNPC-Plus/src/main/java/kamkeel/npcs/controllers/ProfileController.java
T

515 lines
20 KiB
Java
Raw Normal View History

2025-01-29 13:20:04 -05:00
package kamkeel.npcs.controllers;
2025-01-23 16:10:01 -05:00
2025-01-29 13:20:04 -05:00
import kamkeel.npcs.controllers.data.IProfileData;
2025-02-18 06:25:09 -05:00
import kamkeel.npcs.controllers.data.ProfileOperation;
2025-01-29 13:20:04 -05:00
import kamkeel.npcs.controllers.data.Profile;
import kamkeel.npcs.controllers.data.Slot;
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 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 06:25:09 -05:00
import java.io.BufferedReader;
2025-01-23 16:10:01 -05:00
import java.io.File;
import java.io.FileOutputStream;
2025-02-18 06:25:09 -05:00
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
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 06:25:09 -05:00
// Login for online players
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-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 06:25:09 -05:00
// 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)
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-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 06:25:09 -05:00
// Logout removes an active profile
public static synchronized void logout(EntityPlayer player) {
if(player != null) {
activeProfiles.remove(player.getUniqueID());
}
}
// ---------- Profile Retrieval Helpers ----------
// Get profile via EntityPlayer (online)
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
// 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);
}
2025-01-23 16:10:01 -05:00
2025-02-18 06:25:09 -05:00
// 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);
}
}
2025-01-23 16:10:01 -05:00
2025-02-18 06:25:09 -05:00
// ---------- Mojang API UUID Lookup ----------
2025-01-23 16:10:01 -05:00
2025-02-18 06:25:09 -05:00
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.
// 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;
}
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.
// 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 20:08:26 -05:00
// Run verification checks from all registered IProfileData in order of priority.
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;
}
}
}
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 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);
}
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
}
2025-02-18 06:25:09 -05:00
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();
}
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) ----------
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-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;
2025-01-23 16:10:01 -05:00
2025-02-18 06:25:09 -05:00
// Get a copy of the active slot's NBT compound.
NBTTagCompound slotCompound = (NBTTagCompound) profile.slots.get(profile.currentID).getCompound().copy();
2025-01-23 16:10:01 -05:00
2025-02-18 06:25:09 -05:00
// For each registered profile data type, load its saved compound onto the player.
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-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
}
}