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-02-19 16:27:27 -05:00
|
|
|
import kamkeel.npcs.network.packets.data.ChatAlertPacket;
|
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;
|
2025-02-19 16:27:27 -05:00
|
|
|
import noppes.npcs.CustomNpcsPermissions;
|
2025-01-23 16:10:01 -05:00
|
|
|
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
|
|
|
|
2025-02-19 18:15:05 -05:00
|
|
|
import static noppes.npcs.CustomNpcsPermissions.PROFILE_REGION_BYPASS;
|
|
|
|
|
import static noppes.npcs.CustomNpcsPermissions.hasPermission;
|
|
|
|
|
|
2025-01-23 16:10:01 -05:00
|
|
|
public class ProfileController {
|
|
|
|
|
|
2025-02-19 18:15:05 -05:00
|
|
|
// ------------------- Message Constants -------------------
|
|
|
|
|
// General messages
|
|
|
|
|
private static final String MSG_PLAYER_NOT_FOUND = "Player not found.";
|
|
|
|
|
|
|
|
|
|
// Clone Slot messages
|
|
|
|
|
private static final String MSG_PROFILE_LOCKED_CLONE = "Profile is locked; cannot clone slot.";
|
|
|
|
|
private static final String MSG_SOURCE_SLOT_NOT_EXIST = "Source slot does not exist.";
|
|
|
|
|
private static final String MSG_CANNOT_CLONE_CURRENT = "Cannot clone to the current active slot.";
|
|
|
|
|
private static final String MSG_INVALID_DEST_SLOT = "Invalid destination slot id.";
|
|
|
|
|
private static final String MSG_CLONE_SUCCESS = "Slot cloned successfully.";
|
|
|
|
|
|
|
|
|
|
// Remove Slot messages
|
|
|
|
|
private static final String MSG_PROFILE_LOCKED_REMOVE = "Profile is locked; cannot remove slot.";
|
|
|
|
|
private static final String MSG_CANNOT_REMOVE_ACTIVE = "Cannot remove the currently active slot.";
|
|
|
|
|
private static final String MSG_SLOT_NOT_EXIST = "Slot does not exist.";
|
|
|
|
|
private static final String MSG_REMOVE_SUCCESS = "Slot removed successfully.";
|
|
|
|
|
|
|
|
|
|
// Create Slot messages
|
|
|
|
|
private static final String MSG_PROFILE_LOCKED_CREATE = "Profile is locked; cannot create slot.";
|
|
|
|
|
private static final String MSG_MAX_SLOTS_REACHED = "Maximum allowed slots reached.";
|
|
|
|
|
private static final String MSG_NEW_SLOT_CREATED = "New slot created successfully.";
|
|
|
|
|
|
|
|
|
|
// Change Slot messages
|
|
|
|
|
private static final String MSG_PROFILE_LOCKED_CHANGE = "Profile is locked; cannot change slot.";
|
|
|
|
|
private static final String MSG_SLOT_ALREADY_ACTIVE = "Slot is already active.";
|
|
|
|
|
private static final String MSG_REGION_NOT_ALLOWED = "Profile switching not allowed from your current location.";
|
|
|
|
|
private static final String MSG_CHANGE_SUCCESS = "Slot changed successfully.";
|
|
|
|
|
|
|
|
|
|
// ------------------- End Constants -------------------
|
|
|
|
|
|
2025-01-23 16:10:01 -05:00
|
|
|
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) {
|
|
|
|
|
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 ----------
|
|
|
|
|
private static int getNextAvailableTempSlot(Profile profile) {
|
|
|
|
|
int id = -1;
|
|
|
|
|
while(profile.slots.containsKey(id)) {
|
|
|
|
|
id--;
|
|
|
|
|
}
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clone a slot within a Profile.
|
|
|
|
|
private static ProfileOperation cloneSlotInternal(Profile profile, int sourceSlotId, int destinationSlotId, boolean temporary) {
|
|
|
|
|
if(profile.locked) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.locked(MSG_PROFILE_LOCKED_CLONE);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
2025-02-18 20:05:55 -05:00
|
|
|
if(profile.player != null) {
|
|
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
|
|
|
|
for(IProfileData pd : dataList) {
|
2025-02-19 18:15:05 -05:00
|
|
|
if (pd.verifySwitch(profile.player).getResult() != EnumProfileOperation.SUCCESS) {
|
|
|
|
|
return pd.verifySwitch(profile.player);
|
2025-02-18 20:05:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
if(!profile.slots.containsKey(sourceSlotId)) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_SOURCE_SLOT_NOT_EXIST);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
if(destinationSlotId == profile.currentID) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_CANNOT_CLONE_CURRENT);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
if(temporary) {
|
|
|
|
|
destinationSlotId = getNextAvailableTempSlot(profile);
|
|
|
|
|
} else {
|
|
|
|
|
if(destinationSlotId <= 0) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_INVALID_DEST_SLOT);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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);
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.success(MSG_CLONE_SUCCESS);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove a slot from a Profile.
|
|
|
|
|
private static ProfileOperation removeSlotInternal(Profile profile, int slotId) {
|
|
|
|
|
if(profile.locked) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.locked(MSG_PROFILE_LOCKED_REMOVE);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
if(slotId == profile.currentID) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_CANNOT_REMOVE_ACTIVE);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
if(!profile.slots.containsKey(slotId)) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_SLOT_NOT_EXIST);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
profile.slots.remove(slotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.success(MSG_REMOVE_SUCCESS);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-19 16:27:27 -05:00
|
|
|
public static ProfileOperation createSlotInternal(Profile profile) {
|
|
|
|
|
if (profile.locked) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.locked(MSG_PROFILE_LOCKED_CREATE);
|
2025-02-19 16:27:27 -05:00
|
|
|
}
|
|
|
|
|
int newSlotId = 0;
|
|
|
|
|
while (profile.slots.containsKey(newSlotId)) {
|
|
|
|
|
newSlotId++;
|
|
|
|
|
}
|
|
|
|
|
if (!allowSlotPermission(profile.player)) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_MAX_SLOTS_REACHED);
|
2025-02-19 16:27:27 -05:00
|
|
|
}
|
|
|
|
|
if (profile.player != null) {
|
|
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
|
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
|
|
|
|
for (IProfileData pd : dataList) {
|
2025-02-19 18:15:05 -05:00
|
|
|
if (pd.verifySwitch(profile.player).getResult() != EnumProfileOperation.SUCCESS) {
|
|
|
|
|
return pd.verifySwitch(profile.player);
|
2025-02-19 16:27:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Slot newSlot = new Slot(newSlotId, "Slot " + newSlotId);
|
|
|
|
|
newSlot.setCompound(new NBTTagCompound());
|
|
|
|
|
profile.slots.put(newSlotId, newSlot);
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.success(MSG_NEW_SLOT_CREATED);
|
2025-02-19 16:27:27 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-18 06:25:09 -05:00
|
|
|
private static ProfileOperation changeSlotInternal(Profile profile, int newSlotId) {
|
2025-02-19 16:27:27 -05:00
|
|
|
if (profile.locked) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.locked(MSG_PROFILE_LOCKED_CHANGE);
|
2025-02-18 06:25:09 -05:00
|
|
|
}
|
2025-02-19 16:27:27 -05:00
|
|
|
if (profile.currentID == newSlotId) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_SLOT_ALREADY_ACTIVE);
|
2025-02-18 22:22:40 -05:00
|
|
|
}
|
2025-02-19 16:27:27 -05:00
|
|
|
if (!profile.slots.containsKey(newSlotId)) {
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.error(MSG_SLOT_NOT_EXIST);
|
|
|
|
|
}
|
|
|
|
|
// Region-based check: if enabled, ensure the player is within an allowed region.
|
|
|
|
|
if (ConfigMain.RegionProfileSwitching && profile.player != null) {
|
|
|
|
|
boolean allowed = hasPermission(profile.player, PROFILE_REGION_BYPASS);
|
|
|
|
|
if(!allowed){
|
|
|
|
|
int playerDim = profile.player.dimension;
|
|
|
|
|
int playerX = (int) profile.player.posX;
|
|
|
|
|
int playerY = (int) profile.player.posY;
|
|
|
|
|
int playerZ = (int) profile.player.posZ;
|
|
|
|
|
for (List<Integer> region : ConfigMain.RestrictedProfileRegions) {
|
|
|
|
|
if(region.size() == 7) {
|
|
|
|
|
int dim = region.get(0);
|
|
|
|
|
int x1 = Math.min(region.get(1), region.get(4));
|
|
|
|
|
int y1 = Math.min(region.get(2), region.get(5));
|
|
|
|
|
int z1 = Math.min(region.get(3), region.get(6));
|
|
|
|
|
int x2 = Math.max(region.get(1), region.get(4));
|
|
|
|
|
int y2 = Math.max(region.get(2), region.get(5));
|
|
|
|
|
int z2 = Math.max(region.get(3), region.get(6));
|
|
|
|
|
if(playerDim == dim &&
|
|
|
|
|
playerX >= x1 && playerX <= x2 &&
|
|
|
|
|
playerY >= y1 && playerY <= y2 &&
|
|
|
|
|
playerZ >= z1 && playerZ <= z2) {
|
|
|
|
|
allowed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!allowed) {
|
|
|
|
|
return ProfileOperation.error(MSG_REGION_NOT_ALLOWED);
|
|
|
|
|
}
|
2025-02-19 16:27:27 -05:00
|
|
|
}
|
|
|
|
|
if (profile.player != null) {
|
2025-02-18 20:08:26 -05:00
|
|
|
List<IProfileData> dataList = new ArrayList<>(profileTypes.values());
|
2025-02-18 22:22:40 -05:00
|
|
|
dataList.sort(Comparator.comparingInt(IProfileData::getSwitchPriority));
|
2025-02-19 16:27:27 -05:00
|
|
|
for (IProfileData pd : dataList) {
|
2025-02-19 18:15:05 -05:00
|
|
|
if (pd.verifySwitch(profile.player).getResult() != EnumProfileOperation.SUCCESS) {
|
|
|
|
|
return pd.verifySwitch(profile.player);
|
2025-02-18 20:08:26 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-19 16:27:27 -05:00
|
|
|
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
|
|
|
profile.currentID = newSlotId;
|
2025-02-19 16:27:27 -05:00
|
|
|
if (profile.player != null) {
|
2025-02-18 06:25:09 -05:00
|
|
|
loadSlotData(profile.player);
|
|
|
|
|
PlayerData pdata = PlayerData.get(profile.player);
|
|
|
|
|
pdata.profileSlot = newSlotId;
|
|
|
|
|
pdata.save();
|
|
|
|
|
}
|
2025-02-19 18:15:05 -05:00
|
|
|
return ProfileOperation.success(MSG_CHANGE_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);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS && player != null) {
|
2025-02-18 06:25:09 -05:00
|
|
|
save(player, profile);
|
2025-02-19 18:15:05 -05:00
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static ProfileOperation cloneSlot(UUID uuid, int sourceSlotId, int destinationSlotId, boolean temporary) {
|
|
|
|
|
Profile profile = getProfile(uuid);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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 cloneSlot(String username, int sourceSlotId, int destinationSlotId, boolean temporary) {
|
|
|
|
|
Profile profile = getProfile(username);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = cloneSlotInternal(profile, sourceSlotId, destinationSlotId, temporary);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----- Remove Slot -----
|
|
|
|
|
public static ProfileOperation removeSlot(EntityPlayer player, int slotId) {
|
|
|
|
|
Profile profile = getProfile(player);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = removeSlotInternal(profile, slotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS && player != null) {
|
2025-02-18 06:25:09 -05:00
|
|
|
save(player, profile);
|
2025-02-19 18:15:05 -05:00
|
|
|
}
|
2025-02-18 06:25:09 -05:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static ProfileOperation removeSlot(UUID uuid, int slotId) {
|
|
|
|
|
Profile profile = getProfile(uuid);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = removeSlotInternal(profile, slotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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 removeSlot(String username, int slotId) {
|
|
|
|
|
Profile profile = getProfile(username);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = removeSlotInternal(profile, slotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----- Change Slot -----
|
|
|
|
|
public static ProfileOperation changeSlot(EntityPlayer player, int newSlotId) {
|
|
|
|
|
Profile profile = getProfile(player);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = changeSlotInternal(profile, newSlotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS && 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);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = changeSlotInternal(profile, newSlotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(profile == null)
|
|
|
|
|
return ProfileOperation.error(MSG_PLAYER_NOT_FOUND);
|
2025-02-18 06:25:09 -05:00
|
|
|
ProfileOperation result = changeSlotInternal(profile, newSlotId);
|
2025-02-19 18:15:05 -05:00
|
|
|
if(result.getResult() == EnumProfileOperation.SUCCESS) {
|
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) {
|
|
|
|
|
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-19 16:27:27 -05:00
|
|
|
public static boolean allowSlotPermission(EntityPlayer player) {
|
|
|
|
|
Profile profile = ProfileController.getProfile(player);
|
|
|
|
|
int currentSlots = profile.slots.size();
|
|
|
|
|
|
|
|
|
|
// Unlimited slots if the player has the wildcard permission.
|
|
|
|
|
if (CustomNpcsPermissions.hasCustomPermission(player, "customnpcs.profile.max.*")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int highestAllowed = 0;
|
|
|
|
|
for (int i = 1; i <= 50; i++) {
|
|
|
|
|
String perm = "customnpcs.profile.max." + i;
|
|
|
|
|
if (CustomNpcsPermissions.hasCustomPermission(player, perm)) {
|
|
|
|
|
highestAllowed = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (highestAllowed == 0) {
|
|
|
|
|
highestAllowed = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return currentSlots < highestAllowed;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|