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> CachedByHeroId = new Dictionary>(); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetStaticState() { cachedAllSkins = null; CachedByHeroId.Clear(); } public static void InvalidateCache() { cachedAllSkins = null; CachedByHeroId.Clear(); } public static List GetSkinSet(AllyHero_SO hero, bool includeBaseSkin = true) { List result = new List(); if (hero == null) { return result; } if (includeBaseSkin) { result.Add(BuildBaseSkin(hero)); } List 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 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 GetExtraSkinsForHero(int heroId) { EnsureCacheBuilt(); if (heroId <= 0) { return new List(); } if (!CachedByHeroId.TryGetValue(heroId, out List list) || list == null) { return new List(); } return new List(list); } public static HeroSkinSO[] LoadAllHeroSkinAssets() { EnsureCacheBuilt(); return cachedAllSkins ?? Array.Empty(); } 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 list) || list == null) { list = new List(); CachedByHeroId[skin.heroId] = list; } list.Add(skin); } foreach (KeyValuePair> 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 result = new List(guids.Length); for (int i = 0; i < guids.Length; i++) { string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]); HeroSkinSO skin = AssetDatabase.LoadAssetAtPath(assetPath); if (skin != null) { result.Add(skin); } } return result.ToArray(); } string[] fallbackGuids = AssetDatabase.FindAssets("t:HeroSkinSO"); List fallback = new List(fallbackGuids.Length); for (int i = 0; i < fallbackGuids.Length; i++) { string assetPath = AssetDatabase.GUIDToAssetPath(fallbackGuids[i]); HeroSkinSO skin = AssetDatabase.LoadAssetAtPath(assetPath); if (skin != null) { fallback.Add(skin); } } return fallback.ToArray(); } #endif return RuntimeResourcesCache.LoadAllHeroSkins() ?? Array.Empty(); } }