gameplay所有基本功能都装了,加入了全局警告。加入飘字等各种东西。

This commit is contained in:
FloatGaming
2026-02-21 00:20:03 +08:00
parent 9f7c1c57b7
commit 14fb281d6f
254 changed files with 102927 additions and 9427 deletions
+494 -139
View File
@@ -22,6 +22,9 @@ public class EffectSystem : MonoBehaviour
private int _cachedEnemiesFrame = -1;
private readonly List<GameObject> _cachedEnemies = new List<GameObject>(8);
private readonly HashSet<int> _awaitingHitFxTargets = new HashSet<int>();
private readonly Dictionary<int, int> _pendingDamagePopupByTargetId = new Dictionary<int, int>(64);
private void Awake()
{
if (Instance == null) Instance = this;
@@ -101,12 +104,15 @@ public class EffectSystem : MonoBehaviour
{
GfxController.Instance.PlayHitFX(targetToHit, null, isEnemyTarget, finalPos, null, damageInfo);
}
FlushPendingDamagePopupAfterHit(targetToHit);
});
if (targetToHit != null) MarkAwaitingHitFx(targetToHit);
}
}
}
}
switch (effectType)
{
case EffectType.DamageSingleEnemy:
@@ -117,7 +123,24 @@ public class EffectSystem : MonoBehaviour
case EffectType.DamageOverTimeEnemy:
foreach (var t in targets)
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source));
{
if (t == null)
{
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source, null, -1, 0));
continue;
}
var enemyDot = t.GetComponent<EnemyCombatant>() ?? t.GetComponentInChildren<EnemyCombatant>(true);
string iconId = null;
int enemyInstanceId = 0;
if (enemyDot != null && duration > 0f)
{
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = amount / ticks;
iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyDot, PlayerBudeffIconType.ot_bleeding, perTick, duration);
enemyInstanceId = enemyDot.GetInstanceID();
}
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source, iconId, -1, enemyInstanceId));
}
break;
case EffectType.HealSingleEnemy:
@@ -146,7 +169,22 @@ public class EffectSystem : MonoBehaviour
case EffectType.DamageOverTimeAlly:
foreach (var t in targets)
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source));
{
if (t == null)
{
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source, null, -1, 0));
continue;
}
var allyDot = t.GetComponent<AllyCombatant>() ?? t.GetComponentInChildren<AllyCombatant>(true);
string iconId = null;
if (allyDot != null && duration > 0f)
{
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = amount / ticks;
iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(allyDot, PlayerBudeffIconType.ot_bleeding, perTick, duration);
}
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source, iconId, allyDot != null ? allyDot.slotIndex : -1, 0));
}
break;
case EffectType.HealSingleSelf:
@@ -228,7 +266,14 @@ public class EffectSystem : MonoBehaviour
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
int before = ally.currentScore;
ally.AddScoreDirect(Mathf.CeilToInt(amount));
int delta = ally.currentScore - before;
if (delta != 0)
{
var type = delta > 0 ? iNumberPrefabController.InstantNumberType.IscorePlus : iNumberPrefabController.InstantNumberType.IscoreMinus;
iNumberPrefabController.SpawnForAllyStatic(ally.slotIndex, type, delta);
}
}
else
{
@@ -256,17 +301,29 @@ public class EffectSystem : MonoBehaviour
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.SetMaxHP(ally.maxHP + delta, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_up, delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(ally, delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_up, delta, duration);
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(ally, delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy))
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy.SetMaxHP(enemy.maxHP + delta, false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy, PlayerBudeffIconType.ot_maxHP_up, delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(enemy, delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy, PlayerBudeffIconType.ot_maxHP_up, delta, duration);
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(enemy, delta, duration, iconId, enemy.GetInstanceID()));
}
}
}
break;
@@ -280,17 +337,29 @@ public class EffectSystem : MonoBehaviour
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - delta), false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_down, -delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(ally, -delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_down, -delta, duration);
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(ally, -delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy2))
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - delta), false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy2, PlayerBudeffIconType.ot_maxHP_down, -delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(enemy2, -delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy2, PlayerBudeffIconType.ot_maxHP_down, -delta, duration);
StartCoroutine(ApplyTemporaryMaxHPDeltaCoroutine(enemy2, -delta, duration, iconId, enemy2.GetInstanceID()));
}
}
}
break;
@@ -304,17 +373,29 @@ public class EffectSystem : MonoBehaviour
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.SetMaxMana(ally.maxMana + delta, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_up, delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(ally, delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_up, delta, duration);
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(ally, delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3))
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy3.SetMaxMana(enemy3.maxMana + delta, false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy3, PlayerBudeffIconType.ot_maxMana_up, delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(enemy3, delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy3, PlayerBudeffIconType.ot_maxMana_up, delta, duration);
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(enemy3, delta, duration, iconId, enemy3.GetInstanceID()));
}
}
}
break;
@@ -328,17 +409,29 @@ public class EffectSystem : MonoBehaviour
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - delta), false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_down, -delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(ally, -delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_down, -delta, duration);
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(ally, -delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy4))
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - delta), false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy4, PlayerBudeffIconType.ot_maxMana_down, -delta, 0f);
}
else
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(enemy4, -delta, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy4, PlayerBudeffIconType.ot_maxMana_down, -delta, duration);
StartCoroutine(ApplyTemporaryMaxManaDeltaCoroutine(enemy4, -delta, duration, iconId, enemy4.GetInstanceID()));
}
}
}
break;
@@ -354,11 +447,13 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
ally.scoreEfficiency += amount;
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amount, 0f);
}
else
{
// temporary additive change, revert after duration
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amount, duration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, amount, duration, iconId));
}
}
}
@@ -374,10 +469,12 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - amount);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amount, 0f);
}
else
{
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, -amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amount, duration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, -amount, duration, iconId));
}
}
}
@@ -395,19 +492,27 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
ally.ModifyAttack(delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, 0f);
}
else
{
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, delta, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, duration);
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy5))
{
int deltaE = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy5.ModifyAttack(deltaE);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, deltaE, 0f);
}
else
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy5, deltaE, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, deltaE, duration);
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy5, deltaE, duration, iconId, enemy5.GetInstanceID()));
}
}
}
break;
@@ -423,20 +528,28 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
ally.ModifyAttack(-delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
}
else
{
// apply decrease now, revert after duration
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, -delta, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, duration);
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, -delta, duration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy6))
{
int deltaE = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
enemy6.ModifyAttack(-deltaE);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -deltaE, 0f);
}
else
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy6, -deltaE, duration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -deltaE, duration);
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy6, -deltaE, duration, iconId, enemy6.GetInstanceID()));
}
}
}
break;
@@ -466,27 +579,30 @@ public class EffectSystem : MonoBehaviour
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
// amount interpreted as additive delta (0..1). Clamp result 0..0.9 to avoid invulnerability
if (duration <= 0f)
{
ally.damageResistance = Mathf.Clamp01(ally.damageResistance + amount);
ally.damageResistance = ClampDamageResistance(ally.damageResistance + amount);
LogVerbose($"[EffectSystem] IncreaseDamageResistance applied to {t.name}: new resistance={ally.damageResistance}");
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_defend_up, amount, 0f);
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_defend_up, amount, duration);
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, amount, duration, iconId, ally.slotIndex, 0));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemyR))
{
if (duration <= 0f)
{
enemyR.damageResistance = Mathf.Clamp01(enemyR.damageResistance + amount);
enemyR.damageResistance = ClampDamageResistance(enemyR.damageResistance + amount);
LogVerbose($"[EffectSystem] IncreaseDamageResistance applied to enemy {t.name}: new resistance={enemyR.damageResistance}");
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyR, PlayerBudeffIconType.ot_defend_up, amount, 0f);
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR.gameObject, amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyR, PlayerBudeffIconType.ot_defend_up, amount, duration);
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR.gameObject, amount, duration, iconId, -1, enemyR.GetInstanceID()));
}
}
}
@@ -501,25 +617,29 @@ public class EffectSystem : MonoBehaviour
{
if (duration <= 0f)
{
ally.damageResistance = Mathf.Clamp01(ally.damageResistance - amount);
ally.damageResistance = ClampDamageResistance(ally.damageResistance - amount);
LogVerbose($"[EffectSystem] DecreaseDamageResistance applied to {t.name}: new resistance={ally.damageResistance}");
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_defend_down, -amount, 0f);
}
else
{
// apply negative delta
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, -amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_defend_down, -amount, duration);
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, -amount, duration, iconId, ally.slotIndex, 0));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemyR2))
{
if (duration <= 0f)
{
enemyR2.damageResistance = Mathf.Clamp01(enemyR2.damageResistance - amount);
enemyR2.damageResistance = ClampDamageResistance(enemyR2.damageResistance - amount);
LogVerbose($"[EffectSystem] DecreaseDamageResistance applied to enemy {t.name}: new resistance={enemyR2.damageResistance}");
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyR2, PlayerBudeffIconType.ot_defend_down, -amount, 0f);
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR2.gameObject, -amount, duration));
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyR2, PlayerBudeffIconType.ot_defend_down, -amount, duration);
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR2.gameObject, -amount, duration, iconId, -1, enemyR2.GetInstanceID()));
}
}
}
@@ -548,7 +668,7 @@ public class EffectSystem : MonoBehaviour
{
int pmDelta = ally.AddScoreForJudge("Perfect");
float eff = ally.scoreEfficiency;
sm?.AddPmScoreForTrack(slot, pmDelta, eff);
sm?.AddPmScoreForTrack(slot, pmDelta, eff, true);
}
catch { }
@@ -870,8 +990,19 @@ public class EffectSystem : MonoBehaviour
var comp = target.GetComponent<ICombatant>();
if (comp != null)
{
int beforeHP = 0;
if (targetAlly != null) beforeHP = targetAlly.currentHP;
else if (targetEnemy != null) beforeHP = targetEnemy.currentHP;
comp.ReceiveDamage(amount, source);
int afterHP = 0;
if (targetAlly != null) afterHP = targetAlly.currentHP;
else if (targetEnemy != null) afterHP = targetEnemy.currentHP;
int actualDelta = beforeHP - afterHP;
if (actualDelta != 0) QueueOrSpawnDamagePopup(target, targetAlly, targetEnemy, actualDelta);
// Record stats if source is an ally
if (source != null && target.GetComponent<EnemyCombatant>() != null)
{
@@ -900,8 +1031,27 @@ public class EffectSystem : MonoBehaviour
var comp = target.GetComponent<ICombatant>();
if (comp != null)
{
var targetAlly = target.GetComponent<AllyCombatant>();
var targetEnemy = target.GetComponent<EnemyCombatant>();
int beforeHP = 0;
if (targetAlly != null) beforeHP = targetAlly.currentHP;
else if (targetEnemy != null) beforeHP = targetEnemy.currentHP;
comp.ReceiveHeal(amount, source);
int afterHP = 0;
if (targetAlly != null) afterHP = targetAlly.currentHP;
else if (targetEnemy != null) afterHP = targetEnemy.currentHP;
int actualDelta = afterHP - beforeHP;
if (actualDelta != 0)
{
if (targetAlly != null)
iNumberPrefabController.SpawnForAllyStatic(targetAlly.slotIndex, iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
else if (targetEnemy != null)
iNumberPrefabController.SpawnForEnemyStatic(iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
}
// Record stats if source is an ally
if (source != null)
{
@@ -918,70 +1068,86 @@ public class EffectSystem : MonoBehaviour
}
}
private IEnumerator ApplyDamageOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
private IEnumerator ApplyDamageOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source, string iconId, int allySlotIndex, int enemyInstanceId)
{
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply DOT");
yield break;
}
var targetAlly = target.GetComponent<AllyCombatant>();
var targetEnemy = target.GetComponent<EnemyCombatant>();
// Stop immediately if the target is already dead.
if (targetAlly != null && targetAlly.IsDead) yield break;
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) yield break;
if (duration <= 0f)
{
// check death state for immediate damage
if (targetEnemy != null && AreAllEnemiesDead())
{
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because all enemies are dead.");
yield break;
}
comp.ReceiveDamage(totalAmount, source);
// Record stats
if (source != null && targetEnemy != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, totalAmount);
}
// Record damage taken
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, totalAmount);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
try
{
if (target == null) yield break;
// check death state inside loop
if (targetAlly != null && targetAlly.IsDead) yield break;
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) yield break;
if (targetEnemy != null && AreAllEnemiesDead())
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because all enemies are dead.");
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply DOT");
yield break;
}
comp.ReceiveDamage(perTick, source);
// Record stats
if (source != null && targetEnemy != null)
var targetAlly = target.GetComponent<AllyCombatant>();
var targetEnemy = target.GetComponent<EnemyCombatant>();
if (targetAlly != null && targetAlly.IsDead) yield break;
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) yield break;
if (duration <= 0f)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, perTick);
if (targetEnemy != null && AreAllEnemiesDead())
{
LogVerbose($"[EffectSystem] Skip DOT (immediate) to {target.name} because all enemies are dead.");
yield break;
}
comp.ReceiveDamage(totalAmount, source);
if (source != null && targetEnemy != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, totalAmount);
}
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, totalAmount);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
if (target == null) yield break;
if (targetAlly != null && targetAlly.IsDead) yield break;
if (targetEnemy != null && (targetEnemy.IsDead || targetEnemy.currentHP <= 0)) yield break;
if (targetEnemy != null && AreAllEnemiesDead())
{
LogVerbose($"[EffectSystem] Stopping DOT to {target.name} because all enemies are dead.");
yield break;
}
int beforeHP = 0;
if (targetAlly != null) beforeHP = targetAlly.currentHP;
else if (targetEnemy != null) beforeHP = targetEnemy.currentHP;
comp.ReceiveDamage(perTick, source);
int afterHP = 0;
if (targetAlly != null) afterHP = targetAlly.currentHP;
else if (targetEnemy != null) afterHP = targetEnemy.currentHP;
int actualDelta = beforeHP - afterHP;
if (actualDelta != 0) QueueOrSpawnDamagePopup(target, targetAlly, targetEnemy, actualDelta);
if (source != null && targetEnemy != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, perTick);
}
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, perTick);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
finally
{
if (!string.IsNullOrEmpty(iconId))
{
if (allySlotIndex >= 0) iBudeffPrefabController.Instance?.UnregisterTimedEffect(allySlotIndex, iconId);
else if (enemyInstanceId != 0) iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
// Record damage taken
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, perTick);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
@@ -997,7 +1163,27 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
var targetAlly = target.GetComponent<AllyCombatant>();
var targetEnemy = target.GetComponent<EnemyCombatant>();
int beforeHP = 0;
if (targetAlly != null) beforeHP = targetAlly.currentHP;
else if (targetEnemy != null) beforeHP = targetEnemy.currentHP;
comp.ReceiveHeal(totalAmount, source);
int afterHP = 0;
if (targetAlly != null) afterHP = targetAlly.currentHP;
else if (targetEnemy != null) afterHP = targetEnemy.currentHP;
int actualDelta = afterHP - beforeHP;
if (actualDelta != 0)
{
if (targetAlly != null)
iNumberPrefabController.SpawnForAllyStatic(targetAlly.slotIndex, iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
else if (targetEnemy != null)
iNumberPrefabController.SpawnForEnemyStatic(iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
}
// Record stats
if (source != null)
{
@@ -1010,10 +1196,31 @@ public class EffectSystem : MonoBehaviour
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
var targetAllyHot = target.GetComponent<AllyCombatant>();
var targetEnemyHot = target.GetComponent<EnemyCombatant>();
while (elapsed < duration)
{
if (target == null) yield break;
int beforeHP = 0;
if (targetAllyHot != null) beforeHP = targetAllyHot.currentHP;
else if (targetEnemyHot != null) beforeHP = targetEnemyHot.currentHP;
comp.ReceiveHeal(perTick, source);
int afterHP = 0;
if (targetAllyHot != null) afterHP = targetAllyHot.currentHP;
else if (targetEnemyHot != null) afterHP = targetEnemyHot.currentHP;
int actualDelta = afterHP - beforeHP;
if (actualDelta != 0)
{
if (targetAllyHot != null)
iNumberPrefabController.SpawnForAllyStatic(targetAllyHot.slotIndex, iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
else if (targetEnemyHot != null)
iNumberPrefabController.SpawnForEnemyStatic(iNumberPrefabController.InstantNumberType.Heal, Mathf.Abs(actualDelta));
}
// Record stats
if (source != null)
{
@@ -1025,6 +1232,63 @@ public class EffectSystem : MonoBehaviour
}
}
private void MarkAwaitingHitFx(GameObject target)
{
if (target == null) return;
int id = target.GetInstanceID();
_awaitingHitFxTargets.Add(id);
}
private void FlushPendingDamagePopupAfterHit(GameObject target)
{
if (target == null) return;
int id = target.GetInstanceID();
if (_awaitingHitFxTargets.Contains(id)) _awaitingHitFxTargets.Remove(id);
if (!_pendingDamagePopupByTargetId.TryGetValue(id, out var signedValue) || signedValue == 0) return;
_pendingDamagePopupByTargetId.Remove(id);
var ally = target.GetComponent<AllyCombatant>();
if (ally != null)
{
iNumberPrefabController.SpawnForAllyStatic(ally.slotIndex, iNumberPrefabController.InstantNumberType.Damage, signedValue);
return;
}
if (target.GetComponent<EnemyCombatant>() != null)
{
iNumberPrefabController.SpawnForEnemyStatic(iNumberPrefabController.InstantNumberType.Damage, signedValue);
}
}
private void QueueOrSpawnDamagePopup(GameObject target, AllyCombatant targetAlly, EnemyCombatant targetEnemy, int actualDelta)
{
if (target == null) return;
if (actualDelta == 0) return;
int id = target.GetInstanceID();
int signedValue = -Mathf.Abs(actualDelta);
if (_awaitingHitFxTargets.Contains(id))
{
if (_pendingDamagePopupByTargetId.TryGetValue(id, out var prev))
_pendingDamagePopupByTargetId[id] = prev + signedValue;
else
_pendingDamagePopupByTargetId[id] = signedValue;
return;
}
if (targetAlly != null)
{
iNumberPrefabController.SpawnForAllyStatic(targetAlly.slotIndex, iNumberPrefabController.InstantNumberType.Damage, signedValue);
return;
}
if (targetEnemy != null)
{
iNumberPrefabController.SpawnForEnemyStatic(iNumberPrefabController.InstantNumberType.Damage, signedValue);
}
}
private IEnumerator ApplyIncreaseManaOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
@@ -1042,7 +1306,14 @@ public class EffectSystem : MonoBehaviour
// immediate add totalAmount to mana if ally present
if (ally != null)
{
int before = ally.currentMana;
ally.ModifyMana(Mathf.CeilToInt(totalAmount), true, true);
int delta = ally.currentMana - before;
if (delta != 0)
{
var type = delta > 0 ? iNumberPrefabController.InstantNumberType.ManaPlus : iNumberPrefabController.InstantNumberType.ManaMinus;
iNumberPrefabController.SpawnForAllyStatic(ally.slotIndex, type, delta);
}
// Record stats
if (totalAmount > 0f && source != null)
{
@@ -1061,7 +1332,14 @@ public class EffectSystem : MonoBehaviour
if (target == null) yield break;
if (ally != null)
{
int before = ally.currentMana;
ally.ModifyMana(Mathf.CeilToInt(perTick), true, true);
int delta = ally.currentMana - before;
if (delta != 0)
{
var type = delta > 0 ? iNumberPrefabController.InstantNumberType.ManaPlus : iNumberPrefabController.InstantNumberType.ManaMinus;
iNumberPrefabController.SpawnForAllyStatic(ally.slotIndex, type, delta);
}
// Record stats
if (perTick > 0f && source != null)
{
@@ -1106,93 +1384,170 @@ public class EffectSystem : MonoBehaviour
}
// Coroutine to apply temporary scoreEfficiency change and revert after duration
private IEnumerator ApplyTemporaryScoreEfficiencyChangeCoroutine(AllyCombatant ally, float delta, float duration)
private IEnumerator ApplyTemporaryScoreEfficiencyChangeCoroutine(AllyCombatant ally, float delta, float duration, string iconId)
{
if (ally == null) yield break;
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency + delta);
yield return new WaitForSeconds(duration);
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - delta);
try
{
if (ally == null) yield break;
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency + delta);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - delta);
}
finally
{
if (ally != null && !string.IsNullOrEmpty(iconId))
iBudeffPrefabController.Instance?.UnregisterTimedEffect(ally.slotIndex, iconId);
}
}
// Coroutine to apply temporary damageResistance change and revert after duration
private IEnumerator ApplyTemporaryDamageResistanceChangeCoroutine(GameObject target, float delta, float duration)
private IEnumerator ApplyTemporaryDamageResistanceChangeCoroutine(GameObject target, float delta, float duration, string iconId = null, int allySlotIndex = -1, int enemyInstanceId = 0)
{
if (target == null) yield break;
// support both ally and enemy
var ally = target.GetComponent<AllyCombatant>();
if (ally != null)
try
{
float before = ally.damageResistance;
ally.damageResistance = Mathf.Clamp01(ally.damageResistance + delta);
yield return new WaitForSeconds(duration);
// revert by subtracting delta, clamp
ally.damageResistance = Mathf.Clamp01(ally.damageResistance - delta);
yield break;
}
if (target == null) yield break;
var ally = target.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.damageResistance = ClampDamageResistance(ally.damageResistance + delta);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.damageResistance = ClampDamageResistance(ally.damageResistance - delta);
yield break;
}
var enemy = target.GetComponent<EnemyCombatant>();
if (enemy != null)
var enemy = target.GetComponent<EnemyCombatant>();
if (enemy != null)
{
enemy.damageResistance = ClampDamageResistance(enemy.damageResistance + delta);
iBudeffPrefabController.Instance?.RefreshEnemyNow(enemy);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.damageResistance = ClampDamageResistance(enemy.damageResistance - delta);
yield break;
}
}
finally
{
float beforeE = enemy.damageResistance;
enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance + delta);
yield return new WaitForSeconds(duration);
enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance - delta);
yield break;
if (!string.IsNullOrEmpty(iconId))
{
if (allySlotIndex >= 0) iBudeffPrefabController.Instance?.UnregisterTimedEffect(allySlotIndex, iconId);
else if (enemyInstanceId != 0) iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
}
}
// Coroutine to apply temporary attack change for AllyCombatant
private IEnumerator ApplyTemporaryAttackChangeCoroutine(AllyCombatant ally, int delta, float duration)
private IEnumerator ApplyTemporaryAttackChangeCoroutine(AllyCombatant ally, int delta, float duration, string iconId)
{
if (ally == null) yield break;
ally.ModifyAttack(delta);
yield return new WaitForSeconds(duration);
ally.ModifyAttack(-delta);
try
{
if (ally == null) yield break;
ally.ModifyAttack(delta);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.ModifyAttack(-delta);
}
finally
{
if (ally != null && !string.IsNullOrEmpty(iconId))
iBudeffPrefabController.Instance?.UnregisterTimedEffect(ally.slotIndex, iconId);
}
}
private static float ClampDamageResistance(float value)
{
return Mathf.Min(value, 1f);
}
// Coroutine to apply temporary attack change for EnemyCombatant
private IEnumerator ApplyTemporaryAttackChangeCoroutine(EnemyCombatant enemy, int delta, float duration)
private IEnumerator ApplyTemporaryAttackChangeCoroutine(EnemyCombatant enemy, int delta, float duration, string iconId, int enemyInstanceId)
{
if (enemy == null) yield break;
enemy.ModifyAttack(delta);
yield return new WaitForSeconds(duration);
enemy.ModifyAttack(-delta);
try
{
if (enemy == null) yield break;
enemy.ModifyAttack(delta);
iBudeffPrefabController.Instance?.RefreshEnemyNow(enemy);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.ModifyAttack(-delta);
}
finally
{
if (!string.IsNullOrEmpty(iconId) && enemyInstanceId != 0)
iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
}
private IEnumerator ApplyTemporaryMaxHPDeltaCoroutine(AllyCombatant ally, int delta, float duration)
private IEnumerator ApplyTemporaryMaxHPDeltaCoroutine(AllyCombatant ally, int delta, float duration, string iconId)
{
if (ally == null) yield break;
ally.SetMaxHP(Mathf.Max(1, ally.maxHP + delta), false);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - delta), false);
try
{
if (ally == null) yield break;
ally.SetMaxHP(Mathf.Max(1, ally.maxHP + delta), false);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - delta), false);
}
finally
{
if (ally != null && !string.IsNullOrEmpty(iconId))
iBudeffPrefabController.Instance?.UnregisterTimedEffect(ally.slotIndex, iconId);
}
}
private IEnumerator ApplyTemporaryMaxHPDeltaCoroutine(EnemyCombatant enemy, int delta, float duration)
private IEnumerator ApplyTemporaryMaxHPDeltaCoroutine(EnemyCombatant enemy, int delta, float duration, string iconId, int enemyInstanceId)
{
if (enemy == null) yield break;
enemy.SetMaxHP(Mathf.Max(1, enemy.maxHP + delta), false);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.SetMaxHP(Mathf.Max(1, enemy.maxHP - delta), false);
try
{
if (enemy == null) yield break;
enemy.SetMaxHP(Mathf.Max(1, enemy.maxHP + delta), false);
iBudeffPrefabController.Instance?.RefreshEnemyNow(enemy);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.SetMaxHP(Mathf.Max(1, enemy.maxHP - delta), false);
}
finally
{
if (!string.IsNullOrEmpty(iconId) && enemyInstanceId != 0)
iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
}
private IEnumerator ApplyTemporaryMaxManaDeltaCoroutine(AllyCombatant ally, int delta, float duration)
private IEnumerator ApplyTemporaryMaxManaDeltaCoroutine(AllyCombatant ally, int delta, float duration, string iconId)
{
if (ally == null) yield break;
ally.SetMaxMana(Mathf.Max(1, ally.maxMana + delta), false);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - delta), false);
try
{
if (ally == null) yield break;
ally.SetMaxMana(Mathf.Max(1, ally.maxMana + delta), false);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - delta), false);
}
finally
{
if (ally != null && !string.IsNullOrEmpty(iconId))
iBudeffPrefabController.Instance?.UnregisterTimedEffect(ally.slotIndex, iconId);
}
}
private IEnumerator ApplyTemporaryMaxManaDeltaCoroutine(EnemyCombatant enemy, int delta, float duration)
private IEnumerator ApplyTemporaryMaxManaDeltaCoroutine(EnemyCombatant enemy, int delta, float duration, string iconId, int enemyInstanceId)
{
if (enemy == null) yield break;
enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana + delta), false);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana - delta), false);
try
{
if (enemy == null) yield break;
enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana + delta), false);
iBudeffPrefabController.Instance?.RefreshEnemyNow(enemy);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana - delta), false);
}
finally
{
if (!string.IsNullOrEmpty(iconId) && enemyInstanceId != 0)
iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
}
#endregion
}