UI HUGE UPDATE
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 507e69cfce29a0a4f970d9d29f28047b
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user