Files
bansonic_beta_main/Assets/scripts/Data/AllyHero_SO.cs
T
2026-07-16 23:04:59 +08:00

1143 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
private const string EquippedEquipmentSaveCategory = "ally_equipped_equipment";
private const string EquippedEquipmentRecoverySlotPrefix = "ally_equipped_equipment_";
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;
// 【性能】装备存档已从磁盘水合过一次的标记。水合后内存即权威来源:
// SetEquippedEquipment / ApplyEquippedEquipmentPayload / ClearEquippedEquipment 都会同步更新内存与磁盘,
// 因此无需反复解密读盘。idols 界面每次切换角色时 RebuildBag 会对"每件装备 × 每个英雄"调用
// LoadEquippedEquipmentFromLocal 做占用检查,若每次都真正解密 SecureSaveVault 会造成严重卡顿。
// 该标记确保每个英雄整个会话只读盘一次,之后走内存,行为完全不变。
[System.NonSerialized] private bool equippedEquipmentHydrated;
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);
int expQualifiedIndex = ResolveExpQualifiedLevelIndex(sorted);
if (unlockedIndex < 0 || expQualifiedIndex < 0 || sorted == null || sorted.Count == 0)
{
return null;
}
int effectiveIndex = Mathf.Clamp(Mathf.Min(expQualifiedIndex, unlockedIndex), 0, sorted.Count - 1);
return sorted[effectiveIndex];
}
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 GetEquippedEquipmentSaveKey()
{
return ally_heroID.ToString();
}
private string GetEquippedEquipmentRecoverySlotKey()
{
return EquippedEquipmentRecoverySlotPrefix + 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);
SecureSaveVault.SaveJson(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey(), payload);
LocalRecoveryMirror.SaveJson(GetEquippedEquipmentRecoverySlotKey(), payload);
PlayerPrefs.SetString(GetEquippedEquipmentPrefsKey(), json);
PlayerPrefs.Save();
}
public void LoadEquippedEquipmentFromLocal(bool forceReload = false)
{
// 已水合且非强制刷新时直接返回,避免重复解密读盘(见 equippedEquipmentHydrated 说明)。
if (equippedEquipmentHydrated && !forceReload)
{
return;
}
// 无论后续走哪条分支,本次调用都视为已完成水合:
// 命中存档→内存已填充;无存档→内存保持当前值,也不必反复重试读盘。
equippedEquipmentHydrated = true;
string key = GetEquippedEquipmentPrefsKey();
EquippedEquipmentPayload payload;
if (SecureSaveVault.TryLoadJson(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey(), out payload) && payload != null)
{
ApplyEquippedEquipmentPayload(payload);
return;
}
if (LocalRecoveryMirror.TryLoadJson(GetEquippedEquipmentRecoverySlotKey(), out payload) && payload != null)
{
ApplyEquippedEquipmentPayload(payload);
SecureSaveVault.SaveJson(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey(), payload);
return;
}
if (!PlayerPrefs.HasKey(key))
{
return;
}
string json = PlayerPrefs.GetString(key, string.Empty);
if (string.IsNullOrEmpty(json))
{
return;
}
payload = JsonUtility.FromJson<EquippedEquipmentPayload>(json);
if (payload == null)
{
return;
}
ApplyEquippedEquipmentPayload(payload);
SecureSaveVault.SaveJson(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey(), payload);
LocalRecoveryMirror.SaveJson(GetEquippedEquipmentRecoverySlotKey(), payload);
}
private void ApplyEquippedEquipmentPayload(EquippedEquipmentPayload payload)
{
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;
equippedEquipmentHydrated = true;
#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;
equippedEquipmentHydrated = true;
SecureSaveVault.Delete(EquippedEquipmentSaveCategory, GetEquippedEquipmentSaveKey());
LocalRecoveryMirror.DeleteSlot(GetEquippedEquipmentRecoverySlotKey());
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
}
}