using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Xml.Linq; using HG; using JetBrains.Annotations; using RoR2.EntitlementManagement; using RoR2.Skills; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class Loadout { public class BodyLoadoutManager { private sealed class BodyLoadout { public BodyIndex bodyIndex; public uint skinPreference; public uint[] skillPreferences; [NotNull] public BodyLoadout Clone() { return new BodyLoadout { bodyIndex = bodyIndex, skinPreference = skinPreference, skillPreferences = (uint[])skillPreferences.Clone() }; } public bool ValueEquals(BodyLoadout other) { if (this == other) { return true; } if (other == null) { return false; } if (bodyIndex.Equals(other.bodyIndex)) { if (skinPreference.Equals(other.skinPreference)) { return ((IStructuralEquatable)skillPreferences).Equals((object)other.skillPreferences, (IEqualityComparer)EqualityComparer.Default); } return false; } return false; } public uint GetSkillVariant(int skillSlotIndex) { if ((uint)skillSlotIndex < skillPreferences.Length) { return skillPreferences[skillSlotIndex]; } return 0u; } public bool SetSkillVariant(int skillSlotIndex, uint skillVariant) { if ((uint)skillSlotIndex < skillPreferences.Length) { skillPreferences[skillSlotIndex] = HGMath.Clamp(skillVariant, 0u, (uint)LookUpMaxSkillVariants(skillSlotIndex)); return true; } return false; } private bool IsSkillVariantValid(int skillSlotIndex) { SkillFamily skillFamily = GetSkillFamily(skillSlotIndex); if (!skillFamily) { return false; } if (GetSkillVariant(skillSlotIndex) < skillFamily.variants.Length) { return true; } return false; } private bool IsSkillVariantLocked(int skillSlotIndex, UserProfile userProfile) { SkillFamily skillFamily = GetSkillFamily(skillSlotIndex); if (!skillFamily) { return false; } uint skillVariant = GetSkillVariant(skillSlotIndex); bool num = (bool)skillFamily.variants[skillVariant].unlockableDef && (bool)skillFamily.variants[skillVariant].unlockableDef.requiredExpansion; bool flag = false; if (num) { flag = EntitlementManager.localUserEntitlementTracker.AnyUserHasEntitlement(skillFamily.variants[skillVariant].unlockableDef.requiredExpansion.requiredEntitlement); } if (!num || flag) { return !userProfile.HasUnlockable(skillFamily.variants[skillVariant].unlockableDef); } return true; } private void ResetSkillVariant(int skillSlotIndex) { if ((uint)skillSlotIndex < skillPreferences.Length) { skillPreferences[skillSlotIndex] = GetSkillFamily(skillSlotIndex)?.defaultVariantIndex ?? 0; } } private bool IsSkinValid() { SkinDef[] bodySkins = BodyCatalog.GetBodySkins(bodyIndex); return skinPreference < bodySkins.Length; } private bool IsSkinLocked(UserProfile userProfile) { SkinDef safe = ArrayUtils.GetSafe(BodyCatalog.GetBodySkins(bodyIndex), (int)skinPreference); if (!safe) { return true; } bool num = (bool)safe.unlockableDef && (bool)safe.unlockableDef.requiredExpansion; bool flag = false; if (num) { EntitlementDef requiredEntitlement = safe.unlockableDef.requiredExpansion.requiredEntitlement; flag = EntitlementManager.localUserEntitlementTracker.AnyUserHasEntitlement(requiredEntitlement); } if (!num || flag) { return !userProfile.HasUnlockable(safe.unlockableDef); } return true; } private void ResetSkin() { skinPreference = 0u; } public void EnforceValidity() { for (int i = 0; i < skillPreferences.Length; i++) { if (!IsSkillVariantValid(i)) { ResetSkillVariant(i); } } if (!IsSkinValid()) { ResetSkin(); } } public void EnforceUnlockables(UserProfile userProfile) { bool flag = false; for (int i = 0; i < skillPreferences.Length; i++) { if (IsSkillVariantLocked(i, userProfile)) { ResetSkillVariant(i); flag = true; } } if (IsSkinLocked(userProfile)) { ResetSkin(); flag = true; } if (flag) { userProfile.OnLoadoutChanged(); } } [CanBeNull] private SkillFamily GetSkillFamily(int skillSlotIndex) { return BodyCatalog.GetBodyPrefabSkillSlots(bodyIndex)[skillSlotIndex].skillFamily; } public int LookUpMaxSkillVariants(int skillSlotIndex) { if ((uint)skillSlotIndex < skillPreferences.Length) { SkillFamily skillFamily = GetSkillFamily(skillSlotIndex); if ((object)skillFamily == null) { return 0; } return skillFamily.variants.Length; } return 0; } public void Serialize(NetworkWriter writer) { writer.WriteBodyIndex(bodyIndex); writer.WritePackedUInt32(skinPreference); for (int i = 0; i < skillPreferences.Length; i++) { writer.WritePackedUInt32(skillPreferences[i]); } } public void Deserialize(NetworkReader reader) { bodyIndex = reader.ReadBodyIndex(); if (bodyIndex < (BodyIndex)0) { bodyIndex = (BodyIndex)0; } if ((int)bodyIndex >= BodyCatalog.bodyCount) { bodyIndex = (BodyIndex)(BodyCatalog.bodyCount - 1); } skinPreference = reader.ReadPackedUInt32(); Array.Resize(ref skillPreferences, GetSkillSlotCountForBody(bodyIndex)); for (int i = 0; i < skillPreferences.Length; i++) { SetSkillVariant(i, reader.ReadPackedUInt32()); } } public XElement ToXml(string elementName) { XElement xElement = new XElement(elementName); xElement.SetAttributeValue("bodyName", BodyCatalog.GetBodyName(bodyIndex)); xElement.Add(new XElement("Skin", skinPreference.ToString())); ref BodyInfo reference = ref allBodyInfos[(int)bodyIndex]; for (int i = 0; i < reference.prefabSkillSlots.Length; i++) { int skillFamilyIndex = reference.skillFamilyIndices[i]; SkillFamily skillFamily = SkillCatalog.GetSkillFamily(skillFamilyIndex); string skillFamilyName = SkillCatalog.GetSkillFamilyName(skillFamilyIndex); string variantName = skillFamily.GetVariantName((int)skillPreferences[i]); if (variantName != null) { XElement xElement2 = new XElement("SkillPreference", variantName); xElement2.SetAttributeValue("skillFamily", skillFamilyName); xElement.Add(xElement2); } } return xElement; } public bool FromXml(XElement element) { uint.TryParse(element.Element("Skin")?.Value ?? string.Empty, out skinPreference); string text = element.Attribute("bodyName")?.Value; if (text == null) { return false; } bodyIndex = BodyCatalog.FindBodyIndex(text); if (bodyIndex == BodyIndex.None) { Debug.LogFormat("Could not find body index for bodyName={0}", text); return false; } GenericSkill[] prefabSkillSlots = allBodyInfos[(int)bodyIndex].prefabSkillSlots; skillPreferences = new uint[prefabSkillSlots.Length]; foreach (XElement item in element.Elements("SkillPreference")) { string text2 = item.Attribute("skillFamily")?.Value; string value = item.Value; if (text2 == null) { continue; } int num = FindSkillSlotIndex(text2); if (num != -1) { int variantIndex = prefabSkillSlots[num].skillFamily.GetVariantIndex(value); if (variantIndex != -1) { skillPreferences[num] = (uint)variantIndex; continue; } Debug.LogFormat("Could not find variant index for elementSkillFamilyName={0} elementSkillName={1}", text2, value); } else { Debug.LogFormat("Could not find skill slot index for elementSkillFamilyName={0} elementSkillName={1}", text2, value); } } return true; int FindSkillSlotIndex(string requestedSkillFamilyName) { for (int i = 0; i < prefabSkillSlots.Length; i++) { if (SkillCatalog.GetSkillFamilyName(prefabSkillSlots[i].skillFamily.catalogIndex).Equals(requestedSkillFamilyName, StringComparison.Ordinal)) { return i; } } return -1; } } } private struct BodyInfo { public int[] skillFamilyIndices; public GenericSkill[] prefabSkillSlots; public int skillSlotCount => prefabSkillSlots.Length; } private BodyLoadout[] modifiedBodyLoadouts = Array.Empty(); private static BodyLoadout[] defaultBodyLoadouts; private static BodyInfo[] allBodyInfos; private int FindModifiedBodyLoadoutIndexByBodyIndex(BodyIndex bodyIndex) { for (int i = 0; i < modifiedBodyLoadouts.Length; i++) { if (modifiedBodyLoadouts[i].bodyIndex == bodyIndex) { return i; } } return -1; } private BodyLoadout GetReadOnlyBodyLoadout(BodyIndex bodyIndex) { int num = FindModifiedBodyLoadoutIndexByBodyIndex(bodyIndex); if (num == -1) { return defaultBodyLoadouts[(int)bodyIndex]; } return modifiedBodyLoadouts[num]; } private BodyLoadout GetOrCreateModifiedBodyLoadout(BodyIndex bodyIndex) { int num = FindModifiedBodyLoadoutIndexByBodyIndex(bodyIndex); if (num != -1) { return modifiedBodyLoadouts[num]; } BodyLoadout value = GetDefaultLoadoutForBody(bodyIndex).Clone(); ArrayUtils.ArrayAppend(ref modifiedBodyLoadouts, in value); return value; } public uint GetSkillVariant(BodyIndex bodyIndex, int skillSlot) { return GetReadOnlyBodyLoadout(bodyIndex).skillPreferences[skillSlot]; } public void SetSkillVariant(BodyIndex bodyIndex, int skillSlot, uint skillVariant) { if ((uint)bodyIndex >= BodyCatalog.bodyCount) { throw new ArgumentOutOfRangeException("bodyIndex", (int)bodyIndex, $"Value provided for 'bodyIndex' is outside range [0, {(int)bodyIndex}]"); } if (GetSkillVariant(bodyIndex, skillSlot) != skillVariant) { int skillSlotCountForBody = GetSkillSlotCountForBody(bodyIndex); if ((uint)skillSlot >= skillSlotCountForBody) { throw new ArgumentOutOfRangeException("skillSlot", skillSlot, $"Value provided for 'skillSlot' is outside range [0, {skillSlotCountForBody}) for body=[{(int)bodyIndex}]"); } int num = allBodyInfos[(int)bodyIndex].prefabSkillSlots[skillSlot].skillFamily.variants.Length; if (skillVariant >= num) { throw new ArgumentOutOfRangeException("skillVariant", skillVariant, $"Value provided for 'skillVariant' is outside range [0, {num}) for body=[{(int)bodyIndex}] skillSlot=[{skillSlot}]"); } BodyLoadout orCreateModifiedBodyLoadout = GetOrCreateModifiedBodyLoadout(bodyIndex); orCreateModifiedBodyLoadout.SetSkillVariant(skillSlot, skillVariant); RemoveBodyLoadoutIfDefault(orCreateModifiedBodyLoadout); } } public uint GetSkinIndex(BodyIndex bodyIndex) { return GetReadOnlyBodyLoadout(bodyIndex).skinPreference; } public void SetSkinIndex(BodyIndex bodyIndex, uint skinIndex) { BodyLoadout orCreateModifiedBodyLoadout = GetOrCreateModifiedBodyLoadout(bodyIndex); orCreateModifiedBodyLoadout.skinPreference = skinIndex; RemoveBodyLoadoutIfDefault(orCreateModifiedBodyLoadout); } private void RemoveBodyLoadoutIfDefault(BodyLoadout bodyLoadout) { RemoveBodyLoadoutIfDefault(FindModifiedBodyLoadoutIndexByBodyIndex(bodyLoadout.bodyIndex)); } private void RemoveBodyLoadoutIfDefault(int modifiedBodyLoadoutIndex) { BodyLoadout obj = modifiedBodyLoadouts[modifiedBodyLoadoutIndex]; if (obj.ValueEquals(GetDefaultLoadoutForBody(obj.bodyIndex))) { RemoveBodyLoadoutAt(modifiedBodyLoadoutIndex); } } private void RemoveBodyLoadoutAt(int i) { ArrayUtils.ArrayRemoveAtAndResize(ref modifiedBodyLoadouts, i); } private static BodyLoadout GetDefaultLoadoutForBody(BodyIndex bodyIndex) { return defaultBodyLoadouts[(int)bodyIndex]; } private static int GetSkillSlotCountForBody(BodyIndex bodyIndex) { return allBodyInfos[(int)bodyIndex].skillSlotCount; } [SystemInitializer(new Type[] { typeof(SkillCatalog), typeof(BodyCatalog) })] private static void Init() { defaultBodyLoadouts = new BodyLoadout[BodyCatalog.bodyCount]; allBodyInfos = new BodyInfo[defaultBodyLoadouts.Length]; for (BodyIndex bodyIndex = (BodyIndex)0; (int)bodyIndex < defaultBodyLoadouts.Length; bodyIndex++) { BodyInfo bodyInfo = default(BodyInfo); bodyInfo.prefabSkillSlots = BodyCatalog.GetBodyPrefabBodyComponent(bodyIndex).GetComponents(); bodyInfo.skillFamilyIndices = new int[bodyInfo.skillSlotCount]; for (int i = 0; i < bodyInfo.prefabSkillSlots.Length; i++) { bodyInfo.skillFamilyIndices[i] = bodyInfo.prefabSkillSlots[i].skillFamily?.catalogIndex ?? (-1); } allBodyInfos[(int)bodyIndex] = bodyInfo; uint[] array = new uint[bodyInfo.skillSlotCount]; for (int j = 0; j < bodyInfo.prefabSkillSlots.Length; j++) { array[j] = bodyInfo.prefabSkillSlots[j].skillFamily.defaultVariantIndex; } defaultBodyLoadouts[(int)bodyIndex] = new BodyLoadout { bodyIndex = bodyIndex, skinPreference = 0u, skillPreferences = array }; } GenerateViewables(); } public void Copy(BodyLoadoutManager dest) { Array.Resize(ref dest.modifiedBodyLoadouts, modifiedBodyLoadouts.Length); for (int i = 0; i < modifiedBodyLoadouts.Length; i++) { dest.modifiedBodyLoadouts[i] = modifiedBodyLoadouts[i].Clone(); } } public void Clear() { modifiedBodyLoadouts = Array.Empty(); } public void Serialize(NetworkWriter writer) { writer.WritePackedUInt32((uint)modifiedBodyLoadouts.Length); for (int i = 0; i < modifiedBodyLoadouts.Length; i++) { modifiedBodyLoadouts[i].Serialize(writer); } } public void Deserialize(NetworkReader reader) { try { int num = (int)reader.ReadPackedUInt32(); if (num > BodyCatalog.bodyCount) { num = BodyCatalog.bodyCount; } Array.Resize(ref modifiedBodyLoadouts, num); for (int i = 0; i < num; i++) { BodyLoadout bodyLoadout = new BodyLoadout(); bodyLoadout.Deserialize(reader); modifiedBodyLoadouts[i] = bodyLoadout; } } catch (Exception) { modifiedBodyLoadouts = Array.Empty(); throw; } } public bool ValueEquals(BodyLoadoutManager other) { if (this == other) { return true; } if (other == null) { return false; } if (modifiedBodyLoadouts.Length != other.modifiedBodyLoadouts.Length) { return false; } for (int i = 0; i < modifiedBodyLoadouts.Length; i++) { if (!modifiedBodyLoadouts[i].ValueEquals(other.modifiedBodyLoadouts[i])) { return false; } } return true; } public XElement ToXml(string elementName) { XElement xElement = new XElement(elementName); for (int i = 0; i < modifiedBodyLoadouts.Length; i++) { xElement.Add(modifiedBodyLoadouts[i].ToXml("BodyLoadout")); } return xElement; } public bool FromXml(XElement element) { List bodyLoadouts = new List(); foreach (XElement item in element.Elements("BodyLoadout")) { BodyLoadout bodyLoadout = new BodyLoadout(); if (bodyLoadout.FromXml(item) && !BodyLoadoutAlreadyDefined(bodyLoadout.bodyIndex) && !bodyLoadout.ValueEquals(GetDefaultLoadoutForBody(bodyLoadout.bodyIndex))) { bodyLoadouts.Add(bodyLoadout); } } modifiedBodyLoadouts = bodyLoadouts.ToArray(); return true; bool BodyLoadoutAlreadyDefined(BodyIndex bodyIndex) { for (int i = 0; i < bodyLoadouts.Count; i++) { if (bodyLoadouts[i].bodyIndex == bodyIndex) { return true; } } return false; } } public void EnforceUnlockables(UserProfile userProfile) { for (int num = modifiedBodyLoadouts.Length - 1; num >= 0; num--) { modifiedBodyLoadouts[num].EnforceUnlockables(userProfile); RemoveBodyLoadoutIfDefault(num); } } public void EnforceValidity() { for (int num = modifiedBodyLoadouts.Length - 1; num >= 0; num--) { modifiedBodyLoadouts[num].EnforceValidity(); RemoveBodyLoadoutIfDefault(num); } } } public readonly BodyLoadoutManager bodyLoadoutManager = new BodyLoadoutManager(); private static readonly Stack instancePool = new Stack(); public void Serialize(NetworkWriter writer) { bodyLoadoutManager.Serialize(writer); } public void Deserialize(NetworkReader reader) { bodyLoadoutManager.Deserialize(reader); } public void Copy(Loadout dest) { bodyLoadoutManager.Copy(dest.bodyLoadoutManager); } public void Clear() { bodyLoadoutManager.Clear(); } public bool ValueEquals(Loadout other) { if (this == other) { return true; } if (other == null) { return false; } return bodyLoadoutManager.ValueEquals(other.bodyLoadoutManager); } public XElement ToXml(string elementName) { XElement xElement = new XElement(elementName); xElement.Add(bodyLoadoutManager.ToXml("BodyLoadouts")); return xElement; } public bool FromXml(XElement element) { bool flag = true; XElement xElement = element.Element("BodyLoadouts"); flag = xElement != null && (flag & bodyLoadoutManager.FromXml(xElement)); EnforceValidity(); return flag; } public void EnforceValidity() { bodyLoadoutManager.EnforceValidity(); } public void EnforceUnlockables(UserProfile userProfile) { bodyLoadoutManager.EnforceUnlockables(userProfile); } private static void GenerateViewables() { StringBuilder stringBuilder = new StringBuilder(); ViewablesCatalog.Node node = new ViewablesCatalog.Node("Loadout", isFolder: true); ViewablesCatalog.Node parent = new ViewablesCatalog.Node("Bodies", isFolder: true, node); for (BodyIndex bodyIndex = (BodyIndex)0; (int)bodyIndex < BodyCatalog.bodyCount; bodyIndex++) { if (SurvivorCatalog.GetSurvivorIndexFromBodyIndex(bodyIndex) == SurvivorIndex.None) { continue; } string bodyName = BodyCatalog.GetBodyName(bodyIndex); GenericSkill[] bodyPrefabSkillSlots = BodyCatalog.GetBodyPrefabSkillSlots(bodyIndex); ViewablesCatalog.Node parent2 = new ViewablesCatalog.Node(bodyName, isFolder: true, parent); for (int i = 0; i < bodyPrefabSkillSlots.Length; i++) { SkillFamily skillFamily = bodyPrefabSkillSlots[i].skillFamily; if (skillFamily.variants.Length <= 1) { continue; } string skillFamilyName = SkillCatalog.GetSkillFamilyName(skillFamily.catalogIndex); for (uint num = 0u; num < skillFamily.variants.Length; num++) { ref SkillFamily.Variant reference = ref skillFamily.variants[num]; UnlockableDef unlockableDef = reference.unlockableDef; string skillName = SkillCatalog.GetSkillName(reference.skillDef.skillIndex); stringBuilder.Append(skillFamilyName).Append(".").Append(skillName); string name = stringBuilder.ToString(); stringBuilder.Clear(); ViewablesCatalog.Node variantNode = new ViewablesCatalog.Node(name, isFolder: false, parent2); reference.viewableNode = variantNode; variantNode.shouldShowUnviewed = (UserProfile userProfile) => !userProfile.HasViewedViewable(variantNode.fullName) && userProfile.HasUnlockable(unlockableDef); } } SkinDef[] bodySkins = BodyCatalog.GetBodySkins(bodyIndex); if (bodySkins.Length <= 1) { continue; } ViewablesCatalog.Node parent3 = new ViewablesCatalog.Node("Skins", isFolder: true, parent2); for (int j = 0; j < bodySkins.Length; j++) { UnlockableDef unlockableDef2 = bodySkins[j].unlockableDef; if ((bool)unlockableDef2) { ViewablesCatalog.Node skinNode = new ViewablesCatalog.Node(bodySkins[j].name, isFolder: false, parent3); skinNode.shouldShowUnviewed = (UserProfile userProfile) => !userProfile.HasViewedViewable(skinNode.fullName) && userProfile.HasUnlockable(unlockableDef2); } } } ViewablesCatalog.AddNodeToRoot(node); } public static Loadout RequestInstance() { if (instancePool.Count == 0) { return new Loadout(); } return instancePool.Pop(); } public static Loadout ReturnInstance(Loadout instance) { instance.Clear(); instancePool.Push(instance); return null; } [SystemInitializer(new Type[] { typeof(BodyCatalog), typeof(SkillCatalog), typeof(SkinCatalog) })] private static void Init() { } }