notebook实装功能,galgame继续,一些bug修复
This commit is contained in:
@@ -72,9 +72,19 @@ public class SkillBuilder : MonoBehaviour
|
||||
private readonly Dictionary<string, Coroutine> _refreshOnlyTimedEffectCoroutines = new Dictionary<string, Coroutine>(32);
|
||||
private readonly Dictionary<string, RefreshOnlyTimedState> _refreshOnlyTimedEffectStates = new Dictionary<string, RefreshOnlyTimedState>(32);
|
||||
private readonly Dictionary<string, Coroutine> _refreshOnlyOverTimeCoroutines = new Dictionary<string, Coroutine>(16);
|
||||
private readonly Dictionary<int, ManaFullLongingState> _manaFullLongingStateBySlot = new Dictionary<int, ManaFullLongingState>(8);
|
||||
private int _cachedAlliesFrame = -1;
|
||||
private readonly List<GameObject> _cachedAllies = new List<GameObject>(8);
|
||||
|
||||
private const string SkillIdYuetaoLongingDamage = "44109";
|
||||
private const string SkillIdYuetaoLongingCost = "44109_2";
|
||||
|
||||
private struct ManaFullLongingState
|
||||
{
|
||||
public int frame;
|
||||
public int consumedHp;
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> s_refreshOnlyTimedSkillIds = new HashSet<string>
|
||||
{
|
||||
// Lock
|
||||
@@ -859,6 +869,7 @@ public class SkillBuilder : MonoBehaviour
|
||||
case EffectType.IncreaseAttack:
|
||||
case EffectType.DecreaseAttack:
|
||||
case EffectType.RedirectNextDamageToSelf:
|
||||
case EffectType.RedirectSelfDamageToAdjacent:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -1182,10 +1193,11 @@ public class SkillBuilder : MonoBehaviour
|
||||
int groupId = 0;
|
||||
string smallSkillName = null;
|
||||
Sprite smallSkillIcon = null;
|
||||
AllyHero_SO heroSoForName = null;
|
||||
// Resolve group/icon metadata for the ally HUD skill icon queue.
|
||||
try
|
||||
{
|
||||
var heroSoForName = GetAllyHeroSOBySlot(slotIndex);
|
||||
heroSoForName = GetAllyHeroSOBySlot(slotIndex);
|
||||
|
||||
// Documentation text normalized.
|
||||
if (heroSoForName != null && heroSoForName.skillGroups != null)
|
||||
@@ -1308,6 +1320,11 @@ ResolvedGroup:
|
||||
// Log useful debug info for diagnosing OnEnemyDead->Self issues
|
||||
LogVerbose($"[SkillBuilder] UseSkillDefinition: casting skill {def.skillId} for slot {slotIndex} (caster={(caster?caster.name:"null")}) selector={def.defaultSelector} operateDirectly={def.operateDirectly} inputValue={inputValue} specificTarget={(specificTarget?specificTarget.name:"null")}");
|
||||
|
||||
if (TryHandleYuetaoLongingSkill(def, slotIndex, caster, heroSoForName, specificTarget))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float amount = 0f;
|
||||
// If caller provided explicit inputValue use it
|
||||
if (inputValue != -1f)
|
||||
@@ -1372,6 +1389,9 @@ ResolvedGroup:
|
||||
vars["currentScore"] = casterAlly != null ? casterAlly.currentScore : 0f;
|
||||
vars["idolScore"] = vars["currentScore"];
|
||||
vars["currentHP"] = casterAlly != null ? casterAlly.currentHP : 0f;
|
||||
vars["noteBaseScore"] = casterAlly != null
|
||||
? (casterAlly.bmm != null ? Mathf.Max(0, casterAlly.bmm.perNoteScore) : Mathf.Max(0, casterAlly.baseTrackScore))
|
||||
: 0f;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(def.formula))
|
||||
{
|
||||
@@ -1438,6 +1458,26 @@ ResolvedGroup:
|
||||
_lastSkillTriggerTime[repeatKey] = now;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string casterDisplayName = ResolveCasterDisplayNameForLog(slotIndex, heroSoForName, caster);
|
||||
string skillDisplayNameForLog = string.IsNullOrWhiteSpace(smallSkillName)
|
||||
? (!string.IsNullOrWhiteSpace(def.displayName) ? def.displayName : (!string.IsNullOrWhiteSpace(def.skillId) ? def.skillId : def.name))
|
||||
: smallSkillName;
|
||||
string effectSummary = BuildSkillEffectSummaryForLog(def, amountPerTick, amountTotal);
|
||||
string targetSummary = BuildSkillTargetSummaryForLog(def.defaultSelector, specificTarget);
|
||||
GameplaySkillLogger.RecordSkillRelease(
|
||||
casterDisplayName,
|
||||
skillDisplayNameForLog,
|
||||
def.effectType.ToString(),
|
||||
effectSummary,
|
||||
targetSummary);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
LogVerbose("[SkillBuilder] GameplaySkillLogger failed: " + ex.Message);
|
||||
}
|
||||
|
||||
if (def.operateDirectly)
|
||||
{
|
||||
// Resolve targets as GameObjects but operate on their AllyCombatant / ICombatant data directly
|
||||
@@ -1800,6 +1840,79 @@ ResolvedGroup:
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryHandleYuetaoLongingSkill(SkillDefinition def, int slotIndex, GameObject caster, AllyHero_SO heroSoForName, GameObject specificTarget)
|
||||
{
|
||||
if (def == null || string.IsNullOrWhiteSpace(def.skillId)) return false;
|
||||
if (def.skillId != SkillIdYuetaoLongingDamage && def.skillId != SkillIdYuetaoLongingCost) return false;
|
||||
|
||||
var casterAlly = caster != null ? caster.GetComponent<AllyCombatant>() : null;
|
||||
if (casterAlly == null)
|
||||
{
|
||||
Debug.LogWarning($"[SkillBuilder] Yuetao Longing custom handler skipped: caster ally missing for slot {slotIndex}");
|
||||
return false;
|
||||
}
|
||||
|
||||
int consumedHp = 0;
|
||||
int nowFrame = Time.frameCount;
|
||||
if (_manaFullLongingStateBySlot.TryGetValue(slotIndex, out var state) && state.frame == nowFrame)
|
||||
{
|
||||
consumedHp = state.consumedHp;
|
||||
}
|
||||
else
|
||||
{
|
||||
consumedHp = ConsumeYuetaoLongingHpCost(casterAlly);
|
||||
_manaFullLongingStateBySlot[slotIndex] = new ManaFullLongingState { frame = nowFrame, consumedHp = consumedHp };
|
||||
}
|
||||
|
||||
string casterDisplayName = ResolveCasterDisplayNameForLog(slotIndex, heroSoForName, caster);
|
||||
string skillDisplayName = !string.IsNullOrWhiteSpace(def.displayName) ? def.displayName : (!string.IsNullOrWhiteSpace(def.skillId) ? def.skillId : def.name);
|
||||
string targetSummary = BuildSkillTargetSummaryForLog(def.defaultSelector, specificTarget);
|
||||
|
||||
if (def.skillId == SkillIdYuetaoLongingCost)
|
||||
{
|
||||
try
|
||||
{
|
||||
string effectSummary = "生命值" + FormatSignedIntForLog(-Mathf.Abs(consumedHp)) + " (上限3%, 保底>20%)";
|
||||
GameplaySkillLogger.RecordSkillRelease(casterDisplayName, skillDisplayName, def.effectType.ToString(), effectSummary, targetSummary);
|
||||
}
|
||||
catch { }
|
||||
return true;
|
||||
}
|
||||
|
||||
float attackBonus = Mathf.Max(0f, casterAlly.attack) * 0.1f;
|
||||
float damage = consumedHp + attackBonus;
|
||||
if (damage > 0f)
|
||||
{
|
||||
ExecuteSkill(def.displayName ?? def.skillId, def.effectType, damage, def.defaultSelector, caster, specificTarget, def.defaultDuration, def.defaultTickInterval);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string effectSummary = "重击伤害=" + damage.ToString("0.###") + " (耗血" + consumedHp + "+攻击10%:" + attackBonus.ToString("0.###") + ")";
|
||||
GameplaySkillLogger.RecordSkillRelease(casterDisplayName, skillDisplayName, def.effectType.ToString(), effectSummary, targetSummary);
|
||||
}
|
||||
catch { }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int ConsumeYuetaoLongingHpCost(AllyCombatant ally)
|
||||
{
|
||||
if (ally == null || ally.maxHP <= 0 || ally.currentHP <= 0) return 0;
|
||||
|
||||
int rawCost = Mathf.Max(0, Mathf.CeilToInt(ally.maxHP * 0.03f));
|
||||
if (rawCost <= 0) return 0;
|
||||
|
||||
// "cannot drop to 20% max HP or below" => keep HP strictly greater than 20%.
|
||||
int minRemainExclusive = Mathf.FloorToInt(ally.maxHP * 0.2f) + 1;
|
||||
int maxAllowedCost = Mathf.Max(0, ally.currentHP - minRemainExclusive);
|
||||
int finalCost = Mathf.Clamp(rawCost, 0, maxAllowedCost);
|
||||
if (finalCost <= 0) return 0;
|
||||
|
||||
ally.ModifyHP(-finalCost, true);
|
||||
return finalCost;
|
||||
}
|
||||
|
||||
// Use the selected skill index from an AllyHero_SO for a given slot (calls UseSkillDefinition)
|
||||
public void UseSelectedSkillForSlot(int slotIndex, int selectedSkillIndex, float inputValue = -1f, GameObject specificTarget = null)
|
||||
{
|
||||
@@ -2584,6 +2697,170 @@ ResolvedGroup:
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveCasterDisplayNameForLog(int slotIndex, AllyHero_SO heroSo, GameObject caster)
|
||||
{
|
||||
if (heroSo != null && !string.IsNullOrWhiteSpace(heroSo.ally_heroName))
|
||||
{
|
||||
return heroSo.ally_heroName;
|
||||
}
|
||||
|
||||
if (caster != null)
|
||||
{
|
||||
var ally = caster.GetComponent<AllyCombatant>() ?? caster.GetComponentInChildren<AllyCombatant>(true);
|
||||
if (ally != null && !string.IsNullOrWhiteSpace(ally.allyName))
|
||||
{
|
||||
return ally.allyName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(caster.name))
|
||||
{
|
||||
return caster.name;
|
||||
}
|
||||
}
|
||||
|
||||
return "slot" + (slotIndex + 1);
|
||||
}
|
||||
|
||||
private static string BuildSkillTargetSummaryForLog(Selector selector, GameObject specificTarget)
|
||||
{
|
||||
if (specificTarget != null)
|
||||
{
|
||||
return selector + " -> " + specificTarget.name;
|
||||
}
|
||||
|
||||
return selector.ToString();
|
||||
}
|
||||
|
||||
private static string BuildSkillEffectSummaryForLog(SkillDefinition def, float amountPerTick, float amountTotal)
|
||||
{
|
||||
if (def == null) return "-";
|
||||
|
||||
bool isOverTime = IsOverTimeEffectForLog(def.effectType) && def.defaultDuration > 0f;
|
||||
string summary;
|
||||
|
||||
switch (def.effectType)
|
||||
{
|
||||
case EffectType.DamageSingleEnemy:
|
||||
case EffectType.DamageSingleAlly:
|
||||
summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.DamageOverTimeEnemy:
|
||||
case EffectType.DamageOverTimeAlly:
|
||||
if (isOverTime) summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountPerTick)) + "/tick, 总计" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
|
||||
else summary = "生命值" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.HealSingleEnemy:
|
||||
case EffectType.HealSingleSelf:
|
||||
case EffectType.HealGroupSingle:
|
||||
summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.HealOverTimeEnemy:
|
||||
case EffectType.HealOverTimeSelf:
|
||||
case EffectType.HealGroupOverTime:
|
||||
if (isOverTime) summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountPerTick)) + "/tick, 总计" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
|
||||
else summary = "生命值" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.IncreaseManaOverTime:
|
||||
if (isOverTime) summary = "法力值" + FormatSignedFloatForLog(amountPerTick) + "/tick, 总计" + FormatSignedFloatForLog(amountTotal);
|
||||
else summary = "法力值" + FormatSignedFloatForLog(amountTotal);
|
||||
break;
|
||||
case EffectType.ReduceEnemyHealOverTime:
|
||||
summary = "受疗倍率-" + Mathf.RoundToInt(Mathf.Abs(amountPerTick) * 100f) + "%";
|
||||
break;
|
||||
case EffectType.ScoreMultiplier:
|
||||
summary = "得分倍率x" + amountTotal.ToString("0.###");
|
||||
break;
|
||||
case EffectType.AddScore:
|
||||
summary = "偶像分数" + FormatSignedFloatForLog(amountTotal);
|
||||
break;
|
||||
case EffectType.IncreaseMaxHP:
|
||||
summary = "最大生命值" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.DecreaseMaxHP:
|
||||
summary = "最大生命值" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.IncreaseMaxMana:
|
||||
summary = "最大法力值" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.DecreaseMaxMana:
|
||||
summary = "最大法力值" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.IncreaseScoreEfficiency:
|
||||
summary = "得分效率" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.DecreaseScoreEfficiency:
|
||||
summary = "得分效率" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.IncreaseDamageResistance:
|
||||
summary = "伤害抗性" + FormatSignedFloatForLog(Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.DecreaseDamageResistance:
|
||||
summary = "伤害抗性" + FormatSignedFloatForLog(-Mathf.Abs(amountTotal));
|
||||
break;
|
||||
case EffectType.IncreaseAttack:
|
||||
summary = "攻击力" + FormatSignedIntForLog(Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.DecreaseAttack:
|
||||
summary = "攻击力" + FormatSignedIntForLog(-Mathf.CeilToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.RedirectNextDamageToSelf:
|
||||
summary = "下一次承伤转移到自身";
|
||||
break;
|
||||
case EffectType.RedirectSelfDamageToAdjacent:
|
||||
summary = "下一次承伤转移到相邻偶像";
|
||||
break;
|
||||
case EffectType.GrantExtraPerfect:
|
||||
summary = "额外Perfect判定+" + Mathf.Max(1, Mathf.RoundToInt(Mathf.Abs(amountTotal)));
|
||||
break;
|
||||
case EffectType.RewriteNonMissToPerfect:
|
||||
summary = "非Miss判定改写为Perfect";
|
||||
break;
|
||||
case EffectType.BuffDuration:
|
||||
summary = "增益效果倍率x" + amountPerTick.ToString("0.###");
|
||||
break;
|
||||
case EffectType.DebuffDuration:
|
||||
summary = "减益效果倍率x" + amountPerTick.ToString("0.###");
|
||||
break;
|
||||
default:
|
||||
summary = "数值" + FormatSignedFloatForLog(amountTotal);
|
||||
break;
|
||||
}
|
||||
|
||||
if (def.defaultDuration > 0f)
|
||||
{
|
||||
summary += ",持续" + def.defaultDuration.ToString("0.##") + "s";
|
||||
if (isOverTime)
|
||||
{
|
||||
float tick = Mathf.Max(0.01f, def.GetEffectiveTickInterval());
|
||||
summary += ",间隔" + tick.ToString("0.##") + "s";
|
||||
}
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
private static bool IsOverTimeEffectForLog(EffectType effectType)
|
||||
{
|
||||
return effectType == EffectType.DamageOverTimeEnemy
|
||||
|| effectType == EffectType.DamageOverTimeAlly
|
||||
|| effectType == EffectType.HealOverTimeEnemy
|
||||
|| effectType == EffectType.HealOverTimeSelf
|
||||
|| effectType == EffectType.HealGroupOverTime
|
||||
|| effectType == EffectType.IncreaseManaOverTime;
|
||||
}
|
||||
|
||||
private static string FormatSignedFloatForLog(float value)
|
||||
{
|
||||
if (value >= 0f) return "+" + value.ToString("0.###");
|
||||
return value.ToString("0.###");
|
||||
}
|
||||
|
||||
private static string FormatSignedIntForLog(int value)
|
||||
{
|
||||
if (value >= 0) return "+" + value.ToString();
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
// Helper: map judge string to numeric quality
|
||||
private int JudgeQualityFromString(string judge)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user