Files
bansonic_beta_main/Assets/scripts/Data/AllyHero_SO.cs
T

1079 lines
32 KiB
C#

using System;
using UnityEngine;
using System.Collections.Generic;
using Spine.Unity;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(fileName = "NewAllyHero", menuName = "SO_Data/AllyHero")]
public class AllyHero_SO : ScriptableObject
{
public static readonly string[] BehaviourAxisNames =
{
"题海战术",
"诛锄异己",
"逸乐共谋",
"完美至上",
"涵养心境",
"众志成城",
"过激行为",
"二次形变",
"自有主见",
"迷茫或凡想"
};
[Header("Inspector")]
public string ally_heroName;
public string ally_heroDesignation;
public int ally_heroID;
public bool isUnlocked;
public equipmentSO.EquipmentSkillType allyType;
[Tooltip("Optional obsession tag used by memory skills such as 30011012. Leave empty to ignore mismatch checks.")]
public string obsessionTag;
[Header("DLC")]
[Tooltip("DLC key that owns this hero. Leave empty for base-game heroes that are always available. When set, the hero is only accessible when the matching DLC is owned.")]
public string sourceDlcId;
[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("Inspector")]
public Sprite ally_hero_settleDisplay;
[Header("spine")]
public SkeletonDataAsset ally_heroSpineData;
[Header("Skins")]
public string selectedSkinId;
[Header("Inspector")]
public Color ally_heroThemeColor;
[Header("Inspector")]
[TextArea(3, 10)]
[Tooltip("Documentation text normalized.")]
public string ally_heroDescription;
[Header("Inspector")]
public List<AllyLevelInfo> levelStats = new List<AllyLevelInfo>();
[System.Serializable]
public class AllyLevelInfo
{
[Tooltip("Documentation text normalized.")]
public string levelName;
public int levelID;
[Tooltip("Documentation text normalized.")]
public int attack = 0;
[Tooltip("Documentation text normalized.")]
public int maxHP = 100;
[Tooltip("Documentation text normalized.")]
public float damageResistance = 0f;
[Tooltip("Documentation text normalized.")]
public int maxMana = 100;
[Tooltip("Documentation text normalized.")]
public float scoreEfficiency = 1f;
[Header("Inspector")]
[Tooltip("Documentation text normalized.")]
public int requiredEXP;
[Header("Inspector")]
[Tooltip("Documentation text normalized.")]
public int skill_slot_limited;
[Header("Note-hit shared parameters")]
[Tooltip("Documentation text normalized.")]
public int manaGainGood = 1;
[Tooltip("Documentation text normalized.")]
public int manaGainGreat = 2;
[Tooltip("Documentation text normalized.")]
public int manaGainPerfect = 3;
[Tooltip("Documentation text normalized.")]
public int manaGainOnMiss = 5;
[Tooltip("Documentation text normalized.")]
public float damageMultiplierGood = 0.5f;
[Tooltip("Documentation text normalized.")]
public float damageMultiplierGreat = 0.75f;
[Tooltip("Documentation text normalized.")]
public float damageMultiplierPerfect = 1f;
[Tooltip("Documentation text normalized.")]
public float missHpLossBase = 10f;
}
[System.Serializable]
public class AllyBehaviourAxis
{
public string axisName;
[Range(0, 100)] public int value;
}
[Header("Inspector")]
public int ally_currentEXP;
public int ally_growthUnlockedTierIndex;
[Tooltip("When enabled, level display/effective level resolution is clamped to the previous tier while this hero is breakthrough-locked, even if EXP has already reached the next tier threshold.")]
public bool level_lock;
public bool ally_autoBreakthroughEnabled;
public int ally_battleDeployCount;
public int ally_finishCount;
public int ally_mvpCount;
public long ally_joinDateUtcTicks;
[Header("Behaviour Radar")]
public List<AllyBehaviourAxis> behaviourAxes = new List<AllyBehaviourAxis>();
[Header("Skills")]
public SkillDefinition[] availableSkills;
[Tooltip("Indexes into availableSkills for preselected skills; empty means none selected")]
public int[] selectedSkillIndices = new int[0];
[Tooltip("Primary selected skill index into availableSkills (-1 = none)")]
public int primarySkillIndex = -1;
[Tooltip("Skill groups defined on this hero. Each SkillGroup has a numeric skillGroupID which can be equipped into slots to allow activation of the entire group via that ID.")]
public SkillGroup[] skillGroups = new SkillGroup[0];
[Tooltip("Equipped skill group IDs for this ally. Each int references a skillGroupID defined in skillGroups. Use 0 to indicate empty.")]
public int[] equippedSkillGroupIDs = new int[0];
[Header("Equipments")]
public equipmentSO equippedEquipment;
public string equippedEquipmentId;
public SkillDefinition GetPrimarySkill()
{
if (availableSkills == null || primarySkillIndex < 0 || primarySkillIndex >= availableSkills.Length) return null;
return availableSkills[primarySkillIndex];
}
public SkillGroup GetPrimarySkillGroup()
{
int[] runtimeGroupIds = GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = GetSkillGroupByID(gid);
if (group != null) return group;
}
}
return null;
}
public SkillGroup GetSkillGroupByID(int groupID)
{
if (skillGroups != null && skillGroups.Length > 0)
{
foreach (SkillGroup group in skillGroups)
{
if (group != null && group.skillGroupID == groupID) return group;
}
}
return EquipmentSkillGroupLibrary.ResolveSkillGroup(groupID);
}
public bool ValidateEquippedSkillSelections(bool persistIfChanged = true)
{
LoadEquippedSkillsFromLocal();
LoadEquippedEquipmentFromLocal();
if (equippedSkillGroupIDs == null)
{
equippedSkillGroupIDs = Array.Empty<int>();
}
int currentLevelId = GetEffectiveLevelForCurrentEXP()?.levelID ?? 0;
int maxSkillSlots = Mathf.Max(0, GetEffectiveSkillSlotLimit());
List<int> validatedGroupIds = new List<int>(Mathf.Min(equippedSkillGroupIDs.Length, maxSkillSlots));
HashSet<int> seenGroupIds = new HashSet<int>();
for (int i = 0; i < equippedSkillGroupIDs.Length; i++)
{
int groupId = equippedSkillGroupIDs[i];
if (groupId <= 0 || !seenGroupIds.Add(groupId))
{
continue;
}
if (validatedGroupIds.Count >= maxSkillSlots)
{
continue;
}
SkillGroup group = GetOwnedSkillGroupByID(groupId);
if (group == null)
{
continue;
}
int requiredLevelId = Mathf.Clamp(group.thisSkill_levelLimit, 1, 4);
if (currentLevelId < requiredLevelId)
{
continue;
}
validatedGroupIds.Add(groupId);
}
int[] validatedArray = validatedGroupIds.ToArray();
bool changed = !AreSkillGroupIdArraysEqual(equippedSkillGroupIDs, validatedArray);
if (!changed)
{
return false;
}
equippedSkillGroupIDs = validatedArray;
if (persistIfChanged)
{
SaveEquippedSkillsToLocal();
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
EditorUtility.SetDirty(this);
if (persistIfChanged)
{
AssetDatabase.SaveAssets();
}
}
#endif
return true;
}
public int[] GetEffectiveEquippedSkillGroupIDs()
{
var result = new List<int>();
var dedupe = new HashSet<int>();
int heroSkillSlotsRemaining = GetEffectiveSkillSlotLimit();
if (equippedSkillGroupIDs != null)
{
for (int i = 0; i < equippedSkillGroupIDs.Length; i++)
{
if (heroSkillSlotsRemaining <= 0)
{
break;
}
int groupId = equippedSkillGroupIDs[i];
if (groupId <= 0 || !dedupe.Add(groupId))
{
continue;
}
result.Add(groupId);
heroSkillSlotsRemaining--;
}
}
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment != null)
{
TryAddEquipmentSkillGroupId(result, dedupe, equipment.sa_skillID);
TryAddEquipmentSkillGroupId(result, dedupe, equipment.ia_skillID);
}
return result.ToArray();
}
public int GetEffectiveSkillSlotLimit()
{
AllyLevelInfo currentLevelInfo = GetEffectiveLevelForCurrentEXP();
int slotLimit = currentLevelInfo != null ? Mathf.Max(0, currentLevelInfo.skill_slot_limited) : 0;
if (HasExclusiveIllusionExtraSlotBonus())
{
slotLimit += 1;
}
return slotLimit;
}
public AllyLevelInfo GetEffectiveLevelInfoWithEquipment(AllyLevelInfo baseInfo)
{
if (baseInfo == null)
{
return null;
}
AllyLevelInfo resolved = CloneLevelInfo(baseInfo);
resolved.skill_slot_limited = GetEffectiveSkillSlotLimit();
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment == null)
{
return resolved;
}
resolved.maxHP = ApplyIntEquipmentBonus(
resolved.maxHP,
equipment.maxHp,
equipment,
equipmentSO.EquipmentSpecialEffectType.MaxHp);
resolved.attack = ApplyIntEquipmentBonus(
resolved.attack,
equipment.attack,
equipment,
equipmentSO.EquipmentSpecialEffectType.Attack);
resolved.maxMana = ApplyIntEquipmentBonus(
resolved.maxMana,
equipment.maxMana,
equipment,
equipmentSO.EquipmentSpecialEffectType.MaxMana);
resolved.damageResistance = ApplyFloatEquipmentBonus(
resolved.damageResistance,
equipment.damageResistance,
equipment,
equipmentSO.EquipmentSpecialEffectType.DamageResistance);
resolved.scoreEfficiency = ApplyFloatEquipmentBonus(
resolved.scoreEfficiency,
equipment.scoreEfficiency,
equipment,
equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency);
return resolved;
}
private bool HasExclusiveIllusionExtraSlotBonus()
{
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment == null || equipment.ia_skillID != 30021007)
{
return false;
}
AllyHero_SO winner = ResolveExclusiveIllusionExtraSlotOwner();
return winner != null && winner.ally_heroID == ally_heroID;
}
private static AllyHero_SO ResolveExclusiveIllusionExtraSlotOwner()
{
AllyHero_SO[] heroes = LoadAllHeroAssetsForEquipmentChecks();
if (heroes == null || heroes.Length == 0)
{
return null;
}
AllyHero_SO selected = null;
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero == null)
{
continue;
}
hero.LoadEquippedEquipmentFromLocal();
equipmentSO equipped = hero.GetEquippedEquipmentResolved();
if (equipped == null || equipped.ia_skillID != 30021007)
{
continue;
}
if (selected == null
|| hero.ally_heroID < selected.ally_heroID
|| (hero.ally_heroID == selected.ally_heroID
&& string.CompareOrdinal(hero.ally_heroName ?? string.Empty, selected.ally_heroName ?? string.Empty) < 0))
{
selected = hero;
}
}
return selected;
}
private static AllyHero_SO[] LoadAllHeroAssetsForEquipmentChecks()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
string[] guids = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" });
var heroes = new List<AllyHero_SO>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
AllyHero_SO hero = AssetDatabase.LoadAssetAtPath<AllyHero_SO>(path);
if (hero != null)
{
heroes.Add(hero);
}
}
return heroes.ToArray();
}
#endif
return RuntimeResourcesCache.LoadAll<AllyHero_SO>("so/ally");
}
private static void TryAddEquipmentSkillGroupId(List<int> result, HashSet<int> dedupe, int groupId)
{
if (groupId <= 0 || result == null || dedupe == null || !dedupe.Add(groupId))
{
return;
}
result.Add(groupId);
}
private static bool AreSkillGroupIdArraysEqual(int[] left, int[] right)
{
int leftLength = left != null ? left.Length : 0;
int rightLength = right != null ? right.Length : 0;
if (leftLength != rightLength)
{
return false;
}
for (int i = 0; i < leftLength; i++)
{
if (left[i] != right[i])
{
return false;
}
}
return true;
}
private SkillGroup GetOwnedSkillGroupByID(int groupId)
{
if (skillGroups == null || skillGroups.Length == 0)
{
return null;
}
for (int i = 0; i < skillGroups.Length; i++)
{
SkillGroup group = skillGroups[i];
if (group != null && group.skillGroupID == groupId)
{
return group;
}
}
return null;
}
public int GetUnlockedLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveUnlockedLevelIndex(sorted);
}
public int GetExpQualifiedLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveExpQualifiedLevelIndex(sorted);
}
public int GetDisplayLevelIndex()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
return ResolveDisplayLevelIndex(sorted);
}
public string GetDisplayLevelRatingKey()
{
int displayIndex = GetDisplayLevelIndex();
if (displayIndex <= 0) return "C";
if (displayIndex == 1) return "B";
if (displayIndex == 2) return "A";
return "S";
}
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
{
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
if (unlockedIndex < 0 || unlockedIndex >= sorted.Count)
{
return null;
}
return sorted[unlockedIndex];
}
private List<AllyLevelInfo> BuildSortedLevelStats()
{
var sorted = new List<AllyLevelInfo>();
if (levelStats == null || levelStats.Count == 0)
{
return sorted;
}
for (int i = 0; i < levelStats.Count; i++)
{
if (levelStats[i] != null)
{
sorted.Add(levelStats[i]);
}
}
if (sorted.Count == 0)
{
return null;
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
return sorted;
}
private int ResolveUnlockedLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
return Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
}
private int ResolveExpQualifiedLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
int expQualifiedIndex = 0;
for (int i = 0; i < sorted.Count; i++)
{
if (ally_currentEXP >= sorted[i].requiredEXP)
{
expQualifiedIndex = i;
}
else
{
break;
}
}
return Mathf.Clamp(expQualifiedIndex, 0, sorted.Count - 1);
}
private int ResolveDisplayLevelIndex(List<AllyLevelInfo> sorted)
{
if (sorted == null || sorted.Count == 0)
{
return -1;
}
int expQualifiedIndex = ResolveExpQualifiedLevelIndex(sorted);
if (!level_lock)
{
return expQualifiedIndex;
}
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
return Mathf.Clamp(Mathf.Min(expQualifiedIndex, unlockedIndex), 0, sorted.Count - 1);
}
private AllyLevelInfo CloneLevelInfo(AllyLevelInfo source)
{
return new AllyLevelInfo
{
levelName = source.levelName,
levelID = source.levelID,
attack = source.attack,
maxHP = source.maxHP,
damageResistance = source.damageResistance,
maxMana = source.maxMana,
scoreEfficiency = source.scoreEfficiency,
requiredEXP = source.requiredEXP,
skill_slot_limited = source.skill_slot_limited,
manaGainGood = source.manaGainGood,
manaGainGreat = source.manaGainGreat,
manaGainPerfect = source.manaGainPerfect,
manaGainOnMiss = source.manaGainOnMiss,
damageMultiplierGood = source.damageMultiplierGood,
damageMultiplierGreat = source.damageMultiplierGreat,
damageMultiplierPerfect = source.damageMultiplierPerfect,
missHpLossBase = source.missHpLossBase
};
}
private int ApplyIntEquipmentBonus(int baseValue, equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float percent = GetEquipmentPercentBonus(tuning, equipment, effectType);
float flat = GetEquipmentFlatBonus(equipment, effectType);
return Mathf.Max(0, Mathf.FloorToInt(baseValue * (1f + percent) + flat));
}
private float ApplyFloatEquipmentBonus(float baseValue, equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float percent = GetEquipmentPercentBonus(tuning, equipment, effectType);
float flat = GetEquipmentFlatBonus(equipment, effectType);
return Mathf.Max(0f, (baseValue * (1f + percent)) + flat);
}
private float GetEquipmentPercentBonus(equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float total = 0f;
if (tuning != null)
{
if (!Mathf.Approximately(tuning.basicGain, 0f))
{
total += tuning.basicGain;
total += Mathf.Max(0, equipment.level) * tuning.cultivationInterval;
}
}
if (equipment.enableTypeSameEffects && equipment.skillType == allyType)
{
total += SumEffectValues(equipment.typeSameEffects, effectType);
}
total += SumEffectValues(equipment.maxLevelEffects, effectType);
return total;
}
private float GetEquipmentFlatBonus(equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float total = 0f;
total += SumEffectValues(equipment.specialEffects, effectType);
equipmentSO.EquipmentSpecialEffect[] normalizedIllusionEffects = equipment.GetNormalizedIllusionEffects();
total += SumEffectValues(normalizedIllusionEffects, effectType);
return total;
}
private static float SumEffectValues(equipmentSO.EquipmentSpecialEffect[] effects, equipmentSO.EquipmentSpecialEffectType effectType)
{
if (effects == null || effects.Length == 0)
{
return 0f;
}
float total = 0f;
for (int i = 0; i < effects.Length; i++)
{
equipmentSO.EquipmentSpecialEffect effect = effects[i];
if (effect == null || effect.effectType != effectType)
{
continue;
}
total += effect.value;
}
return total;
}
[System.Serializable]
private class EquippedSkillGroupIDsPayload
{
public int[] equippedSkillGroupIDs;
}
[System.Serializable]
private class EquippedEquipmentPayload
{
public string equippedEquipmentId;
}
private string GetEquippedSkillsPrefsKey()
{
return "ally_equippedSkillGroupIDs_" + ally_heroID;
}
private string GetEquippedEquipmentPrefsKey()
{
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] };
string json = JsonUtility.ToJson(payload);
PlayerPrefs.SetString(GetEquippedSkillsPrefsKey(), json);
PlayerPrefs.Save();
}
public void LoadEquippedSkillsFromLocal()
{
string key = GetEquippedSkillsPrefsKey();
if (!PlayerPrefs.HasKey(key)) return;
string json = PlayerPrefs.GetString(key, "");
if (string.IsNullOrEmpty(json)) return;
var payload = JsonUtility.FromJson<EquippedSkillGroupIDsPayload>(json);
if (payload == null) return;
equippedSkillGroupIDs = payload.equippedSkillGroupIDs ?? new int[0];
}
public void SaveEquippedEquipmentToLocal()
{
equippedEquipmentId = equippedEquipment != null ? equippedEquipment.name : string.Empty;
var payload = new EquippedEquipmentPayload { equippedEquipmentId = equippedEquipmentId ?? string.Empty };
string json = JsonUtility.ToJson(payload);
PlayerPrefs.SetString(GetEquippedEquipmentPrefsKey(), json);
PlayerPrefs.Save();
}
public void LoadEquippedEquipmentFromLocal()
{
string key = GetEquippedEquipmentPrefsKey();
if (!PlayerPrefs.HasKey(key))
{
return;
}
string json = PlayerPrefs.GetString(key, string.Empty);
if (string.IsNullOrEmpty(json))
{
return;
}
EquippedEquipmentPayload payload = JsonUtility.FromJson<EquippedEquipmentPayload>(json);
if (payload == null)
{
return;
}
equippedEquipmentId = payload.equippedEquipmentId ?? string.Empty;
equippedEquipment = ResolveEquippedEquipmentById(equippedEquipmentId);
}
public void SetEquippedEquipment(equipmentSO equipment, bool persist = true)
{
equippedEquipment = equipment;
equippedEquipmentId = equipment != null ? equipment.name : string.Empty;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
if (persist)
{
UnityEditor.AssetDatabase.SaveAssets();
}
}
#endif
if (persist)
{
SaveEquippedEquipmentToLocal();
}
}
public equipmentSO GetEquippedEquipmentResolved()
{
if (equippedEquipment != null)
{
return equippedEquipment;
}
if (string.IsNullOrWhiteSpace(equippedEquipmentId))
{
return null;
}
equippedEquipment = ResolveEquippedEquipmentById(equippedEquipmentId);
return equippedEquipment;
}
public void ClearEquippedEquipment()
{
equippedEquipment = null;
equippedEquipmentId = string.Empty;
PlayerPrefs.DeleteKey(GetEquippedEquipmentPrefsKey());
PlayerPrefs.Save();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
UnityEditor.AssetDatabase.SaveAssets();
}
#endif
}
private equipmentSO ResolveEquippedEquipmentById(string equipmentId)
{
if (string.IsNullOrWhiteSpace(equipmentId))
{
return null;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:equipmentSO", new[] { "Assets/Resources/so/uEquip" });
for (int i = 0; i < guids.Length; i++)
{
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
equipmentSO equipment = UnityEditor.AssetDatabase.LoadAssetAtPath<equipmentSO>(path);
if (equipment != null && equipment.name == equipmentId)
{
return equipment;
}
}
}
#endif
var generatedEquipments = Bansonic.equipmentGenerator.GetRuntimeGeneratedEquipments();
for (int i = 0; i < generatedEquipments.Count; i++)
{
if (generatedEquipments[i] != null && generatedEquipments[i].name == equipmentId)
{
return generatedEquipments[i];
}
}
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;
}
public void ClearEquippedSkills()
{
equippedSkillGroupIDs = Array.Empty<int>();
PlayerPrefs.DeleteKey(GetEquippedSkillsPrefsKey());
PlayerPrefs.Save();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public static void ClearAllEquippedSkills()
{
HashSet<int> clearedHeroIds = new HashSet<int>();
#if UNITY_EDITOR
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" });
for (int i = 0; i < guids.Length; i++)
{
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
AllyHero_SO hero = UnityEditor.AssetDatabase.LoadAssetAtPath<AllyHero_SO>(path);
if (hero == null)
{
continue;
}
hero.ClearEquippedSkills();
clearedHeroIds.Add(hero.ally_heroID);
}
UnityEditor.AssetDatabase.SaveAssets();
#endif
AllyHero_SO[] runtimeHeroes = RuntimeResourcesCache.LoadAll<AllyHero_SO>("so/ally");
for (int i = 0; i < runtimeHeroes.Length; i++)
{
AllyHero_SO hero = runtimeHeroes[i];
if (hero == null || clearedHeroIds.Contains(hero.ally_heroID))
{
continue;
}
hero.ClearEquippedSkills();
}
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;
isUnlocked = value;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
private void OnValidate()
{
EnsureBehaviourAxes();
}
public void EnsureBehaviourAxes()
{
if (behaviourAxes == null)
{
behaviourAxes = new List<AllyBehaviourAxis>();
}
while (behaviourAxes.Count < BehaviourAxisNames.Length)
{
behaviourAxes.Add(new AllyBehaviourAxis());
}
if (behaviourAxes.Count > BehaviourAxisNames.Length)
{
behaviourAxes.RemoveRange(BehaviourAxisNames.Length, behaviourAxes.Count - BehaviourAxisNames.Length);
}
for (int i = 0; i < BehaviourAxisNames.Length; i++)
{
if (behaviourAxes[i] == null)
{
behaviourAxes[i] = new AllyBehaviourAxis();
}
behaviourAxes[i].axisName = BehaviourAxisNames[i];
behaviourAxes[i].value = Mathf.Clamp(behaviourAxes[i].value, 0, 100);
}
}
public void IncrementBattleDeployCount(int amount = 1)
{
if (amount <= 0) return;
AllyHeroDeployLedger.EnsureInstance().IncrementDeployCount(this, amount);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public void LoadBattleDeployCountFromLocal()
{
ally_battleDeployCount = AllyHeroDeployLedger.EnsureInstance().GetDeployCount(ally_heroID);
}
public void SaveBattleDeployCountToLocal()
{
AllyHeroDeployLedger.EnsureInstance().SaveNow();
}
public void IncrementFinishCount(int amount = 1)
{
if (amount <= 0) return;
AllyHeroDeployLedger.EnsureInstance().IncrementFinishCount(this, amount);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public void IncrementMvpCount(int amount = 1)
{
if (amount <= 0) return;
AllyHeroDeployLedger.EnsureInstance().IncrementMvpCount(this, amount);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public void SetJoinDateIfMissing(System.DateTime? utcTime = null)
{
AllyHeroDeployLedger.EnsureInstance().SetJoinDateIfMissing(this, utcTime);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
}