|
|
|
@@ -1,9 +1,24 @@
|
|
|
|
|
using System;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
@@ -45,16 +60,12 @@ public class AllyHero_SO : ScriptableObject
|
|
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
|
|
@@ -82,98 +93,101 @@ public class AllyHero_SO : ScriptableObject
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// Documentation text normalized.
|
|
|
|
|
|
|
|
|
|
[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];
|
|
|
|
|
|
|
|
|
|
// Documentation text normalized.
|
|
|
|
|
|
|
|
|
|
public SkillDefinition GetPrimarySkill()
|
|
|
|
|
{
|
|
|
|
|
if (availableSkills == null || primarySkillIndex < 0 || primarySkillIndex >= availableSkills.Length) return null;
|
|
|
|
|
return availableSkills[primarySkillIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjusted: primary group is no longer stored as an index; prefer the first equipped group ID, otherwise fall back to the first non-null defined group.
|
|
|
|
|
public SkillGroup GetPrimarySkillGroup()
|
|
|
|
|
{
|
|
|
|
|
if (skillGroups == null || skillGroups.Length == 0) return null;
|
|
|
|
|
|
|
|
|
|
// If equipped group IDs exist, prefer the first valid referenced group
|
|
|
|
|
if (equippedSkillGroupIDs != null && equippedSkillGroupIDs.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var gid in equippedSkillGroupIDs)
|
|
|
|
|
foreach (int gid in equippedSkillGroupIDs)
|
|
|
|
|
{
|
|
|
|
|
if (gid == 0) continue;
|
|
|
|
|
var g = GetSkillGroupByID(gid);
|
|
|
|
|
if (g != null) return g;
|
|
|
|
|
SkillGroup group = GetSkillGroupByID(gid);
|
|
|
|
|
if (group != null) return group;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback: return first non-null skill group defined on the SO
|
|
|
|
|
foreach (var g in skillGroups)
|
|
|
|
|
foreach (SkillGroup group in skillGroups)
|
|
|
|
|
{
|
|
|
|
|
if (g != null) return g;
|
|
|
|
|
if (group != null) return group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New helper: find a SkillGroup by its numeric skillGroupID
|
|
|
|
|
public SkillGroup GetSkillGroupByID(int groupID)
|
|
|
|
|
{
|
|
|
|
|
if (skillGroups == null || skillGroups.Length == 0) return null;
|
|
|
|
|
foreach (var g in skillGroups)
|
|
|
|
|
foreach (SkillGroup group in skillGroups)
|
|
|
|
|
{
|
|
|
|
|
if (g != null && g.skillGroupID == groupID) return g;
|
|
|
|
|
if (group != null && group.skillGroupID == groupID) return group;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the AllyLevelInfo that corresponds to the current ally_currentEXP.
|
|
|
|
|
// Picks the highest level whose requiredEXP <= ally_currentEXP. If none match, returns levelStats[0] if present, otherwise null.
|
|
|
|
|
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
|
|
|
|
|
{
|
|
|
|
|
if (levelStats == null || levelStats.Count == 0) return null;
|
|
|
|
|
AllyLevelInfo best = null;
|
|
|
|
|
int currentExp = ally_currentEXP;
|
|
|
|
|
foreach (var lvl in levelStats)
|
|
|
|
|
|
|
|
|
|
List<AllyLevelInfo> sorted = new List<AllyLevelInfo>();
|
|
|
|
|
for (int i = 0; i < levelStats.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (lvl == null) continue;
|
|
|
|
|
if (best == null)
|
|
|
|
|
if (levelStats[i] != null)
|
|
|
|
|
{
|
|
|
|
|
if (currentExp >= lvl.requiredEXP) best = lvl;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP) best = lvl;
|
|
|
|
|
sorted.Add(levelStats[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (best == null) return levelStats[0];
|
|
|
|
|
return best;
|
|
|
|
|
|
|
|
|
|
if (sorted.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
|
|
|
|
int unlockedTierIndex = Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
|
|
|
|
|
return sorted[unlockedTierIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[System.Serializable]
|
|
|
|
@@ -206,6 +220,56 @@ public class AllyHero_SO : ScriptableObject
|
|
|
|
|
equippedSkillGroupIDs = payload.equippedSkillGroupIDs ?? new int[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 = Resources.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 void SetUnlocked(bool value)
|
|
|
|
|
{
|
|
|
|
|
if (isUnlocked == value) return;
|
|
|
|
@@ -218,6 +282,40 @@ public class AllyHero_SO : ScriptableObject
|
|
|
|
|
#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;
|
|
|
|
@@ -261,6 +359,17 @@ public class AllyHero_SO : ScriptableObject
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|