524 lines
15 KiB
C#
524 lines
15 KiB
C#
|
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<SkillLocator>() : 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<RectTransform> skillStripAllocator;
|
||
|
|
||
|
private UIElementAllocator<RectTransform> 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<MPEventSystemLocator>();
|
||
|
skillStripAllocator = new UIElementAllocator<RectTransform>(skillStripContainer, skillStripPrefab);
|
||
|
skillStripFillerAllocator = new UIElementAllocator<RectTransform>(skillStripContainer, skillStripFillerPrefab);
|
||
|
bool active = true;
|
||
|
loadoutHeaderButton.SetActive(active);
|
||
|
characterSelectBarController = GetComponentInChildren<CharacterSelectBarController>();
|
||
|
}
|
||
|
|
||
|
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<UnlockableDef> encounteredUnlockables = new HashSet<UnlockableDef>();
|
||
|
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<StripDisplayData> list = new List<StripDisplayData>();
|
||
|
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<Image>();
|
||
|
HGTextMeshProUGUI component2 = skillStrip.Find("Inner/SkillDescriptionPanel/SkillName").GetComponent<HGTextMeshProUGUI>();
|
||
|
HGTextMeshProUGUI component3 = skillStrip.Find("Inner/SkillDescriptionPanel/SkillDescription").GetComponent<HGTextMeshProUGUI>();
|
||
|
HGButton component4 = skillStrip.gameObject.GetComponent<HGButton>();
|
||
|
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<StripDisplayData> 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<NetworkUser> 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<VoteController>();
|
||
|
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<NetworkUser>();
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|