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
+62 -4
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -32,9 +32,12 @@ public class AllyCombatant : MonoBehaviour, ICombatant
// --- Damage redirect (next hit) ---
private static AllyCombatant s_nextDamageRedirector;
private static float s_redirectExpireTime = -1f;
private static string s_redirectIconId;
private static int s_redirectIconSlotIndex = -1;
// --- Self damage redirect to adjacent (duration) ---
private float _selfDamageRedirectToAdjacentExpireTime = -1f;
private string _selfDamageRedirectToAdjacentIconId;
// add attack stat to be available at runtime
[Tooltip("Documentation text normalized.")]
@@ -76,6 +79,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
private Image manaImage;
private Image fadeManaImage;
private TextMeshProUGUI nameText;
public string allyName => nameText != null ? nameText.text : name;
private TextMeshProUGUI healthRateText;
private TextMeshProUGUI currentScoreText;
private Image hurtRedImage;
@@ -177,6 +181,21 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
}
if (Application.isPlaying)
{
if (s_redirectExpireTime > 0f && Time.time > s_redirectExpireTime) ClearRedirect();
if (_selfDamageRedirectToAdjacentExpireTime > 0f && Time.time > _selfDamageRedirectToAdjacentExpireTime)
{
_selfDamageRedirectToAdjacentExpireTime = -1f;
if (!string.IsNullOrEmpty(_selfDamageRedirectToAdjacentIconId))
{
iBudeffPrefabController.Instance?.UnregisterTimedEffect(slotIndex, _selfDamageRedirectToAdjacentIconId);
_selfDamageRedirectToAdjacentIconId = null;
}
}
}
}
private void Start()
@@ -196,6 +215,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
UpdateUIImmediate();
// Ensure score UI shows initial value
UpdateScoreUI();
iBudeffPrefabController.Instance?.RegisterBaseline(this);
// Evaluate HP-percent triggers on start after a short delay to ensure singletons are ready
if (Application.isPlaying)
@@ -224,6 +244,8 @@ public class AllyCombatant : MonoBehaviour, ICombatant
currentHP = Mathf.Max(1, maxHP);
UpdateUIImmediate();
}
iBudeffPrefabController.Instance?.RegisterBaseline(this);
// Final wait to ensure SkillBuilder's internal mapping is ready
while (SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex) == null)
@@ -1071,6 +1093,14 @@ public class AllyCombatant : MonoBehaviour, ICombatant
if (old > 0 && currentHP <= 0)
{
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnSelfDefeated);
string dName = "Unknown";
if (nameText != null) dName = nameText.text;
else if (SkillBuilder.Instance != null)
{
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
if (so != null) dName = so.ally_heroName;
}
SkillTriggerFeedUI.PushDeath(dName);
}
// Evaluate HP-percentage based trigger transitions (entry/exit)
@@ -1236,6 +1266,11 @@ public class AllyCombatant : MonoBehaviour, ICombatant
return mult;
}
public float GetHealReceivedMultiplierForUI()
{
return GetTotalHealReceivedMultiplier();
}
private void RecalculateAttackFromBuffs()
{
EnsureAttackBaseInitialized();
@@ -1460,7 +1495,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
// Apply reversible multipliers. attackMultiplier is applied via baseline+recalc to ensure it can be reverted.
if (buff.scoreMultiplier != 1f) scoreEfficiency *= buff.scoreMultiplier;
if (buff.attackMultiplier != 1f) RecalculateAttackFromBuffs();
// UI/visual could be added
iBudeffPrefabController.Instance?.NotifyBuffApplied(this, buff);
}
public void RemoveBuff(string buffId)
@@ -1471,6 +1506,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
activeBuffs.Remove(b);
if (b.scoreMultiplier != 1f) scoreEfficiency /= b.scoreMultiplier;
if (b.attackMultiplier != 1f) RecalculateAttackFromBuffs();
iBudeffPrefabController.Instance?.NotifyBuffRemoved(this, b);
}
}
@@ -2126,11 +2162,23 @@ public class AllyCombatant : MonoBehaviour, ICombatant
public static void ActivateNextDamageRedirect(AllyCombatant redirector, float duration = 0f)
{
if (redirector == null) return;
if (!string.IsNullOrEmpty(s_redirectIconId) && s_redirectIconSlotIndex >= 0)
{
iBudeffPrefabController.Instance?.UnregisterTimedEffect(s_redirectIconSlotIndex, s_redirectIconId);
s_redirectIconId = null;
s_redirectIconSlotIndex = -1;
}
s_nextDamageRedirector = redirector;
if (duration > 0f && Application.isPlaying)
s_redirectExpireTime = Time.time + duration;
else
s_redirectExpireTime = -1f;
if (Application.isPlaying)
{
s_redirectIconSlotIndex = redirector.slotIndex;
s_redirectIconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(redirector, PlayerBudeffIconType.dmg_redirect_toSelf, 0f, duration);
}
}
private static bool IsRedirectActive()
@@ -2151,6 +2199,12 @@ public class AllyCombatant : MonoBehaviour, ICombatant
private static void ClearRedirect()
{
if (!string.IsNullOrEmpty(s_redirectIconId) && s_redirectIconSlotIndex >= 0)
{
iBudeffPrefabController.Instance?.UnregisterTimedEffect(s_redirectIconSlotIndex, s_redirectIconId);
s_redirectIconId = null;
s_redirectIconSlotIndex = -1;
}
s_nextDamageRedirector = null;
s_redirectExpireTime = -1f;
}
@@ -2160,6 +2214,12 @@ public class AllyCombatant : MonoBehaviour, ICombatant
if (!Application.isPlaying) return;
if (duration <= 0f) duration = 0.01f;
_selfDamageRedirectToAdjacentExpireTime = Time.time + duration;
if (!string.IsNullOrEmpty(_selfDamageRedirectToAdjacentIconId))
{
iBudeffPrefabController.Instance?.UnregisterTimedEffect(slotIndex, _selfDamageRedirectToAdjacentIconId);
_selfDamageRedirectToAdjacentIconId = null;
}
_selfDamageRedirectToAdjacentIconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(this, PlayerBudeffIconType.dmg_redirect_toAdjacent, 0f, duration);
}
private bool IsSelfDamageRedirectToAdjacentActive()
@@ -2297,5 +2357,3 @@ public class AllyCombatant : MonoBehaviour, ICombatant
+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
}
+18 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -64,6 +64,8 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
currentMana = 0;
isDead = false;
iBudeffPrefabController.Instance?.RegisterEnemyBaseline(this);
// notify listeners (UI/manager) that enemy revived
OnEnemyRevived?.Invoke(this);
@@ -158,9 +160,11 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
public void ApplyBuff(Buff buff, GameObject source)
{
if (buff == null) return;
Debug.LogWarning($"[EnemyCombatant] ApplyBuff: {buff.description} (id={buff.buffId}) to {name}. AtkMult={buff.attackMultiplier}, HealMult={buff.healReceivedMultiplier}, ScoreMult={buff.scoreMultiplier}");
activeBuffs.Add(buff);
// Apply reversible multipliers. attackMultiplier is applied via baseline+recalc to ensure it can be reverted.
if (buff.attackMultiplier != 1f) RecalculateAttackFromBuffs();
iBudeffPrefabController.Instance?.NotifyEnemyBuffApplied(this, buff);
}
public void RemoveBuff(string buffId)
@@ -170,9 +174,22 @@ public class EnemyCombatant : MonoBehaviour, ICombatant
{
activeBuffs.Remove(b);
if (b.attackMultiplier != 1f) RecalculateAttackFromBuffs();
iBudeffPrefabController.Instance?.NotifyEnemyBuffRemoved(this, b);
}
}
public float GetHealReceivedMultiplierForUI()
{
float mult = 1f;
for (int i = 0; i < activeBuffs.Count; i++)
{
var b = activeBuffs[i];
if (b == null) continue;
mult *= b.healReceivedMultiplier;
}
return mult;
}
private void EnsureAttackBaseInitialized()
{
if (_attackBaseInitialized) return;
+10 -2
View File
@@ -363,7 +363,7 @@ public class ScoreManager : MonoBehaviour
/// Add a per-note pm score delta to the specified track (0..4) and update named fields + aggregate.
/// Also calculates idol score as pmDelta * scoreEfficiency.
/// </summary>
public void AddPmScoreForTrack(int trackIndex, int pmDelta, float scoreEfficiency)
public void AddPmScoreForTrack(int trackIndex, int pmDelta, float scoreEfficiency, bool isSkillRelated = false)
{
TryHookPerfectBonusEvent();
if (trackIndex < 0 || trackIndex >= pmScoreSums.Length) return;
@@ -373,11 +373,19 @@ public class ScoreManager : MonoBehaviour
if (pmScoreSums[trackIndex] < 0) pmScoreSums[trackIndex] = 0;
if (pmScoreSums[trackIndex] > int.MaxValue - 1) pmScoreSums[trackIndex] = int.MaxValue - 1;
// calculate idol score: pmDelta * scoreEfficiency
int idolBefore = idolScoreSums[trackIndex];
int idolDelta = Mathf.FloorToInt(pmDelta * scoreEfficiency);
idolScoreSums[trackIndex] += idolDelta;
if (idolScoreSums[trackIndex] < 0) idolScoreSums[trackIndex] = 0;
if (idolScoreSums[trackIndex] > int.MaxValue - 1) idolScoreSums[trackIndex] = int.MaxValue - 1;
int idolActualDelta = idolScoreSums[trackIndex] - idolBefore;
// Only spawn popup if it is skill-related
if (isSkillRelated && idolActualDelta != 0)
{
var type = idolActualDelta > 0 ? iNumberPrefabController.InstantNumberType.IscorePlus : iNumberPrefabController.InstantNumberType.IscoreMinus;
iNumberPrefabController.SpawnForAllyStatic(trackIndex, type, idolActualDelta);
}
// update named fields for easy access
red_pmScore_sum = pmScoreSums[0];
+348 -68
View File
@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -112,6 +112,9 @@ public class SkillBuilder : MonoBehaviour
public EffectType effectType;
public float floatDelta;
public int intDelta;
public string budeffIconId;
public int budeffSlotIndex = -1;
public int budeffEnemyInstanceId;
}
private void PrewarmAllyHeroSOIndex()
@@ -205,6 +208,12 @@ public class SkillBuilder : MonoBehaviour
return;
}
// Log Miss event to feed (since HP loss will occur)
string mName = "Unknown";
if (so != null && !string.IsNullOrEmpty(so.ally_heroName)) mName = so.ally_heroName;
else if (ally != null) mName = ally.name; // fallback to GameObject name
SkillTriggerFeedUI.PushMiss(mName);
float missBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
// 播放敌人攻击特效,并将伤害逻辑延迟到特效到达时执行
@@ -751,6 +760,30 @@ public class SkillBuilder : MonoBehaviour
return def.skillId + ":" + target.GetInstanceID();
}
private static bool ShouldSpawnApprForEffect(EffectType effectType)
{
switch (effectType)
{
case EffectType.DamageOverTimeAlly:
case EffectType.ReduceEnemyHealOverTime:
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:
return true;
default:
return false;
}
}
private bool TryApplyRefreshOnlyTimedEffect(SkillDefinition def, GameObject target, float amount, float duration)
{
if (!IsRefreshOnlyTimedSkill(def)) return false;
@@ -773,6 +806,41 @@ public class SkillBuilder : MonoBehaviour
return false;
}
var ally = target.GetComponent<AllyCombatant>() ?? target.GetComponentInChildren<AllyCombatant>(true);
var enemy = target.GetComponent<EnemyCombatant>() ?? target.GetComponentInChildren<EnemyCombatant>(true);
if (iBudeffPrefabController.Instance != null)
{
PlayerBudeffIconType? iconType = null;
switch (def.effectType)
{
case EffectType.IncreaseScoreEfficiency: iconType = PlayerBudeffIconType.ot_scoreEfficiency_up; break;
case EffectType.DecreaseScoreEfficiency: iconType = PlayerBudeffIconType.ot_scoreEfficiency_down; break;
case EffectType.IncreaseDamageResistance: iconType = PlayerBudeffIconType.ot_defend_up; break;
case EffectType.DecreaseDamageResistance: iconType = PlayerBudeffIconType.ot_defend_down; break;
case EffectType.IncreaseAttack: iconType = PlayerBudeffIconType.ot_atk_up; break;
case EffectType.DecreaseAttack: iconType = PlayerBudeffIconType.ot_atk_down; break;
case EffectType.IncreaseMaxHP: iconType = PlayerBudeffIconType.ot_maxHP_up; break;
case EffectType.DecreaseMaxHP: iconType = PlayerBudeffIconType.ot_maxHP_down; break;
case EffectType.IncreaseMaxMana: iconType = PlayerBudeffIconType.ot_maxMana_up; break;
case EffectType.DecreaseMaxMana: iconType = PlayerBudeffIconType.ot_maxMana_down; break;
}
if (iconType.HasValue)
{
float v = applied.intDelta != 0 ? applied.intDelta : applied.floatDelta;
if (ally != null)
{
applied.budeffSlotIndex = ally.slotIndex;
applied.budeffIconId = iBudeffPrefabController.Instance.RegisterTimedEffect(ally, iconType.Value, v, duration);
}
else if (enemy != null && def.effectType != EffectType.IncreaseScoreEfficiency && def.effectType != EffectType.DecreaseScoreEfficiency)
{
applied.budeffEnemyInstanceId = enemy.GetInstanceID();
applied.budeffIconId = iBudeffPrefabController.Instance.RegisterEnemyTimedEffect(enemy, iconType.Value, v, duration);
}
}
}
_refreshOnlyTimedEffectStates[key] = applied;
_refreshOnlyTimedEffectCoroutines[key] = StartCoroutine(RemoveRefreshOnlyTimedEffectAfterDuration(key, target, duration));
return true;
@@ -798,21 +866,28 @@ public class SkillBuilder : MonoBehaviour
var enemy = target != null ? target.GetComponent<EnemyCombatant>() : null;
if (ally == null && enemy == null) return false;
string targetName = ally != null ? ally.allyName : (enemy != null && enemy.sourceData != null ? enemy.sourceData.enemyName : target.name);
switch (effectType)
{
case EffectType.IncreaseScoreEfficiency:
case EffectType.DecreaseScoreEfficiency:
if (ally == null) return false;
float scoreDelta = effectType == EffectType.IncreaseScoreEfficiency ? amount : -amount;
float oldScore = ally.scoreEfficiency;
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency + scoreDelta);
CheckLimitAndWarn(targetName, "分数效率", oldScore, ally.scoreEfficiency, scoreDelta);
state = new RefreshOnlyTimedState { effectType = effectType, floatDelta = scoreDelta };
return true;
case EffectType.IncreaseDamageResistance:
case EffectType.DecreaseDamageResistance:
float resistDelta = effectType == EffectType.IncreaseDamageResistance ? amount : -amount;
if (ally != null) ally.damageResistance = Mathf.Clamp01(ally.damageResistance + resistDelta);
else enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance + resistDelta);
float oldResist = ally != null ? ally.damageResistance : enemy.damageResistance;
if (ally != null) ally.damageResistance = ClampDamageResistance(ally.damageResistance + resistDelta);
else enemy.damageResistance = ClampDamageResistance(enemy.damageResistance + resistDelta);
float newResist = ally != null ? ally.damageResistance : enemy.damageResistance;
CheckLimitAndWarn(targetName, "伤害抗性", oldResist, newResist, resistDelta);
state = new RefreshOnlyTimedState { effectType = effectType, floatDelta = resistDelta };
return true;
@@ -820,8 +895,11 @@ public class SkillBuilder : MonoBehaviour
case EffectType.DecreaseAttack:
int atkDelta = Mathf.CeilToInt(Mathf.Abs(amount));
if (effectType == EffectType.DecreaseAttack) atkDelta = -atkDelta;
int oldAtk = ally != null ? ally.attack : enemy.attack;
if (ally != null) ally.ModifyAttack(atkDelta);
else enemy.ModifyAttack(atkDelta);
int newAtk = ally != null ? ally.attack : enemy.attack;
CheckLimitAndWarnInt(targetName, "攻击力", oldAtk, newAtk, atkDelta);
state = new RefreshOnlyTimedState { effectType = effectType, intDelta = atkDelta };
return true;
@@ -829,8 +907,11 @@ public class SkillBuilder : MonoBehaviour
case EffectType.DecreaseMaxHP:
int hpDelta = Mathf.CeilToInt(Mathf.Abs(amount));
if (effectType == EffectType.DecreaseMaxHP) hpDelta = -hpDelta;
int oldHP = ally != null ? ally.maxHP : enemy.maxHP;
if (ally != null) ally.SetMaxHP(Mathf.Max(1, ally.maxHP + hpDelta), false);
else enemy.SetMaxHP(Mathf.Max(1, enemy.maxHP + hpDelta), false);
int newHP = ally != null ? ally.maxHP : enemy.maxHP;
CheckLimitAndWarnInt(targetName, "最大生命值", oldHP, newHP, hpDelta);
state = new RefreshOnlyTimedState { effectType = effectType, intDelta = hpDelta };
return true;
@@ -838,8 +919,11 @@ public class SkillBuilder : MonoBehaviour
case EffectType.DecreaseMaxMana:
int manaDelta = Mathf.CeilToInt(Mathf.Abs(amount));
if (effectType == EffectType.DecreaseMaxMana) manaDelta = -manaDelta;
int oldMana = ally != null ? ally.maxMana : enemy.maxMana;
if (ally != null) ally.SetMaxMana(Mathf.Max(1, ally.maxMana + manaDelta), false);
else enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana + manaDelta), false);
int newMana = ally != null ? ally.maxMana : enemy.maxMana;
CheckLimitAndWarnInt(targetName, "最大法力值", oldMana, newMana, manaDelta);
state = new RefreshOnlyTimedState { effectType = effectType, intDelta = manaDelta };
return true;
}
@@ -847,6 +931,30 @@ public class SkillBuilder : MonoBehaviour
return false;
}
private void CheckLimitAndWarn(string targetName, string attrName, float startValue, float newValue, float attemptedDelta)
{
float actualChange = newValue - startValue;
// If we attempted a change but the actual change differs significantly from attempted, we hit a limit.
if (Mathf.Abs(attemptedDelta) > 0.001f && Mathf.Abs(actualChange - attemptedDelta) > 0.001f)
{
load_skillwarn.Instance?.ShowWarning(targetName, attrName);
}
}
private void CheckLimitAndWarnInt(string targetName, string attrName, int startValue, int newValue, int attemptedDelta)
{
int actualChange = newValue - startValue;
if (attemptedDelta != 0 && actualChange != attemptedDelta)
{
load_skillwarn.Instance?.ShowWarning(targetName, attrName);
}
}
private static float ClampDamageResistance(float value)
{
return Mathf.Min(value, 1f);
}
private void RevertRefreshOnlyTimedEffect(GameObject target, RefreshOnlyTimedState state)
{
if (target == null || state == null) return;
@@ -864,8 +972,8 @@ public class SkillBuilder : MonoBehaviour
case EffectType.IncreaseDamageResistance:
case EffectType.DecreaseDamageResistance:
if (ally != null) ally.damageResistance = Mathf.Clamp01(ally.damageResistance - state.floatDelta);
else enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance - state.floatDelta);
if (ally != null) ally.damageResistance = ClampDamageResistance(ally.damageResistance - state.floatDelta);
else enemy.damageResistance = ClampDamageResistance(enemy.damageResistance - state.floatDelta);
break;
case EffectType.IncreaseAttack:
@@ -886,6 +994,15 @@ public class SkillBuilder : MonoBehaviour
else enemy.SetMaxMana(Mathf.Max(1, enemy.maxMana - state.intDelta), false);
break;
}
if (!string.IsNullOrEmpty(state.budeffIconId))
{
if (state.budeffSlotIndex >= 0) iBudeffPrefabController.Instance?.UnregisterTimedEffect(state.budeffSlotIndex, state.budeffIconId);
else if (state.budeffEnemyInstanceId != 0) iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(state.budeffEnemyInstanceId, state.budeffIconId);
state.budeffIconId = null;
state.budeffSlotIndex = -1;
state.budeffEnemyInstanceId = 0;
}
}
private bool TryApplyRefreshOnlyOverTimeEffect(SkillDefinition def, GameObject target, float totalAmount, GameObject source)
@@ -973,15 +1090,15 @@ public class SkillBuilder : MonoBehaviour
{
if (def == null) { Debug.LogWarning("UseSkillDefinition: null def"); return; }
int groupId = 0;
string smallSkillName = null;
Sprite smallSkillIcon = null;
// Resolve group/icon metadata for the ally HUD skill icon queue.
try
{
var heroSoForName = GetAllyHeroSOBySlot(slotIndex);
// Documentation text normalized.
int groupId = 0;
string smallSkillName = null;
Sprite smallSkillIcon = null;
if (heroSoForName != null && heroSoForName.skillGroups != null)
{
foreach (var g in heroSoForName.skillGroups)
@@ -1043,6 +1160,13 @@ ResolvedGroup:
if (!s_lastSkillFeedFrameByKey.TryGetValue(key, out lastFrame) || lastFrame != frame)
{
s_lastSkillFeedFrameByKey[key] = frame;
// Push to text feed
if (heroSoForName != null && !string.IsNullOrEmpty(heroSoForName.ally_heroName))
{
SkillTriggerFeedUI.Push(heroSoForName.ally_heroName, smallSkillName);
}
// Ally HUD skill icon feed (newest first, max 3).
if (smallSkillIcon != null && slotIndex >= 0 && slotIndex < 5)
{
@@ -1247,6 +1371,12 @@ ResolvedGroup:
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;
if (apprSprite != null) iBudeffPrefabController.Instance?.SpawnApprForSlot(ally.slotIndex, apprSprite);
}
switch (def.effectType)
{
case EffectType.DamageSingleEnemy:
@@ -1263,7 +1393,25 @@ ResolvedGroup:
}
else
{
StartCoroutine(ApplyDamageOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
string iconId = null;
int allySlotIndex = -1;
int enemyInstanceId = 0;
var enemyDot = t.GetComponent<EnemyCombatant>() ?? t.GetComponentInChildren<EnemyCombatant>(true);
if (enemyDot != null)
{
int ticks = Mathf.Max(1, Mathf.CeilToInt(def.defaultDuration / def.GetEffectiveTickInterval()));
float perTick = amountTotal / ticks;
iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemyDot, PlayerBudeffIconType.ot_bleeding, perTick, def.defaultDuration);
enemyInstanceId = enemyDot.GetInstanceID();
}
else if (ally != null)
{
int ticks = Mathf.Max(1, Mathf.CeilToInt(def.defaultDuration / def.GetEffectiveTickInterval()));
float perTick = amountTotal / ticks;
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));
}
break;
case EffectType.HealSingleSelf:
@@ -1332,20 +1480,60 @@ ResolvedGroup:
break;
// Documentation text normalized.
case EffectType.IncreaseMaxHP:
if (ally != null) ally.SetMaxHP(ally.maxHP + Mathf.CeilToInt(amountTotal), false);
else if (t.TryGetComponent<EnemyCombatant>(out var enemy)) enemy.SetMaxHP(enemy.maxHP + Mathf.CeilToInt(amountTotal), false);
if (ally != null)
{
int d = Mathf.CeilToInt(amountTotal);
ally.SetMaxHP(ally.maxHP + d, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_up, d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy))
{
int d = Mathf.CeilToInt(amountTotal);
enemy.SetMaxHP(enemy.maxHP + d, false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy, PlayerBudeffIconType.ot_maxHP_up, d, 0f);
}
break;
case EffectType.DecreaseMaxHP:
if (ally != null) ally.SetMaxHP(Mathf.Max(1, ally.maxHP - Mathf.CeilToInt(amountTotal)), false);
else if (t.TryGetComponent<EnemyCombatant>(out var enemy2)) enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - Mathf.CeilToInt(amountTotal)), false);
if (ally != null)
{
int d = Mathf.CeilToInt(amountTotal);
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))
{
int d = Mathf.CeilToInt(amountTotal);
enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - d), false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy2, PlayerBudeffIconType.ot_maxHP_down, -d, 0f);
}
break;
case EffectType.IncreaseMaxMana:
if (ally != null) ally.SetMaxMana(ally.maxMana + Mathf.CeilToInt(amountTotal), false);
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3)) enemy3.SetMaxMana(enemy3.maxMana + Mathf.CeilToInt(amountTotal), false);
if (ally != null)
{
int d = Mathf.CeilToInt(amountTotal);
ally.SetMaxMana(ally.maxMana + d, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_up, d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3))
{
int d = Mathf.CeilToInt(amountTotal);
enemy3.SetMaxMana(enemy3.maxMana + d, false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy3, PlayerBudeffIconType.ot_maxMana_up, d, 0f);
}
break;
case EffectType.DecreaseMaxMana:
if (ally != null) ally.SetMaxMana(Mathf.Max(1, ally.maxMana - Mathf.CeilToInt(amountTotal)), false);
else if (t.TryGetComponent<EnemyCombatant>(out var enemy4)) enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - Mathf.CeilToInt(amountTotal)), false);
if (ally != null)
{
int d = Mathf.CeilToInt(amountTotal);
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))
{
int d = Mathf.CeilToInt(amountTotal);
enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - d), false);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy4, PlayerBudeffIconType.ot_maxMana_down, -d, 0f);
}
break;
case EffectType.IncreaseScoreEfficiency:
if (ally != null)
@@ -1353,19 +1541,28 @@ ResolvedGroup:
if (def.defaultDuration <= 0f)
{
ally.scoreEfficiency += amountTotal;
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
{
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, amountTotal, def.defaultDuration));
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, def.defaultDuration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, amountTotal, def.defaultDuration, iconId));
}
}
break;
case EffectType.DecreaseScoreEfficiency:
if (ally != null)
{
if (def.defaultDuration <= 0f) ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - amountTotal);
if (def.defaultDuration <= 0f)
{
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))
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, -amountTotal, def.defaultDuration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amountTotal, def.defaultDuration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, -amountTotal, def.defaultDuration, iconId));
}
}
break;
case EffectType.IncreaseAttack:
@@ -1373,15 +1570,29 @@ ResolvedGroup:
int delta = Mathf.CeilToInt(amountTotal);
if (ally != null)
{
if (def.defaultDuration <= 0f) ally.ModifyAttack(delta);
if (def.defaultDuration <= 0f)
{
ally.ModifyAttack(delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, delta, 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))
{
if (def.defaultDuration <= 0f) enemy5.ModifyAttack(delta);
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))
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy5, delta, def.defaultDuration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy5, delta, def.defaultDuration, iconId, enemy5.GetInstanceID()));
}
}
}
break;
@@ -1390,15 +1601,29 @@ ResolvedGroup:
int delta = Mathf.CeilToInt(amountTotal);
if (ally != null)
{
if (def.defaultDuration <= 0f) ally.ModifyAttack(-delta);
if (def.defaultDuration <= 0f)
{
ally.ModifyAttack(-delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, -delta, 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))
{
if (def.defaultDuration <= 0f) enemy6.ModifyAttack(-delta);
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))
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy6, -delta, def.defaultDuration));
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy6, -delta, def.defaultDuration, iconId, enemy6.GetInstanceID()));
}
}
}
break;
@@ -1419,6 +1644,24 @@ ResolvedGroup:
Debug.Log($"<color=#00FFFF>[SkillDebug]</color> <color=#FFD700>ID: {def.skillId}</color> | <color=#00FF00>Trigger: {def.triggerCondition}</color> | <color=#FFA500>Target: (Resolving via EffectSystem)</color> | <color=#EE82EE>Effect: {def.effectType}</color> | <color=#FF4500>Value: {amountTotal:F2}</color>");
}
if (ShouldSpawnApprForEffect(def.effectType))
{
var apprSprite = iBudeffPrefabController.Instance != null ? iBudeffPrefabController.Instance.GetSpriteForEffect(def.effectType) : null;
if (apprSprite != null)
{
var apprTargets = ResolveTargetsLocal(def.defaultSelector, caster, specificTarget);
if (apprTargets != null)
{
foreach (var t in apprTargets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>() ?? t.GetComponentInChildren<AllyCombatant>(true);
if (ally != null) iBudeffPrefabController.Instance?.SpawnApprForSlot(ally.slotIndex, apprSprite);
}
}
}
}
if (def.defaultDuration > 0f)
{
var resolvedTargets = ResolveTargetsLocal(def.defaultSelector, caster, specificTarget);
@@ -1984,61 +2227,98 @@ ResolvedGroup:
ic?.RemoveBuff(buffId);
}
private IEnumerator ApplyTemporaryScoreEfficiencyChangeDirect(AllyCombatant ally, float delta, float duration)
private IEnumerator ApplyTemporaryScoreEfficiencyChangeDirect(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);
if (ally == null) yield break;
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);
}
}
private IEnumerator ApplyTemporaryAttackChangeDirect(AllyCombatant ally, int delta, float duration)
private IEnumerator ApplyTemporaryAttackChangeDirect(AllyCombatant ally, int delta, float duration, string iconId)
{
if (ally == null) yield break;
ally.ModifyAttack(delta);
yield return new WaitForSeconds(duration);
if (ally == null) yield break;
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 IEnumerator ApplyTemporaryAttackChangeDirect(EnemyCombatant enemy, int delta, float duration)
private IEnumerator ApplyTemporaryAttackChangeDirect(EnemyCombatant enemy, int delta, float duration, string iconId, int enemyInstanceId)
{
if (enemy == null) yield break;
enemy.ModifyAttack(delta);
yield return new WaitForSeconds(duration);
if (enemy == null) yield break;
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);
}
}
// Coroutine helpers for direct DoT/HoT on GameObject targets
private IEnumerator ApplyDamageOverTimeDirect(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
private IEnumerator ApplyDamageOverTimeDirect(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source, string iconId, int allySlotIndex, int enemyInstanceId)
{
if (target == null) yield break;
var ic = target.GetComponent<ICombatant>();
var ally = target.GetComponent<AllyCombatant>();
if (ally == null && ic == null)
try
{
Debug.LogWarning($"ApplyDamageOverTimeDirect: target {target.name} cannot receive damage");
yield break;
}
if (target == null) yield break;
var ic = target.GetComponent<ICombatant>();
var ally = target.GetComponent<AllyCombatant>();
if (ally == null && ic == null)
{
Debug.LogWarning($"ApplyDamageOverTimeDirect: target {target.name} cannot receive damage");
yield break;
}
if (duration <= 0f || tickInterval <= 0f)
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(totalAmount), true);
else ic?.ReceiveDamage(totalAmount, source);
yield break;
}
if (duration <= 0f || tickInterval <= 0f)
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(totalAmount), true);
else ic?.ReceiveDamage(totalAmount, source);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
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 (ally != null) ally.ModifyHP(-Mathf.CeilToInt(perTick), true);
else ic?.ReceiveDamage(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
finally
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(perTick), true);
else ic?.ReceiveDamage(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
if (!string.IsNullOrEmpty(iconId))
{
if (allySlotIndex >= 0) iBudeffPrefabController.Instance?.UnregisterTimedEffect(allySlotIndex, iconId);
else if (enemyInstanceId != 0) iBudeffPrefabController.Instance?.UnregisterEnemyTimedEffect(enemyInstanceId, iconId);
}
}
}