using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using HG; using Rewired; using RoR2.EntitlementManagement; using RoR2.Skills; using TMPro; using UnityEngine; using UnityEngine.UI; namespace RoR2.UI; [RequireComponent(typeof(MPEventSystemLocator))] public class CharacterSelectController : MonoBehaviour { [Serializable] public struct SkillStrip { public GameObject stripRoot; public Image skillIcon; public TextMeshProUGUI skillName; public TextMeshProUGUI skillDescription; } private struct BodyInfo { public readonly BodyIndex bodyIndex; public readonly GameObject bodyPrefab; public readonly CharacterBody bodyPrefabBodyComponent; public readonly Color bodyColor; public readonly SkillLocator skillLocator; public readonly GenericSkill[] skillSlots; public BodyInfo(BodyIndex bodyIndex) { this.bodyIndex = bodyIndex; bodyPrefab = BodyCatalog.GetBodyPrefab(bodyIndex); bodyPrefabBodyComponent = BodyCatalog.GetBodyPrefabBodyComponent(bodyIndex); bodyColor = (bodyPrefabBodyComponent ? bodyPrefabBodyComponent.bodyColor : Color.gray); skillLocator = (bodyPrefab ? bodyPrefab.GetComponent() : null); skillSlots = BodyCatalog.GetBodyPrefabSkillSlots(bodyIndex); } } private struct StripDisplayData { public bool enabled; public Color primaryColor; public Sprite icon; public string titleString; public string descriptionString; public string keywordString; public string actionName; } [Header("Survivor Panel")] public TextMeshProUGUI survivorName; public Image[] primaryColorImages; public TextMeshProUGUI[] primaryColorTexts; public GameObject activeSurvivorInfoPanel; public GameObject inactiveSurvivorInfoPanel; [Header("Overview Panel")] public TextMeshProUGUI survivorDescription; [Header("Skill Panel")] public GameObject skillStripPrefab; public GameObject skillStripFillerPrefab; public RectTransform skillStripContainer; private UIElementAllocator skillStripAllocator; private UIElementAllocator skillStripFillerAllocator; [Tooltip("The header button for the loadout tab. Will be disabled if the user has no unlocked loadout options.")] [Header("Loadout Panel")] public GameObject loadoutHeaderButton; public ViewableTag loadoutViewableTag; [Header("Ready and Misc")] public MPButton readyButton; public MPButton unreadyButton; public GameObject[] multiplayerOnlyObjects; private MPEventSystemLocator eventSystemLocator; private CharacterSelectBarController characterSelectBarController; private MPEventSystem eventSystem; private LocalUser localUser; private SurvivorDef currentSurvivorDef; private static UnlockableDef[] loadoutAssociatedUnlockableDefs; private bool shouldRebuild = true; private bool shouldShowSurvivorInfoPanel; private void Awake() { EntitlementManager.UpdateLocalUsersEntitlements(); eventSystemLocator = GetComponent(); skillStripAllocator = new UIElementAllocator(skillStripContainer, skillStripPrefab); skillStripFillerAllocator = new UIElementAllocator(skillStripContainer, skillStripFillerPrefab); bool active = true; loadoutHeaderButton.SetActive(active); characterSelectBarController = GetComponentInChildren(); } private void OnEnable() { UserProfile.onLoadoutChangedGlobal += OnLoadoutChangedGlobal; EntitlementManager.onEntitlementsUpdated += OnEntitlementsUpdated; eventSystemLocator.onEventSystemDiscovered += OnEventSystemDiscovered; eventSystemLocator.onEventSystemLost += OnEventSystemLost; if ((bool)eventSystemLocator.eventSystem) { OnEventSystemDiscovered(eventSystemLocator.eventSystem); } } private void OnDisable() { eventSystemLocator.onEventSystemLost -= OnEventSystemLost; eventSystemLocator.onEventSystemDiscovered -= OnEventSystemDiscovered; if ((bool)eventSystemLocator.eventSystem) { OnEventSystemLost(eventSystemLocator.eventSystem); } EntitlementManager.onEntitlementsUpdated -= OnEntitlementsUpdated; UserProfile.onLoadoutChangedGlobal -= OnLoadoutChangedGlobal; } private void Update() { SurvivorDef survivorDef = null; NetworkUser networkUser = localUser?.currentNetworkUser; if ((bool)networkUser) { survivorDef = networkUser.GetSurvivorPreference(); } if ((object)currentSurvivorDef != survivorDef) { bool deselectButtons = false; if ((bool)currentSurvivorDef && (bool)survivorDef) { deselectButtons = currentSurvivorDef.cachedName != survivorDef.cachedName; } currentSurvivorDef = survivorDef; RebuildLocal(deselectButtons); } if (shouldRebuild) { shouldRebuild = false; RebuildLocal(); } UpdateSurvivorInfoPanel(); if (!RoR2Application.isInSinglePlayer) { bool flag = IsClientReady(); readyButton.gameObject.SetActive(!flag); unreadyButton.gameObject.SetActive(flag); } } private void OnEntitlementsUpdated() { shouldRebuild = true; } private void OnEventSystemDiscovered(MPEventSystem discoveredEventSystem) { eventSystem = discoveredEventSystem; localUser = (eventSystem ? eventSystem.localUser : null); shouldRebuild = true; } private void OnEventSystemLost(MPEventSystem lostEventSystem) { eventSystem = null; localUser = null; shouldRebuild = true; } private static UnlockableDef[] GenerateLoadoutAssociatedUnlockableDefs() { HashSet encounteredUnlockables = new HashSet(); foreach (SkillFamily allSkillFamily in SkillCatalog.allSkillFamilies) { for (int i = 0; i < allSkillFamily.variants.Length; i++) { TryAddUnlockableByDef(allSkillFamily.variants[i].unlockableDef); } } foreach (CharacterBody allBodyPrefabBodyBodyComponent in BodyCatalog.allBodyPrefabBodyBodyComponents) { SkinDef[] bodySkins = BodyCatalog.GetBodySkins(allBodyPrefabBodyBodyComponent.bodyIndex); for (int j = 0; j < bodySkins.Length; j++) { TryAddUnlockableByDef(bodySkins[j].unlockableDef); } } return encounteredUnlockables.ToArray(); void TryAddUnlockableByDef(UnlockableDef unlockableDef) { if (unlockableDef != null) { encounteredUnlockables.Add(unlockableDef); } } } private static bool UserHasAnyLoadoutUnlockables(LocalUser localUser) { if (loadoutAssociatedUnlockableDefs == null) { loadoutAssociatedUnlockableDefs = GenerateLoadoutAssociatedUnlockableDefs(); } UserProfile userProfile = localUser.userProfile; UnlockableDef[] array = loadoutAssociatedUnlockableDefs; foreach (UnlockableDef unlockableDef in array) { if (userProfile.HasUnlockable(unlockableDef)) { return true; } } return false; } private void RebuildLocal(bool deselectButtons = false) { Loadout loadout = Loadout.RequestInstance(); try { NetworkUser networkUser = localUser?.currentNetworkUser; SurvivorDef survivorDef = (networkUser ? networkUser.GetSurvivorPreference() : null); if (localUser.userProfile != null) { localUser.userProfile.loadout.EnforceUnlockables(localUser.userProfile); } localUser?.userProfile.CopyLoadout(loadout); BodyInfo bodyInfo = new BodyInfo(SurvivorCatalog.GetBodyIndexFromSurvivorIndex(survivorDef ? survivorDef.survivorIndex : SurvivorIndex.None)); string sourceText = string.Empty; string sourceText2 = string.Empty; if ((bool)survivorDef) { sourceText = Language.GetString(survivorDef.displayNameToken); sourceText2 = Language.GetString(survivorDef.descriptionToken); } survivorName.SetText(sourceText); survivorDescription.SetText(sourceText2); Image[] array = primaryColorImages; for (int i = 0; i < array.Length; i++) { array[i].color = bodyInfo.bodyColor; } TextMeshProUGUI[] array2 = primaryColorTexts; for (int i = 0; i < array2.Length; i++) { array2[i].color = bodyInfo.bodyColor; } List list = new List(); BuildSkillStripDisplayData(loadout, in bodyInfo, list); skillStripFillerAllocator.AllocateElements(0); skillStripAllocator.AllocateElements(list.Count); for (int j = 0; j < list.Count; j++) { RebuildStrip(skillStripAllocator.elements[j], list[j]); } if (deselectButtons) { DeselectButton(); } skillStripFillerAllocator.AllocateElements(Mathf.Max(0, 5 - list.Count)); skillStripFillerAllocator.MoveElementsToContainerEnd(); string empty = string.Empty; StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); stringBuilder.Append("/Loadout/Bodies/").Append(BodyCatalog.GetBodyName(bodyInfo.bodyIndex)).Append("/"); empty = stringBuilder.ToString(); HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); loadoutViewableTag.viewableName = empty; bool active = RoR2Application.isInMultiPlayer && !RoR2Application.isInLocalMultiPlayer; GameObject[] array3 = multiplayerOnlyObjects; for (int i = 0; i < array3.Length; i++) { array3[i].SetActive(active); } } finally { Loadout.ReturnInstance(loadout); } } public void RebuildOptions() { characterSelectBarController?.Build(); } public void DeselectButton() { if (eventSystem.currentSelectedGameObject != null) { eventSystem.SetSelectedGameObject(null); } } private void RebuildStrip(RectTransform skillStrip, StripDisplayData stripDisplayData) { GameObject gameObject = skillStrip.gameObject; Image component = skillStrip.Find("Inner/Icon").GetComponent(); HGTextMeshProUGUI component2 = skillStrip.Find("Inner/SkillDescriptionPanel/SkillName").GetComponent(); HGTextMeshProUGUI component3 = skillStrip.Find("Inner/SkillDescriptionPanel/SkillDescription").GetComponent(); HGButton component4 = skillStrip.gameObject.GetComponent(); if (stripDisplayData.enabled) { gameObject.SetActive(value: true); component.sprite = stripDisplayData.icon; component2.SetText(stripDisplayData.titleString); component2.color = stripDisplayData.primaryColor; component3.SetText(stripDisplayData.descriptionString); component4.hoverToken = stripDisplayData.keywordString; } else { gameObject.SetActive(value: false); } } private void BuildSkillStripDisplayData(Loadout loadout, in BodyInfo bodyInfo, List dest) { if (!bodyInfo.bodyPrefab || !bodyInfo.bodyPrefabBodyComponent || !bodyInfo.skillLocator) { return; } BodyIndex bodyIndex = bodyInfo.bodyIndex; SkillLocator skillLocator = bodyInfo.skillLocator; GenericSkill[] skillSlots = bodyInfo.skillSlots; Color bodyColor = bodyInfo.bodyColor; if (skillLocator.passiveSkill.enabled) { dest.Add(new StripDisplayData { enabled = true, primaryColor = bodyColor, icon = skillLocator.passiveSkill.icon, titleString = Language.GetString(skillLocator.passiveSkill.skillNameToken), descriptionString = Language.GetString(skillLocator.passiveSkill.skillDescriptionToken), keywordString = (string.IsNullOrEmpty(skillLocator.passiveSkill.keywordToken) ? "" : Language.GetString(skillLocator.passiveSkill.keywordToken)), actionName = "" }); } for (int i = 0; i < skillSlots.Length; i++) { GenericSkill genericSkill = skillSlots[i]; if (genericSkill.hideInCharacterSelect) { continue; } uint skillVariant = loadout.bodyLoadoutManager.GetSkillVariant(bodyIndex, i); SkillDef skillDef = genericSkill.skillFamily.variants[skillVariant].skillDef; string actionName = ""; switch (skillLocator.FindSkillSlot(genericSkill)) { case SkillSlot.Primary: actionName = "PrimarySkill"; break; case SkillSlot.Secondary: actionName = "SecondarySkill"; break; case SkillSlot.Utility: actionName = "UtilitySkill"; break; case SkillSlot.Special: actionName = "SpecialSkill"; break; } string keywordString = string.Empty; if (skillDef.keywordTokens != null) { StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); for (int j = 0; j < skillDef.keywordTokens.Length; j++) { string @string = Language.GetString(skillDef.keywordTokens[j]); stringBuilder.Append(@string).Append("\n\n"); } keywordString = stringBuilder.ToString(); stringBuilder = HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); } dest.Add(new StripDisplayData { enabled = true, primaryColor = bodyColor, icon = skillDef.icon, titleString = Language.GetString(skillDef.skillNameToken), descriptionString = Language.GetString(skillDef.skillDescriptionToken), keywordString = keywordString, actionName = actionName }); } } private void OnLoadoutChangedGlobal(UserProfile userProfile) { if (userProfile == localUser?.userProfile) { shouldRebuild = true; } } private void UpdateSurvivorInfoPanel() { if ((bool)eventSystem && eventSystem.currentInputSource == MPEventSystem.InputSource.MouseAndKeyboard) { shouldShowSurvivorInfoPanel = true; } activeSurvivorInfoPanel.SetActive(shouldShowSurvivorInfoPanel); inactiveSurvivorInfoPanel.SetActive(!shouldShowSurvivorInfoPanel); } public void SetSurvivorInfoPanelActive(bool active) { shouldShowSurvivorInfoPanel = active; UpdateSurvivorInfoPanel(); } private static bool InputPlayerIsAssigned(Player inputPlayer) { ReadOnlyCollection readOnlyInstancesList = NetworkUser.readOnlyInstancesList; for (int i = 0; i < readOnlyInstancesList.Count; i++) { if (readOnlyInstancesList[i].inputPlayer == inputPlayer) { return true; } } return false; } public bool IsClientReady() { int num = 0; if (!PreGameController.instance) { return false; } VoteController component = PreGameController.instance.GetComponent(); if (!component) { return false; } int i = 0; for (int voteCount = component.GetVoteCount(); i < voteCount; i++) { UserVote vote = component.GetVote(i); if ((bool)vote.networkUserObject && vote.receivedVote) { NetworkUser component2 = vote.networkUserObject.GetComponent(); if ((bool)component2 && component2.isLocalPlayer) { num++; } } } return num == NetworkUser.readOnlyLocalPlayersList.Count; } public void ClientSetReady() { if (PauseManager.isPaused) { return; } foreach (NetworkUser readOnlyLocalPlayers in NetworkUser.readOnlyLocalPlayersList) { if ((bool)readOnlyLocalPlayers && eventSystemLocator.eventSystem == readOnlyLocalPlayers.localUser.eventSystem) { readOnlyLocalPlayers.CallCmdSubmitVote(PreGameController.instance.gameObject, 0); } else { _ = (bool)readOnlyLocalPlayers; } } } public void ClientSetUnready() { foreach (NetworkUser readOnlyLocalPlayers in NetworkUser.readOnlyLocalPlayersList) { readOnlyLocalPlayers.CallCmdSubmitVote(PreGameController.instance.gameObject, -1); } } }