成就系统 巨大修改 新的ui 黑白效果 等一堆
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
@@ -6,15 +7,15 @@ using TMPro;
|
||||
|
||||
[ExecuteAlways]
|
||||
/// <summary>
|
||||
/// AllyCombatant 绑定到场上友军的运行时 GameObject(命名为 ally_01..ally_05),
|
||||
/// 实现 ICombatant 接口以供 EffectSystem 调用。
|
||||
/// - 从 teamUIController 获取 UI 引用并同步显示血量/法力
|
||||
/// - 启动时从 TeamCharacterDataInfo 或 AllyHero_SO 填充最大生命/法力等基础属性
|
||||
/// - 初始生命值为最大生命值,初始法力为 0
|
||||
/// AllyCombatant �������Ѿ�������ʱ GameObject������Ϊ ally_01..ally_05����
|
||||
/// ʵ�� ICombatant �ӿ��Թ� EffectSystem ���á�
|
||||
/// - �� teamUIController ��ȡ UI ���ò�ͬ����ʾѪ��/����
|
||||
/// - ����ʱ�� TeamCharacterDataInfo �� AllyHero_SO ����������/�����Ȼ�������
|
||||
/// - ��ʼ����ֵΪ�������ֵ����ʼ����Ϊ 0
|
||||
/// </summary>
|
||||
public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
public int slotIndex = 0; // 0..4 对应 ally_01..ally_05
|
||||
public int slotIndex = 0; // 0..4 ��Ӧ ally_01..ally_05
|
||||
|
||||
// runtime stats
|
||||
public int maxHP = 100;
|
||||
@@ -28,7 +29,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
public BeatmapManager bmm;
|
||||
|
||||
// add attack stat to be available at runtime
|
||||
[Tooltip("基础攻击力(运行时使用),会从 AllyHero_SO.levelStats[0].attack 填充(如果存在)")]
|
||||
[Tooltip("����������������ʱʹ�ã������ AllyHero_SO.levelStats[0].attack ��䣨������ڣ�")]
|
||||
public int attack = 0;
|
||||
|
||||
// scoring fields
|
||||
@@ -46,10 +47,10 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
[Tooltip("Score multiplier for Miss (e.g. 0)")]
|
||||
public float missRatio = 0f;
|
||||
|
||||
[Tooltip("角色当前轨道得分(累加),显示于 UI 的 currentScore 文本)")]
|
||||
[Tooltip("��ɫ��ǰ����÷֣��ۼӣ�����ʾ�� UI �� currentScore �ı���")]
|
||||
public int currentScore = 0;
|
||||
|
||||
[Tooltip("轨道分数上限,暂设为 int.MaxValue - 1")]
|
||||
[Tooltip("����������ޣ�����Ϊ int.MaxValue - 1")]
|
||||
public int maxTrackScore = int.MaxValue - 1;
|
||||
|
||||
// When true, InitializeStatsFromData() will overwrite inspector values on Start.
|
||||
@@ -71,10 +72,25 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
private Coroutine fadeManaCoroutine;
|
||||
private List<Buff> activeBuffs = new List<Buff>();
|
||||
|
||||
// Track skills that are currently active because HP percent condition is satisfied.
|
||||
private HashSet<string> _activeHpPercentSkills = new HashSet<string>();
|
||||
|
||||
// Track applied reversible effects for HP-percent skills so we can revert them on leave
|
||||
private Dictionary<string, AppliedEffect> _appliedHpPercentEffects = new Dictionary<string, AppliedEffect>();
|
||||
|
||||
private class AppliedEffect
|
||||
{
|
||||
public EffectType effectType;
|
||||
public float amount;
|
||||
public AppliedEffect(EffectType t, float a) { effectType = t; amount = a; }
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// ensure GameObject name matches pattern so EffectSystem can find it
|
||||
gameObject.name = $"ally_0{Mathf.Clamp(slotIndex + 1, 1, 5)}";
|
||||
if (_activeHpPercentSkills == null) _activeHpPercentSkills = new HashSet<string>();
|
||||
if (_appliedHpPercentEffects == null) _appliedHpPercentEffects = new Dictionary<string, AppliedEffect>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
@@ -84,13 +100,52 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
if (allowOverwriteFromSO)
|
||||
InitializeStatsFromData();
|
||||
|
||||
// Ensure runtime invariants
|
||||
currentHP = Mathf.Clamp(currentHP, 0, Mathf.Max(1, maxHP));
|
||||
currentMana = Mathf.Clamp(currentMana, 0, Mathf.Max(1, maxMana));
|
||||
// �ؼ������볡ʱӦ��Ѫ/0 ����
|
||||
// ֮ǰ����ֻ�� Clamp���ᱣ�� prefab/������ inspector �ϵľ� currentHP�������볡����Ѫ��
|
||||
isDead = false;
|
||||
currentHP = Mathf.Max(1, maxHP);
|
||||
currentMana = 0;
|
||||
|
||||
UpdateUIImmediate();
|
||||
// Ensure score UI shows initial value
|
||||
UpdateScoreUI();
|
||||
|
||||
// Evaluate HP-percent triggers on start after a short delay to ensure singletons are ready
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
StartCoroutine(InitialTriggerCheckCoroutine());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator InitialTriggerCheckCoroutine()
|
||||
{
|
||||
// Wait for singletons to be ready
|
||||
while (SkillBuilder.Instance == null || teamUIController.Instance == null)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Wait until teamUIController has finished loading ally SOs if they are async
|
||||
// or just wait a couple of frames to be sure
|
||||
yield return null;
|
||||
yield return null;
|
||||
|
||||
// Re-run initialization to ensure we have the correct MaxHP and skills from the loaded team
|
||||
if (allowOverwriteFromSO)
|
||||
{
|
||||
InitializeStatsFromData();
|
||||
currentHP = Mathf.Max(1, maxHP);
|
||||
UpdateUIImmediate();
|
||||
}
|
||||
|
||||
// Final wait to ensure SkillBuilder's internal mapping is ready
|
||||
while (SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex) == null)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Perform the initial HP-percentage trigger check (oldHP = currentHP to trigger "ENTER" if condition met)
|
||||
EvaluateHPPercentageTriggers(currentHP);
|
||||
}
|
||||
|
||||
// Allow editing in inspector to immediately reflect on UI (editor only)
|
||||
@@ -107,10 +162,10 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
// Do not start coroutines from OnValidate - update immediately
|
||||
UpdateUIImmediate();
|
||||
|
||||
// If user adjusts currentMana in inspector while in Play mode, attempt to trigger mana-full behaviors
|
||||
// Guard to avoid calling TryCastOnFullMana before singletons are initialized
|
||||
// If user adjusts currentHP or currentMana in inspector while in Play mode, attempt to trigger behaviors
|
||||
if (Application.isPlaying && SkillBuilder.Instance != null)
|
||||
{
|
||||
EvaluateHPPercentageTriggers(currentHP);
|
||||
TryCastOnFullMana();
|
||||
}
|
||||
}
|
||||
@@ -270,17 +325,26 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
if (a != null && a.ally_heroID == id)
|
||||
{
|
||||
// pick effective level based on current EXP if available
|
||||
// �ؼ�����ͬһ�� AllyHero_SO �������λʹ��ʱ������ֱ���� a.ally_currentEXP����������
|
||||
// ���Դ���Ҵ浵/PlayerPrefs ��ȡ������λ���ľ���ֵ��
|
||||
// Լ��������selected_heroSlot0{slotIndex+1}_exp������ selected_heroSlot01_exp����
|
||||
int expKeySlot = Mathf.Clamp(slotIndex + 1, 1, 5);
|
||||
string expKey = $"selected_heroSlot0{expKeySlot}_exp";
|
||||
int slotExp = a.ally_currentEXP;
|
||||
if (PlayerPrefs.HasKey(expKey))
|
||||
slotExp = PlayerPrefs.GetInt(expKey, a.ally_currentEXP);
|
||||
|
||||
// pick effective level based on per-slot EXP
|
||||
if (a.levelStats != null && a.levelStats.Count > 0)
|
||||
{
|
||||
var eff = a.GetEffectiveLevelForCurrentEXP();
|
||||
var eff = GetEffectiveLevelForExp(a, slotExp);
|
||||
if (eff != null)
|
||||
{
|
||||
maxHP = eff.maxHP;
|
||||
maxMana = eff.maxMana;
|
||||
damageResistance = eff.damageResistance;
|
||||
scoreEfficiency = eff.scoreEfficiency;
|
||||
attack = eff.attack; // populate attack from SO effective level
|
||||
attack = eff.attack;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,6 +365,23 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
|
||||
// ���ݴ��� EXP ����ȼ������� SO �ϵ� ally_currentEXP����������λ����״̬��
|
||||
private static AllyHero_SO.AllyLevelInfo GetEffectiveLevelForExp(AllyHero_SO so, int exp)
|
||||
{
|
||||
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return null;
|
||||
AllyHero_SO.AllyLevelInfo best = null;
|
||||
foreach (var lvl in so.levelStats)
|
||||
{
|
||||
if (lvl == null) continue;
|
||||
if (exp >= lvl.requiredEXP)
|
||||
{
|
||||
if (best == null || lvl.requiredEXP >= best.requiredEXP)
|
||||
best = lvl;
|
||||
}
|
||||
}
|
||||
return best ?? so.levelStats[0];
|
||||
}
|
||||
|
||||
// Scoring API
|
||||
// Modified to return the amount added so callers can track per-track contributions
|
||||
public int AddScoreForJudge(string judge)
|
||||
@@ -390,6 +471,20 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
currentHP = Mathf.Clamp(hp, 0, maxHP);
|
||||
if (currentHP <= 0) isDead = true;
|
||||
UpdateHealthVisuals(old, animateFade);
|
||||
|
||||
// Trigger HP changed events
|
||||
int delta = currentHP - old;
|
||||
if (delta > 0)
|
||||
{
|
||||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnHPHealed);
|
||||
}
|
||||
else if (delta < 0)
|
||||
{
|
||||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnHPLost);
|
||||
}
|
||||
|
||||
// Evaluate HP-percentage based trigger transitions (entry/exit)
|
||||
EvaluateHPPercentageTriggers(old);
|
||||
}
|
||||
|
||||
public void ModifyHP(int delta, bool animateFade = true)
|
||||
@@ -405,44 +500,66 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
public void SetMaxHP(int newMax, bool keepCurrentRatio = true)
|
||||
{
|
||||
if (newMax < 1) newMax = 1;
|
||||
int oldMax = maxHP;
|
||||
int oldHP = currentHP;
|
||||
float ratio = maxHP > 0 ? (float)currentHP / maxHP : 1f;
|
||||
maxHP = newMax;
|
||||
if (keepCurrentRatio)
|
||||
currentHP = Mathf.Clamp(Mathf.RoundToInt(ratio * maxHP), 0, maxHP);
|
||||
else
|
||||
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
|
||||
|
||||
UpdateUIImmediate();
|
||||
|
||||
// Max HP change affects percentage triggers
|
||||
EvaluateHPPercentageTriggers(oldHP);
|
||||
}
|
||||
|
||||
public void SetCurrentMana(int mana, bool animateFade = true)
|
||||
public void SetCurrentMana(int mana, bool animateFade = true, bool flashVisuals = true)
|
||||
{
|
||||
if (isDead) return; // dead characters cannot change mana
|
||||
int old = currentMana;
|
||||
currentMana = Mathf.Clamp(mana, 0, maxMana);
|
||||
UpdateManaVisuals(old, animateFade);
|
||||
UpdateManaVisuals(old, animateFade, flashVisuals);
|
||||
TryCastOnFullMana();
|
||||
|
||||
// Trigger mana changed events
|
||||
int delta = currentMana - old;
|
||||
if (delta > 0)
|
||||
{
|
||||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnManaGained);
|
||||
}
|
||||
else if (delta < 0)
|
||||
{
|
||||
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnManaLost);
|
||||
}
|
||||
}
|
||||
|
||||
public void ModifyMana(int delta, bool animateFade = true)
|
||||
public void ModifyMana(int delta, bool animateFade = true, bool flashVisuals = true)
|
||||
{
|
||||
if (isDead && delta > 0)
|
||||
{
|
||||
// dead units cannot gain mana
|
||||
return;
|
||||
}
|
||||
SetCurrentMana(currentMana + delta, animateFade);
|
||||
SetCurrentMana(currentMana + delta, animateFade, flashVisuals);
|
||||
}
|
||||
|
||||
public void SetMaxMana(int newMax, bool keepCurrentRatio = true)
|
||||
{
|
||||
if (newMax < 1) newMax = 1;
|
||||
int oldMax = maxMana;
|
||||
int oldMana = currentMana;
|
||||
float ratio = maxMana > 0 ? (float)currentMana / maxMana : 1f;
|
||||
maxMana = newMax;
|
||||
if (keepCurrentRatio)
|
||||
currentMana = Mathf.Clamp(Mathf.RoundToInt(ratio * maxMana), 0, maxMana);
|
||||
else
|
||||
currentMana = Mathf.Clamp(currentMana, 0, maxMana);
|
||||
|
||||
UpdateUIImmediate();
|
||||
// Mana doesn't currently have percentage triggers, but for consistency:
|
||||
// EvaluateHPPercentageTriggers(currentHP); // Not needed for mana but keep in mind
|
||||
}
|
||||
|
||||
private void UpdateHealthVisuals(int oldHP, bool animateFade)
|
||||
@@ -468,21 +585,39 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
fadeHealthImage.fillAmount = target;
|
||||
}
|
||||
|
||||
// if HP decreased, show hurt flash; if dead, keep hurt overlay visible
|
||||
if (currentHP < oldHP && hurtRedImage != null && Application.isPlaying)
|
||||
// HP change flash effects
|
||||
if (hurtRedImage != null && Application.isPlaying)
|
||||
{
|
||||
StopCoroutine("HurtFlash");
|
||||
StartCoroutine(HurtFlash());
|
||||
var ui = teamUIController.Instance;
|
||||
if (ui != null)
|
||||
{
|
||||
if (currentHP < oldHP)
|
||||
{
|
||||
// Damage flash (Red)
|
||||
if (!isDead)
|
||||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.hurtColor));
|
||||
}
|
||||
else if (currentHP > oldHP)
|
||||
{
|
||||
// Heal flash (Green)
|
||||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.healColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDead && hurtRedImage != null)
|
||||
|
||||
// if dead, trigger grayscale effect on character image
|
||||
if (isDead && characterImage != null && Application.isPlaying)
|
||||
{
|
||||
Color c = hurtRedImage.color;
|
||||
c.a = 1f;
|
||||
hurtRedImage.color = c; // keep overlay visible when dead
|
||||
var ui = teamUIController.Instance;
|
||||
if (ui != null)
|
||||
{
|
||||
// 移除原有的红闪常亮逻辑,改为调用黑白渐变效果
|
||||
StartCoroutine(ui.AllyDeathGrayscale(characterImage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateManaVisuals(int oldMana, bool animateFade)
|
||||
private void UpdateManaVisuals(int oldMana, bool animateFade, bool flashVisuals = true)
|
||||
{
|
||||
// main bar snaps, fade bar lerps to new value for visual effect
|
||||
if (manaImage != null)
|
||||
@@ -500,6 +635,16 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
else
|
||||
fadeManaImage.fillAmount = target;
|
||||
}
|
||||
|
||||
// Mana recovery flash effect
|
||||
if (flashVisuals && currentMana > oldMana && hurtRedImage != null && Application.isPlaying)
|
||||
{
|
||||
var ui = teamUIController.Instance;
|
||||
if (ui != null)
|
||||
{
|
||||
StartCoroutine(ui.AllyImageFlash(hurtRedImage, ui.manaColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUIImmediate()
|
||||
@@ -564,31 +709,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
ModifyHP(-delta, true);
|
||||
}
|
||||
|
||||
private IEnumerator HurtFlash()
|
||||
{
|
||||
if (hurtRedImage == null) yield break;
|
||||
Color c = hurtRedImage.color;
|
||||
c.a = 1f;
|
||||
hurtRedImage.color = c;
|
||||
// when injured, remain visible a bit longer; if dead, keep it on
|
||||
if (isDead)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
yield return new WaitForSeconds(0.2f);
|
||||
float dur = 0.4f;
|
||||
float t = 0f;
|
||||
while (t < dur)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
c.a = Mathf.Lerp(1f, 0f, t / dur);
|
||||
hurtRedImage.color = c;
|
||||
yield return null;
|
||||
}
|
||||
c.a = 0f;
|
||||
hurtRedImage.color = c;
|
||||
}
|
||||
|
||||
public void ReceiveHeal(float amount, GameObject source)
|
||||
{
|
||||
int delta = Mathf.CeilToInt(amount);
|
||||
@@ -616,6 +736,62 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
|
||||
private void TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger when)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (SkillBuilder.Instance == null) return;
|
||||
|
||||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||||
if (so == null) return;
|
||||
|
||||
// If equipped groups exist, iterate them first
|
||||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||||
{
|
||||
foreach (var gid in so.equippedSkillGroupIDs)
|
||||
{
|
||||
if (gid == 0) continue;
|
||||
SkillGroup group = null;
|
||||
for (int k = 0; k < so.skillGroups.Length; k++)
|
||||
{
|
||||
var g = so.skillGroups[k];
|
||||
if (g != null && g.skillGroupID == gid) { group = g; break; }
|
||||
}
|
||||
if (group == null) continue;
|
||||
foreach (var def in group.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != when) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: check primary group
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != when) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Final fallback: iterate availableSkills
|
||||
if (so.availableSkills != null)
|
||||
{
|
||||
foreach (var def in so.availableSkills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != when) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryCastOnFullMana()
|
||||
{
|
||||
if (currentMana >= maxMana && Application.isPlaying)
|
||||
@@ -655,23 +831,72 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
return;
|
||||
}
|
||||
|
||||
var def = so.GetPrimarySkill();
|
||||
if (def == null)
|
||||
bool anyTriggered = false;
|
||||
|
||||
// First: equipped groups
|
||||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||||
{
|
||||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast: primary skill not set in SO.");
|
||||
return;
|
||||
foreach (int gid in so.equippedSkillGroupIDs)
|
||||
{
|
||||
if (gid == 0) continue;
|
||||
SkillGroup group = null;
|
||||
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
|
||||
if (group == null) continue;
|
||||
foreach (var skill in group.skills)
|
||||
{
|
||||
if (skill == null) continue;
|
||||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast skill from group: {skill.skillId} (OnManaFull)");
|
||||
anyTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only auto-cast here if the skill's trigger condition matches ManaFull
|
||||
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
|
||||
// Second: primary group
|
||||
if (!anyTriggered)
|
||||
{
|
||||
// Use the SkillDefinition API to ensure selector/resolution/operateDirectly are respected
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var skill in fallbackGroup.skills)
|
||||
{
|
||||
if (skill == null) continue;
|
||||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast skill from primary group: {skill.skillId} (OnManaFull)");
|
||||
anyTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// Third: availableSkills / primary skill fallback
|
||||
if (!anyTriggered)
|
||||
{
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} primary skill '{def.skillId}' not set to trigger on ManaFull (trigger={def.triggerCondition}); skipping cast.");
|
||||
// try primary skill specifically
|
||||
var def = so.GetPrimarySkill();
|
||||
if (def != null && def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
|
||||
{
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
|
||||
anyTriggered = true;
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
foreach (var skill in so.availableSkills)
|
||||
{
|
||||
if (skill == null) continue;
|
||||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast available skill: {skill.skillId} (OnManaFull)");
|
||||
anyTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyTriggered)
|
||||
{
|
||||
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1}: no skills configured for OnManaFull.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,4 +932,211 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
SetCurrentMana(maxMana, true);
|
||||
}
|
||||
|
||||
// New: Evaluate HP-percent triggers and handle enter/leave transitions
|
||||
private void EvaluateHPPercentageTriggers(int oldHP)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (SkillBuilder.Instance == null) return;
|
||||
|
||||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||||
if (so == null) return;
|
||||
|
||||
float oldPct = (maxHP > 0) ? (float)oldHP / (float)maxHP : 0f;
|
||||
float currPct = (maxHP > 0) ? (float)currentHP / (float)maxHP : 0f;
|
||||
|
||||
Action<SkillDefinition> processDef = (def) =>
|
||||
{
|
||||
if (def == null) return;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnHPAbovePercent && def.triggerCondition != SkillDefinition.SkillTrigger.OnHPBelowPercent)
|
||||
return;
|
||||
|
||||
bool wasActive = _activeHpPercentSkills.Contains(def.skillId);
|
||||
bool nowActive = false;
|
||||
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnHPAbovePercent)
|
||||
nowActive = currPct >= def.hpTriggerPercent;
|
||||
else if (def.triggerCondition == SkillDefinition.SkillTrigger.OnHPBelowPercent)
|
||||
nowActive = currPct <= def.hpTriggerPercent;
|
||||
|
||||
// Evaluate formula into amount using similar variables as SkillBuilder
|
||||
float amount = 0f;
|
||||
var vars = new Dictionary<string, float>();
|
||||
vars["slot"] = slotIndex;
|
||||
vars["attack"] = attack;
|
||||
if (so != null && so.levelStats != null && so.levelStats.Count > 0)
|
||||
{
|
||||
var eff = so.GetEffectiveLevelForCurrentEXP();
|
||||
var lvl = eff ?? so.levelStats[0];
|
||||
vars["maxHP"] = lvl.maxHP;
|
||||
vars["maxMana"] = lvl.maxMana;
|
||||
vars["damageResistance"] = lvl.damageResistance;
|
||||
vars["scoreEfficiency"] = lvl.scoreEfficiency;
|
||||
vars["attack"] = lvl.attack;
|
||||
}
|
||||
else
|
||||
{
|
||||
vars["maxHP"] = maxHP;
|
||||
vars["maxMana"] = maxMana;
|
||||
vars["damageResistance"] = damageResistance;
|
||||
vars["scoreEfficiency"] = scoreEfficiency;
|
||||
}
|
||||
vars["ally_currentEXP"] = so != null ? so.ally_currentEXP : 0f;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(def.formula))
|
||||
{
|
||||
if (!SkillDefinition.TryEvaluateFormula(def.formula, vars, out float fresult))
|
||||
amount = 0f;
|
||||
else
|
||||
amount = fresult;
|
||||
}
|
||||
|
||||
// Decide if this effect type is reversible (state-based)
|
||||
bool isReversible = IsEffectReversible(def.effectType);
|
||||
|
||||
if (!isReversible)
|
||||
{
|
||||
// For non-reversible effects (like Damage or instant Heal), we don't track state
|
||||
// Just trigger it once when the condition is first met
|
||||
if (!wasActive && nowActive)
|
||||
{
|
||||
_activeHpPercentSkills.Add(def.skillId);
|
||||
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null);
|
||||
}
|
||||
else if (wasActive && !nowActive)
|
||||
{
|
||||
_activeHpPercentSkills.Remove(def.skillId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasActive && nowActive)
|
||||
{
|
||||
// ENTER condition: Apply effect and record amount
|
||||
_activeHpPercentSkills.Add(def.skillId);
|
||||
EffectSystem.Instance.ApplyEffect(def.defaultSelector, def.effectType, amount, 0f, this.gameObject, null);
|
||||
_appliedHpPercentEffects[def.skillId] = new AppliedEffect(def.effectType, amount);
|
||||
}
|
||||
else if (wasActive && !nowActive)
|
||||
{
|
||||
// LEAVE condition: Revert effect using recorded amount
|
||||
_activeHpPercentSkills.Remove(def.skillId);
|
||||
if (_appliedHpPercentEffects.TryGetValue(def.skillId, out var applied))
|
||||
{
|
||||
ApplyInverseEffect(def.defaultSelector, applied.effectType, applied.amount);
|
||||
_appliedHpPercentEffects.Remove(def.skillId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// iterate equipped groups -> primary group -> availableSkills (same as other triggers)
|
||||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||||
{
|
||||
foreach (var gid in so.equippedSkillGroupIDs)
|
||||
{
|
||||
if (gid == 0) continue;
|
||||
SkillGroup group = null;
|
||||
for (int k = 0; k < so.skillGroups.Length; k++)
|
||||
{
|
||||
var g = so.skillGroups[k];
|
||||
if (g != null && g.skillGroupID == gid) { group = g; break; }
|
||||
}
|
||||
if (group == null) continue;
|
||||
foreach (var def in group.skills)
|
||||
{
|
||||
processDef(def);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
processDef(def);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (so.availableSkills != null)
|
||||
{
|
||||
foreach (var def in so.availableSkills)
|
||||
{
|
||||
processDef(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEffectReversible(EffectType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EffectType.IncreaseAttack:
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.IncreaseMaxHP:
|
||||
case EffectType.DecreaseMaxHP:
|
||||
case EffectType.IncreaseMaxMana:
|
||||
case EffectType.DecreaseMaxMana:
|
||||
case EffectType.IncreaseScoreEfficiency:
|
||||
case EffectType.DecreaseScoreEfficiency:
|
||||
case EffectType.IncreaseDamageResistance:
|
||||
case EffectType.DecreaseDamageResistance:
|
||||
case EffectType.ScoreMultiplier:
|
||||
case EffectType.AddScore:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyInverseEffect(Selector selector, EffectType type, float amount)
|
||||
{
|
||||
if (EffectSystem.Instance == null) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case EffectType.IncreaseAttack:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseAttack, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.DecreaseAttack:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseAttack, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.IncreaseMaxHP:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseMaxHP, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.DecreaseMaxHP:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseMaxHP, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.IncreaseMaxMana:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseMaxMana, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.DecreaseMaxMana:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseMaxMana, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.IncreaseScoreEfficiency:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseScoreEfficiency, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.DecreaseScoreEfficiency:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseScoreEfficiency, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.IncreaseDamageResistance:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.DecreaseDamageResistance, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.DecreaseDamageResistance:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.IncreaseDamageResistance, amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.ScoreMultiplier:
|
||||
if (Mathf.Abs(amount) > 0.0001f)
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.ScoreMultiplier, 1f / amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
case EffectType.AddScore:
|
||||
EffectSystem.Instance.ApplyEffect(selector, EffectType.AddScore, -amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
default:
|
||||
// Fallback: apply same type with negative amount if it's likely a numeric stat
|
||||
EffectSystem.Instance.ApplyEffect(selector, type, -amount, 0f, this.gameObject, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user