UI HUGE UPDATE

This commit is contained in:
FloatGaming
2026-06-25 11:48:56 +08:00
parent bd2a06f4c7
commit b2a1e307a4
1806 changed files with 359863 additions and 20822 deletions
+88 -9
View File
@@ -1,6 +1,7 @@
using System;
using UnityEngine;
using System.Collections.Generic;
using Spine.Unity;
#if UNITY_EDITOR
using UnityEditor;
#endif
@@ -46,6 +47,12 @@ public class AllyHero_SO : ScriptableObject
[Header("Inspector")]
public Sprite ally_hero_squareProfile;
[Header("spine")]
public SkeletonDataAsset ally_heroSpineData;
[Header("Skins")]
public string selectedSkinId;
[Header("Inspector")]
public Color ally_heroThemeColor;
@@ -494,6 +501,11 @@ public class AllyHero_SO : ScriptableObject
return "ally_equippedEquipment_" + ally_heroID;
}
private string GetSelectedSkinPrefsKey()
{
return "ally_selectedSkin_" + ally_heroID;
}
public void SaveEquippedSkillsToLocal()
{
var payload = new EquippedSkillGroupIDsPayload { equippedSkillGroupIDs = equippedSkillGroupIDs ?? new int[0] };
@@ -622,15 +634,6 @@ public class AllyHero_SO : ScriptableObject
}
#endif
equipmentSO[] runtimeEquipments = Resources.LoadAll<equipmentSO>("so/uEquip");
for (int i = 0; i < runtimeEquipments.Length; i++)
{
if (runtimeEquipments[i] != null && runtimeEquipments[i].name == equipmentId)
{
return runtimeEquipments[i];
}
}
var generatedEquipments = Bansonic.equipmentGenerator.GetRuntimeGeneratedEquipments();
for (int i = 0; i < generatedEquipments.Count; i++)
{
@@ -640,6 +643,15 @@ public class AllyHero_SO : ScriptableObject
}
}
equipmentSO[] runtimeEquipments = Resources.LoadAll<equipmentSO>("so/uEquip");
for (int i = 0; i < runtimeEquipments.Length; i++)
{
if (runtimeEquipments[i] != null && runtimeEquipments[i].name == equipmentId)
{
return runtimeEquipments[i];
}
}
return null;
}
@@ -693,6 +705,73 @@ public class AllyHero_SO : ScriptableObject
PlayerPrefs.Save();
}
public List<HeroSkinResolvedData> GetResolvedSkinSet(bool includeBaseSkin = true)
{
return HeroSkinResolver.GetSkinSet(this, includeBaseSkin);
}
public HeroSkinResolvedData GetResolvedSelectedSkin(bool fallbackToBase = true)
{
return HeroSkinResolver.GetSkinById(this, selectedSkinId, fallbackToBase);
}
public void SetSelectedSkin(string skinId, bool persist = true)
{
selectedSkinId = string.IsNullOrWhiteSpace(skinId) ? HeroSkinResolver.BuildBaseSkinId(ally_heroID) : skinId.Trim();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
if (persist)
{
UnityEditor.AssetDatabase.SaveAssets();
}
}
#endif
if (persist)
{
SaveSelectedSkinToLocal();
}
}
public void SaveSelectedSkinToLocal()
{
string safeSkinId = string.IsNullOrWhiteSpace(selectedSkinId) ? HeroSkinResolver.BuildBaseSkinId(ally_heroID) : selectedSkinId.Trim();
PlayerPrefs.SetString(GetSelectedSkinPrefsKey(), safeSkinId);
PlayerPrefs.Save();
}
public void LoadSelectedSkinFromLocal()
{
string key = GetSelectedSkinPrefsKey();
if (!PlayerPrefs.HasKey(key))
{
return;
}
selectedSkinId = PlayerPrefs.GetString(key, HeroSkinResolver.BuildBaseSkinId(ally_heroID));
if (string.IsNullOrWhiteSpace(selectedSkinId))
{
selectedSkinId = HeroSkinResolver.BuildBaseSkinId(ally_heroID);
}
}
public void ClearSelectedSkin()
{
selectedSkinId = HeroSkinResolver.BuildBaseSkinId(ally_heroID);
PlayerPrefs.DeleteKey(GetSelectedSkinPrefsKey());
PlayerPrefs.Save();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
UnityEditor.AssetDatabase.SaveAssets();
}
#endif
}
public void SetUnlocked(bool value)
{
if (isUnlocked == value) return;
+348
View File
@@ -0,0 +1,348 @@
using System;
using System.Collections.Generic;
using Spine.Unity;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public sealed class HeroSkinResolvedData
{
public string skinId;
public int heroId;
public string skinName;
public string ally_heroDesignation;
public bool isBaseSkin;
public bool isDefaultSkin;
public bool unlockedByDefault;
public string sourceDlcId;
public int sortOrder;
public Sprite ally_heroImage;
public Sprite ally_heroProfile;
public Sprite ally_heroSelectIcon;
public Sprite ally_heroIcon;
public Sprite ally_heroPoster;
public Sprite ally_hero_HD_image;
public Sprite ally_hero_squareProfile;
public SkeletonDataAsset ally_heroSpineData;
public Color ally_heroThemeColor;
public string skinDescription;
public AllyHero_SO ownerHero;
public HeroSkinSO sourceSkin;
}
public static class HeroSkinResolver
{
private const string RuntimeResourcesFolder = "so/heroSkins";
private const string EditorSearchFolder = "Assets/Resources/so/heroSkins";
private static HeroSkinSO[] cachedAllSkins;
private static readonly Dictionary<int, List<HeroSkinSO>> CachedByHeroId = new Dictionary<int, List<HeroSkinSO>>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetStaticState()
{
cachedAllSkins = null;
CachedByHeroId.Clear();
}
public static void InvalidateCache()
{
cachedAllSkins = null;
CachedByHeroId.Clear();
}
public static List<HeroSkinResolvedData> GetSkinSet(AllyHero_SO hero, bool includeBaseSkin = true)
{
List<HeroSkinResolvedData> result = new List<HeroSkinResolvedData>();
if (hero == null)
{
return result;
}
if (includeBaseSkin)
{
result.Add(BuildBaseSkin(hero));
}
List<HeroSkinSO> extras = GetExtraSkinsForHero(hero.ally_heroID);
for (int i = 0; i < extras.Count; i++)
{
HeroSkinSO extraSkin = extras[i];
if (extraSkin == null)
{
continue;
}
result.Add(BuildExtraSkin(hero, extraSkin));
}
result.Sort(CompareResolvedSkin);
return result;
}
public static HeroSkinResolvedData GetSkinById(AllyHero_SO hero, string skinId, bool fallbackToBase = true)
{
if (hero == null)
{
return null;
}
List<HeroSkinResolvedData> skinSet = GetSkinSet(hero, true);
string safeSkinId = string.IsNullOrWhiteSpace(skinId) ? string.Empty : skinId.Trim();
for (int i = 0; i < skinSet.Count; i++)
{
HeroSkinResolvedData skin = skinSet[i];
if (skin == null)
{
continue;
}
if (string.Equals(skin.skinId, safeSkinId, StringComparison.Ordinal))
{
return skin;
}
}
return fallbackToBase ? BuildBaseSkin(hero) : null;
}
public static HeroSkinResolvedData GetAccessibleSkinById(AllyHero_SO hero, string skinId, bool fallbackToBase = true)
{
HeroSkinResolvedData resolved = GetSkinById(hero, skinId, fallbackToBase);
if (DlcContentAccess.IsSkinAccessible(resolved))
{
return resolved;
}
return fallbackToBase ? BuildBaseSkin(hero) : null;
}
public static HeroSkinResolvedData BuildBaseSkin(AllyHero_SO hero)
{
if (hero == null)
{
return null;
}
return new HeroSkinResolvedData
{
skinId = BuildBaseSkinId(hero.ally_heroID),
heroId = hero.ally_heroID,
skinName = hero.ally_heroName,
ally_heroDesignation = hero.ally_heroDesignation,
isBaseSkin = true,
isDefaultSkin = true,
unlockedByDefault = true,
sourceDlcId = string.Empty,
sortOrder = int.MinValue,
ally_heroImage = hero.ally_heroImage,
ally_heroProfile = hero.ally_heroProfile,
ally_heroSelectIcon = hero.ally_heroSelectIcon,
ally_heroIcon = hero.ally_heroIcon,
ally_heroPoster = hero.ally_heroPoster,
ally_hero_HD_image = hero.ally_hero_HD_image,
ally_hero_squareProfile = hero.ally_hero_squareProfile,
ally_heroSpineData = hero.ally_heroSpineData,
ally_heroThemeColor = hero.ally_heroThemeColor,
skinDescription = hero.ally_heroDescription,
ownerHero = hero,
sourceSkin = null
};
}
public static string BuildBaseSkinId(int heroId)
{
return "hero_" + heroId + "_base";
}
public static List<HeroSkinSO> GetExtraSkinsForHero(int heroId)
{
EnsureCacheBuilt();
if (heroId <= 0)
{
return new List<HeroSkinSO>();
}
if (!CachedByHeroId.TryGetValue(heroId, out List<HeroSkinSO> list) || list == null)
{
return new List<HeroSkinSO>();
}
return new List<HeroSkinSO>(list);
}
public static HeroSkinSO[] LoadAllHeroSkinAssets()
{
EnsureCacheBuilt();
return cachedAllSkins ?? Array.Empty<HeroSkinSO>();
}
private static HeroSkinResolvedData BuildExtraSkin(AllyHero_SO hero, HeroSkinSO skin)
{
return new HeroSkinResolvedData
{
skinId = skin.GetResolvedSkinId(),
heroId = hero != null ? hero.ally_heroID : skin.heroId,
skinName = string.IsNullOrWhiteSpace(skin.skinName) ? skin.name : skin.skinName,
ally_heroDesignation = string.IsNullOrWhiteSpace(skin.ally_heroDesignation) && hero != null ? hero.ally_heroDesignation : skin.ally_heroDesignation,
isBaseSkin = false,
isDefaultSkin = skin.isDefaultSkin,
unlockedByDefault = skin.unlockedByDefault,
sourceDlcId = skin.sourceDlcId ?? string.Empty,
sortOrder = skin.sortOrder,
ally_heroImage = skin.ally_heroImage,
ally_heroProfile = skin.ally_heroProfile,
ally_heroSelectIcon = skin.ally_heroSelectIcon,
ally_heroIcon = skin.ally_heroIcon,
ally_heroPoster = skin.ally_heroPoster,
ally_hero_HD_image = skin.ally_hero_HD_image,
ally_hero_squareProfile = skin.ally_hero_squareProfile,
ally_heroSpineData = skin.ally_heroSpineData,
ally_heroThemeColor = skin.ally_heroThemeColor,
skinDescription = skin.skinDescription,
ownerHero = hero,
sourceSkin = skin
};
}
private static int CompareResolvedSkin(HeroSkinResolvedData left, HeroSkinResolvedData right)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return 1;
}
if (right == null)
{
return -1;
}
if (left.isBaseSkin != right.isBaseSkin)
{
return left.isBaseSkin ? -1 : 1;
}
int sortCompare = left.sortOrder.CompareTo(right.sortOrder);
if (sortCompare != 0)
{
return sortCompare;
}
return string.CompareOrdinal(left.skinId ?? string.Empty, right.skinId ?? string.Empty);
}
private static void EnsureCacheBuilt()
{
if (cachedAllSkins != null)
{
return;
}
cachedAllSkins = LoadAllHeroSkinsInternal();
CachedByHeroId.Clear();
for (int i = 0; i < cachedAllSkins.Length; i++)
{
HeroSkinSO skin = cachedAllSkins[i];
if (skin == null || skin.heroId <= 0)
{
continue;
}
if (!CachedByHeroId.TryGetValue(skin.heroId, out List<HeroSkinSO> list) || list == null)
{
list = new List<HeroSkinSO>();
CachedByHeroId[skin.heroId] = list;
}
list.Add(skin);
}
foreach (KeyValuePair<int, List<HeroSkinSO>> pair in CachedByHeroId)
{
pair.Value.Sort(CompareSkinAsset);
}
}
private static int CompareSkinAsset(HeroSkinSO left, HeroSkinSO right)
{
if (ReferenceEquals(left, right))
{
return 0;
}
if (left == null)
{
return 1;
}
if (right == null)
{
return -1;
}
int sortCompare = left.sortOrder.CompareTo(right.sortOrder);
if (sortCompare != 0)
{
return sortCompare;
}
return string.CompareOrdinal(left.GetResolvedSkinId(), right.GetResolvedSkinId());
}
private static HeroSkinSO[] LoadAllHeroSkinsInternal()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
string[] searchFolders = { EditorSearchFolder };
string[] guids = AssetDatabase.FindAssets("t:HeroSkinSO", searchFolders);
if (guids != null && guids.Length > 0)
{
List<HeroSkinSO> result = new List<HeroSkinSO>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
HeroSkinSO skin = AssetDatabase.LoadAssetAtPath<HeroSkinSO>(assetPath);
if (skin != null)
{
result.Add(skin);
}
}
return result.ToArray();
}
string[] fallbackGuids = AssetDatabase.FindAssets("t:HeroSkinSO");
List<HeroSkinSO> fallback = new List<HeroSkinSO>(fallbackGuids.Length);
for (int i = 0; i < fallbackGuids.Length; i++)
{
string assetPath = AssetDatabase.GUIDToAssetPath(fallbackGuids[i]);
HeroSkinSO skin = AssetDatabase.LoadAssetAtPath<HeroSkinSO>(assetPath);
if (skin != null)
{
fallback.Add(skin);
}
}
return fallback.ToArray();
}
#endif
HeroSkinSO[] loaded = RuntimeResourcesCache.LoadAllHeroSkins();
if (loaded != null && loaded.Length > 0)
{
return loaded;
}
return Resources.LoadAll<HeroSkinSO>(string.Empty) ?? Array.Empty<HeroSkinSO>();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fb5e938359e26ea4291b6676c626a331
+56
View File
@@ -0,0 +1,56 @@
using Spine.Unity;
using UnityEngine;
[CreateAssetMenu(fileName = "HeroSkin_", menuName = "SO_Data/HeroSkin")]
public class HeroSkinSO : ScriptableObject
{
[Header("Identity")]
public string skinId;
public int heroId;
public string skinName;
public string ally_heroDesignation;
[Header("Ownership")]
public bool isDefaultSkin;
public bool unlockedByDefault;
public string sourceDlcId;
[Header("Sort")]
public int sortOrder;
[Header("Inspector")]
public Sprite ally_heroImage;
[Header("Inspector")]
public Sprite ally_heroProfile;
[Header("Inspector")]
public Sprite ally_heroSelectIcon;
[Header("Inspector")]
public Sprite ally_heroIcon;
[Header("Inspector")]
public Sprite ally_heroPoster;
[Header("Inspector")]
public Sprite ally_hero_HD_image;
[Header("Inspector")]
public Sprite ally_hero_squareProfile;
[Header("spine")]
public SkeletonDataAsset ally_heroSpineData;
[Header("Inspector")]
public Color ally_heroThemeColor = Color.white;
[Header("Inspector")]
[TextArea(3, 10)]
public string skinDescription;
public string GetResolvedSkinId()
{
if (!string.IsNullOrWhiteSpace(skinId))
{
return skinId.Trim();
}
string safeName = string.IsNullOrWhiteSpace(name) ? "skin" : name.Trim();
return "hero_" + heroId + "_" + safeName;
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 507e69cfce29a0a4f970d9d29f28047b
+26
View File
@@ -58,6 +58,11 @@ public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler,
/// Documentation text normalized.
public void beSelected()
{
if (!IsCharacterUnlocked(characterID))
{
return;
}
if (!isSelected)
{
isSelected = true;
@@ -79,6 +84,7 @@ public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler,
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log($"CharacterCardView OnBeginDrag: {gameObject.name}");
if (!IsCharacterUnlocked(characterID)) return;
if (characterImage == null || characterImage.sprite == null) return;
if (rootCanvas == null) rootCanvas = GetComponentInParent<Canvas>();
if (rootCanvas == null) return;
@@ -147,6 +153,26 @@ public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler,
dragGhost.transform.localPosition = localPoint;
}
private bool IsCharacterUnlocked(int id)
{
if (id <= 0)
{
return false;
}
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero != null && hero.ally_heroID == id)
{
return hero.isUnlocked;
}
}
return false;
}
public void OnPointerEnter(PointerEventData eventData)
{
if (!enableHoverAnim || isDragging) return;
+270
View File
@@ -0,0 +1,270 @@
using System.Text;
using Bansonic;
using UnityEngine;
using UnityEngine.UI;
public class TeamShareApplyPanel : MonoBehaviour
{
[Header("Inputs")]
[SerializeField] private InputField schemeNameInputField;
[SerializeField] private InputField shareCodeInputField;
[Header("Buttons")]
[SerializeField] private Button copyButton;
[SerializeField] private Button applyButton;
[Header("Config")]
[SerializeField] private bool autoRefreshShareCode = true;
private string lockedVisibleSchemeName;
private void Awake()
{
BindUiEvents();
}
private void OnEnable()
{
RefreshShareCodeDisplay();
}
private void OnDestroy()
{
UnbindUiEvents();
}
public void RefreshShareCodeDisplay()
{
if (!autoRefreshShareCode || shareCodeInputField == null)
{
return;
}
string shareCode;
string errorMessage;
if (!TeamShareCodec.TryExportCurrentTeam(GetSchemeNameOrDefault(), out shareCode, out errorMessage))
{
if (!string.IsNullOrWhiteSpace(errorMessage))
{
gNotice.error.display(errorMessage);
}
return;
}
EnsureLockedVisibleSchemeName();
shareCodeInputField.text = ReplaceVisibleSchemeName(shareCode, lockedVisibleSchemeName);
}
public void HandleCopyClicked()
{
if (shareCodeInputField == null)
{
gNotice.error.display("\u672a\u914d\u7f6e\u7f16\u961f\u7801\u8f93\u5165\u6846");
return;
}
string content = string.IsNullOrWhiteSpace(shareCodeInputField.text) ? string.Empty : shareCodeInputField.text.Trim();
if (string.IsNullOrEmpty(content))
{
RefreshShareCodeDisplay();
content = string.IsNullOrWhiteSpace(shareCodeInputField.text) ? string.Empty : shareCodeInputField.text.Trim();
}
if (string.IsNullOrEmpty(content))
{
gNotice.error.display("\u5f53\u524d\u6ca1\u6709\u53ef\u590d\u5236\u7684\u7f16\u961f\u7801");
return;
}
GUIUtility.systemCopyBuffer = content;
gNotice.recommendation.display("\u5df2\u590d\u5236\u7f16\u961f\u5206\u4eab\u7801");
}
public void HandleApplyClicked()
{
if (shareCodeInputField == null)
{
gNotice.error.display("\u672a\u914d\u7f6e\u7f16\u961f\u7801\u8f93\u5165\u6846");
return;
}
string content = string.IsNullOrWhiteSpace(shareCodeInputField.text) ? string.Empty : shareCodeInputField.text.Trim();
if (string.IsNullOrEmpty(content))
{
gNotice.error.display("\u8bf7\u8f93\u5165\u7f16\u961f\u5206\u4eab\u7801");
return;
}
TeamShareApplyReport report;
string errorMessage;
if (!TeamShareCodec.TryApplyToCurrentTeam(content, out report, out errorMessage))
{
gNotice.error.display(string.IsNullOrWhiteSpace(errorMessage) ? "\u7f16\u961f\u7801\u5e94\u7528\u5931\u8d25" : errorMessage);
return;
}
lockedVisibleSchemeName = ExtractVisibleSchemeName(content);
if (schemeNameInputField != null && !string.IsNullOrWhiteSpace(report.schemeName))
{
schemeNameInputField.text = report.schemeName;
}
RefreshShareCodeDisplay();
if (report != null && report.messages != null && report.messages.Count > 0)
{
gNotice.error.display(BuildSkipReasonText(report));
return;
}
gNotice.recommendation.display("\u5df2\u5e94\u7528\u7f16\u961f\u5206\u4eab\u7801");
}
private void HandleSchemeNameChanged(string _)
{
RefreshShareCodeDisplay();
}
private void BindUiEvents()
{
UnbindUiEvents();
if (copyButton != null)
{
copyButton.onClick.AddListener(HandleCopyClicked);
}
if (applyButton != null)
{
applyButton.onClick.AddListener(HandleApplyClicked);
}
if (schemeNameInputField != null)
{
schemeNameInputField.onValueChanged.AddListener(HandleSchemeNameChanged);
}
}
private void UnbindUiEvents()
{
if (copyButton != null)
{
copyButton.onClick.RemoveListener(HandleCopyClicked);
}
if (applyButton != null)
{
applyButton.onClick.RemoveListener(HandleApplyClicked);
}
if (schemeNameInputField != null)
{
schemeNameInputField.onValueChanged.RemoveListener(HandleSchemeNameChanged);
}
}
private string GetSchemeNameOrDefault()
{
if (schemeNameInputField == null || string.IsNullOrWhiteSpace(schemeNameInputField.text))
{
return TeamShareSnapshot.DefaultSchemeName;
}
return schemeNameInputField.text;
}
private void EnsureLockedVisibleSchemeName()
{
if (!string.IsNullOrWhiteSpace(lockedVisibleSchemeName))
{
return;
}
if (shareCodeInputField != null && !string.IsNullOrWhiteSpace(shareCodeInputField.text))
{
lockedVisibleSchemeName = ExtractVisibleSchemeName(shareCodeInputField.text);
}
if (string.IsNullOrWhiteSpace(lockedVisibleSchemeName))
{
lockedVisibleSchemeName = GetSchemeNameOrDefault();
}
}
private static string ReplaceVisibleSchemeName(string shareCode, string visibleSchemeName)
{
if (string.IsNullOrWhiteSpace(shareCode))
{
return string.Empty;
}
int splitIndex = FindPrefixSplitIndex(shareCode);
if (splitIndex < 0)
{
return shareCode;
}
string safeVisibleName = string.IsNullOrWhiteSpace(visibleSchemeName) ? TeamShareSnapshot.DefaultSchemeName : visibleSchemeName.Trim();
return safeVisibleName + shareCode.Substring(splitIndex);
}
private static string ExtractVisibleSchemeName(string shareCode)
{
if (string.IsNullOrWhiteSpace(shareCode))
{
return TeamShareSnapshot.DefaultSchemeName;
}
int splitIndex = FindPrefixSplitIndex(shareCode);
if (splitIndex <= 0)
{
return TeamShareSnapshot.DefaultSchemeName;
}
return shareCode.Substring(0, splitIndex);
}
private static int FindPrefixSplitIndex(string shareCode)
{
if (string.IsNullOrWhiteSpace(shareCode))
{
return -1;
}
int index = shareCode.IndexOf("-T3", System.StringComparison.Ordinal);
if (index >= 0)
{
return index;
}
index = shareCode.IndexOf("-TS3", System.StringComparison.Ordinal);
if (index >= 0)
{
return index;
}
index = shareCode.IndexOf("-BT2", System.StringComparison.Ordinal);
if (index >= 0)
{
return index;
}
index = shareCode.IndexOf("-BT1", System.StringComparison.Ordinal);
return index;
}
private static string BuildSkipReasonText(TeamShareApplyReport report)
{
StringBuilder builder = new StringBuilder();
builder.Append("\u7f16\u961f\u7801\u5df2\u90e8\u5206\u5e94\u7528\uff0c\u4ee5\u4e0b\u5185\u5bb9\u88ab\u8df3\u8fc7\uff1a");
for (int i = 0; i < report.messages.Count; i++)
{
builder.Append('\n');
builder.Append(report.messages[i]);
}
return builder.ToString();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 509f30be3d4e6d14e8dd0f6a612583ae
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 94ebb2cfccd43df4fa08b450637a22f9
+278 -42
View File
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.UI;
public class newTeamSelector : MonoBehaviour
{
@@ -32,11 +32,17 @@ public class newTeamSelector : MonoBehaviour
[Tooltip("Optional override colors used for header images per slot. If not set, prefab's headerImage_colors will be used.")]
public Color[] overrideHeaderColors;
[Tooltip("Per-slot default images used when the slot is empty. Index 0-4 maps to slot 1-5.")]
public Sprite[] slotDefaultImage;
[Tooltip("If true, create slots automatically on Start")]
public bool instantiateOnStart = true;
[Tooltip("Delay skill list rebuild until after slot enter animation to reduce hitching.")]
public float delayedSkillRefreshSeconds = 0.75f;
[Header("Team Share")]
public TeamShareApplyPanel teamShareApplyPanel;
private readonly List<slots_heroSlots> createdSlots = new List<slots_heroSlots>();
private static AllyHero_SO[] cachedHeroes;
private static Dictionary<int, AllyHero_SO> cachedHeroesById;
@@ -57,6 +63,7 @@ public class newTeamSelector : MonoBehaviour
void Start()
{
RefreshTeamShareCodeDisplay();
if (instantiateOnStart)
CreateSlots();
StartCoroutine(LoadSelectedHeroesNextFrame());
@@ -172,31 +179,38 @@ public class newTeamSelector : MonoBehaviour
public void UpdateAndSaveSelectedHeroes()
{
if (createdSlots.Count > 0) selected_heroSlot01_heroID = createdSlots[0].heroSlot_heroID;
if (createdSlots.Count > 1) selected_heroSlot02_heroID = createdSlots[1].heroSlot_heroID;
if (createdSlots.Count > 2) selected_heroSlot03_heroID = createdSlots[2].heroSlot_heroID;
if (createdSlots.Count > 3) selected_heroSlot04_heroID = createdSlots[3].heroSlot_heroID;
if (createdSlots.Count > 4) selected_heroSlot05_heroID = createdSlots[4].heroSlot_heroID;
int[] ids = new int[slotCount > 0 ? slotCount : 5];
for (int i = 0; i < ids.Length; i++)
{
ids[i] = i < createdSlots.Count && createdSlots[i] != null ? Mathf.Max(0, createdSlots[i].heroSlot_heroID) : 0;
}
PlayerPrefs.SetInt("selected_heroSlot01_heroID", selected_heroSlot01_heroID);
PlayerPrefs.SetInt("selected_heroSlot02_heroID", selected_heroSlot02_heroID);
PlayerPrefs.SetInt("selected_heroSlot03_heroID", selected_heroSlot03_heroID);
PlayerPrefs.SetInt("selected_heroSlot04_heroID", selected_heroSlot04_heroID);
PlayerPrefs.SetInt("selected_heroSlot05_heroID", selected_heroSlot05_heroID);
ApplySelectedHeroIdsToFields(ids);
SyncCurrentTeamFromIds(ids);
SaveSelectedHeroIdsToPlayerPrefs(ids);
PlayerPrefs.Save();
Debug.Log("已保存当前英雄到 PlayerPrefs");
RefreshTeamShareCodeDisplay();
}
public void LoadSelectedHeroes()
{
if (createdSlots.Count == 0) return;
selected_heroSlot01_heroID = PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0);
selected_heroSlot02_heroID = PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0);
selected_heroSlot03_heroID = PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0);
selected_heroSlot04_heroID = PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0);
selected_heroSlot05_heroID = PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0);
int[] selectedIds;
bool loadedFromCurrentTeam = TryGetCurrentTeamHeroIds(out selectedIds);
if (!loadedFromCurrentTeam)
{
selectedIds = ReadSelectedHeroIdsFromPlayerPrefs();
SyncCurrentTeamFromIds(selectedIds);
}
else
{
SaveSelectedHeroIdsToPlayerPrefs(selectedIds);
}
ApplySelectedHeroIdsToFields(selectedIds);
// Set the slots
if (createdSlots.Count > 0) createdSlots[0].heroSlot_heroID = selected_heroSlot01_heroID;
@@ -205,52 +219,225 @@ public class newTeamSelector : MonoBehaviour
if (createdSlots.Count > 3) createdSlots[3].heroSlot_heroID = selected_heroSlot04_heroID;
if (createdSlots.Count > 4) createdSlots[4].heroSlot_heroID = selected_heroSlot05_heroID;
bool changed = false;
// Update each slot's UI
for (int i = 0; i < createdSlots.Count; i++)
{
var slot = createdSlots[i];
if (slot == null)
continue;
if (slot.heroSlot_heroID != 0)
{
// Find the hero SO
AllyHero_SO hero = FindHeroById(slot.heroSlot_heroID);
if (hero != null)
if (hero != null && hero.isUnlocked)
{
// Delay expensive skill UI refresh to avoid slot-enter stutters.
slot.SetSlot(slot.heroSlot_heroID, hero.ally_heroName, hero.ally_heroPoster, false);
slot.RefreshSlotVisualsFromCurrentHero(false);
}
else
{
slot.heroSlot_heroID = 0;
slot.RefreshSlotVisualsFromCurrentHero(false);
changed = true;
}
}
else
{
// Clear slot logic
slot.heroSlot_heroID = 0;
if (slot.heroSlot_heroName != null) slot.heroSlot_heroName.text = "未选择";
if (slot.heroImage != null)
{
// revert to default sprite if provided, otherwise keep transparent
if (slot.defaultHeroSprite != null)
{
slot.heroImage.sprite = slot.defaultHeroSprite;
slot.heroImage.color = new Color(slot.heroImage.color.r, slot.heroImage.color.g, slot.heroImage.color.b, 1f);
}
else
{
slot.heroImage.sprite = null;
slot.heroImage.color = new Color(slot.heroImage.color.r, slot.heroImage.color.g, slot.heroImage.color.b, 0f);
}
}
if (slot.heroLevelImage != null)
{
slot.heroLevelImage.sprite = null;
slot.heroLevelImage.color = new Color(1f, 1f, 1f, 0f);
}
// Update skill list
slot.UpdateSkillList(false);
slot.RefreshSlotVisualsFromCurrentHero(false);
}
}
if (changed)
{
UpdateAndSaveSelectedHeroes();
}
if (refreshSkillsRoutine != null)
StopCoroutine(refreshSkillsRoutine);
refreshSkillsRoutine = StartCoroutine(RefreshSkillsGradually());
StartCoroutine(ForceRefreshSlotNamesNextFrame());
RefreshTeamShareCodeDisplay();
}
private IEnumerator ForceRefreshSlotNamesNextFrame()
{
yield return null;
for (int i = 0; i < createdSlots.Count; i++)
{
var slot = createdSlots[i];
if (slot == null)
continue;
slot.RefreshSlotVisualsFromCurrentHero(false);
}
}
private int[] ReadSelectedHeroIdsFromPlayerPrefs()
{
int[] ids = new int[Mathf.Max(5, slotCount)];
if (ids.Length > 0) ids[0] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot01_heroID", 0));
if (ids.Length > 1) ids[1] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot02_heroID", 0));
if (ids.Length > 2) ids[2] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot03_heroID", 0));
if (ids.Length > 3) ids[3] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot04_heroID", 0));
if (ids.Length > 4) ids[4] = Mathf.Max(0, PlayerPrefs.GetInt("selected_heroSlot05_heroID", 0));
return ids;
}
private void SaveSelectedHeroIdsToPlayerPrefs(int[] ids)
{
PlayerPrefs.SetInt("selected_heroSlot01_heroID", GetIdAt(ids, 0));
PlayerPrefs.SetInt("selected_heroSlot02_heroID", GetIdAt(ids, 1));
PlayerPrefs.SetInt("selected_heroSlot03_heroID", GetIdAt(ids, 2));
PlayerPrefs.SetInt("selected_heroSlot04_heroID", GetIdAt(ids, 3));
PlayerPrefs.SetInt("selected_heroSlot05_heroID", GetIdAt(ids, 4));
}
private void ApplySelectedHeroIdsToFields(int[] ids)
{
selected_heroSlot01_heroID = GetIdAt(ids, 0);
selected_heroSlot02_heroID = GetIdAt(ids, 1);
selected_heroSlot03_heroID = GetIdAt(ids, 2);
selected_heroSlot04_heroID = GetIdAt(ids, 3);
selected_heroSlot05_heroID = GetIdAt(ids, 4);
}
private bool TryGetCurrentTeamHeroIds(out int[] ids)
{
ids = new int[Mathf.Max(5, slotCount)];
TeamManager teamManager = TeamManager.Instance;
if (teamManager == null)
return false;
TeamSetting currentTeam = null;
try
{
currentTeam = teamManager.getCurrentSelectedTeam();
}
catch
{
currentTeam = null;
}
if (currentTeam == null || currentTeam.teamIdList == null)
return false;
bool hasAnyHero = false;
for (int i = 0; i < ids.Length && i < currentTeam.teamIdList.Count; i++)
{
CharacterView slot = currentTeam.teamIdList[i];
ids[i] = slot != null ? Mathf.Max(0, slot.characterId) : 0;
if (ids[i] > 0)
hasAnyHero = true;
}
return hasAnyHero;
}
private void SyncCurrentTeamFromIds(int[] ids)
{
TeamManager teamManager = TeamManager.Instance;
if (teamManager == null)
return;
TeamSetting currentTeam = null;
try
{
currentTeam = teamManager.getCurrentSelectedTeam();
}
catch
{
currentTeam = null;
}
if (currentTeam == null)
{
currentTeam = new TeamSetting(new List<CharacterView>(), -1);
}
if (currentTeam.teamIdList == null)
{
currentTeam.teamIdList = new List<CharacterView>();
}
int targetCount = Mathf.Max(5, ids != null ? ids.Length : 0);
while (currentTeam.teamIdList.Count < targetCount)
{
currentTeam.teamIdList.Add(new CharacterView(0, null));
}
for (int i = 0; i < targetCount; i++)
{
int heroId = GetIdAt(ids, i);
string boundaryLevel = ResolveBoundaryLevel(heroId);
if (currentTeam.teamIdList[i] == null)
{
currentTeam.teamIdList[i] = new CharacterView(heroId, boundaryLevel);
}
else
{
currentTeam.teamIdList[i].characterId = heroId;
currentTeam.teamIdList[i].currentBoundaryLevel = boundaryLevel;
}
}
if (currentTeam.LeaderId <= 0 || !ContainsHeroId(currentTeam.teamIdList, currentTeam.LeaderId))
{
currentTeam.LeaderId = FindFirstHeroId(ids);
}
teamManager.setCurrentSelectedTeam(currentTeam, teamManager.currentSelectedTeam);
}
private static int GetIdAt(int[] ids, int index)
{
if (ids == null || index < 0 || index >= ids.Length)
return 0;
return Mathf.Max(0, ids[index]);
}
private static bool ContainsHeroId(List<CharacterView> teamIdList, int heroId)
{
if (teamIdList == null || heroId <= 0)
return false;
for (int i = 0; i < teamIdList.Count; i++)
{
CharacterView view = teamIdList[i];
if (view != null && view.characterId == heroId)
return true;
}
return false;
}
private static int FindFirstHeroId(int[] ids)
{
if (ids == null)
return -1;
for (int i = 0; i < ids.Length; i++)
{
if (ids[i] > 0)
return ids[i];
}
return -1;
}
private string ResolveBoundaryLevel(int heroId)
{
if (heroId <= 0)
return null;
AllyHero_SO hero = FindHeroById(heroId);
return hero != null ? GetRatingFromSO(hero) : null;
}
private IEnumerator RefreshSkillsGradually()
@@ -273,6 +460,15 @@ public class newTeamSelector : MonoBehaviour
}
TeamSelectorAnimController.PlaySkillsFlash(null);
refreshSkillsRoutine = null;
RefreshTeamShareCodeDisplay();
}
public void RefreshTeamShareCodeDisplay()
{
if (teamShareApplyPanel != null)
{
teamShareApplyPanel.RefreshShareCodeDisplay();
}
}
private AllyHero_SO FindHeroById(int id)
@@ -280,6 +476,11 @@ public class newTeamSelector : MonoBehaviour
EnsureHeroCache();
if (cachedHeroesById != null && cachedHeroesById.TryGetValue(id, out var hero))
return hero;
RebuildHeroCache();
if (cachedHeroesById != null && cachedHeroesById.TryGetValue(id, out hero))
return hero;
return null;
}
@@ -288,6 +489,12 @@ public class newTeamSelector : MonoBehaviour
if (cachedHeroesById != null && cachedHeroesById.Count > 0)
return;
RebuildHeroCache();
}
private static void RebuildHeroCache()
{
cachedHeroes = Resources.LoadAll<AllyHero_SO>("so/ally");
if (cachedHeroes == null || cachedHeroes.Length == 0)
cachedHeroes = Resources.LoadAll<AllyHero_SO>("");
@@ -301,4 +508,33 @@ public class newTeamSelector : MonoBehaviour
cachedHeroesById[hero.ally_heroID] = hero;
}
}
private string GetRatingFromSO(AllyHero_SO so)
{
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C";
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
foreach (var level in so.levelStats)
{
if (level != null)
sorted.Add(level);
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
int currentExp = so.ally_currentEXP;
int selectedIndex = 0;
for (int i = 0; i < sorted.Count; i++)
{
if (currentExp >= sorted[i].requiredEXP)
selectedIndex = i;
else
break;
}
if (selectedIndex <= 0) return "C";
if (selectedIndex == 1) return "B";
if (selectedIndex == 2) return "A";
return "S";
}
}
+26
View File
@@ -232,6 +232,11 @@ public class teamSettingPanel : MonoBehaviour
List<CharacterView> selectAbleCharacters = teamManager.SelectableCharacterList;
foreach (CharacterView c in selectAbleCharacters) //id and level
{
if (!IsCharacterUnlocked(c != null ? c.characterId : 0))
{
continue;
}
GameObject characterObj = Instantiate(teamCharacterPrefab, teamCharacterViewContent.transform);
CharacterCardView ccv = characterObj.GetComponent<CharacterCardView>();
TeamCharacterDataInfo tcdi = getCharacterViewInfo(c.characterId);
@@ -251,6 +256,27 @@ public class teamSettingPanel : MonoBehaviour
}
}
}
private bool IsCharacterUnlocked(int characterID)
{
if (characterID <= 0)
{
return false;
}
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero != null && hero.ally_heroID == characterID)
{
return hero.isUnlocked;
}
}
return false;
}
private bool checkExistenceStatus(List<CharacterView> currentTeam,int characterID)
{
foreach (CharacterView c in currentTeam)
@@ -1531,7 +1531,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
@@ -1623,8 +1623,8 @@ MonoBehaviour:
entry:
path: MusicPic
text: "\u8FD9\u4E9B\u662F\u6211\u4ECE\u623F\u95F4\u91CC\u641C\u96C6\u6765\u7684\uFF0C\u60A8\u6311\u4E24\u9996\uFF1F\u4E0D\u5FC5\u5728\u610F\u6211\u5BF9\u60A8\u54C1\u5473\u7684\u770B\u6CD5\u3002\u4E0D\u8FC7\u5728\u67D0\u4E9B\u60C5\u51B5\u4E0B...\u6211\u662F\u4F1A\u5207\u6389\u7684\u54E6\u3002"
iconPath: "Assets/artworks/Ul_Ul/\u65B0\u7248\u4E3B\u754C\u9762\uFF082\uFF09/\u97F3\u4E50"
icon: {fileID: 7605814977653748970, guid: fd7da29c38f47ea4695ec23268ebdd9d, type: 3}
iconPath: Assets/__ui_new/hallway/ui_icon_maininterface_lowebar_music.png
icon: {fileID: 21300000, guid: e43a4710f4f3c4846a18d85cb4a109b5, type: 3}
--- !u!1 &1207165874131126891
GameObject:
m_ObjectHideFlags: 0
@@ -2659,7 +2659,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
@@ -5821,7 +5821,7 @@ Canvas:
m_AdditionalShaderChannelsFlag: 25
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 1235
m_SortingOrder: 1233
m_TargetDisplay: 0
--- !u!114 &5224241407313794548
MonoBehaviour:
@@ -8279,7 +8279,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0.5499878, y: 4.5996094}
m_AnchoredPosition: {x: 0.54992676, y: 4.5996094}
m_SizeDelta: {x: 0, y: 52.4}
m_Pivot: {x: 0, y: 0}
--- !u!114 &2708908290101177617
@@ -9836,7 +9836,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
@@ -10734,7 +10734,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
@@ -129,6 +129,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
private string currentGuideScene = string.Empty;
private static btmandtopController activeInstance;
private RectTransform topNavigationRoot;
private static readonly List<string> sceneHistory = new List<string>();
private static bool sceneHistoryHooked = false;
@@ -201,6 +202,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
closeGuideButton.onClick.AddListener(CloseGuideDisplay);
BindNewBackButtons();
EnsureTopNavigationFront();
EnsureMusicPicRoot();
SetupMusicPicDefault();
@@ -333,6 +335,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
CurrentOverlayPanelsVisible = overlayVisible;
GlobalOverlayPanelVisibilityChanged?.Invoke(overlayVisible);
}
EnsureTopNavigationFront();
}
@@ -887,6 +891,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
BroadcastOverlayPanelsVisibility();
}
});
EnsureTopNavigationFront();
return;
}
@@ -905,6 +910,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
});
}
BroadcastOverlayPanelsVisibility();
EnsureTopNavigationFront();
}
private void BroadcastSettingsVisibility(bool visible)
@@ -931,6 +937,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private bool AreOverlayPanelsVisible()
{
return (settingsInstance != null && settingsInstance.activeInHierarchy)
|| (userInfoInstance != null && userInfoInstance.activeInHierarchy)
|| (storeInstance != null && storeInstance.activeInHierarchy)
|| (showLevelInstance != null && showLevelInstance.activeInHierarchy)
|| (emailInstance != null && emailInstance.activeInHierarchy)
@@ -944,6 +951,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
lastOverlayPanelsVisibilityState = visible;
CurrentOverlayPanelsVisible = visible;
GlobalOverlayPanelVisibilityChanged?.Invoke(visible);
EnsureTopNavigationFront();
}
private void RegisterManagedPanel(GameObject panel, System.Action closeAction)
@@ -1091,6 +1099,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
PlacePanelBelowSettings(instance);
BroadcastOverlayPanelsVisibility();
EnsureTopNavigationFront();
return true;
}
@@ -1237,10 +1246,18 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
settings_navButton.onClick.RemoveListener(navSettingsAction);
settings_navButton.onClick.AddListener(navSettingsAction);
}
EnsureTopNavigationFront();
}
private void ResolveNewBackButtons()
{
Transform topRoot = FindChildRecursive(transform, "TOP");
if (topRoot != null)
{
topNavigationRoot = topRoot as RectTransform;
}
Transform navRoot = FindChildRecursive(transform, "New back");
if (navRoot == null)
{
@@ -1257,6 +1274,89 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
settings_navButton = FindButtonByName(navRoot, "settings");
}
private void EnsureTopNavigationFront()
{
ResolveTopNavigationRoot();
if (topNavigationRoot == null)
{
return;
}
Transform parent = topNavigationRoot.parent;
if (parent == null)
{
return;
}
if (topNavigationRoot.GetSiblingIndex() != parent.childCount - 1)
{
topNavigationRoot.SetAsLastSibling();
}
RefreshTopNavigationGeometry();
}
private void ResolveTopNavigationRoot()
{
if (topNavigationRoot != null)
{
return;
}
Transform topRoot = FindChildRecursive(transform, "TOP");
if (topRoot == null)
{
GameObject sceneTop = FindByName("TOP");
if (sceneTop != null)
{
topRoot = sceneTop.transform;
}
}
if (topRoot != null)
{
topNavigationRoot = topRoot as RectTransform;
}
}
private void RefreshTopNavigationGeometry()
{
if (topNavigationRoot == null)
{
return;
}
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate(topNavigationRoot);
RectTransform navRoot = null;
if (back_navButton != null)
{
navRoot = back_navButton.transform.parent as RectTransform;
}
if (navRoot == null)
{
Transform candidate = FindChildRecursive(topNavigationRoot, "New back");
navRoot = candidate as RectTransform;
}
if (navRoot != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(navRoot);
}
if (back_navButton != null)
{
RectTransform backRect = back_navButton.transform as RectTransform;
if (backRect != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(backRect);
}
}
Canvas.ForceUpdateCanvases();
}
private static Transform FindChildRecursive(Transform root, string childName)
{
if (root == null || string.IsNullOrEmpty(childName)) return null;
@@ -0,0 +1,69 @@
using System.Collections.Generic;
using UnityEngine;
public static class DlcContentAccess
{
public static bool IsSongAccessible(SongData song)
{
if (song == null)
{
return false;
}
dlcData owningDlc = SongDlcContentResolver.GetOwningDlc(song);
return owningDlc == null || DlcOwnershipService.IsDlcOwned(owningDlc);
}
public static bool IsSkinAccessible(HeroSkinResolvedData skin)
{
if (skin == null)
{
return false;
}
if (skin.isBaseSkin || skin.unlockedByDefault)
{
return true;
}
if (string.IsNullOrWhiteSpace(skin.sourceDlcId))
{
return true;
}
return DlcOwnershipService.IsDlcOwned(skin.sourceDlcId);
}
public static HeroSkinResolvedData GetAccessibleSelectedSkin(AllyHero_SO hero, bool fallbackToBase = true)
{
if (hero == null)
{
return null;
}
HeroSkinResolvedData selected = hero.GetResolvedSelectedSkin(fallbackToBase);
if (IsSkinAccessible(selected))
{
return selected;
}
return fallbackToBase ? HeroSkinResolver.BuildBaseSkin(hero) : null;
}
public static List<HeroSkinResolvedData> GetAccessibleSkinSet(AllyHero_SO hero, bool includeBaseSkin = true)
{
List<HeroSkinResolvedData> all = hero != null ? hero.GetResolvedSkinSet(includeBaseSkin) : new List<HeroSkinResolvedData>();
List<HeroSkinResolvedData> result = new List<HeroSkinResolvedData>(all.Count);
for (int i = 0; i < all.Count; i++)
{
HeroSkinResolvedData skin = all[i];
if (IsSkinAccessible(skin))
{
result.Add(skin);
}
}
return result;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f91afe93cf46f314a9350783be3258f4
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO;
[Serializable]
public class DlcManifestFile
{
public string manifestId;
public string manifestVersion = "1";
public List<DlcManifestEntry> entries = new List<DlcManifestEntry>();
}
[Serializable]
public class DlcManifestEntry
{
public bool enabled = true;
public string dlcKey;
public string version;
public string catalogPath;
public bool autoDownloadDependencies = true;
public string[] dependencyKeys;
public string[] dlcDataLabels;
public string[] songLabels;
public string[] songContentLabels;
public string[] heroSkinLabels;
public string GetSafeDlcKey()
{
return string.IsNullOrWhiteSpace(dlcKey) ? string.Empty : dlcKey.Trim();
}
public string[] GetDlcDataLabels()
{
return GetResolvedLabels(dlcDataLabels, "dlc:" + GetSafeDlcKey() + ":dlcdata");
}
public string[] GetSongLabels()
{
return GetResolvedLabels(songLabels, "dlc:" + GetSafeDlcKey() + ":songs");
}
public string[] GetSongContentLabels()
{
return GetResolvedLabels(songContentLabels, "dlc:" + GetSafeDlcKey() + ":songcontent");
}
public string[] GetHeroSkinLabels()
{
return GetResolvedLabels(heroSkinLabels, "dlc:" + GetSafeDlcKey() + ":heroskins");
}
public string[] GetDependencyKeys()
{
List<string> result = new List<string>();
AppendUnique(result, dependencyKeys);
AppendUnique(result, GetDlcDataLabels());
AppendUnique(result, GetSongLabels());
AppendUnique(result, GetSongContentLabels());
AppendUnique(result, GetHeroSkinLabels());
return result.ToArray();
}
private static string[] GetResolvedLabels(string[] labels, string fallbackLabel)
{
List<string> result = new List<string>();
AppendUnique(result, labels);
if (result.Count == 0 && !string.IsNullOrWhiteSpace(fallbackLabel))
{
result.Add(fallbackLabel.Trim());
}
return result.ToArray();
}
private static void AppendUnique(List<string> target, string[] values)
{
if (target == null || values == null)
{
return;
}
for (int i = 0; i < values.Length; i++)
{
string value = values[i];
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
string trimmed = value.Trim();
if (!target.Contains(trimmed))
{
target.Add(trimmed);
}
}
}
}
public sealed class DlcManifestRuntimeEntry
{
public string manifestId;
public string manifestSourcePath;
public string manifestDirectory;
public DlcManifestEntry entry;
public string resolvedCatalogPath;
public string GetRuntimePackageId()
{
string safeManifest = string.IsNullOrWhiteSpace(manifestId) ? "manifest" : manifestId.Trim();
string safeDlc = entry != null ? entry.GetSafeDlcKey() : string.Empty;
if (string.IsNullOrWhiteSpace(safeDlc))
{
safeDlc = Path.GetFileNameWithoutExtension(manifestSourcePath ?? string.Empty);
}
return safeManifest + "::" + safeDlc;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0b78a2ac9a0db9e4fbc6b91bac3ac68d
@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class DlcManifestService
{
private static readonly string[] ManifestFilePatterns =
{
"*.json"
};
private const string RuntimeFolderName = "DLC";
private const string ManifestFolderName = "manifests";
public static List<DlcManifestRuntimeEntry> LoadInstalledEntries()
{
List<DlcManifestRuntimeEntry> result = new List<DlcManifestRuntimeEntry>();
HashSet<string> visitedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string[] roots = GetManifestSearchRoots();
for (int i = 0; i < roots.Length; i++)
{
string root = roots[i];
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
{
continue;
}
CollectManifestEntries(root, result, visitedFiles);
}
return result;
}
public static string[] GetManifestSearchRoots()
{
List<string> result = new List<string>();
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName));
string playerRoot = GetPlayerRootDirectory();
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName, ManifestFolderName));
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName));
return result.ToArray();
}
public static DlcManifestFile ParseManifestJson(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
return null;
}
try
{
return JsonUtility.FromJson<DlcManifestFile>(json);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to parse manifest json: " + ex.Message);
return null;
}
}
private static void CollectManifestEntries(
string root,
List<DlcManifestRuntimeEntry> result,
HashSet<string> visitedFiles)
{
for (int patternIndex = 0; patternIndex < ManifestFilePatterns.Length; patternIndex++)
{
string pattern = ManifestFilePatterns[patternIndex];
string[] files;
try
{
files = Directory.GetFiles(root, pattern, SearchOption.AllDirectories);
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to enumerate manifest files in '" + root + "': " + ex.Message);
continue;
}
for (int i = 0; i < files.Length; i++)
{
string manifestPath = files[i];
if (string.IsNullOrWhiteSpace(manifestPath))
{
continue;
}
string fullPath = Path.GetFullPath(manifestPath);
if (!visitedFiles.Add(fullPath))
{
continue;
}
TryLoadManifestFile(fullPath, result);
}
}
}
private static void TryLoadManifestFile(string manifestPath, List<DlcManifestRuntimeEntry> result)
{
try
{
string json = File.ReadAllText(manifestPath);
DlcManifestFile file = ParseManifestJson(json);
if (file == null || file.entries == null || file.entries.Count == 0)
{
return;
}
string manifestDirectory = Path.GetDirectoryName(manifestPath) ?? string.Empty;
string manifestId = string.IsNullOrWhiteSpace(file.manifestId)
? Path.GetFileNameWithoutExtension(manifestPath)
: file.manifestId.Trim();
for (int i = 0; i < file.entries.Count; i++)
{
DlcManifestEntry entry = file.entries[i];
if (entry == null || !entry.enabled)
{
continue;
}
string resolvedCatalogPath = ResolveCatalogPath(entry.catalogPath, manifestDirectory);
if (string.IsNullOrWhiteSpace(resolvedCatalogPath))
{
Debug.LogWarning("[DLC] Manifest entry '" + manifestId + "' has no valid catalogPath.");
continue;
}
result.Add(new DlcManifestRuntimeEntry
{
manifestId = manifestId,
manifestSourcePath = manifestPath,
manifestDirectory = manifestDirectory,
entry = entry,
resolvedCatalogPath = resolvedCatalogPath
});
}
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Failed to load manifest '" + manifestPath + "': " + ex.Message);
}
}
private static string ResolveCatalogPath(string catalogPath, string manifestDirectory)
{
if (string.IsNullOrWhiteSpace(catalogPath))
{
return string.Empty;
}
string trimmed = catalogPath.Trim();
if (trimmed.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
trimmed.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
trimmed.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
return trimmed;
}
string combined = trimmed;
if (!Path.IsPathRooted(combined))
{
combined = Path.Combine(manifestDirectory ?? string.Empty, combined);
}
try
{
return new Uri(Path.GetFullPath(combined)).AbsoluteUri;
}
catch
{
return string.Empty;
}
}
private static string GetPlayerRootDirectory()
{
try
{
string dataPath = Application.dataPath;
if (string.IsNullOrWhiteSpace(dataPath))
{
return string.Empty;
}
DirectoryInfo parent = Directory.GetParent(dataPath);
return parent != null ? parent.FullName : string.Empty;
}
catch
{
return string.Empty;
}
}
private static void AppendIfValid(List<string> result, string path)
{
if (result == null || string.IsNullOrWhiteSpace(path))
{
return;
}
string fullPath;
try
{
fullPath = Path.GetFullPath(path);
}
catch
{
return;
}
if (!result.Contains(fullPath))
{
result.Add(fullPath);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d1f397358f56e0f4db600090bb09da94
@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using UnityEngine;
#if !UNITY_WEBGL
using Steamworks;
#endif
public enum DlcEntitlementSource
{
None = 0,
BuiltIn = 1,
LocalFlag = 2,
Steam = 3,
RemoteCache = 4
}
public readonly struct DlcEntitlementState
{
public readonly bool owned;
public readonly bool installed;
public readonly DlcEntitlementSource source;
public readonly string dlcKey;
public DlcEntitlementState(string key, bool ownedValue, bool installedValue, DlcEntitlementSource entitlementSource)
{
dlcKey = key ?? string.Empty;
owned = ownedValue;
installed = installedValue;
source = entitlementSource;
}
}
public static class DlcOwnershipService
{
private const string RemoteOwnershipPrefsPrefix = "dlc_remote_owned_";
private const string LocalOverridePrefsPrefix = "dlc_local_override_";
private static readonly Dictionary<string, DlcEntitlementState> Cache =
new Dictionary<string, DlcEntitlementState>(StringComparer.OrdinalIgnoreCase);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetState()
{
Cache.Clear();
}
public static bool IsDlcOwned(dlcData dlc)
{
return GetEntitlement(dlc).owned;
}
public static bool IsDlcOwned(string dlcKey)
{
dlcData dlc = FindDlcByKey(dlcKey);
if (dlc == null)
{
return true;
}
return IsDlcOwned(dlc);
}
public static DlcEntitlementState GetEntitlement(dlcData dlc)
{
if (dlc == null)
{
return new DlcEntitlementState(string.Empty, true, true, DlcEntitlementSource.BuiltIn);
}
string key = dlc.GetResolvedDlcKey();
if (Cache.TryGetValue(key, out DlcEntitlementState cached))
{
return cached;
}
DlcEntitlementState resolved = ResolveEntitlement(dlc, key);
Cache[key] = resolved;
return resolved;
}
public static void SetRemoteOwned(string dlcKey, bool owned)
{
if (string.IsNullOrWhiteSpace(dlcKey))
{
return;
}
string safeKey = dlcKey.Trim();
PlayerPrefs.SetInt(RemoteOwnershipPrefsPrefix + safeKey, owned ? 1 : 0);
PlayerPrefs.Save();
Cache.Remove(safeKey);
}
public static void SetLocalOwnershipOverride(string dlcKey, bool? owned)
{
if (string.IsNullOrWhiteSpace(dlcKey))
{
return;
}
string safeKey = dlcKey.Trim();
string prefsKey = LocalOverridePrefsPrefix + safeKey;
if (owned.HasValue)
{
PlayerPrefs.SetInt(prefsKey, owned.Value ? 1 : 0);
}
else
{
PlayerPrefs.DeleteKey(prefsKey);
}
PlayerPrefs.Save();
Cache.Remove(safeKey);
}
public static void InvalidateCache()
{
Cache.Clear();
}
private static DlcEntitlementState ResolveEntitlement(dlcData dlc, string key)
{
if (!dlc.ShouldEnforceEntitlement())
{
return new DlcEntitlementState(key, true, dlc.builtInContent, DlcEntitlementSource.BuiltIn);
}
bool? localOverride = ReadOverride(key);
if (localOverride.HasValue)
{
bool overrideValue = localOverride.Value;
return new DlcEntitlementState(key, overrideValue, overrideValue, DlcEntitlementSource.LocalFlag);
}
if (dlc.dlcIsUnlocked)
{
return new DlcEntitlementState(key, true, true, DlcEntitlementSource.LocalFlag);
}
#if !UNITY_WEBGL
if (dlc.steamAppId > 0 && SteamManager.Initialized)
{
try
{
AppId_t appId = new AppId_t((uint)Mathf.Max(0, dlc.steamAppId));
bool installed = SteamApps.BIsDlcInstalled(appId);
if (installed)
{
return new DlcEntitlementState(key, true, true, DlcEntitlementSource.Steam);
}
}
catch (Exception ex)
{
Debug.LogWarning("[DLC] Steam entitlement check failed for '" + key + "': " + ex.Message);
}
}
#endif
bool remoteOwned = PlayerPrefs.GetInt(RemoteOwnershipPrefsPrefix + key, 0) == 1;
if (remoteOwned)
{
return new DlcEntitlementState(key, true, dlc.builtInContent, DlcEntitlementSource.RemoteCache);
}
return new DlcEntitlementState(key, false, false, DlcEntitlementSource.None);
}
private static bool? ReadOverride(string key)
{
string prefsKey = LocalOverridePrefsPrefix + key;
if (!PlayerPrefs.HasKey(prefsKey))
{
return null;
}
return PlayerPrefs.GetInt(prefsKey, 0) == 1;
}
private static dlcData FindDlcByKey(string dlcKey)
{
if (string.IsNullOrWhiteSpace(dlcKey))
{
return null;
}
string safeKey = dlcKey.Trim();
dlcData[] allDlcs = RuntimeResourcesCache.LoadAllDlcs();
for (int i = 0; i < allDlcs.Length; i++)
{
dlcData dlc = allDlcs[i];
if (dlc == null)
{
continue;
}
if (string.Equals(dlc.GetResolvedDlcKey(), safeKey, StringComparison.OrdinalIgnoreCase))
{
return dlc;
}
}
return null;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85520393e3bc7584cb8246dfe82f8f23
@@ -0,0 +1,309 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
public static class DlcRemoteContentService
{
private sealed class RuntimeLoadedState
{
public readonly List<AsyncOperationHandle> retainedAssetHandles = new List<AsyncOperationHandle>();
public readonly List<AsyncOperationHandle> retainedCatalogHandles = new List<AsyncOperationHandle>();
public readonly List<DlcRuntimePackage> packages = new List<DlcRuntimePackage>();
}
private sealed class DlcRemoteContentRunner : MonoBehaviour
{
private void Start()
{
if (DlcRemoteContentService.autoRefreshOnStart)
{
DlcRemoteContentService.RefreshInstalledDlc();
}
}
}
private static DlcRemoteContentRunner runner;
private static RuntimeLoadedState activeState = new RuntimeLoadedState();
private static bool autoRefreshOnStart = true;
private static bool refreshRequestedOnBoot;
public static bool IsRefreshing { get; private set; }
public static event Action<bool> RefreshCompleted;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void EnsureRunner()
{
if (runner != null)
{
return;
}
GameObject go = new GameObject(nameof(DlcRemoteContentService));
UnityEngine.Object.DontDestroyOnLoad(go);
runner = go.AddComponent<DlcRemoteContentRunner>();
refreshRequestedOnBoot = false;
}
public static void SetAutoRefreshOnStart(bool enabled)
{
autoRefreshOnStart = enabled;
}
public static void RefreshInstalledDlc()
{
EnsureRunner();
if (runner == null)
{
return;
}
if (IsRefreshing)
{
refreshRequestedOnBoot = true;
return;
}
runner.StartCoroutine(RefreshInstalledDlcRoutine());
}
public static void ClearRuntimeDlc()
{
ReleaseState(activeState);
activeState = new RuntimeLoadedState();
DlcRuntimeRegistry.ClearAll();
InvalidateRuntimeCaches();
}
private static IEnumerator RefreshInstalledDlcRoutine()
{
if (IsRefreshing)
{
yield break;
}
IsRefreshing = true;
refreshRequestedOnBoot = false;
List<DlcManifestRuntimeEntry> manifests = DlcManifestService.LoadInstalledEntries();
RuntimeLoadedState nextState = new RuntimeLoadedState();
bool success = true;
for (int i = 0; i < manifests.Count; i++)
{
DlcManifestRuntimeEntry manifest = manifests[i];
if (manifest == null || manifest.entry == null)
{
continue;
}
yield return LoadManifestEntry(manifest, nextState, result => success &= result);
}
if (success)
{
RuntimeLoadedState oldState = activeState;
activeState = nextState;
DlcRuntimeRegistry.ReplaceAll(nextState.packages);
InvalidateRuntimeCaches();
ReleaseState(oldState);
}
else
{
ReleaseState(nextState);
}
IsRefreshing = false;
RefreshCompleted?.Invoke(success);
if (refreshRequestedOnBoot)
{
RefreshInstalledDlc();
}
}
private static IEnumerator LoadManifestEntry(
DlcManifestRuntimeEntry manifest,
RuntimeLoadedState state,
Action<bool> reportResult)
{
bool localSuccess = true;
AsyncOperationHandle<IResourceLocator> catalogHandle = Addressables.LoadContentCatalogAsync(manifest.resolvedCatalogPath, false);
yield return catalogHandle;
if (catalogHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogWarning("[DLC] Failed to load catalog: " + manifest.resolvedCatalogPath);
reportResult?.Invoke(false);
yield break;
}
state.retainedCatalogHandles.Add(catalogHandle);
if (manifest.entry.autoDownloadDependencies)
{
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
for (int i = 0; i < dependencyKeys.Length; i++)
{
string dependencyKey = dependencyKeys[i];
if (string.IsNullOrWhiteSpace(dependencyKey))
{
continue;
}
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
yield return downloadHandle;
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
{
state.retainedAssetHandles.Add(downloadHandle);
}
else
{
localSuccess = false;
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
Addressables.Release(downloadHandle);
}
}
}
List<dlcData> loadedDlcs = new List<dlcData>();
List<SongData> loadedSongs = new List<SongData>();
List<SongDlcContentSO> loadedSongContents = new List<SongDlcContentSO>();
List<HeroSkinSO> loadedHeroSkins = new List<HeroSkinSO>();
yield return LoadAssetsByLabels(manifest.entry.GetDlcDataLabels(), loadedDlcs, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
DlcRuntimePackage package = new DlcRuntimePackage
{
packageId = manifest.GetRuntimePackageId(),
dlcKey = manifest.entry.GetSafeDlcKey(),
version = manifest.entry.version ?? string.Empty,
sourceCatalogPath = manifest.resolvedCatalogPath,
dlcs = loadedDlcs.ToArray(),
songs = loadedSongs.ToArray(),
songContents = loadedSongContents.ToArray(),
heroSkins = loadedHeroSkins.ToArray()
};
state.packages.Add(package);
reportResult?.Invoke(localSuccess);
}
private static IEnumerator LoadAssetsByLabels<T>(
string[] labels,
List<T> output,
List<AsyncOperationHandle> retainedHandles,
Action<bool> reportResult) where T : UnityEngine.Object
{
if (labels == null || labels.Length == 0)
{
reportResult?.Invoke(true);
yield break;
}
List<object> keys = new List<object>(labels.Length);
for (int i = 0; i < labels.Length; i++)
{
if (!string.IsNullOrWhiteSpace(labels[i]))
{
keys.Add(labels[i].Trim());
}
}
if (keys.Count == 0)
{
reportResult?.Invoke(true);
yield break;
}
AsyncOperationHandle<IList<T>> loadHandle = Addressables.LoadAssetsAsync<T>(keys, null, Addressables.MergeMode.Union, false);
yield return loadHandle;
if (loadHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogWarning("[DLC] Failed to load runtime assets of type " + typeof(T).Name + ".");
Addressables.Release(loadHandle);
reportResult?.Invoke(false);
yield break;
}
retainedHandles.Add(loadHandle);
IList<T> result = loadHandle.Result;
if (result != null)
{
for (int i = 0; i < result.Count; i++)
{
T asset = result[i];
if (asset != null && !output.Contains(asset))
{
output.Add(asset);
}
}
}
reportResult?.Invoke(true);
}
private static void InvalidateRuntimeCaches()
{
RuntimeResourcesCache.InvalidateAll();
SongDlcContentResolver.InvalidateCache();
HeroSkinResolver.InvalidateCache();
DlcOwnershipService.InvalidateCache();
SongDataLibrary library = SongDataLibrary.Instance;
if (library != null)
{
library.RefreshLibrary();
}
loadDlcListPrefab[] uiPanels = UnityEngine.Object.FindObjectsByType<loadDlcListPrefab>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < uiPanels.Length; i++)
{
if (uiPanels[i] != null && uiPanels[i].isActiveAndEnabled)
{
uiPanels[i].RefreshUI();
}
}
}
private static void ReleaseState(RuntimeLoadedState state)
{
if (state == null)
{
return;
}
for (int i = 0; i < state.retainedAssetHandles.Count; i++)
{
AsyncOperationHandle handle = state.retainedAssetHandles[i];
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
state.retainedAssetHandles.Clear();
for (int i = 0; i < state.retainedCatalogHandles.Count; i++)
{
AsyncOperationHandle handle = state.retainedCatalogHandles[i];
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
state.retainedCatalogHandles.Clear();
state.packages.Clear();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d480f2906126fc845b6ddc6ae9d7d3e0
@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using UnityEngine;
public sealed class DlcRuntimePackage
{
public string packageId;
public string dlcKey;
public string version;
public string sourceCatalogPath;
public dlcData[] dlcs = Array.Empty<dlcData>();
public SongData[] songs = Array.Empty<SongData>();
public SongDlcContentSO[] songContents = Array.Empty<SongDlcContentSO>();
public HeroSkinSO[] heroSkins = Array.Empty<HeroSkinSO>();
}
public static class DlcRuntimeRegistry
{
private static readonly List<DlcRuntimePackage> Packages = new List<DlcRuntimePackage>();
private static readonly Dictionary<string, dlcData> DlcsByKey =
new Dictionary<string, dlcData>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<int, SongData> SongsById =
new Dictionary<int, SongData>();
private static readonly Dictionary<string, SongDlcContentSO> SongContentsById =
new Dictionary<string, SongDlcContentSO>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, HeroSkinSO> HeroSkinsById =
new Dictionary<string, HeroSkinSO>(StringComparer.OrdinalIgnoreCase);
private static dlcData[] cachedDlcs = Array.Empty<dlcData>();
private static SongData[] cachedSongs = Array.Empty<SongData>();
private static SongDlcContentSO[] cachedSongContents = Array.Empty<SongDlcContentSO>();
private static HeroSkinSO[] cachedHeroSkins = Array.Empty<HeroSkinSO>();
public static event Action RuntimeContentChanged;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetState()
{
ClearSilently();
}
public static bool HasRuntimeContent
{
get { return Packages.Count > 0; }
}
public static IReadOnlyList<DlcRuntimePackage> GetPackages()
{
return Packages;
}
public static dlcData[] GetAllDlcs()
{
return cachedDlcs;
}
public static SongData[] GetAllSongs()
{
return cachedSongs;
}
public static SongDlcContentSO[] GetAllSongContents()
{
return cachedSongContents;
}
public static HeroSkinSO[] GetAllHeroSkins()
{
return cachedHeroSkins;
}
public static void ReplaceAll(IList<DlcRuntimePackage> packages)
{
ClearSilently();
if (packages != null)
{
for (int i = 0; i < packages.Count; i++)
{
RegisterPackageInternal(packages[i]);
}
}
RebuildSnapshots();
RuntimeContentChanged?.Invoke();
}
public static void ClearAll()
{
ClearSilently();
RuntimeContentChanged?.Invoke();
}
private static void ClearSilently()
{
Packages.Clear();
DlcsByKey.Clear();
SongsById.Clear();
SongContentsById.Clear();
HeroSkinsById.Clear();
cachedDlcs = Array.Empty<dlcData>();
cachedSongs = Array.Empty<SongData>();
cachedSongContents = Array.Empty<SongDlcContentSO>();
cachedHeroSkins = Array.Empty<HeroSkinSO>();
}
private static void RegisterPackageInternal(DlcRuntimePackage package)
{
if (package == null)
{
return;
}
Packages.Add(package);
if (package.dlcs != null)
{
for (int i = 0; i < package.dlcs.Length; i++)
{
dlcData dlc = package.dlcs[i];
if (dlc == null)
{
continue;
}
string key = dlc.GetResolvedDlcKey();
if (string.IsNullOrWhiteSpace(key))
{
continue;
}
DlcsByKey[key] = dlc;
}
}
if (package.songs != null)
{
for (int i = 0; i < package.songs.Length; i++)
{
SongData song = package.songs[i];
if (song == null || song.songID <= 0)
{
continue;
}
SongsById[song.songID] = song;
}
}
if (package.songContents != null)
{
for (int i = 0; i < package.songContents.Length; i++)
{
SongDlcContentSO content = package.songContents[i];
if (content == null)
{
continue;
}
string contentId = content.GetResolvedContentId();
if (string.IsNullOrWhiteSpace(contentId))
{
continue;
}
SongContentsById[contentId] = content;
}
}
if (package.heroSkins != null)
{
for (int i = 0; i < package.heroSkins.Length; i++)
{
HeroSkinSO skin = package.heroSkins[i];
if (skin == null)
{
continue;
}
string skinId = skin.GetResolvedSkinId();
if (string.IsNullOrWhiteSpace(skinId))
{
continue;
}
HeroSkinsById[skinId] = skin;
}
}
}
private static void RebuildSnapshots()
{
cachedDlcs = new dlcData[DlcsByKey.Count];
DlcsByKey.Values.CopyTo(cachedDlcs, 0);
cachedSongs = new SongData[SongsById.Count];
SongsById.Values.CopyTo(cachedSongs, 0);
cachedSongContents = new SongDlcContentSO[SongContentsById.Count];
SongContentsById.Values.CopyTo(cachedSongContents, 0);
cachedHeroSkins = new HeroSkinSO[HeroSkinsById.Count];
HeroSkinsById.Values.CopyTo(cachedHeroSkins, 0);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 01c44e9909c1a1544bbf058e015c3bc8
@@ -74,6 +74,11 @@ public sealed class PlayerSkillService : MonoBehaviour
return EnsureInstance().GetSelectedSkillIndexInternal();
}
public static bool IsRevealLockedSkillsEnabled()
{
return EnsureInstance().IsSkillEnabledInternal(RevealLockedIdolSkillsSkillIndex);
}
public static int GetSkillSwitchCooldownRemainingMatches()
{
return EnsureInstance().GetSkillSwitchCooldownRemainingMatchesInternal();
@@ -109,9 +114,14 @@ public sealed class PlayerSkillService : MonoBehaviour
EnsureInstance().HandleSettlementCompletedInternal(idolScore);
}
public static void AppendDynamicStoreItems(List<storeItemSO> target, Sprite coinSprite, Sprite materialSprite)
public static void AppendDynamicStoreItems(
List<storeItemSO> target,
Sprite coinSprite,
Sprite materialSprite,
Sprite memoryBuyPlanSprite,
Sprite memorySellPlanSprite)
{
EnsureInstance().AppendDynamicStoreItemsInternal(target, coinSprite, materialSprite);
EnsureInstance().AppendDynamicStoreItemsInternal(target, coinSprite, materialSprite, memoryBuyPlanSprite, memorySellPlanSprite);
}
public static bool IsPlayerSkillStoreItem(storeItemSO item)
@@ -290,7 +300,12 @@ public sealed class PlayerSkillService : MonoBehaviour
}
}
private void AppendDynamicStoreItemsInternal(List<storeItemSO> target, Sprite coinSprite, Sprite materialSprite)
private void AppendDynamicStoreItemsInternal(
List<storeItemSO> target,
Sprite coinSprite,
Sprite materialSprite,
Sprite memoryBuyPlanSprite,
Sprite memorySellPlanSprite)
{
InitializeIfNeeded();
ResolveSkillAssetIfNeeded();
@@ -303,12 +318,12 @@ public sealed class PlayerSkillService : MonoBehaviour
if (IsSkillEnabledInternal(MemoryBuySkillIndex))
{
target.Add(CreateMemoryBuyStoreItem(materialSprite));
target.Add(CreateMemoryBuyStoreItem(memoryBuyPlanSprite != null ? memoryBuyPlanSprite : materialSprite));
}
if (IsSkillEnabledInternal(MemorySellSkillIndex))
{
target.Add(CreateMemorySellStoreItem(coinSprite));
target.Add(CreateMemorySellStoreItem(memorySellPlanSprite != null ? memorySellPlanSprite : coinSprite));
}
}
@@ -59,17 +59,17 @@ public static class RuntimeResourcesCache
public static SongData[] LoadAllSongs()
{
return LoadAll<SongData>(string.Empty);
return MergeArrays(LoadAll<SongData>(string.Empty), DlcRuntimeRegistry.GetAllSongs());
}
public static SongData[] LoadSongIndex()
{
return LoadAll<SongData>("song_songIndex");
return MergeArrays(LoadAll<SongData>("song_songIndex"), DlcRuntimeRegistry.GetAllSongs());
}
public static SongData[] LoadSongsFromPath(string path)
{
return LoadAll<SongData>(path);
return MergeArrays(LoadAll<SongData>(path), DlcRuntimeRegistry.GetAllSongs());
}
public static AllyHero_SO[] LoadAllAllyHeroes()
@@ -84,7 +84,17 @@ public static class RuntimeResourcesCache
public static dlcData[] LoadAllDlcs()
{
return LoadAll<dlcData>(string.Empty);
return MergeArrays(LoadAll<dlcData>(string.Empty), DlcRuntimeRegistry.GetAllDlcs());
}
public static HeroSkinSO[] LoadAllHeroSkins()
{
return MergeArrays(LoadAll<HeroSkinSO>("so/heroSkins"), DlcRuntimeRegistry.GetAllHeroSkins());
}
public static SongDlcContentSO[] LoadAllSongDlcContents()
{
return MergeArrays(LoadAll<SongDlcContentSO>("so/songDlcContents"), DlcRuntimeRegistry.GetAllSongContents());
}
public static expBottlesSO[] LoadAllExpBottles()
@@ -117,4 +127,38 @@ public static class RuntimeResourcesCache
{
return string.IsNullOrWhiteSpace(path) ? string.Empty : path.Trim().Replace("\\", "/");
}
private static T[] MergeArrays<T>(T[] builtIn, T[] runtime) where T : UnityEngine.Object
{
if (runtime == null || runtime.Length == 0)
{
return builtIn ?? Array.Empty<T>();
}
if (builtIn == null || builtIn.Length == 0)
{
return runtime;
}
List<T> result = new List<T>(builtIn.Length + runtime.Length);
for (int i = 0; i < builtIn.Length; i++)
{
T item = builtIn[i];
if (item != null && !result.Contains(item))
{
result.Add(item);
}
}
for (int i = 0; i < runtime.Length; i++)
{
T item = runtime[i];
if (item != null && !result.Contains(item))
{
result.Add(item);
}
}
return result.ToArray();
}
}
@@ -27,6 +27,10 @@ public class friendCardPrefab : MonoBehaviour
[Header("offline heibai")]
[SerializeField] private Material offline_material;
[Header("currently chatting")]
[SerializeField] private Sprite chattingSprite;
[SerializeField] private Sprite normalSprite;
private Material _profileBorderMaterial;
private Material _friendProfileMaterial;
private Coroutine _avatarLoadRoutine;
@@ -39,11 +43,20 @@ public class friendCardPrefab : MonoBehaviour
private Action<string> _openDetails;
private Action<string> _openPrivateConversation;
private bool _bindingToggle;
private Sprite _defaultProfileBorderSprite;
private bool _subscribedToChatState;
private void Awake()
{
_profileBorderMaterial = profileBorder != null ? profileBorder.material : null;
_friendProfileMaterial = friendProfile != null ? friendProfile.material : null;
_defaultProfileBorderSprite = profileBorder != null ? profileBorder.sprite : null;
}
private void OnEnable()
{
SubscribeChatState();
RefreshChattingVisual();
}
private void OnDestroy()
@@ -55,6 +68,12 @@ public class friendCardPrefab : MonoBehaviour
}
ReleaseRuntimeAvatarResources();
UnsubscribeChatState();
}
private void OnDisable()
{
UnsubscribeChatState();
}
public void Bind(FriendCardViewData data, Action<string, bool> onStarChanged, Action<string> onOpenDetails, Action<string> onOpenPrivateConversation)
@@ -102,6 +121,8 @@ public class friendCardPrefab : MonoBehaviour
StopCoroutine(_avatarLoadRoutine);
}
_avatarLoadRoutine = StartCoroutine(LoadAvatarRoutine(data));
RefreshChattingVisual();
}
private void HandleStarToggleChanged(bool isOn)
@@ -143,6 +164,53 @@ public class friendCardPrefab : MonoBehaviour
{
friendProfile.material = profileMaterial;
}
RefreshChattingVisual();
}
private void SubscribeChatState()
{
if (_subscribedToChatState)
{
return;
}
globalChatSystem.ActivePrivateConversationChanged += HandleActivePrivateConversationChanged;
_subscribedToChatState = true;
}
private void UnsubscribeChatState()
{
if (!_subscribedToChatState)
{
return;
}
globalChatSystem.ActivePrivateConversationChanged -= HandleActivePrivateConversationChanged;
_subscribedToChatState = false;
}
private void HandleActivePrivateConversationChanged(string _)
{
RefreshChattingVisual();
}
private void RefreshChattingVisual()
{
if (profileBorder == null)
{
return;
}
bool isCurrentPrivateTarget = globalChatSystem.IsViewingPrivateConversation(_steamId);
Sprite targetSprite = isCurrentPrivateTarget
? (chattingSprite != null ? chattingSprite : normalSprite)
: (normalSprite != null ? normalSprite : _defaultProfileBorderSprite);
if (targetSprite != null)
{
profileBorder.sprite = targetSprite;
}
}
private IEnumerator LoadAvatarRoutine(FriendCardViewData data)
+26 -24
View File
@@ -119,8 +119,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0.8834}
m_SizeDelta: {x: 0, y: -1.7668}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7327486215132614031
CanvasRenderer:
@@ -143,7 +143,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 0.54901963, g: 0.3529412, b: 0.09411765, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -151,8 +151,8 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
m_FontSize: 14
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
m_FontSize: 15
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -230,8 +230,8 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: e2c536559de04a84bae8d1e1752633e7, type: 3}
m_Type: 0
m_Sprite: {fileID: 21300000, guid: c11bc9633cbeb384498dffc7701810cd, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -298,7 +298,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -453,6 +453,8 @@ MonoBehaviour:
isStarFriend: {fileID: 6035437394128710287}
friendOffline_blackMask: {fileID: 7553350037342771812}
offline_material: {fileID: 2100000, guid: 7701c15f96b443d408ac83222c9b869a, type: 2}
chattingSprite: {fileID: 21300000, guid: d21d271b6bfa1b14fbfe7f66f86336f7, type: 3}
normalSprite: {fileID: 21300000, guid: 3da9887018b7160418ba2ffc7eb426d4, type: 3}
--- !u!1 &2767911455405563055
GameObject:
m_ObjectHideFlags: 0
@@ -489,8 +491,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 88.483, y: -27.128}
m_SizeDelta: {x: 79.168, y: 25.744}
m_AnchoredPosition: {x: 88.483, y: -28.206614}
m_SizeDelta: {x: 79.168, y: 27.9011}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5476373932116253543
CanvasRenderer:
@@ -520,8 +522,8 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 9bc3fa56d039611458eeaa6637e47a1c, type: 3}
m_Type: 1
m_Sprite: {fileID: 21300000, guid: 9dcab5132d08efb4d981e8795f5b4bef, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -608,8 +610,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0.6237}
m_SizeDelta: {x: 0, y: -1.2472}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4946082784691209714
CanvasRenderer:
@@ -632,7 +634,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 0.09803922, g: 0.32941177, b: 0.6, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -640,8 +642,8 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bc54bd51ea8b84448ba1b65311872862, type: 3}
m_FontSize: 14
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
m_FontSize: 15
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -921,8 +923,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 173, y: -27.128}
m_SizeDelta: {x: 79.168, y: 25.744}
m_AnchoredPosition: {x: 173, y: -27.789886}
m_SizeDelta: {x: 79.168, y: 27.0669}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5483242628005795134
CanvasRenderer:
@@ -952,7 +954,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: ecdc06bf2954ac64a9447a76825dd902, type: 3}
m_Sprite: {fileID: 21300000, guid: 51e58a5536bbaac47bcc2942875d4b17, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
@@ -1064,15 +1066,15 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.78431374}
m_Color: {r: 0, g: 0, b: 0, a: 0.19607843}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: e2c536559de04a84bae8d1e1752633e7, type: 3}
m_Type: 0
m_Sprite: {fileID: 21300000, guid: d21d271b6bfa1b14fbfe7f66f86336f7, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -1139,7 +1141,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -34,8 +34,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: -4}
m_SizeDelta: {x: 143.18, y: 12.573}
m_AnchoredPosition: {x: 30.39, y: -2.7}
m_SizeDelta: {x: 143.18, y: 11.216}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7766852143305575412
CanvasRenderer:
@@ -115,8 +115,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -9.2046, y: -17.465}
m_SizeDelta: {x: 63.9929, y: 17.071}
m_AnchoredPosition: {x: -9.2046, y: -18.441}
m_SizeDelta: {x: 63.9929, y: 19.0228}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &752898224887842841
CanvasRenderer:
@@ -139,15 +139,15 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.4198113, g: 1, b: 0.5353115, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_Sprite: {fileID: 21300000, guid: 339aa1c69b6ac86429db52c478d9affc, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -240,7 +240,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 220, y: 70}
m_SizeDelta: {x: 220, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8398379083156494532
CanvasRenderer:
@@ -270,7 +270,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 537c5cf3bf53c2f4a9404248acd2d336, type: 3}
m_Sprite: {fileID: 21300000, guid: fdf9576f0470ca144ae19e6cd41ec750, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -389,8 +389,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: -1.7067986, y: 1.0861998}
m_SizeDelta: {x: -3.4136, y: -2.1722}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4345137299206345491
CanvasRenderer:
@@ -413,7 +413,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 0.09803922, g: 0.32941177, b: 0.6, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -421,8 +421,8 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -469,7 +469,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_SizeDelta: {x: 220, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &2770649873537820046
MonoBehaviour:
@@ -522,7 +522,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 30.39, y: 14}
m_AnchoredPosition: {x: 30.39, y: 14.9}
m_SizeDelta: {x: 143.18, y: 26}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3709975975265226361
@@ -603,8 +603,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 63.383705, y: -17.465}
m_SizeDelta: {x: 62.1074, y: 17.071}
m_AnchoredPosition: {x: 63.383705, y: -18.441}
m_SizeDelta: {x: 62.1074, y: 19.0228}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9058361928411038020
CanvasRenderer:
@@ -627,15 +627,15 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 0.7684825, b: 0.759434, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Type: 1
m_Sprite: {fileID: 21300000, guid: 51e58a5536bbaac47bcc2942875d4b17, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
@@ -722,8 +722,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_AnchoredPosition: {x: -1.7068, y: 1.0862}
m_SizeDelta: {x: -3.4137, y: -2.1722}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3245753848347248243
CanvasRenderer:
@@ -746,7 +746,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_Color: {r: 0.54901963, g: 0.3529412, b: 0.09411765, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
@@ -754,8 +754,8 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 10
m_Font: {fileID: 12800000, guid: d254d20d93651ae448ff13d10dff30ab, type: 3}
m_FontSize: 12
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -64,6 +64,7 @@ public class friendSystem : MonoBehaviour
[Header("text")]
public Text defaultText;
public Text onlineFriendsCountText;
[Header("sync")]
[SerializeField] private float steamFriendResyncIntervalSeconds = 8f;
@@ -88,8 +89,10 @@ public class friendSystem : MonoBehaviour
private bool _dropdownInitialized;
private bool _isRefreshing;
private bool _lastRefreshFailed;
private bool _hasResolvedFriendCounts;
private Coroutine _resyncCoroutine;
private bool _languageSubscribed;
private bool _chatStateSubscribed;
private static List<string> _startupSteamFriendIds = new List<string>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
@@ -102,9 +105,12 @@ public class friendSystem : MonoBehaviour
{
LocalizationService.EnsureInitialized();
ClearDisplayedFriends();
_hasResolvedFriendCounts = false;
SetDefaultText(DefaultStatusLoading);
UpdateOnlineFriendsCountText();
InitializeDropdown();
SubscribeLanguageChanged();
SubscribeChatStateChanged();
if (ArenaRoomService.Instance != null)
{
@@ -122,6 +128,7 @@ public class friendSystem : MonoBehaviour
private void OnDisable()
{
UnsubscribeLanguageChanged();
UnsubscribeChatStateChanged();
if (ArenaRoomService.Instance != null)
{
ArenaRoomService.Instance.OnFriendsListChanged -= HandleFriendsListChanged;
@@ -199,6 +206,7 @@ public class friendSystem : MonoBehaviour
&& friendsTask.Result != null
&& friendsTask.Result.success)
{
_hasResolvedFriendCounts = true;
ReplaceGameFriends(friendsTask.Result.friends);
}
else
@@ -266,6 +274,7 @@ public class friendSystem : MonoBehaviour
private void HandleFriendsListChanged(IReadOnlyList<SocialFriendEntry> friends)
{
_lastRefreshFailed = false;
_hasResolvedFriendCounts = true;
RefreshSteamFriendCache();
ReplaceGameFriends(friends);
Render();
@@ -428,6 +437,7 @@ public class friendSystem : MonoBehaviour
if (friendsDisplayContent == null || friendCardPrefab == null)
{
SetDefaultText(DefaultStatusServiceUnavailable);
UpdateOnlineFriendsCountText();
return;
}
@@ -467,6 +477,7 @@ public class friendSystem : MonoBehaviour
}
UpdateDefaultTextState(target.Count);
UpdateOnlineFriendsCountText();
}
private void UpdateDefaultTextState(int visibleCount)
@@ -496,6 +507,32 @@ public class friendSystem : MonoBehaviour
defaultText.text = message ?? string.Empty;
}
private void UpdateOnlineFriendsCountText()
{
if (onlineFriendsCountText == null)
{
return;
}
if (!_hasResolvedFriendCounts)
{
onlineFriendsCountText.text = "-/-";
return;
}
int totalCount = _mergedFriends.Count;
int onlineCount = 0;
foreach (FriendCardViewData friend in _mergedFriends.Values)
{
if (friend != null && friend.IsOnline)
{
onlineCount++;
}
}
onlineFriendsCountText.text = $"{onlineCount}/{totalCount}";
}
private IEnumerable<FriendCardViewData> ApplyFilter(IEnumerable<FriendCardViewData> source)
{
FriendFilterType filter = GetSelectedFilter();
@@ -571,6 +608,38 @@ public class friendSystem : MonoBehaviour
globalChatSystem.OpenPrivateConversation(steamId);
}
private void SubscribeChatStateChanged()
{
if (_chatStateSubscribed)
{
return;
}
globalChatSystem.ActivePrivateConversationChanged += HandleActivePrivateConversationChanged;
_chatStateSubscribed = true;
}
private void UnsubscribeChatStateChanged()
{
if (!_chatStateSubscribed)
{
return;
}
globalChatSystem.ActivePrivateConversationChanged -= HandleActivePrivateConversationChanged;
_chatStateSubscribed = false;
}
private void HandleActivePrivateConversationChanged(string _)
{
if (!isActiveAndEnabled)
{
return;
}
Render();
}
private void SubscribeLanguageChanged()
{
if (_languageSubscribed)
@@ -294,19 +294,19 @@ public class BeatmapManager : MonoBehaviour
}
// Documentation text normalized.
public bool LoadBeatmapFromSongData(SongData song, int difficulty, bool parseOnly = false)
{
if (song == null)
{
Debug.LogWarning("LoadBeatmapFromSongData: song is null");
return false;
}
TextAsset ta = song.GetChartFile(difficulty);
if (ta == null)
{
Debug.LogWarning($"LoadBeatmapFromSongData: chart TextAsset for difficulty {difficulty} is null on song {song.songName}");
return false;
}
public bool LoadBeatmapFromSongData(SongData song, int difficulty, bool parseOnly = false)
{
if (song == null)
{
Debug.LogWarning("LoadBeatmapFromSongData: song is null");
return false;
}
TextAsset ta = song.GetResolvedChartFile(difficulty);
if (ta == null)
{
Debug.LogWarning($"LoadBeatmapFromSongData: chart TextAsset for difficulty {difficulty} is null on song {song.songName}");
return false;
}
if (VerboseLogs) Debug.Log($"LoadBeatmapFromSongData: loading chart for song {song.songName}, difficulty {difficulty}, parseOnly={parseOnly}, chartSize={(ta.text != null ? ta.text.Length : 0)}");
return LoadBeatmapFromTextAsset(ta, parseOnly);
}
@@ -339,17 +339,18 @@ public class BeatmapManager : MonoBehaviour
if (VerboseLogs) Debug.Log("BeatmapManager.Start: chart parsed from SongData, attempting to assign audio and pause system");
// try to assign audio to GameManager.musicSource
var gm = SceneObjectLookupCache.FindAny<GameManager>();
if (gm != null && gm.musicSource != null)
{
if (assignedSongData != null && assignedSongData.audioFile != null)
{
gm.musicSource.clip = assignedSongData.audioFile;
gm.musicSource.loop = false;
if (VerboseLogs) Debug.Log("Assigned SongData.audioFile to GameManager.musicSource.clip (from SO)");
}
else if (!string.IsNullOrEmpty(parsedMusicFile))
{
var gm = SceneObjectLookupCache.FindAny<GameManager>();
if (gm != null && gm.musicSource != null)
{
AudioClip resolvedAudio = assignedSongData != null ? assignedSongData.GetResolvedAudioFile() : null;
if (resolvedAudio != null)
{
gm.musicSource.clip = resolvedAudio;
gm.musicSource.loop = false;
if (VerboseLogs) Debug.Log("Assigned resolved SongData audio to GameManager.musicSource.clip (from SO)");
}
else if (!string.IsNullOrEmpty(parsedMusicFile))
{
if (VerboseLogs) Debug.Log($"Attempting Resources.Load for audio: {parsedMusicFile}");
var ac = Resources.Load<AudioClip>(parsedMusicFile);
if (ac != null)
@@ -374,22 +375,23 @@ public class BeatmapManager : MonoBehaviour
}
// Assign fullscreen image from SongData to background images if available
if (assignedSongData != null && assignedSongData.fullscreen_songPicture != null)
{
Sprite bgSprite = assignedSongData.fullscreen_songPicture;
Sprite resolvedBackground = assignedSongData != null ? assignedSongData.GetResolvedFullscreenSongPicture() : null;
if (resolvedBackground != null)
{
Sprite bgSprite = resolvedBackground;
if (bgMainImage != null)
{
bgMainImage.sprite = bgSprite;
// ensure fully visible
bgMainImage.color = new Color(bgMainImage.color.r, bgMainImage.color.g, bgMainImage.color.b, 1f);
if (VerboseLogs) Debug.Log($"Assigned SongData.fullscreen_songPicture to bgMainImage for song {assignedSongData.songName}");
if (VerboseLogs) Debug.Log($"Assigned resolved fullscreen song picture to bgMainImage for song {assignedSongData.songName}");
}
if (bgSpriteRenderer != null)
{
bgSpriteRenderer.sprite = bgSprite;
if (VerboseLogs) Debug.Log($"Assigned SongData.fullscreen_songPicture to bgSpriteRenderer for song {assignedSongData.songName}");
if (VerboseLogs) Debug.Log($"Assigned resolved fullscreen song picture to bgSpriteRenderer for song {assignedSongData.songName}");
// 释放引用以节省内存(按要求:完毕后令 sprite renderer = null
bgSpriteRenderer = null;
}
@@ -397,7 +399,7 @@ public class BeatmapManager : MonoBehaviour
if (startCanvas_image != null)
{
startCanvas_image.sprite = bgSprite;
if (VerboseLogs) Debug.Log($"Assigned SongData.fullscreen_songPicture to startCanvas_image for song {assignedSongData.songName}");
if (VerboseLogs) Debug.Log($"Assigned resolved fullscreen song picture to startCanvas_image for song {assignedSongData.songName}");
}
if (bgMainImage == null && startCanvas_image == null)
@@ -1651,9 +1651,10 @@ public class GameManager : MonoBehaviour
{
if (sd == null) return;
// set background sprite if present
if (backgroundRenderer != null && sd.fullscreen_songPicture != null)
Sprite resolvedBackground = sd.GetResolvedFullscreenSongPicture();
if (backgroundRenderer != null && resolvedBackground != null)
{
backgroundRenderer.sprite = sd.fullscreen_songPicture;
backgroundRenderer.sprite = resolvedBackground;
Color color = backgroundRenderer.color;
color.a = 1f;
backgroundRenderer.color = color;
@@ -1,4 +1,4 @@
%YAML 1.1
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &118873712428923354
GameObject:
@@ -560,7 +560,10 @@ public class settlementController : MonoBehaviour
if(sm != null)
{
songName_Text.text = bmm.parsedTitle;
thisSong_backPic.sprite = bmm.assignedSongData.fullscreen_songPicture;
if (bmm.assignedSongData != null)
{
thisSong_backPic.sprite = bmm.assignedSongData.GetResolvedFullscreenSongPicture();
}
targetPmScore = sm.allSum_pmScore;
targetIdolScore = sm.allSum_idolScore;
@@ -1379,35 +1382,27 @@ public class settlementController : MonoBehaviour
return;
}
dlcData[] allDlcs = RuntimeResourcesCache.LoadAllDlcs();
dlcData foundDlc = null;
dlcData foundDlc = SongDlcContentResolver.GetOwningDlc(thisSong_so);
AudioClip resolvedSettlementMusic = thisSong_so.GetResolvedSettlementMusic();
for (int i = 0; i < allDlcs.Length; i++)
{
dlcData dlc = allDlcs[i];
if (dlc == null || dlc.songList == null) continue;
if (!dlc.songList.Contains(thisSong_so)) continue;
foundDlc = dlc;
break;
}
if (foundDlc == null)
if (foundDlc == null && resolvedSettlementMusic == null)
{
Debug.LogWarning($"[SettlementController] No DLC found containing song '{thisSong_so.songName}'");
return;
}
if (foundDlc.settlementMusic == null)
if (resolvedSettlementMusic == null)
{
Debug.LogWarning($"[SettlementController] DLC '{foundDlc.dlcName}' does not have settlement music assigned");
string dlcName = foundDlc != null ? foundDlc.dlcName : thisSong_so.belongsTo_whichDLC;
Debug.LogWarning($"[SettlementController] DLC '{dlcName}' does not have settlement music assigned");
return;
}
bool clipChanged = settlementAudioSource.clip != foundDlc.settlementMusic;
bool clipChanged = settlementAudioSource.clip != resolvedSettlementMusic;
if (clipChanged)
{
settlementAudioSource.Stop();
settlementAudioSource.clip = foundDlc.settlementMusic;
settlementAudioSource.clip = resolvedSettlementMusic;
settlementAudioSource.time = 0f;
}
@@ -1416,7 +1411,8 @@ public class settlementController : MonoBehaviour
settlementAudioSource.volume = 1f;
settlementAudioSource.mute = false;
Debug.Log($"[SettlementController] Prepared settlement music from DLC '{foundDlc.dlcName}': {foundDlc.settlementMusic.name}");
string resolvedDlcName = foundDlc != null ? foundDlc.dlcName : (thisSong_so.belongsTo_whichDLC ?? "DLC");
Debug.Log($"[SettlementController] Prepared settlement music from DLC '{resolvedDlcName}': {resolvedSettlementMusic.name}");
}
/// <summary>
+349 -2
View File
@@ -8,6 +8,8 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Bansonic;
public class globalChatSystem : MonoBehaviour
{
private enum ChatChannelMode
@@ -37,6 +39,9 @@ public class globalChatSystem : MonoBehaviour
public GameObject worldSystemMessagePrefab;
public Transform playerMessageParent;
[Header("extra")]
public Button emojiButton;
[Header("send")]
public InputField messageInputField;
public Button sendButton;
@@ -55,6 +60,8 @@ public class globalChatSystem : MonoBehaviour
[SerializeField] Text channelNameText;
[SerializeField] Button back_to_worldwideChatButton;
[SerializeField] Toggle globalChatToggle;
[SerializeField] Toggle friendChatToggle;
private ArenaRoomService _service;
private ScrollRect _scrollRect;
@@ -75,6 +82,7 @@ public class globalChatSystem : MonoBehaviour
private long _nextFeedSequence = 1;
private ChatChannelMode _currentMode = ChatChannelMode.World;
private string _currentPrivatePartnerId;
private string _lastPrivatePartnerId = string.Empty;
private string _defaultChannelName = string.Empty;
private static string _pendingPrivatePartnerId;
private Coroutine _layoutRefreshCoroutine;
@@ -83,7 +91,11 @@ public class globalChatSystem : MonoBehaviour
private readonly List<RectTransform> _pendingLayoutDirtyRoots = new List<RectTransform>();
private Coroutine _refocusInputCoroutine;
private bool _refocusInputRequested;
private bool _suppressModeToggleEvents;
private bool _modeTogglesAutoCreated;
private ToggleGroup _chatModeToggleGroup;
public static globalChatSystem Instance { get; private set; }
public static event Action<string> ActivePrivateConversationChanged;
private const float WorldChatSendCooldownSeconds = 10f;
private const float PrivateChatSendCooldownSeconds = 1f;
@@ -93,6 +105,9 @@ public class globalChatSystem : MonoBehaviour
private const string DefaultWorldChannelName = "世界聊天";
private const string CooldownPlaceholderFormat = "\u53d1\u9001\u51b7\u5374\u4e2d {0}s";
private const string PrivateChannelNameFormat = "\u6b63\u5728\u4e0e[{0}]\u79c1\u804a";
private const string DefaultFriendChannelName = "\u597d\u53cb\u804a\u5929";
private const string NoRecentPrivateConversationNotice = "\u6682\u65e0\u6700\u8fd1\u79c1\u804a\u5bf9\u8c61";
private const string LastPrivatePartnerPrefKey = "global_chat_last_private_partner_id";
public static void OpenPrivateConversation(string steamId)
{
@@ -132,6 +147,7 @@ public class globalChatSystem : MonoBehaviour
private void Awake()
{
Instance = this;
_lastPrivatePartnerId = PlayerPrefs.GetString(LastPrivatePartnerPrefKey, string.Empty);
}
private void Start()
@@ -182,6 +198,8 @@ public class globalChatSystem : MonoBehaviour
back_to_worldwideChatButton.onClick.AddListener(HandleBackToWorldClicked);
}
EnsureChatModeToggles();
if (_service != null)
{
_service.SetWorldChatCacheLimit(GetEffectiveCacheLimit());
@@ -211,6 +229,8 @@ public class globalChatSystem : MonoBehaviour
ClearDisplayedMessages();
_isViewPinnedToBottom = true;
_windowStartIndex = -1;
EnsureChatModeToggles();
NotifyActivePrivateConversationChanged();
if (!string.IsNullOrWhiteSpace(_pendingPrivatePartnerId))
{
@@ -237,6 +257,8 @@ public class globalChatSystem : MonoBehaviour
_scrollRect.onValueChanged.RemoveListener(OnScrollValueChanged);
}
UnbindChatModeToggleListeners();
if (_chatCooldownRoutine != null)
{
StopCoroutine(_chatCooldownRoutine);
@@ -256,6 +278,7 @@ public class globalChatSystem : MonoBehaviour
}
RestoreDefaultPlaceholder();
NotifyActivePrivateConversationChanged();
}
private void OnDestroy()
@@ -270,6 +293,8 @@ public class globalChatSystem : MonoBehaviour
back_to_worldwideChatButton.onClick.RemoveListener(HandleBackToWorldClicked);
}
UnbindChatModeToggleListeners();
if (sendButton != null)
{
sendButton.onClick.RemoveListener(OnSendClicked);
@@ -365,6 +390,7 @@ public class globalChatSystem : MonoBehaviour
_currentPrivatePartnerId = null;
ResetFeedForChannelSwitch();
ApplyChannelHeader();
NotifyActivePrivateConversationChanged();
await _service.EnsureWorldChatConnected();
await _service.LoadWorldHistory(InitialHistoryLoadLimit);
RenderMessages(_service.GetWorldChatMessages(), false);
@@ -379,8 +405,10 @@ public class globalChatSystem : MonoBehaviour
_currentMode = ChatChannelMode.Private;
_currentPrivatePartnerId = partnerId.Trim();
RememberPrivatePartner(_currentPrivatePartnerId);
ResetFeedForChannelSwitch();
ApplyChannelHeader();
NotifyActivePrivateConversationChanged();
await _service.LoadPrivateHistory(_currentPrivatePartnerId, InitialHistoryLoadLimit);
RenderMessages(_service.GetPrivateChatMessages(_currentPrivatePartnerId), false);
}
@@ -394,10 +422,17 @@ public class globalChatSystem : MonoBehaviour
{
if (back_to_worldwideChatButton != null)
{
back_to_worldwideChatButton.gameObject.SetActive(_currentMode == ChatChannelMode.Private);
back_to_worldwideChatButton.gameObject.SetActive(_currentMode == ChatChannelMode.Private && !HasChatModeToggles());
}
if (channelNameText == null)
if (_modeTogglesAutoCreated && channelNameText != null)
{
channelNameText.gameObject.SetActive(false);
}
UpdateChatModeToggleVisuals();
if (channelNameText == null || _modeTogglesAutoCreated)
{
return;
}
@@ -1168,6 +1203,318 @@ public class globalChatSystem : MonoBehaviour
gameObject.SetActive(false);
}
public static bool IsViewingPrivateConversation(string steamId)
{
if (string.IsNullOrWhiteSpace(steamId) || Instance == null || !Instance.isActiveAndEnabled)
{
return false;
}
return Instance._currentMode == ChatChannelMode.Private
&& string.Equals(Instance._currentPrivatePartnerId, steamId.Trim(), StringComparison.Ordinal);
}
private void EnsureChatModeToggles()
{
if (globalChatToggle == null || friendChatToggle == null)
{
CreateFallbackChatModeToggles();
}
BindChatModeToggleListeners();
UpdateChatModeToggleVisuals();
}
private void BindChatModeToggleListeners()
{
if (globalChatToggle != null)
{
globalChatToggle.onValueChanged.RemoveListener(HandleGlobalChatToggleChanged);
globalChatToggle.onValueChanged.AddListener(HandleGlobalChatToggleChanged);
}
if (friendChatToggle != null)
{
friendChatToggle.onValueChanged.RemoveListener(HandleFriendChatToggleChanged);
friendChatToggle.onValueChanged.AddListener(HandleFriendChatToggleChanged);
}
}
private void UnbindChatModeToggleListeners()
{
if (globalChatToggle != null)
{
globalChatToggle.onValueChanged.RemoveListener(HandleGlobalChatToggleChanged);
}
if (friendChatToggle != null)
{
friendChatToggle.onValueChanged.RemoveListener(HandleFriendChatToggleChanged);
}
}
private void HandleGlobalChatToggleChanged(bool isOn)
{
if (_suppressModeToggleEvents || !isOn)
{
return;
}
_ = SwitchToWorldChannelAsync();
}
private void HandleFriendChatToggleChanged(bool isOn)
{
if (_suppressModeToggleEvents || !isOn)
{
return;
}
OpenRecentPrivateConversationFromToggle();
}
private async void OpenRecentPrivateConversationFromToggle()
{
string partnerId = ResolvePreferredPrivatePartnerId();
if (string.IsNullOrWhiteSpace(partnerId))
{
AddSystemMessage(NoRecentPrivateConversationNotice);
UpdateChatModeToggleVisuals();
return;
}
await SwitchToPrivateChannelAsync(partnerId);
}
private void UpdateChatModeToggleVisuals()
{
if (globalChatToggle != null)
{
SetToggleWithoutNotify(globalChatToggle, _currentMode == ChatChannelMode.World);
}
if (friendChatToggle != null)
{
SetToggleWithoutNotify(friendChatToggle, _currentMode == ChatChannelMode.Private);
}
}
private void SetToggleWithoutNotify(Toggle toggle, bool value)
{
if (toggle == null)
{
return;
}
_suppressModeToggleEvents = true;
toggle.SetIsOnWithoutNotify(value);
_suppressModeToggleEvents = false;
}
private bool HasChatModeToggles()
{
return globalChatToggle != null && friendChatToggle != null;
}
private void CreateFallbackChatModeToggles()
{
if (globalChatToggle != null && friendChatToggle != null)
{
return;
}
RectTransform headerRect = channelNameText != null ? channelNameText.rectTransform.parent as RectTransform : null;
if (headerRect == null)
{
return;
}
if (channelNameText != null)
{
channelNameText.gameObject.SetActive(false);
}
headerRect.sizeDelta = new Vector2(360f, 34f);
if (_chatModeToggleGroup == null)
{
_chatModeToggleGroup = headerRect.GetComponent<ToggleGroup>();
if (_chatModeToggleGroup == null)
{
_chatModeToggleGroup = headerRect.gameObject.AddComponent<ToggleGroup>();
}
_chatModeToggleGroup.allowSwitchOff = false;
}
if (globalChatToggle == null)
{
globalChatToggle = CreateRuntimeModeToggle(headerRect, "globalChatToggle", DefaultWorldChannelName, new Vector2(-90f, 0f));
}
if (friendChatToggle == null)
{
friendChatToggle = CreateRuntimeModeToggle(headerRect, "friendChatToggle", DefaultFriendChannelName, new Vector2(90f, 0f));
}
if (globalChatToggle != null)
{
globalChatToggle.group = _chatModeToggleGroup;
}
if (friendChatToggle != null)
{
friendChatToggle.group = _chatModeToggleGroup;
}
_modeTogglesAutoCreated = globalChatToggle != null && friendChatToggle != null;
}
private Toggle CreateRuntimeModeToggle(RectTransform parent, string objectName, string labelText, Vector2 anchoredPosition)
{
Sprite defaultSprite = Resources.GetBuiltinResource<Sprite>("UISprite.psd");
Font defaultFont = channelNameText != null && channelNameText.font != null
? channelNameText.font
: Resources.GetBuiltinResource<Font>("Arial.ttf");
GameObject toggleObject = new GameObject(objectName, typeof(RectTransform), typeof(Image), typeof(Toggle));
RectTransform toggleRect = toggleObject.GetComponent<RectTransform>();
toggleRect.SetParent(parent, false);
toggleRect.anchorMin = new Vector2(0.5f, 0.5f);
toggleRect.anchorMax = new Vector2(0.5f, 0.5f);
toggleRect.pivot = new Vector2(0.5f, 0.5f);
toggleRect.anchoredPosition = anchoredPosition;
toggleRect.sizeDelta = new Vector2(164f, 30f);
Image background = toggleObject.GetComponent<Image>();
background.sprite = defaultSprite;
background.type = Image.Type.Sliced;
background.color = new Color(1f, 1f, 1f, 0.32f);
Toggle toggle = toggleObject.GetComponent<Toggle>();
toggle.targetGraphic = background;
toggle.transition = Selectable.Transition.ColorTint;
ColorBlock colors = toggle.colors;
colors.normalColor = new Color(1f, 1f, 1f, 0.45f);
colors.highlightedColor = new Color(1f, 1f, 1f, 0.55f);
colors.pressedColor = new Color(0.86f, 0.94f, 1f, 0.85f);
colors.selectedColor = new Color(0.86f, 0.94f, 1f, 0.95f);
colors.disabledColor = new Color(1f, 1f, 1f, 0.2f);
toggle.colors = colors;
GameObject checkmarkObject = new GameObject("Checkmark", typeof(RectTransform), typeof(Image));
RectTransform checkmarkRect = checkmarkObject.GetComponent<RectTransform>();
checkmarkRect.SetParent(toggleRect, false);
checkmarkRect.anchorMin = Vector2.zero;
checkmarkRect.anchorMax = Vector2.one;
checkmarkRect.offsetMin = new Vector2(3f, 3f);
checkmarkRect.offsetMax = new Vector2(-3f, -3f);
Image checkmark = checkmarkObject.GetComponent<Image>();
checkmark.sprite = defaultSprite;
checkmark.type = Image.Type.Sliced;
checkmark.color = new Color(0.63f, 0.87f, 1f, 0.95f);
toggle.graphic = checkmark;
GameObject labelObject = new GameObject("Label", typeof(RectTransform), typeof(Text));
RectTransform labelRect = labelObject.GetComponent<RectTransform>();
labelRect.SetParent(toggleRect, false);
labelRect.anchorMin = Vector2.zero;
labelRect.anchorMax = Vector2.one;
labelRect.offsetMin = new Vector2(10f, 0f);
labelRect.offsetMax = new Vector2(-10f, 0f);
Text label = labelObject.GetComponent<Text>();
label.font = defaultFont;
label.fontSize = 16;
label.alignment = TextAnchor.MiddleCenter;
label.horizontalOverflow = HorizontalWrapMode.Overflow;
label.verticalOverflow = VerticalWrapMode.Overflow;
label.color = new Color(0.19607843f, 0.19607843f, 0.19607843f, 1f);
label.text = labelText;
return toggle;
}
private void RememberPrivatePartner(string partnerId)
{
if (string.IsNullOrWhiteSpace(partnerId))
{
return;
}
_lastPrivatePartnerId = partnerId.Trim();
PlayerPrefs.SetString(LastPrivatePartnerPrefKey, _lastPrivatePartnerId);
PlayerPrefs.Save();
}
private string ResolvePreferredPrivatePartnerId()
{
if (!string.IsNullOrWhiteSpace(_lastPrivatePartnerId))
{
return _lastPrivatePartnerId;
}
if (TryGetMostRecentPrivatePartnerId(out string recentPartnerId))
{
return recentPartnerId;
}
return string.Empty;
}
private bool TryGetMostRecentPrivatePartnerId(out string partnerId)
{
partnerId = string.Empty;
if (_service == null)
{
return false;
}
string bestPartner = string.Empty;
long bestTimestamp = long.MinValue;
foreach (SocialFriendEntry friend in _service.GetFriendsSnapshot())
{
string candidateId = friend != null ? friend.friend_steam_id : string.Empty;
if (string.IsNullOrWhiteSpace(candidateId))
{
continue;
}
IReadOnlyList<ArenaRoomChatMessage> messages = _service.GetPrivateChatMessages(candidateId);
if (messages == null || messages.Count == 0)
{
continue;
}
ArenaRoomChatMessage lastMessage = messages[messages.Count - 1];
long timestamp = ParseMessageUnixTimestamp(lastMessage != null ? lastMessage.created_at : string.Empty);
if (timestamp > bestTimestamp)
{
bestTimestamp = timestamp;
bestPartner = candidateId;
}
}
if (string.IsNullOrWhiteSpace(bestPartner))
{
return false;
}
partnerId = bestPartner;
return true;
}
private void NotifyActivePrivateConversationChanged()
{
string activePartnerId = isActiveAndEnabled && _currentMode == ChatChannelMode.Private
? _currentPrivatePartnerId ?? string.Empty
: string.Empty;
ActivePrivateConversationChanged?.Invoke(activePartnerId);
}
private int GetEffectiveCacheLimit()
{
return Mathf.Max(InitialHistoryLoadLimit, Mathf.Max(1, maxMessageHistory));
@@ -30,16 +30,16 @@ RectTransform:
m_GameObject: {fileID: 195535439825785701}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 5207749702406868266}
m_Father: {fileID: 752414209697581781}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 41, y: 20}
m_SizeDelta: {x: 256, y: 0}
m_AnchoredPosition: {x: 49, y: 15}
m_SizeDelta: {x: 683, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &1794360726728255707
CanvasRenderer:
@@ -69,7 +69,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Sprite: {fileID: 21300000, guid: 92ee161b43f04104a994bbca17b57652, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
@@ -92,10 +92,10 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 8
m_Right: 8
m_Top: 8
m_Bottom: 8
m_Left: 10
m_Right: 10
m_Top: 10
m_Bottom: 10
m_ChildAlignment: 0
m_Spacing: 0
m_ChildForceExpandWidth: 0
@@ -549,9 +549,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 41
m_Left: 49
m_Right: 0
m_Top: -20
m_Top: -15
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 0
@@ -860,7 +860,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 15
m_FontSize: 30
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -871,7 +871,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 1
m_LineSpacing: 1
m_Text: "\u7AD9\u5728\u8FD9\u57CE\u5E02\u7684\u5BC2\u9759\u5904 123123123123123123123123123"
m_Text: 123123123123123123123123123123123123123
--- !u!114 &4855724856644423190
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -887,7 +887,7 @@ MonoBehaviour:
m_IgnoreLayout: 0
m_MinWidth: 0
m_MinHeight: -1
m_PreferredWidth: 240
m_PreferredWidth: 663
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
@@ -904,4 +904,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 3fb66c09204f49b4bfeb513c894b52e0, type: 3}
m_Name:
m_EditorClassIdentifier:
maxPreferredWidth: 240
maxPreferredWidth: 810
@@ -30,17 +30,17 @@ RectTransform:
m_GameObject: {fileID: 195535439825785701}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 5207749702406868266}
m_Father: {fileID: 752414209697581781}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: -247.36, y: 20}
m_SizeDelta: {x: 256, y: 0}
m_Pivot: {x: -0.81, y: 1}
m_AnchoredPosition: {x: 207, y: 15}
m_SizeDelta: {x: 196, y: 0}
m_Pivot: {x: 1, y: 1}
--- !u!222 &1794360726728255707
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -69,7 +69,7 @@ MonoBehaviour:
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 7f3b1220317607e44bbb8f0e1998044a, type: 3}
m_Sprite: {fileID: 21300000, guid: b2d6b856c968dca459c952435a8e55f6, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
@@ -363,7 +363,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 53.001953}
m_AnchoredPosition: {x: 0, y: 62.501953}
m_SizeDelta: {x: 40, y: 40}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &225269693782171328
@@ -533,7 +533,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 3840, y: -53.001953}
m_AnchoredPosition: {x: 3840, y: -62.501953}
m_SizeDelta: {x: 256, y: 0}
m_Pivot: {x: 1, y: 0.5}
--- !u!114 &5490276556566493605
@@ -549,9 +549,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 180
m_Right: 40
m_Top: -20
m_Left: 0
m_Right: 49
m_Top: -15
m_Bottom: 0
m_ChildAlignment: 2
m_Spacing: 0
@@ -616,7 +616,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: -143.99913}
m_SizeDelta: {x: 0, y: 53.001953}
m_SizeDelta: {x: 0, y: 62.501953}
m_Pivot: {x: 0.5, y: 1}
--- !u!114 &6728628998878729785
MonoBehaviour:
@@ -691,7 +691,7 @@ MonoBehaviour:
m_MinWidth: -1
m_MinHeight: -1
m_PreferredWidth: -1
m_PreferredHeight: 53.001953
m_PreferredHeight: 62.501953
m_FlexibleWidth: -1
m_FlexibleHeight: -1
m_LayoutPriority: 2
@@ -860,7 +860,7 @@ MonoBehaviour:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: bd4be44636ad6c24a9b117a7108a75e9, type: 3}
m_FontSize: 15
m_FontSize: 30
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 1
@@ -871,7 +871,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 1
m_LineSpacing: 1
m_Text: "\u4F60\u597D123123123123123123123123123123123123123"
m_Text: "\u4F60\u597D\u518D\u89C1\u4F60\u597D"
--- !u!114 &4855724856644423190
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -887,7 +887,7 @@ MonoBehaviour:
m_IgnoreLayout: 0
m_MinWidth: 0
m_MinHeight: -1
m_PreferredWidth: 240
m_PreferredWidth: 180
m_PreferredHeight: -1
m_FlexibleWidth: -1
m_FlexibleHeight: -1
@@ -904,4 +904,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 3fb66c09204f49b4bfeb513c894b52e0, type: 3}
m_Name:
m_EditorClassIdentifier:
maxPreferredWidth: 240
maxPreferredWidth: 810