更新
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Continentis.MainGame.Character;
|
||||
using Continentis.MainGame.Equipment;
|
||||
using SoftCircuits.Collections;
|
||||
using SLSFramework.General;
|
||||
using SLSFramework.UModAssistance;
|
||||
using UnityEngine;
|
||||
@@ -36,14 +37,129 @@ namespace Continentis.MainGame.Card
|
||||
return null;
|
||||
}
|
||||
|
||||
// 存储所有通过 SubscribeCombatEvent 注册的订阅的移除委托
|
||||
private readonly List<Action> _managedUnsubscribers = new List<Action>();
|
||||
|
||||
public virtual void Initialize(CardInstance cardInstance)
|
||||
{
|
||||
card = cardInstance;
|
||||
logicComponents = new HashSet<CardLogicComponentBase>();
|
||||
card.eventSubmodule.onTargeting += TargetingEffect;
|
||||
card.eventSubmodule.onUntargeting += UntargetingEffect;
|
||||
|
||||
// 自动将卡牌事件子模块的生命周期钩子接入虚方法
|
||||
card.eventSubmodule.onDraw.InsertByPriority(
|
||||
$"{GetType().Name}_OnDraw_{GetHashCode()}",
|
||||
new PrioritizedAction(OnDraw));
|
||||
|
||||
card.eventSubmodule.onCombatStart.InsertByPriority(
|
||||
$"{GetType().Name}_OnCombatStart_{GetHashCode()}",
|
||||
new PrioritizedAction(OnCombatStart));
|
||||
|
||||
card.eventSubmodule.onCombatEnd.InsertByPriority(
|
||||
$"{GetType().Name}_OnCombatEnd_{GetHashCode()}",
|
||||
new PrioritizedAction(OnCombatEnd));
|
||||
|
||||
card.eventSubmodule.onRoundStart.InsertByPriority(
|
||||
$"{GetType().Name}_OnRoundStart_{GetHashCode()}",
|
||||
new PrioritizedAction(OnRoundStart));
|
||||
|
||||
card.eventSubmodule.onRoundEnd.InsertByPriority(
|
||||
$"{GetType().Name}_OnRoundEnd_{GetHashCode()}",
|
||||
new PrioritizedAction(OnRoundEnd));
|
||||
|
||||
card.eventSubmodule.onActionStart.InsertByPriority(
|
||||
$"{GetType().Name}_OnActionStart_{GetHashCode()}",
|
||||
new PrioritizedAction(OnActionStart));
|
||||
|
||||
card.eventSubmodule.onActionEnd.InsertByPriority(
|
||||
$"{GetType().Name}_OnActionEnd_{GetHashCode()}",
|
||||
new PrioritizedAction(OnActionEnd));
|
||||
|
||||
// 关键词驱动的行为统一在此处注册
|
||||
if (card.HasKeyword("Instant"))
|
||||
{
|
||||
//含有Instant关键词,抽到后直接打出
|
||||
card.eventSubmodule.onDraw.InsertByPriority("Instant", new PrioritizedAction(() =>
|
||||
{
|
||||
card.DetectTargetsValidity(out List<CharacterBase> valid, out _, out _);
|
||||
card.Play(card.SetRandomTargets(valid), card.user);
|
||||
}, 99));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向战斗全局事件字典注册一个托管订阅。
|
||||
/// Dispose() 时无需手动取消——基类会自动移除所有通过此方法注册的订阅。
|
||||
/// </summary>
|
||||
protected void SubscribeCombatEvent(
|
||||
OrderedDictionary<string, PrioritizedAction> eventDict,
|
||||
PrioritizedAction action,
|
||||
int priority = 0)
|
||||
{
|
||||
string key = $"{GetType().Name}_{GetHashCode()}_{_managedUnsubscribers.Count}";
|
||||
action.Priority = priority;
|
||||
eventDict.InsertByPriority(key, action);
|
||||
_managedUnsubscribers.Add(() => eventDict.Remove(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向战斗全局事件字典注册一个带参数的托管订阅。
|
||||
/// Dispose() 时无需手动取消——基类会自动移除所有通过此方法注册的订阅。
|
||||
/// </summary>
|
||||
protected void SubscribeCombatEvent<T>(
|
||||
OrderedDictionary<string, PrioritizedAction<T>> eventDict,
|
||||
PrioritizedAction<T> action,
|
||||
int priority = 0)
|
||||
{
|
||||
string key = $"{GetType().Name}_{GetHashCode()}_{_managedUnsubscribers.Count}";
|
||||
action.Priority = priority;
|
||||
eventDict.InsertByPriority(key, action);
|
||||
_managedUnsubscribers.Add(() => eventDict.Remove(key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卡牌销毁时调用(打出、弃牌、消耗)。
|
||||
/// 自动清理所有通过 SubscribeCombatEvent 注册的托管订阅。
|
||||
/// 子类重写时无需调用 base.Dispose(),除非有额外资源需要释放。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (Action unsubscribe in _managedUnsubscribers)
|
||||
unsubscribe();
|
||||
_managedUnsubscribers.Clear();
|
||||
OnDispose();
|
||||
}
|
||||
|
||||
// ── 生命周期虚方法 ─────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>抽到此卡牌时调用。</summary>
|
||||
protected virtual void OnDraw() { }
|
||||
|
||||
/// <summary>战斗开始时调用。</summary>
|
||||
protected virtual void OnCombatStart() { }
|
||||
|
||||
/// <summary>战斗结束时调用。</summary>
|
||||
protected virtual void OnCombatEnd() { }
|
||||
|
||||
/// <summary>每回合开始时调用。</summary>
|
||||
protected virtual void OnRoundStart() { }
|
||||
|
||||
/// <summary>每回合结束时调用。</summary>
|
||||
protected virtual void OnRoundEnd() { }
|
||||
|
||||
/// <summary>每次行动开始时调用。</summary>
|
||||
protected virtual void OnActionStart() { }
|
||||
|
||||
/// <summary>每次行动结束时调用。</summary>
|
||||
protected virtual void OnActionEnd() { }
|
||||
|
||||
/// <summary>
|
||||
/// 卡牌销毁时的扩展清理钩子。
|
||||
/// 子类有额外资源需要释放时重写此方法,无需处理 SubscribeCombatEvent 的取消订阅。
|
||||
/// </summary>
|
||||
protected virtual void OnDispose() { }
|
||||
|
||||
public virtual void SetUpLogicComponents() { }
|
||||
|
||||
public T AddLogicComponent<T>() where T : CardLogicComponentBase, new()
|
||||
@@ -233,43 +349,66 @@ namespace Continentis.MainGame.Card
|
||||
#region Attack
|
||||
public partial class CardLogicBase
|
||||
{
|
||||
/// <summary>获取对指定目标的最终伤害值。</summary>
|
||||
public virtual int GetTargetedFinalDamage(CharacterBase target, List<string> elementalTags = null)
|
||||
/// <summary>
|
||||
/// 以当前卡牌作为来源,对目标发动攻击。
|
||||
/// 内部自动构建携带 sourceCard 的 AttackContext,确保 Buff 层能正确识别来源卡牌信息。
|
||||
/// 卡牌脚本中所有的攻击调用都应优先使用此方法,而非直接调用 user.Attack()。
|
||||
/// </summary>
|
||||
protected AttackResult AttackTarget(CharacterBase target, int damage, AttackContext ctx = null)
|
||||
{
|
||||
return GetFinalDamage(target, elementalTags, out _, out _, out _, out _);
|
||||
ctx ??= new AttackContext(card);
|
||||
if (ctx.sourceCard == null) ctx.sourceCard = card;
|
||||
return user.Attack(target, damage, ctx);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取对指定目标的最终伤害值。
|
||||
/// ctx 中的 damageKeywords 驱动 offset 和元素乘区计算,baseDamageAttributeName 指定基础伤害属性名。
|
||||
/// ctx 为 null 时回退到卡牌元素关键词和默认 "Damage" 属性(向后兼容)。
|
||||
/// </summary>
|
||||
public virtual int GetTargetedFinalDamage(CharacterBase target, AttackContext ctx = null)
|
||||
{
|
||||
return GetFinalDamage(target, ctx, out _, out _, out _, out _);
|
||||
}
|
||||
|
||||
/// <summary>获取无目标时的最终伤害值。</summary>
|
||||
public virtual int GetNoTargetFinalDamage(List<string> elementalTags = null)
|
||||
public virtual int GetNoTargetFinalDamage(AttackContext ctx = null)
|
||||
{
|
||||
return GetFinalDamage(null, elementalTags, out _, out _, out _, out _);
|
||||
return GetFinalDamage(null, ctx, out _, out _, out _, out _);
|
||||
}
|
||||
|
||||
protected virtual int GetFinalDamage(
|
||||
CharacterBase target, List<string> elementalTags,
|
||||
CharacterBase target, AttackContext ctx,
|
||||
out float baseDamageAfterOffset, out float elementalMultiplier,
|
||||
out float magicMultiplier, out float finalMultiplier)
|
||||
{
|
||||
bool haveTarget = target != null;
|
||||
elementalTags ??= card.GetElementalKeywords();
|
||||
|
||||
// 从 AttackContext 中读取伤害关键词和属性名,null 时回退到卡牌默认值
|
||||
List<string> damageKeywords = ctx?.damageKeywords ?? card.GetElementalKeywords();
|
||||
string baseDamageAttr = ctx?.baseDamageAttributeName;
|
||||
|
||||
// Physics / Magic offset 由 damageKeywords 驱动,与卡牌标记关键词无关
|
||||
int physicsOffset = 0;
|
||||
if (card.HasKeyword("Physics") || card.HasKeyword("Slash") || card.HasKeyword("Prick") || card.HasKeyword("Strike"))
|
||||
physicsOffset = user.GetAttribute("PhysicsDamageDealtOffset");
|
||||
if (damageKeywords.Contains("Physics"))
|
||||
physicsOffset = user.GetAttribute(CharacterAttributes.PhysicsDamageDealtOffset);
|
||||
|
||||
int magicOffset = 0;
|
||||
if (card.HasKeyword("Magic") || card.HasKeyword("Arcane") || card.HasKeyword("Sorcery"))
|
||||
magicOffset = user.GetAttribute("MagicDamageDealtOffset");
|
||||
if (damageKeywords.Contains("Magic"))
|
||||
magicOffset = user.GetAttribute(CharacterAttributes.MagicDamageDealtOffset);
|
||||
|
||||
// 元素乘区:遍历 damageKeywords 中属于 elementTags 的部分
|
||||
elementalMultiplier = 1f;
|
||||
foreach (string element in elementalTags)
|
||||
foreach (string keyword in damageKeywords)
|
||||
{
|
||||
float targetGain = haveTarget ? target.GetRawAttribute(element + "DamageGainMultiplier", 1f) : 1f;
|
||||
elementalMultiplier *= user.GetRawAttribute(element + "DamageDealtMultiplier", 1f) * targetGain;
|
||||
if (!MainGameManager.Instance.elementTags.Contains(keyword)) continue;
|
||||
float targetGain = haveTarget ? target.GetRawAttribute(keyword + "DamageGainMultiplier", 1f) : 1f;
|
||||
elementalMultiplier *= user.GetRawAttribute(keyword + "DamageDealtMultiplier", 1f) * targetGain;
|
||||
}
|
||||
|
||||
// 魔法乘区:由 damageKeywords 中含 Magic/Arcane/Sorcery 时触发
|
||||
magicMultiplier = 1f;
|
||||
if (card.HasKeyword("Magic") || card.HasKeyword("Arcane") || card.HasKeyword("Sorcery"))
|
||||
if (damageKeywords.Contains("Magic") || damageKeywords.Contains("Arcane") || damageKeywords.Contains("Sorcery"))
|
||||
{
|
||||
float targetGain = haveTarget ? target.GetRawAttribute("MagicDamageGainMultiplier", 1f) : 1f;
|
||||
magicMultiplier = user.GetRawAttribute("MagicDamageDealtMultiplier", 1f) * targetGain;
|
||||
@@ -278,7 +417,8 @@ namespace Continentis.MainGame.Card
|
||||
float targetFinalGain = haveTarget ? target.GetRawAttribute("FinalDamageGainMultiplier", 1f) : 1f;
|
||||
finalMultiplier = user.GetRawAttribute("FinalDamageDealtMultiplier", 1f) * targetFinalGain;
|
||||
|
||||
baseDamageAfterOffset = card.attributeSubmodule.GetCurrentAttribute("Damage") + physicsOffset + magicOffset;
|
||||
string damageAttr = string.IsNullOrEmpty(baseDamageAttr) ? "Damage" : baseDamageAttr;
|
||||
baseDamageAfterOffset = card.attributeSubmodule.GetCurrentAttribute(damageAttr) + physicsOffset + magicOffset;
|
||||
float finalDamage = baseDamageAfterOffset * elementalMultiplier * magicMultiplier * finalMultiplier;
|
||||
|
||||
return Mathf.RoundToInt(finalDamage);
|
||||
@@ -358,6 +498,17 @@ namespace Continentis.MainGame.Card
|
||||
|
||||
/// <summary>取消选中目标时触发的效果(在逻辑组件的 Untargeting 之前执行)。</summary>
|
||||
public virtual void UntargetingEffect() { }
|
||||
|
||||
/// <summary>
|
||||
/// 标记 hint shadow 在下一帧刷新,不触发文本重解析。
|
||||
/// 子类在战场状态变化时调用此方法,而非直接操作 dirtyMark。
|
||||
/// </summary>
|
||||
protected void InvalidateHint() => card.contentSubmodule.hintDirtyMark = true;
|
||||
/// 返回 null 表示不显示提示阴影;返回具体颜色则启用对应颜色的 hintShadow。
|
||||
/// 此方法在 ContentSubmodule.RefreshContent() 时自动调用,
|
||||
/// 子类可重写以实现"有可用目标时绿色/无可用目标时红色"等动态提示。
|
||||
/// </summary>
|
||||
public virtual Color? GetHintColor() => null;
|
||||
}
|
||||
|
||||
/// <summary>卡牌逻辑组件基类。</summary>
|
||||
|
||||
Reference in New Issue
Block a user