修了很多bug和增加功能,优化不少问题

This commit is contained in:
FloatGaming
2026-02-28 06:59:22 +08:00
parent 508a40bba0
commit e11cc1da7a
345 changed files with 173803 additions and 69332 deletions
+253 -26
View File
@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -163,7 +163,7 @@ public class SkillBuilder : MonoBehaviour
var allyGO = GetAllyObjectBySlot(trackIndex);
if (allyGO == null) return;
var ally = allyGO.GetComponent<AllyCombatant>();
if (ally != null && ally.IsDead) return; // dead allies should not gain mana / take miss damage / deal note-hit damage
if (ally != null && (ally.IsDead || ally.maxHP == 0)) return; // dead or empty allies should not gain mana / take miss damage / deal note-hit damage
var so = GetAllyHeroSOBySlot(trackIndex);
// try to obtain per-level params from SO if present
AllyHero_SO.AllyLevelInfo levelInfo = so != null ? (so.GetEffectiveLevelForCurrentEXP()) : null;
@@ -214,7 +214,35 @@ public class SkillBuilder : MonoBehaviour
else if (ally != null) mName = ally.name; // fallback to GameObject name
SkillTriggerFeedUI.PushMiss(mName);
float missBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
// --- New Miss Damage Formula ---
// Formula: EnemyAttack * DifficultyLevel * 0.03 * AllyMissHpLossBase * (1 - Resistance)
// Note: AllyCombatant.ReceiveDamage already applies (1 - Resistance), so we calculate the base here.
float enemyAtk = 0f;
var enemyGO = GameObject.Find("thisEnemy");
if (enemyGO != null)
{
var ec = enemyGO.GetComponent<EnemyCombatant>();
if (ec != null) enemyAtk = ec.attack;
}
float diffLevel = 1f;
if (BeatmapManager.Instance != null && BeatmapManager.Instance.assignedSongData != null)
{
int curDiff = BeatmapManager.Instance.assignedDifficulty;
if (BeatmapManager.Instance.assignedSongData.chartFiles != null)
{
var chart = BeatmapManager.Instance.assignedSongData.chartFiles.Find(c => c.difficulty == curDiff);
if (chart != null) diffLevel = chart.difficultyLEVEL;
}
}
float fixedMult = 0.03f;
float allyMissBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
float missBase = enemyAtk * diffLevel * fixedMult * allyMissBase;
LogVerbose($"[SkillBuilder] Miss Damage Calc: Atk({enemyAtk}) * DiffLvl({diffLevel}) * Mult({fixedMult}) * AllyBase({allyMissBase}) = {missBase}");
// 播放敌人攻击特效,并将伤害逻辑延迟到特效到达时执行
bool effectStarted = false;
@@ -779,6 +807,64 @@ public class SkillBuilder : MonoBehaviour
case EffectType.DecreaseAttack:
case EffectType.RedirectNextDamageToSelf:
case EffectType.RedirectSelfDamageToAdjacent:
case EffectType.RewriteNonMissToPerfect:
return true;
default:
return false;
}
}
// Buff redirection (交给我!/都给你!) should affect any "buff-like" effect that is represented
// by budeff icons or timed status changes, not only Buff objects.
private static bool IsRedirectableBudeffEvent(EffectType effectType)
{
switch (effectType)
{
case EffectType.DamageOverTimeEnemy:
case EffectType.DamageOverTimeAlly:
case EffectType.ReduceEnemyHealOverTime:
case EffectType.BuffDuration:
case EffectType.DebuffDuration:
case EffectType.IncreaseManaOverTime:
case EffectType.IncreaseMaxHP:
case EffectType.DecreaseMaxHP:
case EffectType.IncreaseMaxMana:
case EffectType.DecreaseMaxMana:
case EffectType.IncreaseScoreEfficiency:
case EffectType.DecreaseScoreEfficiency:
case EffectType.IncreaseDamageResistance:
case EffectType.DecreaseDamageResistance:
case EffectType.IncreaseAttack:
case EffectType.DecreaseAttack:
case EffectType.RedirectNextDamageToSelf:
case EffectType.RedirectSelfDamageToAdjacent:
case EffectType.RewriteNonMissToPerfect:
return true;
default:
return false;
}
}
// Subset for def.operateDirectly branch where effects are applied inside SkillBuilder switch.
// Do not include types that fall through to default ExecuteSkill, otherwise redirect would be consumed too early.
private static bool IsRedirectableDirectBranchEffect(EffectType effectType)
{
switch (effectType)
{
case EffectType.DamageOverTimeEnemy:
case EffectType.IncreaseManaOverTime:
case EffectType.ReduceEnemyHealOverTime:
case EffectType.BuffDuration:
case EffectType.DebuffDuration:
case EffectType.IncreaseMaxHP:
case EffectType.DecreaseMaxHP:
case EffectType.IncreaseMaxMana:
case EffectType.DecreaseMaxMana:
case EffectType.IncreaseScoreEfficiency:
case EffectType.DecreaseScoreEfficiency:
case EffectType.IncreaseAttack:
case EffectType.DecreaseAttack:
case EffectType.RedirectNextDamageToSelf:
return true;
default:
return false;
@@ -1372,14 +1458,23 @@ ResolvedGroup:
{
if (t == null) continue;
GameObject target = t;
var ally = target.GetComponent<AllyCombatant>();
if (ally != null && IsRedirectableDirectBranchEffect(def.effectType))
{
if (ally.TryRedirectIncomingGenericBuff(out var redirectedAlly) && redirectedAlly != null)
{
target = redirectedAlly.gameObject;
ally = redirectedAlly;
}
}
var ic = target.GetComponent<ICombatant>();
if (GameConfig.skillDebugMode)
{
Debug.Log($"<color=#00FFFF>[SkillDebug]</color> <color=#FFD700>ID: {def.skillId}</color> | <color=#00FF00>Trigger: {def.triggerCondition}</color> | <color=#FFA500>Target: {t.name}</color> | <color=#EE82EE>Effect: {def.effectType}</color> | <color=#FF4500>Value: {amountTotal:F2}</color>");
Debug.Log($"<color=#00FFFF>[SkillDebug]</color> <color=#FFD700>ID: {def.skillId}</color> | <color=#00FF00>Trigger: {def.triggerCondition}</color> | <color=#FFA500>Target: {target.name}</color> | <color=#EE82EE>Effect: {def.effectType}</color> | <color=#FF4500>Value: {amountTotal:F2}</color>");
}
var ally = t.GetComponent<AllyCombatant>();
var ic = t.GetComponent<ICombatant>();
if (ally != null && ShouldSpawnApprForEffect(def.effectType))
{
var apprSprite = iBudeffPrefabController.Instance != null ? iBudeffPrefabController.Instance.GetSpriteForEffect(def.effectType) : null;
@@ -1405,7 +1500,7 @@ ResolvedGroup:
string iconId = null;
int allySlotIndex = -1;
int enemyInstanceId = 0;
var enemyDot = t.GetComponent<EnemyCombatant>() ?? t.GetComponentInChildren<EnemyCombatant>(true);
var enemyDot = target.GetComponent<EnemyCombatant>() ?? target.GetComponentInChildren<EnemyCombatant>(true);
if (enemyDot != null)
{
int ticks = Mathf.Max(1, Mathf.CeilToInt(def.defaultDuration / def.GetEffectiveTickInterval()));
@@ -1420,7 +1515,7 @@ ResolvedGroup:
iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_bleeding, perTick, def.defaultDuration);
allySlotIndex = ally.slotIndex;
}
StartCoroutine(ApplyDamageOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster, iconId, allySlotIndex, enemyInstanceId));
StartCoroutine(ApplyDamageOverTimeDirect(target, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster, iconId, allySlotIndex, enemyInstanceId));
}
break;
case EffectType.HealSingleSelf:
@@ -1437,7 +1532,7 @@ ResolvedGroup:
}
else
{
StartCoroutine(ApplyHealOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
StartCoroutine(ApplyHealOverTimeDirect(target, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
}
break;
case EffectType.IncreaseManaOverTime:
@@ -1467,7 +1562,7 @@ ResolvedGroup:
healReceivedMultiplier = 1f - reduction
};
ic.ApplyBuff(deb, caster);
StartCoroutine(RemoveBuffAfterDuration(t, deb.buffId, deb.duration));
StartCoroutine(RemoveBuffAfterDuration(target, deb.buffId, deb.duration));
}
break;
case EffectType.BuffDuration:
@@ -1484,7 +1579,7 @@ ResolvedGroup:
scoreMultiplier = mult
};
ic.ApplyBuff(b, caster);
StartCoroutine(RemoveBuffAfterDuration(t, b.buffId, b.duration));
StartCoroutine(RemoveBuffAfterDuration(target, b.buffId, b.duration));
}
break;
// Documentation text normalized.
@@ -1496,7 +1591,7 @@ ResolvedGroup:
if (d > 0) ally.SetCurrentHP(ally.currentHP + d, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_up, d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy))
{
int d = Mathf.CeilToInt(amountTotal);
enemy.SetMaxHP(enemy.maxHP + d, false);
@@ -1511,7 +1606,7 @@ ResolvedGroup:
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - d), false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxHP_down, -d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy2))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy2))
{
int d = Mathf.CeilToInt(amountTotal);
enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - d), false);
@@ -1525,7 +1620,7 @@ ResolvedGroup:
ally.SetMaxMana(ally.maxMana + d, false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_up, d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy3))
{
int d = Mathf.CeilToInt(amountTotal);
enemy3.SetMaxMana(enemy3.maxMana + d, false);
@@ -1539,7 +1634,7 @@ ResolvedGroup:
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - d), false);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_maxMana_down, -d, 0f);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy4))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy4))
{
int d = Mathf.CeilToInt(amountTotal);
enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - d), false);
@@ -1554,7 +1649,7 @@ ResolvedGroup:
ally.scoreEfficiency += amountTotal;
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, amountTotal, def.defaultDuration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, amountTotal, def.defaultDuration, iconId));
@@ -1569,7 +1664,7 @@ ResolvedGroup:
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - amountTotal);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amountTotal, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_down, -amountTotal, def.defaultDuration);
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeDirect(ally, -amountTotal, def.defaultDuration, iconId));
@@ -1586,20 +1681,20 @@ ResolvedGroup:
ally.ModifyAttack(delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_up, delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, delta, def.defaultDuration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy5))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy5))
{
if (def.defaultDuration <= 0f)
{
enemy5.ModifyAttack(delta);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy5, PlayerBudeffIconType.ot_atk_up, delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy5, delta, def.defaultDuration, iconId, enemy5.GetInstanceID()));
@@ -1617,20 +1712,20 @@ ResolvedGroup:
ally.ModifyAttack(-delta);
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_atk_down, -delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(ally, -delta, def.defaultDuration, iconId));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy6))
else if (target.TryGetComponent<EnemyCombatant>(out var enemy6))
{
if (def.defaultDuration <= 0f)
{
enemy6.ModifyAttack(-delta);
iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -delta, 0f);
}
else if (!TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration))
else if (!TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration))
{
var iconId = iBudeffPrefabController.Instance?.RegisterEnemyTimedEffect(enemy6, PlayerBudeffIconType.ot_atk_down, -delta, def.defaultDuration);
StartCoroutine(ApplyTemporaryAttackChangeDirect(enemy6, -delta, def.defaultDuration, iconId, enemy6.GetInstanceID()));
@@ -1677,13 +1772,27 @@ ResolvedGroup:
{
var resolvedTargets = ResolveTargetsLocal(def.defaultSelector, caster, specificTarget);
bool appliedRefreshOnly = false;
bool mayHandleLocally = IsRefreshOnlyTimedSkill(def) || IsRefreshOnlyOverTimeSkill(def);
if (resolvedTargets != null)
{
foreach (var t in resolvedTargets)
{
if (t == null) continue;
bool handled = TryApplyRefreshOnlyTimedEffect(def, t, amountTotal, def.defaultDuration);
if (!handled) handled = TryApplyRefreshOnlyOverTimeEffect(def, t, amountTotal, caster);
var target = t;
if (mayHandleLocally)
{
var ally = target.GetComponent<AllyCombatant>() ?? target.GetComponentInChildren<AllyCombatant>(true);
if (ally != null && IsRedirectableBudeffEvent(def.effectType))
{
if (ally.TryRedirectIncomingGenericBuff(out var redirectedAlly) && redirectedAlly != null)
{
target = redirectedAlly.gameObject;
}
}
}
bool handled = TryApplyRefreshOnlyTimedEffect(def, target, amountTotal, def.defaultDuration);
if (!handled) handled = TryApplyRefreshOnlyOverTimeEffect(def, target, amountTotal, caster);
if (handled) appliedRefreshOnly = true;
}
}
@@ -1992,6 +2101,89 @@ ResolvedGroup:
}
}
// Trigger ally skills when an adjacent ally releases a skill (mana-full cast path).
public void TriggerOnAdjacentAllySkillCast(int casterSlotIndex)
{
if (!Application.isPlaying) return;
if (casterSlotIndex < 0) return;
int slots = 5;
if (teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null)
slots = teamUIController.Instance.allySlotIds.Count;
for (int i = 0; i < slots; i++)
{
if (i == casterSlotIndex) continue;
bool isAdjacent = false;
if (teamUIController.Instance != null)
{
int[] adj = teamUIController.Instance.GetAdjacentAllyIndices(i);
if (adj != null)
{
for (int j = 0; j < adj.Length; j++)
{
if (adj[j] == casterSlotIndex) { isAdjacent = true; break; }
}
}
}
else
{
isAdjacent = Mathf.Abs(i - casterSlotIndex) == 1;
}
if (!isAdjacent) continue;
var allyObj = GetAllyObjectBySlot(i);
var allyComp = allyObj != null ? allyObj.GetComponent<AllyCombatant>() : null;
if (allyComp != null && allyComp.IsDead) continue;
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
{
foreach (int gid in so.equippedSkillGroupIDs)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
if (group == null || group.skills == null) continue;
foreach (var def in group.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
UseSkillDefinition(def, i, -1f, null);
}
}
continue;
}
var fallbackGroup = so.GetPrimarySkillGroup();
if (fallbackGroup != null && fallbackGroup.skills != null)
{
foreach (var def in fallbackGroup.skills)
{
if (def == null) continue;
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
UseSkillDefinition(def, i, -1f, null);
}
continue;
}
var primary = so.GetPrimarySkill();
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast)
{
UsePrimarySkillForSlot(i, -1f, null);
}
}
}
private Dictionary<string, float> _lastOnNoteHitTriggerTime = new Dictionary<string, float>();
// track processed unique note IDs (e.g. long-hold note id) so shared effects (mana/hp) are applied only once
@@ -2306,6 +2498,23 @@ ResolvedGroup:
if (duration <= 0f || tickInterval <= 0f)
{
if (target.GetComponent<EnemyCombatant>() != null && teamUIController.Instance != null)
{
if (!teamUIController.Instance.IsAnyAllyActive())
{
yield break;
}
if (source != null)
{
var sourceAlly = source.GetComponent<AllyCombatant>();
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
{
yield break;
}
}
}
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(totalAmount), true);
else ic?.ReceiveDamage(totalAmount, source);
yield break;
@@ -2317,6 +2526,24 @@ ResolvedGroup:
while (elapsed < duration)
{
if (target == null) yield break;
if (target.GetComponent<EnemyCombatant>() != null && teamUIController.Instance != null)
{
if (!teamUIController.Instance.IsAnyAllyActive())
{
yield break;
}
if (source != null)
{
var sourceAlly = source.GetComponent<AllyCombatant>();
if (sourceAlly != null && !teamUIController.Instance.IsAllySlotActive(sourceAlly.slotIndex))
{
yield break;
}
}
}
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(perTick), true);
else ic?.ReceiveDamage(perTick, source);
yield return new WaitForSeconds(tickInterval);