using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml.Linq; using Assets.RoR2.Scripts.Platform; using Facepunch.Steamworks; using JetBrains.Annotations; using Rewired; using RoR2.Stats; using UnityEngine; using Zio; namespace RoR2; public class UserProfile { public struct TutorialProgression { public uint showCount; public bool shouldShow; } public bool isClaimed; public bool canSave; public string fileName; public ulong saveID; public bool isInUse; public IFileSystem fileSystem; public UPath filePath = UPath.Empty; [SaveField] public string name; [SaveField] public uint coins; [SaveField] public uint totalCollectedCoins; [SaveField] public string version = "2"; [SaveField] public float screenShakeScale = 1f; [SaveField] public float directionalDamageIndicatorScale; [SaveField] public bool increasedDifficultyBarFX; [SaveField] public bool useInspectFeature; [SaveField] public bool useInspectAutopause; [SaveField] public bool useToggleScoreboard; [SaveField] public bool useTeleporterParticleScaling; [SaveField] public bool useTeleporterDiscoveryIndicator; [SaveField(explicitSetupMethod = "SetupKeyboardMap")] public KeyboardMap keyboardMap = new KeyboardMap(DefaultControllerMaps.defaultKeyboardMap); [SaveField(explicitSetupMethod = "SetupMouseMap")] public MouseMap mouseMap = new MouseMap(DefaultControllerMaps.defaultMouseMap); [SaveField(explicitSetupMethod = "SetupJoystickMap")] public JoystickMap joystickMap = new JoystickMap(DefaultControllerMaps.DefaultJoystickMap); [SaveField(explicitSetupMethod = "SetupJoystickHardwareMap")] public Dictionary HardwareJoystickMaps2 = new Dictionary(); [SaveField] public float mouseLookSensitivity = 0.25f; [SaveField] public float mouseLookScaleX = 1f; [SaveField] public float mouseLookScaleY = 1f; [SaveField] public bool mouseLookInvertX; [SaveField] public bool mouseLookInvertY; [SaveField] public float stickLookSensitivity = 4f; [SaveField] public float stickLookScaleX = 1f; [SaveField] public float stickLookScaleY = 1f; [SaveField] public int hudSizeMode; [SaveField] public bool gyroEnabled; [SaveField] public float gyroLookSensitivity = 1f; [SaveField] public float gyroLookScaleX = 1f; [SaveField] public float gyroLookScaleY = 0.65f; [SaveField] public bool gyroLookInvertX; [SaveField] public bool gyroLookInvertY; [SaveField] public bool gyroDisabledDuringSprint = true; [SaveField] public bool gyroDisabledDuringSprint_Huntress; [SaveField] public bool gyroDisabledDuringSprint_Melee; [SaveField] public bool toggleArtificerHover = true; [SaveField] public bool toggleSeekerSojourn = true; [SaveField] public bool stickLookInvertX; [SaveField] public bool stickLookInvertY; [SaveField] public float gamepadVibrationScale = 1f; [SaveField] public bool aimAssistEnabled = true; [SaveField] public float aimAssistSensitivity = 6f; [SaveField] public bool disableItemDisplays; [SaveField] public float inputAimAssistSensitivity = 4f; [SaveField] public float mouseTrajectoryAimAssistSensitivity; [SaveField] public float gamePadTrajectoryAimAssistSensitivity = 4f; public bool saveRequestPending; private static string[] saveFieldNames; public static SaveFieldAttribute[] saveFields; private static readonly Dictionary nameToSaveFieldMap = new Dictionary(); public static UserProfile defaultProfile; [SaveField(explicitSetupMethod = "SetupTokenList")] public List viewedUnlockablesList = new List(); [SaveField(explicitSetupMethod = "SetupPickupsSet")] private readonly bool[] discoveredPickups = PickupCatalog.GetPerPickupBuffer(); public Action onPickupDiscovered; public Action onStatsReceived; [SaveField(explicitSetupMethod = "SetupTokenList")] private List achievementsList = new List(); [SaveField(explicitSetupMethod = "SetupTokenList")] private List unviewedAchievementsList = new List(); public StatSheet statSheet = StatSheet.New(); private const uint maxShowCount = 3u; public TutorialProgression tutorialDifficulty; public TutorialProgression tutorialSprint; public TutorialProgression tutorialEquipment; [SaveField] private SurvivorDef survivorPreference = SurvivorCatalog.defaultSurvivor; public readonly Loadout loadout = new Loadout(); [SaveField] public uint totalLoginSeconds; [SaveField] public uint totalRunSeconds; [SaveField] public uint totalAliveSeconds; [SaveField] public uint totalRunCount; private bool ownsPortrait; [SaveField] public bool chefAchievementIngredient1; [SaveField] public bool chefAchievementIngredient2; [SaveField] public bool chefAchievementIngredient3; [SaveField] public string RebirthItem; private const string defaultProfileContents = "\r\n Survivor\r\n 0.2\r\n 1\r\n 1\r\n 5\r\n 1\r\n 1\r\n 4\r\n 0\r\n 4\r\n"; [SaveField(defaultValue = "", explicitSetupMethod = "SetupTokenList", fieldName = "viewedViewables")] private readonly List viewedViewables = new List(); public bool isCorrupted { get; set; } public bool hasUnviewedAchievement => unviewedAchievementsList.Count > 0; public bool loggedIn { get; set; } public Texture portraitTexture { get; private set; } public event Action onSurvivorPreferenceChanged; public static event Action onSurvivorPreferenceChangedGlobal; public event Action onLoadoutChanged; public static event Action onLoadoutChangedGlobal; public static event Action onUnlockableGranted; public static event Action onUserProfileViewedViewablesChanged; public void FillDefaultJoystickMaps() { Dictionary defaultJoystickMaps = DefaultControllerMaps.GetDefaultJoystickMaps(); if (defaultJoystickMaps == null) { return; } foreach (KeyValuePair item in defaultJoystickMaps) { DefaultControllerMaps.RewiredDebugLog($"FillDefaultJoystickMaps> Guid {item.Key} --- {item.Value.ToXmlString()}"); HardwareJoystickMaps2.Add(item.Key, item.Value); LocalUser.DumpControllerMap(item.Value); } } public JoystickMap GetJoystickMap(Guid inControllerGuid) { if (HardwareJoystickMaps2.Count == 0) { FillDefaultJoystickMaps(); } if (HardwareJoystickMaps2.ContainsKey(inControllerGuid)) { DefaultControllerMaps.RewiredDebugLog($"***** GetJoystickMap: return controller for guid {inControllerGuid}"); return new JoystickMap(HardwareJoystickMaps2[inControllerGuid]); } if (inControllerGuid == DefaultControllerMaps.xboneControllerGuid && HardwareJoystickMaps2.ContainsKey(DefaultControllerMaps.xbox360ControllerGuid)) { DefaultControllerMaps.RewiredDebugWarn("***** GetJoystickMap: Failed to find guid {0}... returning Xbox360 mapping (XInput detected)", inControllerGuid); return new JoystickMap(HardwareJoystickMaps2[DefaultControllerMaps.xbox360ControllerGuid]); } if (inControllerGuid == DefaultControllerMaps.xbox360ControllerGuid && HardwareJoystickMaps2.ContainsKey(DefaultControllerMaps.xboneControllerGuid)) { DefaultControllerMaps.RewiredDebugWarn("***** GetJoystickMap: Failed to find guid {0}... returning Xbone mapping (XInput detected)", inControllerGuid); return new JoystickMap(HardwareJoystickMaps2[DefaultControllerMaps.xboneControllerGuid]); } if (HardwareJoystickMaps2.ContainsKey(PlatformManager.DefaultControllerGuid)) { DefaultControllerMaps.RewiredDebugWarn("***** GetJoystickMap: Failed to find guid {0}... returning default", inControllerGuid); return new JoystickMap(HardwareJoystickMaps2[PlatformManager.DefaultControllerGuid]); } return null; } public void SwitchJoystickMap(Guid inNewControllerGuid) { DefaultControllerMaps.RewiredDebugLog($"***** SwitchJoystickMap: From {this.joystickMap.hardwareGuid} to {inNewControllerGuid}"); JoystickMap joystickMap = GetJoystickMap(inNewControllerGuid); if (joystickMap != null) { this.joystickMap = joystickMap; } } public void UpdateJoystickMap(Guid inControllerGuid, JoystickMap inJoystickMap) { if (!HardwareJoystickMaps2.ContainsKey(inControllerGuid)) { DefaultControllerMaps.RewiredDebugWarn($"***** UpdateJoystickMap: {inControllerGuid} not found!"); return; } HardwareJoystickMaps2[inControllerGuid] = inJoystickMap; if (inControllerGuid == joystickMap.hardwareGuid) { SwitchJoystickMap(inControllerGuid); } } public void UpdateKeyboardMap(KeyboardMap inKeyboardMap) { keyboardMap = inKeyboardMap; } public void UpdateMouseMap(MouseMap inMouseMap) { mouseMap = inMouseMap; } public static void GenerateSaveFieldFunctions() { nameToSaveFieldMap.Clear(); FieldInfo[] fields = typeof(UserProfile).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { SaveFieldAttribute customAttribute = CustomAttributeExtensions.GetCustomAttribute(fieldInfo); if (customAttribute != null) { customAttribute.Setup(fieldInfo); nameToSaveFieldMap[fieldInfo.Name] = customAttribute; } } saveFieldNames = nameToSaveFieldMap.Keys.ToArray(); Array.Sort(saveFieldNames); saveFields = saveFieldNames.Select((string name) => nameToSaveFieldMap[name]).ToArray(); } public void SetSaveFieldString([NotNull] string fieldName, [NotNull] string value) { if (nameToSaveFieldMap.TryGetValue(fieldName, out var value2)) { value2.setter(this, value); return; } Debug.LogErrorFormat("Save field {0} is not defined.", fieldName); } public string GetSaveFieldString([NotNull] string fieldName) { if (nameToSaveFieldMap.TryGetValue(fieldName, out var value)) { return value.getter(this); } Debug.LogErrorFormat("Save field {0} is not defined.", fieldName); return string.Empty; } public static XDocument ToXml(UserProfile userProfile) { return XmlUtility.ToXml(userProfile); } public static UserProfile FromXml(XDocument doc) { return XmlUtility.FromXml(doc); } public bool HasUnlockable([NotNull] string unlockableToken) { UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(unlockableToken); if (!(unlockableDef == null)) { return HasUnlockable(unlockableDef); } return true; } public bool HasUnlockable([CanBeNull] UnlockableDef unlockableDef) { return statSheet.HasUnlockable(unlockableDef); } public void AddUnlockToken(string unlockableToken) { UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(unlockableToken); if (unlockableDef != null) { GrantUnlockable(unlockableDef); } } public void GrantUnlockable(UnlockableDef unlockableDef) { if (!statSheet.HasUnlockable(unlockableDef)) { statSheet.AddUnlockable(unlockableDef); Debug.LogFormat("{0} unlocked {1}", name, unlockableDef.nameToken); RequestEventualSave(); UserProfile.onUnlockableGranted?.Invoke(this, unlockableDef); } } public void RequestEventualSave() { if (canSave) { saveRequestPending = true; } } public void RevokeUnlockable(UnlockableDef unlockableDef) { if (statSheet.HasUnlockable(unlockableDef)) { statSheet.RemoveUnlockable(unlockableDef.index); Debug.LogFormat("{0} had {1} revoked", name, unlockableDef.nameToken); RequestEventualSave(); } } public bool HasSurvivorUnlocked(SurvivorIndex survivorIndex) { SurvivorDef survivorDef = SurvivorCatalog.GetSurvivorDef(survivorIndex); if (survivorDef == null) { return false; } if (!survivorDef.unlockableDef) { return true; } return HasUnlockable(survivorDef.unlockableDef); } public bool HasDiscoveredPickup(PickupIndex pickupIndex) { if (pickupIndex.isValid) { return discoveredPickups[pickupIndex.value]; } return false; } public void DiscoverPickup(PickupIndex pickupIndex) { SetPickupDiscovered(pickupIndex, newDiscovered: true); } private void SetPickupDiscovered(PickupIndex pickupIndex, bool newDiscovered) { if (!pickupIndex.isValid) { return; } ref bool reference = ref discoveredPickups[pickupIndex.value]; if (reference != newDiscovered) { reference = newDiscovered; if (newDiscovered) { onPickupDiscovered?.Invoke(pickupIndex); RequestEventualSave(); } } } [ConCommand(commandName = "user_profile_set_pickup_discovered", flags = ConVarFlags.Cheat, helpText = "Sets the pickup discovery state for the sender's profile.")] private static void CCUserProfileSetPickupDiscovered(ConCommandArgs args) { UserProfile userProfile = args.GetSenderLocalUser().userProfile; IEnumerable enumerable = null; enumerable = (IEnumerable)((!(args.TryGetArgString(0) == "all")) ? new PickupIndex[1] { args.GetArgPickupIndex(0) } : ((object)PickupCatalog.allPickupIndices)); bool argBool = args.GetArgBool(1); foreach (PickupIndex item in enumerable) { userProfile.SetPickupDiscovered(item, argBool); } } public bool HasAchievement(string achievementName) { return achievementsList.Contains(achievementName); } public bool CanSeeAchievement(string achievementName) { if (HasAchievement(achievementName)) { return true; } AchievementDef achievementDef = AchievementManager.GetAchievementDef(achievementName); if (achievementDef != null) { if (string.IsNullOrEmpty(achievementDef.prerequisiteAchievementIdentifier)) { return true; } return HasAchievement(achievementDef.prerequisiteAchievementIdentifier); } return false; } public void AddAchievement(string achievementName, bool isExternal) { achievementsList.Add(achievementName); unviewedAchievementsList.Add(achievementName); if (isExternal) { PlatformSystems.achievementSystem.AddAchievement(achievementName); } RequestEventualSave(); } public void RevokeAchievement(string achievementName) { achievementsList.Remove(achievementName); unviewedAchievementsList.Remove(achievementName); RequestEventualSave(); } public string PopNextUnviewedAchievementName() { if (unviewedAchievementsList.Count == 0) { return null; } string result = unviewedAchievementsList[0]; unviewedAchievementsList.RemoveAt(0); return result; } public void ClearAllAchievementNotifications() { unviewedAchievementsList.Clear(); } public void ApplyDeltaStatSheet(StatSheet deltaStatSheet) { int i = 0; for (int unlockableCount = deltaStatSheet.GetUnlockableCount(); i < unlockableCount; i++) { GrantUnlockable(deltaStatSheet.GetUnlockable(i)); } for (int j = 0; j < deltaStatSheet.fields.Length; j++) { if (deltaStatSheet.fields[j].hasChanged) { deltaStatSheet.fields[j].statDef.onChangeCallback?.Invoke(); } } statSheet.ApplyDelta(deltaStatSheet); onStatsReceived?.Invoke(); } [ConCommand(commandName = "user_profile_stats_stress_test", flags = ConVarFlags.Cheat, helpText = "Sets the stats of the sender's user profile to the maximum their datatypes support for stress-testing purposes.")] private static void CCUserProfileStatsStressTest(ConCommandArgs args) { LocalUser senderLocalUser = args.GetSenderLocalUser(); senderLocalUser.userProfile.statSheet.SetAllFieldsToMaxValue(); PlatformSystems.saveSystem.Save(senderLocalUser.userProfile, blocking: true); } private void ResetShouldShowTutorial(ref TutorialProgression tutorialProgression) { tutorialProgression.shouldShow = tutorialProgression.showCount < 3; } private void RebuildTutorialProgressions() { ResetShouldShowTutorial(ref tutorialDifficulty); ResetShouldShowTutorial(ref tutorialSprint); ResetShouldShowTutorial(ref tutorialEquipment); } [NotNull] public SurvivorDef GetSurvivorPreference() { return survivorPreference; } public void SetSurvivorPreference([NotNull] SurvivorDef newSurvivorPreference) { if (!newSurvivorPreference) { throw new ArgumentException("Provided object is null or invalid", "newSurvivorPreference"); } if ((object)survivorPreference != newSurvivorPreference) { survivorPreference = newSurvivorPreference; this.onSurvivorPreferenceChanged?.Invoke(); UserProfile.onSurvivorPreferenceChangedGlobal?.Invoke(this); } } public void OnLoadoutChanged() { this.onLoadoutChanged?.Invoke(); UserProfile.onLoadoutChangedGlobal?.Invoke(this); RequestEventualSave(); } public void CopyLoadout(Loadout dest) { loadout.Copy(dest); } public void SetLoadout(Loadout newLoadout) { if (!loadout.ValueEquals(newLoadout)) { newLoadout.Copy(loadout); OnLoadoutChanged(); } } [ConCommand(commandName = "loadout_set_skill_variant", flags = (ConVarFlags.ExecuteOnServer | ConVarFlags.Cheat), helpText = "loadout_set_skill_variant [body_name] [skill_slot_index] [skill_variant_index]\nSets the skill variant for the sender's user profile.")] private static void CCLoadoutSetSkillVariant(ConCommandArgs args) { BodyIndex argBodyIndex = args.GetArgBodyIndex(0); int argInt = args.GetArgInt(1); int argInt2 = args.GetArgInt(2); UserProfile userProfile = args.GetSenderLocalUser().userProfile; Loadout loadout = new Loadout(); userProfile.loadout.Copy(loadout); loadout.bodyLoadoutManager.SetSkillVariant(argBodyIndex, argInt, (uint)argInt2); userProfile.SetLoadout(loadout); if ((bool)args.senderMaster) { args.senderMaster.SetLoadoutServer(loadout); } if ((bool)args.senderBody) { args.senderBody.SetLoadoutServer(loadout); } } public void OnLogin() { if (loggedIn) { Debug.LogErrorFormat("Profile {0} is already logged in!", fileName); return; } loggedIn = true; PlatformSystems.saveSystem.loggedInProfiles.Add(this); RebuildTutorialProgressions(); foreach (string achievements in achievementsList) { PlatformSystems.achievementSystem.AddAchievement(achievements); UserProfile userProfile = LocalUserManager.GetFirstLocalUser().userProfile; if (userProfile != null && userProfile == this) { BaseActivitySelector baseActivitySelector = new BaseActivitySelector(); baseActivitySelector.activityAchievementID = achievements; PlatformSystems.activityManager.TryToCompleteActivity(baseActivitySelector); } } LoadPortrait(); } public void OnLogout() { if (!loggedIn) { Debug.LogErrorFormat("Profile {0} is already logged out!", fileName); return; } UnloadPortrait(); loggedIn = false; if (canSave) { PlatformSystems.saveSystem.Save(this, blocking: true); } PlatformSystems.saveSystem.loggedInProfiles.Remove(this); } private void LoadPortrait() { Texture2D newPortraitTexture = new Texture2D(184, 184, TextureFormat.ARGB32, mipChain: false, linear: false); portraitTexture = newPortraitTexture; ownsPortrait = true; Client.Instance.Friends.GetAvatar(Friends.AvatarSize.Large, Client.Instance.SteamId, LoadSteamImage); void LoadSteamImage(Image image) { if (image?.Data != null && (bool)newPortraitTexture && !(newPortraitTexture != portraitTexture)) { byte[] data = image.Data; Color32[] array = new Color32[data.Length / 4]; int width = image.Width; int height = image.Height; newPortraitTexture.Reinitialize(width, height); for (int i = 0; i < height; i++) { int num = height - 1 - i; for (int j = 0; j < width; j++) { int num2 = (i * width + j) * 4; array[num * width + j] = new Color32(data[num2], data[num2 + 1], data[num2 + 2], data[num2 + 3]); } } newPortraitTexture.SetPixels32(array); newPortraitTexture.Apply(); } } } public void ReloadPortrait() { RoR2Application.onUpdate -= ReloadPortrait; if (loggedIn) { LoadPortrait(); } } private void UnloadPortrait() { if (ownsPortrait) { UnityEngine.Object.Destroy(portraitTexture); portraitTexture = null; ownsPortrait = false; } } [ConCommand(commandName = "revert_chef_achievement_tracking_bools", flags = ConVarFlags.Cheat, helpText = "Reverts the bools associated with the chef achievement ingredient tracking.")] private static void CCRevertChefAchievementTrackingBools(ConCommandArgs args) { LocalUser senderLocalUser = args.GetSenderLocalUser(); senderLocalUser.userProfile.chefAchievementIngredient1 = false; senderLocalUser.userProfile.chefAchievementIngredient2 = false; senderLocalUser.userProfile.chefAchievementIngredient3 = false; } [ConCommand(commandName = "empty_rebirth_items", flags = ConVarFlags.Cheat, helpText = "Empties rebirth items stored in user's profile.")] private static void CCEmptyRebirthItems(ConCommandArgs args) { args.GetSenderLocalUser().userProfile.RebirthItem = null; } [ConCommand(commandName = "give_rebirth_reward", flags = ConVarFlags.Cheat, helpText = "gives rebirth items")] private static void CCGiveRebirth(ConCommandArgs args) { LocalUser senderLocalUser = args.GetSenderLocalUser(); if (args.userArgs.Count > 0) { PickupIndex pickupIndex = PickupCatalog.FindPickupIndex(args.GetArgItemIndex(0)); if (!(pickupIndex != PickupIndex.none)) { return; } senderLocalUser.userProfile.RebirthItem = PickupCatalog.GetPickupDef(pickupIndex).internalName; } else { senderLocalUser.userProfile.RebirthItem = PickupCatalog.GetPickupDef(new PickupIndex(UnityEngine.Random.Range(0, ItemCatalog.itemCount))).internalName; } senderLocalUser.userProfile.AddUnlockToken("Artifacts.Rebirth"); } public static void LoadDefaultProfile() { defaultProfile = XmlUtility.FromXml(XDocument.Parse("\r\n Survivor\r\n 0.2\r\n 1\r\n 1\r\n 5\r\n 1\r\n 1\r\n 4\r\n 0\r\n 4\r\n")); defaultProfile.canSave = false; } public bool HasViewedViewable(string viewableName) { return viewedViewables.Contains(viewableName); } public void MarkViewableAsViewed(string viewableName) { if (!HasViewedViewable(viewableName)) { viewedViewables.Add(viewableName); UserProfile.onUserProfileViewedViewablesChanged?.Invoke(this); RequestEventualSave(); } } public void ClearAllViewablesAsViewed() { viewedViewables.Clear(); UserProfile.onUserProfileViewedViewablesChanged?.Invoke(this); RequestEventualSave(); } }