修了很多bug和增加功能,优化不少问题
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -45,7 +45,9 @@ public enum EffectType
|
||||
// Adds a virtual "Perfect" judgement to targeted ally tracks (counts + score + triggers note-hit hooks).
|
||||
GrantExtraPerfect,
|
||||
// While active, redirects damage that would be taken by this ally to an adjacent ally.
|
||||
RedirectSelfDamageToAdjacent
|
||||
RedirectSelfDamageToAdjacent,
|
||||
// While active, any non-Miss note judgement on the ally's track is rewritten to Perfect.
|
||||
RewriteNonMissToPerfect
|
||||
}
|
||||
|
||||
public interface ICombatant
|
||||
|
||||
@@ -46,8 +46,19 @@ public class EffectSystem : MonoBehaviour
|
||||
/// Documentation text normalized.
|
||||
/// Documentation text normalized.
|
||||
/// </summary>
|
||||
public void ApplyEffect(Selector selector, EffectType effectType, float amount, float duration = 0f, GameObject source = null, GameObject specificTarget = null, float tickInterval = 1f)
|
||||
public void ApplyEffect(Selector selector, EffectType effectType, float amount, float duration = 0f, GameObject source = null, GameObject specificTarget = null, float tickInterval = 1f, bool skipVFX = false)
|
||||
{
|
||||
// --- Prevent empty ally slots (maxHP == 0) from being a source of any effect ---
|
||||
if (source != null)
|
||||
{
|
||||
var allySource = source.GetComponent<AllyCombatant>();
|
||||
if (allySource != null && allySource.maxHP == 0)
|
||||
{
|
||||
LogVerbose($"[EffectSystem] ApplyEffect: source is empty ally {source.name}, skipping.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If caller provided a specificTarget, prefer it as the single target regardless of selector.
|
||||
// Many callers pass specificTarget when they intend to hit a single unit (ally or enemy).
|
||||
List<GameObject> targets;
|
||||
@@ -66,9 +77,16 @@ public class EffectSystem : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// For direct buff-like effects (not Buff object path), allow AllyCombatant redirect states
|
||||
// to reroute the receiver before the effect is applied.
|
||||
if (IsDirectRedirectableBuffEffectType(effectType))
|
||||
{
|
||||
targets = RedirectDirectBuffTargets(targets);
|
||||
}
|
||||
|
||||
// --- Projectile Logic ---
|
||||
// Trigger projectile if source exists and is targeting the opposite side
|
||||
if (GfxController.Instance != null && source != null)
|
||||
if (GfxController.Instance != null && source != null && !skipVFX)
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
@@ -82,6 +100,42 @@ public class EffectSystem : MonoBehaviour
|
||||
|
||||
if ((sourceIsAlly && targetIsEnemy) || (sourceIsEnemy && targetIsAlly))
|
||||
{
|
||||
// --- Check if the specific ally slot is active before playing VFX ---
|
||||
if (sourceIsAlly)
|
||||
{
|
||||
var ally = source.GetComponent<AllyCombatant>();
|
||||
if (ally != null && teamUIController.Instance != null)
|
||||
{
|
||||
bool isActive = false;
|
||||
switch (ally.slotIndex)
|
||||
{
|
||||
case 0: isActive = teamUIController.Instance.isAlly01_active; break;
|
||||
case 1: isActive = teamUIController.Instance.isAlly02_active; break;
|
||||
case 2: isActive = teamUIController.Instance.isAlly03_active; break;
|
||||
case 3: isActive = teamUIController.Instance.isAlly04_active; break;
|
||||
case 4: isActive = teamUIController.Instance.isAlly05_active; break;
|
||||
}
|
||||
if (!isActive) continue; // Skip VFX for inactive ally
|
||||
}
|
||||
}
|
||||
else if (targetIsAlly)
|
||||
{
|
||||
var ally = t.GetComponent<AllyCombatant>();
|
||||
if (ally != null && teamUIController.Instance != null)
|
||||
{
|
||||
bool isActive = false;
|
||||
switch (ally.slotIndex)
|
||||
{
|
||||
case 0: isActive = teamUIController.Instance.isAlly01_active; break;
|
||||
case 1: isActive = teamUIController.Instance.isAlly02_active; break;
|
||||
case 2: isActive = teamUIController.Instance.isAlly03_active; break;
|
||||
case 3: isActive = teamUIController.Instance.isAlly04_active; break;
|
||||
case 4: isActive = teamUIController.Instance.isAlly05_active; break;
|
||||
}
|
||||
if (!isActive) continue; // Skip VFX for inactive ally
|
||||
}
|
||||
}
|
||||
|
||||
// Captured target for callback
|
||||
GameObject targetToHit = t;
|
||||
bool isEnemyTarget = targetIsEnemy;
|
||||
@@ -688,6 +742,20 @@ public class EffectSystem : MonoBehaviour
|
||||
}
|
||||
break;
|
||||
|
||||
case EffectType.RewriteNonMissToPerfect:
|
||||
foreach (var t in targets)
|
||||
{
|
||||
if (t == null) continue;
|
||||
var ally = t.GetComponent<AllyCombatant>() ?? t.GetComponentInChildren<AllyCombatant>(true);
|
||||
if (ally == null) continue;
|
||||
if (ally.IsDead) continue;
|
||||
|
||||
// Prefer configured duration; fallback to amount when designers authored duration in formula.
|
||||
float activeDuration = duration > 0f ? duration : amount;
|
||||
ally.ActivateNonMissToPerfectRewrite(activeDuration);
|
||||
}
|
||||
break;
|
||||
|
||||
case EffectType.RedirectSelfDamageToAdjacent:
|
||||
foreach (var t in targets)
|
||||
{
|
||||
@@ -855,13 +923,13 @@ public class EffectSystem : MonoBehaviour
|
||||
foreach (var g in list)
|
||||
if (!uniq.Contains(g)) uniq.Add(g);
|
||||
|
||||
// Filter out dead combatants so they won't receive further effects (damage/score/etc.).
|
||||
// Filter out dead or empty combatants so they won't receive further effects (damage/score/etc.).
|
||||
// This prevents "dead units still get hit / still gain score" edge cases.
|
||||
uniq.RemoveAll(go =>
|
||||
{
|
||||
if (go == null) return true;
|
||||
var ally = go.GetComponent<AllyCombatant>();
|
||||
if (ally != null) return ally.IsDead;
|
||||
if (ally != null) return ally.IsDead || ally.maxHP == 0; // Skip dead or empty allies
|
||||
var enemy = go.GetComponent<EnemyCombatant>();
|
||||
if (enemy != null) return enemy.IsDead || enemy.currentHP <= 0;
|
||||
return false;
|
||||
@@ -983,11 +1051,33 @@ public class EffectSystem : MonoBehaviour
|
||||
var targetEnemy = target.GetComponent<EnemyCombatant>();
|
||||
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) return;
|
||||
|
||||
// If target is an enemy, check if all enemies are already dead
|
||||
if (targetEnemy != null && AreAllEnemiesDead())
|
||||
// If target is an enemy, check if all enemies are already dead or no allies are active
|
||||
if (targetEnemy != null)
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip ApplyInstantDamage to {target.name} because all enemies are dead.");
|
||||
return;
|
||||
if (AreAllEnemiesDead())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip ApplyInstantDamage to {target.name} because all enemies are dead.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (teamUIController.Instance != null)
|
||||
{
|
||||
if (!teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip ApplyInstantDamage to {target.name} because no allies are active.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
var sourceAlly = source.GetComponent<AllyCombatant>();
|
||||
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip ApplyInstantDamage to {target.name} because source ally slot {sourceAlly.slotIndex} is not active.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var comp = target.GetComponent<ICombatant>();
|
||||
@@ -1090,10 +1180,32 @@ public class EffectSystem : MonoBehaviour
|
||||
|
||||
if (duration <= 0f)
|
||||
{
|
||||
if (targetEnemy != null && AreAllEnemiesDead())
|
||||
if (targetEnemy != null)
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because all enemies are dead.");
|
||||
yield break;
|
||||
if (AreAllEnemiesDead())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because all enemies are dead.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (teamUIController.Instance != null)
|
||||
{
|
||||
if (!teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because no allies are active.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
var sourceAlly = source.GetComponent<AllyCombatant>();
|
||||
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because source ally slot {sourceAlly.slotIndex} is not active.");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comp.ReceiveDamage(totalAmount, source);
|
||||
@@ -1115,10 +1227,32 @@ public class EffectSystem : MonoBehaviour
|
||||
|
||||
if (targetAlly != null && targetAlly.IsDead) yield break;
|
||||
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) yield break;
|
||||
if (targetEnemy != null && AreAllEnemiesDead())
|
||||
if (targetEnemy != null)
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because all enemies are dead.");
|
||||
yield break;
|
||||
if (AreAllEnemiesDead())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because all enemies are dead.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (teamUIController.Instance != null)
|
||||
{
|
||||
if (!teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because no allies are active.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
var sourceAlly = source.GetComponent<AllyCombatant>();
|
||||
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
|
||||
{
|
||||
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because source ally slot {sourceAlly.slotIndex} is not active.");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int beforeHP = 0;
|
||||
@@ -1554,6 +1688,55 @@ public class EffectSystem : MonoBehaviour
|
||||
iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDirectRedirectableBuffEffectType(EffectType effectType)
|
||||
{
|
||||
switch (effectType)
|
||||
{
|
||||
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.IncreaseAttack:
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.RewriteNonMissToPerfect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<GameObject> RedirectDirectBuffTargets(List<GameObject> targets)
|
||||
{
|
||||
if (targets == null || targets.Count == 0) return targets;
|
||||
var redirectedTargets = new List<GameObject>(targets.Count);
|
||||
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
var t = targets[i];
|
||||
if (t == null)
|
||||
{
|
||||
redirectedTargets.Add(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
var ally = t.GetComponent<AllyCombatant>() ?? t.GetComponentInChildren<AllyCombatant>(true);
|
||||
if (ally != null && ally.TryRedirectIncomingGenericBuff(out var redirected) && redirected != null)
|
||||
{
|
||||
redirectedTargets.Add(redirected.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
redirectedTargets.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
return redirectedTargets;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -8,6 +8,9 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
public EnemyData_SO sourceData;
|
||||
|
||||
private int _currentDifficultyID;
|
||||
private GameObject _lastAttacker;
|
||||
|
||||
// runtime stats
|
||||
public int maxHP = 100;
|
||||
public int currentHP = 100;
|
||||
@@ -35,21 +38,24 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
public void InitializeFromSO(EnemyData_SO so)
|
||||
public void InitializeFromSO(EnemyData_SO so, int difficultyID)
|
||||
{
|
||||
// This enemy GameObject can be reused for multiple enemy entries in sequence.
|
||||
// Clear any runtime buffs so they don't leak across enemy transitions.
|
||||
activeBuffs.Clear();
|
||||
_attackBaseInitialized = false;
|
||||
_attackBaseUnbuffed = 0;
|
||||
_lastAttacker = null;
|
||||
_currentDifficultyID = difficultyID;
|
||||
|
||||
sourceData = so;
|
||||
if (so != null)
|
||||
{
|
||||
maxHP = Mathf.Max(1, so.enemy_maxHP);
|
||||
damageResistance = so.enemy_damageResistance;
|
||||
SetAttack(so.enemy_baseAttack);
|
||||
maxMana = Mathf.Max(0, so.GetEffectiveMaxMana());
|
||||
var stats = so.GetStatsByDifficultyID(difficultyID);
|
||||
maxHP = Mathf.Max(1, stats.enemy_maxHP);
|
||||
damageResistance = stats.enemy_damageResistance;
|
||||
SetAttack(stats.enemy_baseAttack);
|
||||
maxMana = Mathf.Max(0, stats.enemy_maxMana);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -131,6 +137,13 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
// Check if any allies are active before receiving damage
|
||||
if (teamUIController.Instance != null && !teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastAttacker = source;
|
||||
float effective = amount * (1f - damageResistance);
|
||||
int delta = Mathf.CeilToInt(effective);
|
||||
currentHP = Mathf.Clamp(currentHP - delta, 0, maxHP);
|
||||
@@ -278,9 +291,27 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
// Finally mark dead so further damage/heal is ignored.
|
||||
// We set this EARLY because listeners (OnEnemyDied) might re-initialize this instance
|
||||
// for the next enemy in sequence, which would set isDead = false.
|
||||
// award score to last attacker
|
||||
if (sourceData != null)
|
||||
{
|
||||
var stats = sourceData.GetStatsByDifficultyID(_currentDifficultyID);
|
||||
int award = stats.scoreOnBeingDefeated;
|
||||
if (award > 0)
|
||||
{
|
||||
var ally = _lastAttacker != null ? _lastAttacker.GetComponent<AllyCombatant>() : null;
|
||||
if (ally != null)
|
||||
{
|
||||
ally.AddScoreDirect(award);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no specific attacker, try to find current primary track or just add to global
|
||||
ScoreManager.Instance?.AddIdolScoreForTrack(0, award);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally mark dead
|
||||
isDead = true;
|
||||
|
||||
// 播放 KO 特效
|
||||
@@ -318,11 +349,19 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
|
||||
// notify listeners (UI/manager) that enemy died AFTER triggering skills so they operate on the current instance
|
||||
OnEnemyDied?.Invoke(this);
|
||||
|
||||
// Check whether all enemies are now defeated. Use currentHP/IsDead to determine status.
|
||||
// Check whether all enemies are now defeated.
|
||||
// If teamUIController exists, it owns enemy-sequence completion and will fire this event exactly once.
|
||||
// Fallback to legacy scene scan only when teamUIController is not present.
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ui = UnityEngine.Object.FindAnyObjectByType<teamUIController>();
|
||||
if (ui != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var all = UnityEngine.Object.FindObjectsByType<EnemyCombatant>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
bool anyAlive = false;
|
||||
foreach (var e in all)
|
||||
|
||||
@@ -448,6 +448,56 @@ public class ScoreManager : MonoBehaviour
|
||||
UpdateIdolscoreKeyScales();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add idol score directly to a track (0..4) without changing pm score.
|
||||
/// Used by skills that grant/consume idol score independently from note judgement pm.
|
||||
/// </summary>
|
||||
public void AddIdolScoreForTrack(int trackIndex, int idolDelta)
|
||||
{
|
||||
TryHookPerfectBonusEvent();
|
||||
if (trackIndex < 0 || trackIndex >= idolScoreSums.Length) return;
|
||||
if (idolDelta == 0) return;
|
||||
|
||||
idolScoreSums[trackIndex] += idolDelta;
|
||||
if (idolScoreSums[trackIndex] < 0) idolScoreSums[trackIndex] = 0;
|
||||
if (idolScoreSums[trackIndex] > int.MaxValue - 1) idolScoreSums[trackIndex] = int.MaxValue - 1;
|
||||
|
||||
red_idolScore_sum = idolScoreSums[0];
|
||||
green_idolScore_sum = idolScoreSums[1];
|
||||
yellow_idolScore_sum = idolScoreSums[2];
|
||||
purple_idolScore_sum = idolScoreSums[3];
|
||||
blue_idolScore_sum = idolScoreSums[4];
|
||||
|
||||
long aggPm = 0;
|
||||
long aggIdol = 0;
|
||||
for (int i = 0; i < pmScoreSums.Length; i++) aggPm += pmScoreSums[i];
|
||||
for (int i = 0; i < idolScoreSums.Length; i++) aggIdol += idolScoreSums[i];
|
||||
long aggPmWithBonus = aggPm + (long)perfectClearBonusPm;
|
||||
|
||||
allSum_pmScore = (int)Mathf.Min((float)aggPmWithBonus, (float)int.MaxValue - 1f);
|
||||
allSum_idolScore = (int)Mathf.Min((float)aggIdol, (float)int.MaxValue - 1f);
|
||||
|
||||
var ui = teamUIController.Instance;
|
||||
if (ui != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ui.red_idolScore_sum != null) ui.red_idolScore_sum.text = red_idolScore_sum.ToString();
|
||||
if (ui.green_idolScore_sum != null) ui.green_idolScore_sum.text = green_idolScore_sum.ToString();
|
||||
if (ui.yellow_idolScore_sum != null) ui.yellow_idolScore_sum.text = yellow_idolScore_sum.ToString();
|
||||
if (ui.purple_idolScore_sum != null) ui.purple_idolScore_sum.text = purple_idolScore_sum.ToString();
|
||||
if (ui.blue_idolScore_sum != null) ui.blue_idolScore_sum.text = blue_idolScore_sum.ToString();
|
||||
if (ui.allSum_idolScore != null) ui.allSum_idolScore.text = allSum_idolScore.ToString();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
if (JudgeManager.IsDebugEnabled) Debug.LogWarning($"[ScoreManager] Failed to write idol sums to teamUIController fields: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
RecalculateTotal();
|
||||
}
|
||||
|
||||
public void RecalculateTotal()
|
||||
{
|
||||
EnsureAllyCache();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -163,7 +163,7 @@ public class SkillBuilder : MonoBehaviour
|
||||
var allyGO = GetAllyObjectBySlot(trackIndex);
|
||||
if (allyGO == null) return;
|
||||
var ally = allyGO.GetComponent<AllyCombatant>();
|
||||
if (ally != null && ally.IsDead) return; // dead allies should not gain mana / take miss damage / deal note-hit damage
|
||||
if (ally != null && (ally.IsDead || ally.maxHP == 0)) return; // dead or empty allies should not gain mana / take miss damage / deal note-hit damage
|
||||
var so = GetAllyHeroSOBySlot(trackIndex);
|
||||
// try to obtain per-level params from SO if present
|
||||
AllyHero_SO.AllyLevelInfo levelInfo = so != null ? (so.GetEffectiveLevelForCurrentEXP()) : null;
|
||||
@@ -214,7 +214,35 @@ public class SkillBuilder : MonoBehaviour
|
||||
else if (ally != null) mName = ally.name; // fallback to GameObject name
|
||||
SkillTriggerFeedUI.PushMiss(mName);
|
||||
|
||||
float missBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
|
||||
// --- New Miss Damage Formula ---
|
||||
// Formula: EnemyAttack * DifficultyLevel * 0.03 * AllyMissHpLossBase * (1 - Resistance)
|
||||
// Note: AllyCombatant.ReceiveDamage already applies (1 - Resistance), so we calculate the base here.
|
||||
|
||||
float enemyAtk = 0f;
|
||||
var enemyGO = GameObject.Find("thisEnemy");
|
||||
if (enemyGO != null)
|
||||
{
|
||||
var ec = enemyGO.GetComponent<EnemyCombatant>();
|
||||
if (ec != null) enemyAtk = ec.attack;
|
||||
}
|
||||
|
||||
float diffLevel = 1f;
|
||||
if (BeatmapManager.Instance != null && BeatmapManager.Instance.assignedSongData != null)
|
||||
{
|
||||
int curDiff = BeatmapManager.Instance.assignedDifficulty;
|
||||
if (BeatmapManager.Instance.assignedSongData.chartFiles != null)
|
||||
{
|
||||
var chart = BeatmapManager.Instance.assignedSongData.chartFiles.Find(c => c.difficulty == curDiff);
|
||||
if (chart != null) diffLevel = chart.difficultyLEVEL;
|
||||
}
|
||||
}
|
||||
|
||||
float fixedMult = 0.03f;
|
||||
float allyMissBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
|
||||
|
||||
float missBase = enemyAtk * diffLevel * fixedMult * allyMissBase;
|
||||
|
||||
LogVerbose($"[SkillBuilder] Miss Damage Calc: Atk({enemyAtk}) * DiffLvl({diffLevel}) * Mult({fixedMult}) * AllyBase({allyMissBase}) = {missBase}");
|
||||
|
||||
// 播放敌人攻击特效,并将伤害逻辑延迟到特效到达时执行
|
||||
bool effectStarted = false;
|
||||
@@ -779,6 +807,64 @@ public class SkillBuilder : MonoBehaviour
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.RedirectNextDamageToSelf:
|
||||
case EffectType.RedirectSelfDamageToAdjacent:
|
||||
case EffectType.RewriteNonMissToPerfect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Buff redirection (交给我!/都给你!) should affect any "buff-like" effect that is represented
|
||||
// by budeff icons or timed status changes, not only Buff objects.
|
||||
private static bool IsRedirectableBudeffEvent(EffectType effectType)
|
||||
{
|
||||
switch (effectType)
|
||||
{
|
||||
case EffectType.DamageOverTimeEnemy:
|
||||
case EffectType.DamageOverTimeAlly:
|
||||
case EffectType.ReduceEnemyHealOverTime:
|
||||
case EffectType.BuffDuration:
|
||||
case EffectType.DebuffDuration:
|
||||
case EffectType.IncreaseManaOverTime:
|
||||
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.IncreaseAttack:
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.RedirectNextDamageToSelf:
|
||||
case EffectType.RedirectSelfDamageToAdjacent:
|
||||
case EffectType.RewriteNonMissToPerfect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Subset for def.operateDirectly branch where effects are applied inside SkillBuilder switch.
|
||||
// Do not include types that fall through to default ExecuteSkill, otherwise redirect would be consumed too early.
|
||||
private static bool IsRedirectableDirectBranchEffect(EffectType effectType)
|
||||
{
|
||||
switch (effectType)
|
||||
{
|
||||
case EffectType.DamageOverTimeEnemy:
|
||||
case EffectType.IncreaseManaOverTime:
|
||||
case EffectType.ReduceEnemyHealOverTime:
|
||||
case EffectType.BuffDuration:
|
||||
case EffectType.DebuffDuration:
|
||||
case EffectType.IncreaseMaxHP:
|
||||
case EffectType.DecreaseMaxHP:
|
||||
case EffectType.IncreaseMaxMana:
|
||||
case EffectType.DecreaseMaxMana:
|
||||
case EffectType.IncreaseScoreEfficiency:
|
||||
case EffectType.DecreaseScoreEfficiency:
|
||||
case EffectType.IncreaseAttack:
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.RedirectNextDamageToSelf:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -1372,14 +1458,23 @@ ResolvedGroup:
|
||||
{
|
||||
if (t == null) continue;
|
||||
|
||||
GameObject target = t;
|
||||
var ally = target.GetComponent<AllyCombatant>();
|
||||
if (ally != null && IsRedirectableDirectBranchEffect(def.effectType))
|
||||
{
|
||||
if (ally.TryRedirectIncomingGenericBuff(out var redirectedAlly) && redirectedAlly != null)
|
||||
{
|
||||
target = redirectedAlly.gameObject;
|
||||
ally = redirectedAlly;
|
||||
}
|
||||
}
|
||||
var ic = target.GetComponent<ICombatant>();
|
||||
|
||||
if (GameConfig.skillDebugMode)
|
||||
{
|
||||
Debug.Log($"<color=#00FFFF>[SkillDebug]</color> <color=#FFD700>ID: {def.skillId}</color> | <color=#00FF00>Trigger: {def.triggerCondition}</color> | <color=#FFA500>Target: {t.name}</color> | <color=#EE82EE>Effect: {def.effectType}</color> | <color=#FF4500>Value: {amountTotal:F2}</color>");
|
||||
Debug.Log($"<color=#00FFFF>[SkillDebug]</color> <color=#FFD700>ID: {def.skillId}</color> | <color=#00FF00>Trigger: {def.triggerCondition}</color> | <color=#FFA500>Target: {target.name}</color> | <color=#EE82EE>Effect: {def.effectType}</color> | <color=#FF4500>Value: {amountTotal:F2}</color>");
|
||||
}
|
||||
|
||||
var ally = t.GetComponent<AllyCombatant>();
|
||||
var ic = t.GetComponent<ICombatant>();
|
||||
|
||||
if (ally != null && ShouldSpawnApprForEffect(def.effectType))
|
||||
{
|
||||
var apprSprite = iBudeffPrefabController.Instance != null ? iBudeffPrefabController.Instance.GetSpriteForEffect(def.effectType) : null;
|
||||
@@ -1405,7 +1500,7 @@ ResolvedGroup:
|
||||
string iconId = null;
|
||||
int allySlotIndex = -1;
|
||||
int enemyInstanceId = 0;
|
||||
var enemyDot = t.GetComponent<EnemyCombatant>() ?? t.GetComponentInChildren<EnemyCombatant>(true);
|
||||
var enemyDot = target.GetComponent<EnemyCombatant>() ?? target.GetComponentInChildren<EnemyCombatant>(true);
|
||||
if (enemyDot != null)
|
||||
{
|
||||
int ticks = Mathf.Max(1, Mathf.CeilToInt(def.defaultDuration / def.GetEffectiveTickInterval()));
|
||||
@@ -1420,7 +1515,7 @@ ResolvedGroup:
|
||||
iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_bleeding, perTick, def.defaultDuration);
|
||||
allySlotIndex = ally.slotIndex;
|
||||
}
|
||||
StartCoroutine(ApplyDamageOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster, iconId, allySlotIndex, enemyInstanceId));
|
||||
StartCoroutine(ApplyDamageOverTimeDirect(target, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster, iconId, allySlotIndex, enemyInstanceId));
|
||||
}
|
||||
break;
|
||||
case EffectType.HealSingleSelf:
|
||||
@@ -1437,7 +1532,7 @@ ResolvedGroup:
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutine(ApplyHealOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
|
||||
StartCoroutine(ApplyHealOverTimeDirect(target, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
|
||||
}
|
||||
break;
|
||||
case EffectType.IncreaseManaOverTime:
|
||||
@@ -1467,7 +1562,7 @@ ResolvedGroup:
|
||||
healReceivedMultiplier = 1f - reduction
|
||||
};
|
||||
ic.ApplyBuff(deb, caster);
|
||||
StartCoroutine(RemoveBuffAfterDuration(t, deb.buffId, deb.duration));
|
||||
StartCoroutine(RemoveBuffAfterDuration(target, deb.buffId, deb.duration));
|
||||
}
|
||||
break;
|
||||
case EffectType.BuffDuration:
|
||||
@@ -1484,7 +1579,7 @@ ResolvedGroup:
|
||||
scoreMultiplier = mult
|
||||
};
|
||||
ic.ApplyBuff(b, caster);
|
||||
StartCoroutine(RemoveBuffAfterDuration(t, b.buffId, b.duration));
|
||||
StartCoroutine(RemoveBuffAfterDuration(target, b.buffId, b.duration));
|
||||
}
|
||||
break;
|
||||
// Documentation text normalized.
|
||||
@@ -1496,7 +1591,7 @@ ResolvedGroup:
|
||||
if (d > 0) ally.SetCurrentHP(ally.currentHP + d, false);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_up, d, 0f);
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy))
|
||||
{
|
||||
int d = Mathf.CeilToInt(amountTotal);
|
||||
enemy.SetMaxHP(enemy.maxHP + d, false);
|
||||
@@ -1511,7 +1606,7 @@ ResolvedGroup:
|
||||
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - d), false);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_down, -d, 0f);
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy2))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy2))
|
||||
{
|
||||
int d = Mathf.CeilToInt(amountTotal);
|
||||
enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - d), false);
|
||||
@@ -1525,7 +1620,7 @@ ResolvedGroup:
|
||||
ally.SetMaxMana(ally.maxMana + d, false);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_up, d, 0f);
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy3))
|
||||
{
|
||||
int d = Mathf.CeilToInt(amountTotal);
|
||||
enemy3.SetMaxMana(enemy3.maxMana + d, false);
|
||||
@@ -1539,7 +1634,7 @@ ResolvedGroup:
|
||||
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - d), false);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_down, -d, 0f);
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy4))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy4))
|
||||
{
|
||||
int d = Mathf.CeilToInt(amountTotal);
|
||||
enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - d), false);
|
||||
@@ -1554,7 +1649,7 @@ ResolvedGroup:
|
||||
ally.scoreEfficiency += amountTotal;
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, amountTotal, def.defaultDuration, iconId));
|
||||
@@ -1569,7 +1664,7 @@ ResolvedGroup:
|
||||
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - amountTotal);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amountTotal, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amountTotal, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, -amountTotal, def.defaultDuration, iconId));
|
||||
@@ -1586,20 +1681,20 @@ ResolvedGroup:
|
||||
ally.ModifyAttack(delta);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, delta, def.defaultDuration, iconId));
|
||||
}
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy5))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy5))
|
||||
{
|
||||
if (def.defaultDuration <= 0f)
|
||||
{
|
||||
enemy5.ModifyAttack(delta);
|
||||
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, delta, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, delta, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy5, delta, def.defaultDuration, iconId, enemy5.GetInstanceID()));
|
||||
@@ -1617,20 +1712,20 @@ ResolvedGroup:
|
||||
ally.ModifyAttack(-delta);
|
||||
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, -delta, def.defaultDuration, iconId));
|
||||
}
|
||||
}
|
||||
else if (t.TryGetComponent<EnemyCombatant>(out var enemy6))
|
||||
else if (target.TryGetComponent<EnemyCombatant>(out var enemy6))
|
||||
{
|
||||
if (def.defaultDuration <= 0f)
|
||||
{
|
||||
enemy6.ModifyAttack(-delta);
|
||||
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
|
||||
}
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
|
||||
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
|
||||
{
|
||||
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -delta, def.defaultDuration);
|
||||
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy6, -delta, def.defaultDuration, iconId, enemy6.GetInstanceID()));
|
||||
@@ -1677,13 +1772,27 @@ ResolvedGroup:
|
||||
{
|
||||
var resolvedTargets = ResolveTargetsLocal(def.defaultSelector, caster, specificTarget);
|
||||
bool appliedRefreshOnly = false;
|
||||
bool mayHandleLocally = IsRefreshOnlyTimedSkill(def) || IsRefreshOnlyOverTimeSkill(def);
|
||||
if (resolvedTargets != null)
|
||||
{
|
||||
foreach (var t in resolvedTargets)
|
||||
{
|
||||
if (t == null) continue;
|
||||
bool handled = TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration);
|
||||
if (!handled) handled = TryApplyRefreshOnlyOverTimeEffect(def, t, amountTotal, caster);
|
||||
var target = t;
|
||||
if (mayHandleLocally)
|
||||
{
|
||||
var ally = target.GetComponent<AllyCombatant>() ?? target.GetComponentInChildren<AllyCombatant>(true);
|
||||
if (ally != null && IsRedirectableBudeffEvent(def.effectType))
|
||||
{
|
||||
if (ally.TryRedirectIncomingGenericBuff(out var redirectedAlly) && redirectedAlly != null)
|
||||
{
|
||||
target = redirectedAlly.gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool handled = TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration);
|
||||
if (!handled) handled = TryApplyRefreshOnlyOverTimeEffect(def, target, amountTotal, caster);
|
||||
if (handled) appliedRefreshOnly = true;
|
||||
}
|
||||
}
|
||||
@@ -1992,6 +2101,89 @@ ResolvedGroup:
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger ally skills when an adjacent ally releases a skill (mana-full cast path).
|
||||
public void TriggerOnAdjacentAllySkillCast(int casterSlotIndex)
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (casterSlotIndex < 0) return;
|
||||
|
||||
int slots = 5;
|
||||
if (teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null)
|
||||
slots = teamUIController.Instance.allySlotIds.Count;
|
||||
|
||||
for (int i = 0; i < slots; i++)
|
||||
{
|
||||
if (i == casterSlotIndex) continue;
|
||||
|
||||
bool isAdjacent = false;
|
||||
if (teamUIController.Instance != null)
|
||||
{
|
||||
int[] adj = teamUIController.Instance.GetAdjacentAllyIndices(i);
|
||||
if (adj != null)
|
||||
{
|
||||
for (int j = 0; j < adj.Length; j++)
|
||||
{
|
||||
if (adj[j] == casterSlotIndex) { isAdjacent = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isAdjacent = Mathf.Abs(i - casterSlotIndex) == 1;
|
||||
}
|
||||
|
||||
if (!isAdjacent) continue;
|
||||
|
||||
var allyObj = GetAllyObjectBySlot(i);
|
||||
var allyComp = allyObj != null ? allyObj.GetComponent<AllyCombatant>() : null;
|
||||
if (allyComp != null && allyComp.IsDead) continue;
|
||||
|
||||
var so = GetAllyHeroSOBySlot(i);
|
||||
if (so == null) continue;
|
||||
|
||||
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
|
||||
{
|
||||
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 || group.skills == null) continue;
|
||||
|
||||
foreach (var def in group.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
|
||||
UseSkillDefinition(def, i, -1f, null);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
|
||||
UseSkillDefinition(def, i, -1f, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var primary = so.GetPrimarySkill();
|
||||
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast)
|
||||
{
|
||||
UsePrimarySkillForSlot(i, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, float> _lastOnNoteHitTriggerTime = new Dictionary<string, float>();
|
||||
|
||||
// track processed unique note IDs (e.g. long-hold note id) so shared effects (mana/hp) are applied only once
|
||||
@@ -2306,6 +2498,23 @@ ResolvedGroup:
|
||||
|
||||
if (duration <= 0f || tickInterval <= 0f)
|
||||
{
|
||||
if (target.GetComponent<EnemyCombatant>() != null && teamUIController.Instance != null)
|
||||
{
|
||||
if (!teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
var sourceAlly = source.GetComponent<AllyCombatant>();
|
||||
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(totalAmount), true);
|
||||
else ic?.ReceiveDamage(totalAmount, source);
|
||||
yield break;
|
||||
@@ -2317,6 +2526,24 @@ ResolvedGroup:
|
||||
while (elapsed < duration)
|
||||
{
|
||||
if (target == null) yield break;
|
||||
|
||||
if (target.GetComponent<EnemyCombatant>() != null && teamUIController.Instance != null)
|
||||
{
|
||||
if (!teamUIController.Instance.IsAnyAllyActive())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
var sourceAlly = source.GetComponent<AllyCombatant>();
|
||||
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(perTick), true);
|
||||
else ic?.ReceiveDamage(perTick, source);
|
||||
yield return new WaitForSeconds(tickInterval);
|
||||
|
||||
@@ -35,7 +35,9 @@ public class SkillDefinition : ScriptableObject
|
||||
// New: attack threshold trigger (attack < attackTriggerValue)
|
||||
OnAttackBelowValue,
|
||||
// New: triggers once when this ally is defeated (HP reaches 0 from above).
|
||||
OnSelfDefeated
|
||||
OnSelfDefeated,
|
||||
// New: triggers on this ally when an adjacent ally releases a skill (casts on mana full).
|
||||
OnAdjacentAllySkillCast
|
||||
}
|
||||
|
||||
[Header("Inspector")]
|
||||
|
||||
Reference in New Issue
Block a user