Files
Continentis/Assets/Scripts/MainGame/Card/CardLogicBase.cs

534 lines
23 KiB
C#
Raw Normal View History

2025-10-03 00:02:43 -04:00
using System;
using System.Collections.Generic;
using System.Linq;
using Continentis.MainGame.Character;
2025-10-24 09:11:22 -04:00
using Continentis.MainGame.Equipment;
2026-04-01 12:23:27 -04:00
using SoftCircuits.Collections;
2025-10-23 00:49:44 -04:00
using SLSFramework.General;
2025-10-24 09:11:22 -04:00
using SLSFramework.UModAssistance;
2025-10-03 00:02:43 -04:00
using UnityEngine;
namespace Continentis.MainGame.Card
{
public abstract partial class CardLogicBase
{
2026-03-20 11:56:50 -04:00
[Header("Reference")]
2025-11-15 12:17:34 -05:00
public CardInstance card;
public CardData cardData => card.cardData;
public CharacterBase user => card.user;
2025-10-23 00:49:44 -04:00
public HashSet<CardLogicComponentBase> logicComponents { get; private set; }
2026-03-20 11:56:50 -04:00
/// <summary>生成卡牌逻辑实例。</summary>
2025-10-24 09:11:22 -04:00
public static CardLogicBase GenerateCardLogic(CardData data)
{
2025-11-10 12:57:04 -05:00
string typeID = ModManager.GetTypeID(data.modName, "Cards", data.categoryName, data.className);
2025-10-24 09:11:22 -04:00
Type logicType = ModManager.GetType(typeID);
2026-03-20 11:56:50 -04:00
if (logicType == null)
2025-10-24 09:11:22 -04:00
{
Debug.LogError($"Card class '{typeID}' not found in assemblies.");
return null;
}
2026-03-20 11:56:50 -04:00
2025-10-24 09:11:22 -04:00
if (Activator.CreateInstance(logicType) is CardLogicBase cardLogic)
return cardLogic;
2026-03-20 11:56:50 -04:00
2025-10-24 09:11:22 -04:00
Debug.LogError($"Card class '{typeID}' not found or could not be instantiated.");
return null;
}
2026-03-20 11:56:50 -04:00
2026-04-01 12:23:27 -04:00
// 存储所有通过 SubscribeCombatEvent 注册的订阅的移除委托
private readonly List<Action> _managedUnsubscribers = new List<Action>();
2025-11-15 12:17:34 -05:00
public virtual void Initialize(CardInstance cardInstance)
2025-10-03 00:02:43 -04:00
{
2025-11-15 12:17:34 -05:00
card = cardInstance;
logicComponents = new HashSet<CardLogicComponentBase>();
card.eventSubmodule.onTargeting += TargetingEffect;
card.eventSubmodule.onUntargeting += UntargetingEffect;
2026-04-01 12:23:27 -04:00
// 自动将卡牌事件子模块的生命周期钩子接入虚方法
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));
2025-10-03 00:02:43 -04:00
}
2026-04-01 12:23:27 -04:00
/// <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() { }
2026-03-20 11:56:50 -04:00
public virtual void SetUpLogicComponents() { }
2025-10-23 00:49:44 -04:00
public T AddLogicComponent<T>() where T : CardLogicComponentBase, new()
{
if (logicComponents.Any(component => component is T))
{
2025-11-15 12:17:34 -05:00
Debug.LogWarning($"Card {card.cardData.className} already has component of type {typeof(T)}, cannot add duplicate.");
2025-10-23 00:49:44 -04:00
return null;
}
2026-03-20 11:56:50 -04:00
T component = new T();
component.Initialize(this);
logicComponents.Add(component);
return component;
2025-10-23 00:49:44 -04:00
}
2026-03-20 11:56:50 -04:00
2025-10-23 00:49:44 -04:00
public T LogicComponent<T>() where T : CardLogicComponentBase
{
return logicComponents.OfType<T>().FirstOrDefault();
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
public virtual void ApplyAttributeChangesByCard() { }
2025-11-15 12:17:34 -05:00
2026-03-20 11:56:50 -04:00
/// <summary>卡牌出牌效果,返回一个 CommandGroup 供队列执行。</summary>
public virtual CommandGroup PlayEffect(List<CharacterBase> targetList)
2025-11-15 12:17:34 -05:00
{
2026-03-20 11:56:50 -04:00
return new CommandGroup();
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
public virtual void AfterPlayEffect(List<CharacterBase> targetList) { }
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
#region Attributes
2025-11-15 12:17:34 -05:00
public partial class CardLogicBase
{
2026-03-20 11:56:50 -04:00
/// <summary>设置可变属性值。</summary>
2025-11-15 12:17:34 -05:00
public void SetVariableAttribute(string attributeName, int baseOffset, bool additive = false, int originalValue = 0)
{
2026-03-20 11:56:50 -04:00
card.SetVariableAttribute(attributeName, baseOffset, additive, originalValue);
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
/// <summary>检查卡牌是否具有某属性。</summary>
public bool HasAttribute(string attributeName) => card.HasAttribute(attributeName);
/// <summary>获取卡牌的属性值。</summary>
public int GetAttribute(string attributeName, int defaultValue = 0) => card.GetAttribute(attributeName, defaultValue);
public float GetRawAttribute(string attributeName, float defaultValue = 0) => card.GetRawAttribute(attributeName, defaultValue);
/// <summary>设置卡牌的属性值int。</summary>
public void SetAttribute(string attributeName, int value) => card.SetAttribute(attributeName, value);
/// <summary>设置卡牌的属性值float。</summary>
public void SetAttribute(string attributeName, float value) => card.SetAttribute(attributeName, value);
/// <summary>修改卡牌的属性值。</summary>
public void ModifyAttribute(string attributeName, int delta) => card.ModifyAttribute(attributeName, delta);
}
#endregion
#region Command
public partial class CardLogicBase
{
// ── 新 API闭包工厂推荐Mod 制作者优先使用) ─────────────────────
2025-11-15 12:17:34 -05:00
/// <summary>
2026-03-20 11:56:50 -04:00
/// 对 targetList 中的每个目标调用工厂 lambda生成的命令按 mainExecutionMode 组合。
2025-11-15 12:17:34 -05:00
/// </summary>
2026-03-20 11:56:50 -04:00
protected CommandGroup ForEachTarget(
List<CharacterBase> targetList,
Func<CharacterBase, CommandBase> factory,
ExecutionMode mainExecutionMode = ExecutionMode.Sequential)
2025-11-15 12:17:34 -05:00
{
2026-03-20 11:56:50 -04:00
var group = new CommandGroup(mainExecutionMode);
foreach (CharacterBase target in targetList)
{
CharacterBase captured = target;
group.AddCommand(factory(captured));
}
return group;
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
2025-11-15 12:17:34 -05:00
/// <summary>
2026-03-20 11:56:50 -04:00
/// 对 targetList 中的每个目标调用工厂 lambda生成的 CommandGroup 按 mainExecutionMode 组合。
2025-11-15 12:17:34 -05:00
/// </summary>
2026-03-20 11:56:50 -04:00
protected CommandGroup ForEachTarget(
List<CharacterBase> targetList,
Func<CharacterBase, CommandGroup> factory,
ExecutionMode mainExecutionMode = ExecutionMode.Sequential)
2025-11-15 12:17:34 -05:00
{
2026-03-20 11:56:50 -04:00
var group = new CommandGroup(mainExecutionMode);
foreach (CharacterBase target in targetList)
{
CharacterBase captured = target;
group.AddCommand(factory(captured));
}
return group;
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
// ── 旧 API模板 Clone 模式(保留供向后兼容,后续迁移完成后移除) ──
2025-11-15 12:17:34 -05:00
/// <summary>
2026-03-20 11:56:50 -04:00
/// 克隆命令模板列表,组合为单个并行 CommandGroup。
2025-11-15 12:17:34 -05:00
/// </summary>
protected CommandGroup SingleCommandGroup(params CommandBase[] commands)
{
return SingleCommandGroup(ExecutionMode.Parallel, commands);
}
/// <summary>
2026-03-20 11:56:50 -04:00
/// 克隆命令模板列表,按 executionMode 组合为单个 CommandGroup。
2025-11-15 12:17:34 -05:00
/// </summary>
protected virtual CommandGroup SingleCommandGroup(
ExecutionMode executionMode = ExecutionMode.Parallel, params CommandBase[] commands)
{
2026-03-20 11:56:50 -04:00
var group = new CommandGroup(executionMode);
2025-11-15 12:17:34 -05:00
foreach (CommandBase template in commands)
2026-03-20 11:56:50 -04:00
group.AddCommand(template.Clone());
return group;
2025-11-15 12:17:34 -05:00
}
/// <summary>
2026-03-20 11:56:50 -04:00
/// 对目标列表中的每个目标克隆命令模板并注入 Target生成嵌套 CommandGroup。
/// 新代码请改用 <see cref="ForEachTarget"/> 闭包工厂模式。
2025-11-15 12:17:34 -05:00
/// </summary>
2026-03-20 11:56:50 -04:00
[Obsolete("请改用 ForEachTarget(targetList, target => ...) 闭包工厂模式。")]
protected CommandGroup TargetListCommandGroup(
List<CharacterBase> targetList,
2025-11-15 12:17:34 -05:00
params CommandBase[] singleCommands)
{
2026-03-20 11:56:50 -04:00
return TemplateTargetGroup(targetList, ExecutionMode.Sequential, ExecutionMode.Parallel, singleCommands);
2025-11-15 12:17:34 -05:00
}
/// <summary>
2026-03-20 11:56:50 -04:00
/// 对目标列表中的每个目标克隆命令模板并注入 Target生成嵌套 CommandGroup。
/// 新代码请改用 <see cref="ForEachTarget"/> 闭包工厂模式。
2025-11-15 12:17:34 -05:00
/// </summary>
2026-03-20 11:56:50 -04:00
[Obsolete("请改用 ForEachTarget(targetList, target => ...) 闭包工厂模式。")]
2025-11-15 12:17:34 -05:00
protected virtual CommandGroup TargetListCommandGroup(
2026-03-20 11:56:50 -04:00
List<CharacterBase> targetList,
ExecutionMode mainExecutionMode = ExecutionMode.Sequential,
2025-11-15 12:17:34 -05:00
ExecutionMode singleExecutionMode = ExecutionMode.Parallel,
params CommandBase[] singleCommands)
{
2026-03-20 11:56:50 -04:00
return TemplateTargetGroup(targetList, mainExecutionMode, singleExecutionMode, singleCommands);
}
/// <summary>
/// 模板 Clone 模式的底层实现,供旧代码向后兼容。
/// 克隆每条模板命令并向所有子命令的 selfContext 注入 Target。
/// </summary>
protected virtual CommandGroup TemplateTargetGroup(
List<CharacterBase> targetList,
ExecutionMode mainExecutionMode = ExecutionMode.Sequential,
ExecutionMode singleExecutionMode = ExecutionMode.Parallel,
params CommandBase[] singleCommands)
{
var mainGroup = new CommandGroup(mainExecutionMode);
2025-11-15 12:17:34 -05:00
foreach (CharacterBase target in targetList)
{
2026-03-20 11:56:50 -04:00
var singleGroup = new CommandGroup(singleExecutionMode);
2025-11-15 12:17:34 -05:00
foreach (CommandBase template in singleCommands)
2025-10-03 00:02:43 -04:00
{
2025-11-15 12:17:34 -05:00
CommandBase clone = template.Clone();
2026-03-20 11:56:50 -04:00
// 收集所有子命令(含嵌套组内的命令)并注入 Target
var allCommands = clone is CommandGroup group
? group.GetAllCommands(true)
: new List<CommandBase> { clone };
2025-11-15 12:17:34 -05:00
foreach (CommandBase cmd in allCommands)
2026-03-20 11:56:50 -04:00
cmd.selfContext.Set(CommandContextKeys.Target, target);
2025-11-15 12:17:34 -05:00
singleGroup.AddCommand(clone);
2025-10-03 00:02:43 -04:00
}
2025-11-15 12:17:34 -05:00
mainGroup.AddCommand(singleGroup);
2025-10-03 00:02:43 -04:00
}
2025-11-15 12:17:34 -05:00
return mainGroup;
}
}
#endregion
2026-03-20 11:56:50 -04:00
2025-11-15 12:17:34 -05:00
#region Attack
public partial class CardLogicBase
{
2026-04-01 12:23:27 -04:00
/// <summary>
/// 以当前卡牌作为来源,对目标发动攻击。
/// 内部自动构建携带 sourceCard 的 AttackContext确保 Buff 层能正确识别来源卡牌信息。
/// 卡牌脚本中所有的攻击调用都应优先使用此方法,而非直接调用 user.Attack()。
/// </summary>
protected AttackResult AttackTarget(CharacterBase target, int damage, AttackContext ctx = null)
2025-11-15 12:17:34 -05:00
{
2026-04-01 12:23:27 -04:00
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 _);
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
/// <summary>获取无目标时的最终伤害值。</summary>
2026-04-01 12:23:27 -04:00
public virtual int GetNoTargetFinalDamage(AttackContext ctx = null)
2025-11-15 12:17:34 -05:00
{
2026-04-01 12:23:27 -04:00
return GetFinalDamage(null, ctx, out _, out _, out _, out _);
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
protected virtual int GetFinalDamage(
2026-04-01 12:23:27 -04:00
CharacterBase target, AttackContext ctx,
2026-03-20 11:56:50 -04:00
out float baseDamageAfterOffset, out float elementalMultiplier,
2025-11-15 12:17:34 -05:00
out float magicMultiplier, out float finalMultiplier)
{
bool haveTarget = target != null;
2026-04-01 12:23:27 -04:00
// 从 AttackContext 中读取伤害关键词和属性名null 时回退到卡牌默认值
List<string> damageKeywords = ctx?.damageKeywords ?? card.GetElementalKeywords();
string baseDamageAttr = ctx?.baseDamageAttributeName;
// Physics / Magic offset 由 damageKeywords 驱动,与卡牌标记关键词无关
2025-11-15 12:17:34 -05:00
int physicsOffset = 0;
2026-04-01 12:23:27 -04:00
if (damageKeywords.Contains("Physics"))
physicsOffset = user.GetAttribute(CharacterAttributes.PhysicsDamageDealtOffset);
2025-10-23 00:49:44 -04:00
2025-11-15 12:17:34 -05:00
int magicOffset = 0;
2026-04-01 12:23:27 -04:00
if (damageKeywords.Contains("Magic"))
magicOffset = user.GetAttribute(CharacterAttributes.MagicDamageDealtOffset);
2025-11-15 12:17:34 -05:00
2026-04-01 12:23:27 -04:00
// 元素乘区:遍历 damageKeywords 中属于 elementTags 的部分
2026-03-20 11:56:50 -04:00
elementalMultiplier = 1f;
2026-04-01 12:23:27 -04:00
foreach (string keyword in damageKeywords)
2025-11-15 12:17:34 -05:00
{
2026-04-01 12:23:27 -04:00
if (!MainGameManager.Instance.elementTags.Contains(keyword)) continue;
float targetGain = haveTarget ? target.GetRawAttribute(keyword + "DamageGainMultiplier", 1f) : 1f;
elementalMultiplier *= user.GetRawAttribute(keyword + "DamageDealtMultiplier", 1f) * targetGain;
2025-11-15 12:17:34 -05:00
}
2026-04-01 12:23:27 -04:00
// 魔法乘区:由 damageKeywords 中含 Magic/Arcane/Sorcery 时触发
2026-03-20 11:56:50 -04:00
magicMultiplier = 1f;
2026-04-01 12:23:27 -04:00
if (damageKeywords.Contains("Magic") || damageKeywords.Contains("Arcane") || damageKeywords.Contains("Sorcery"))
2025-11-15 12:17:34 -05:00
{
2026-03-20 11:56:50 -04:00
float targetGain = haveTarget ? target.GetRawAttribute("MagicDamageGainMultiplier", 1f) : 1f;
magicMultiplier = user.GetRawAttribute("MagicDamageDealtMultiplier", 1f) * targetGain;
2025-11-15 12:17:34 -05:00
}
2026-03-20 11:56:50 -04:00
float targetFinalGain = haveTarget ? target.GetRawAttribute("FinalDamageGainMultiplier", 1f) : 1f;
finalMultiplier = user.GetRawAttribute("FinalDamageDealtMultiplier", 1f) * targetFinalGain;
2026-04-01 12:23:27 -04:00
string damageAttr = string.IsNullOrEmpty(baseDamageAttr) ? "Damage" : baseDamageAttr;
baseDamageAfterOffset = card.attributeSubmodule.GetCurrentAttribute(damageAttr) + physicsOffset + magicOffset;
2025-11-15 12:17:34 -05:00
float finalDamage = baseDamageAfterOffset * elementalMultiplier * magicMultiplier * finalMultiplier;
return Mathf.RoundToInt(finalDamage);
}
}
#endregion
#region Buffs
public partial class CardLogicBase
{
2026-03-20 11:56:50 -04:00
/// <summary>创建一个角色战斗 Buff 实例(通过 ModManager 类型注册)。</summary>
public T CreateCharacterBuff<T>(params object[] parameters) where T : CharacterCombatBuffBase
2025-11-15 12:17:34 -05:00
{
string buffTypeID = ModManager.GetTypeID(typeof(T));
if (string.IsNullOrEmpty(buffTypeID))
{
Debug.LogError($"Failed to get buff name for type {typeof(T).FullName}");
return null;
2025-10-03 00:02:43 -04:00
}
2025-11-15 12:17:34 -05:00
return ModManager.CreateInstance<T>(buffTypeID, parameters);
}
2026-03-20 11:56:50 -04:00
public T CreateCharacterBuff<T>(string buffTypeID, params object[] parameters) where T : CharacterCombatBuffBase
2025-11-15 12:17:34 -05:00
{
if (string.IsNullOrEmpty(buffTypeID))
{
Debug.LogError($"Failed to get buff name for type {typeof(T).FullName}");
return null;
}
return ModManager.CreateInstance<T>(buffTypeID, parameters);
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
/// <summary>创建一个卡牌战斗 Buff 实例(通过 ModManager 类型注册)。</summary>
public T CreateCardBuff<T>(params object[] parameters) where T : CardBuffBase
2025-11-15 12:17:34 -05:00
{
string buffTypeID = ModManager.GetTypeID(typeof(T));
if (string.IsNullOrEmpty(buffTypeID))
{
Debug.LogError($"Failed to get buff name for type {typeof(T).FullName}");
return null;
}
return ModManager.CreateInstance<T>(buffTypeID, parameters);
}
2026-03-20 11:56:50 -04:00
public T CreateCardBuff<T>(string buffTypeID, params object[] parameters) where T : CardBuffBase
2025-11-15 12:17:34 -05:00
{
if (string.IsNullOrEmpty(buffTypeID))
{
Debug.LogError($"Failed to get buff name for type {typeof(T).FullName}");
return null;
}
return ModManager.CreateInstance<T>(buffTypeID, parameters);
}
}
#endregion
2026-03-20 11:56:50 -04:00
2025-11-15 12:17:34 -05:00
public abstract partial class CardLogicBase
{
2026-03-20 11:56:50 -04:00
/// <summary>获取衍生卡牌数据(按索引)。</summary>
2025-10-27 07:04:34 -04:00
public CardData GetDerivativeCardData(int index)
{
return ModManager.GetData<CardData>(cardData.derivativeCardDataRefs[index]);
}
2026-03-20 11:56:50 -04:00
/// <summary>获取衍生卡牌数据(按名称)。</summary>
2025-10-30 12:07:59 -04:00
public CardData GetDerivativeCardData(string dataName)
{
if (cardData.derivativeCardDataRefs.Contains(dataName))
return ModManager.GetData<CardData>(dataName);
Debug.LogError($"Card {cardData.className} does not contain derivative card data '{dataName}'.");
return null;
}
2025-11-15 12:17:34 -05:00
2026-03-20 11:56:50 -04:00
/// <summary>选中目标时触发的效果(在逻辑组件的 Targeting 之前执行)。</summary>
public virtual void TargetingEffect(CharacterBase target) { }
/// <summary>取消选中目标时触发的效果(在逻辑组件的 Untargeting 之前执行)。</summary>
public virtual void UntargetingEffect() { }
2026-04-01 12:23:27 -04:00
/// <summary>
/// 标记 hint shadow 在下一帧刷新,不触发文本重解析。
/// 子类在战场状态变化时调用此方法,而非直接操作 dirtyMark。
/// </summary>
protected void InvalidateHint() => card.contentSubmodule.hintDirtyMark = true;
/// 返回 null 表示不显示提示阴影;返回具体颜色则启用对应颜色的 hintShadow。
/// 此方法在 ContentSubmodule.RefreshContent() 时自动调用,
/// 子类可重写以实现"有可用目标时绿色/无可用目标时红色"等动态提示。
/// </summary>
public virtual Color? GetHintColor() => null;
2025-10-03 00:02:43 -04:00
}
2025-10-23 00:49:44 -04:00
2026-03-20 11:56:50 -04:00
/// <summary>卡牌逻辑组件基类。</summary>
2025-10-23 00:49:44 -04:00
public abstract class CardLogicComponentBase
2025-10-03 00:02:43 -04:00
{
2025-11-15 12:17:34 -05:00
protected CardLogicBase mainLogic;
protected CardInstance card => mainLogic.card;
2025-10-23 00:49:44 -04:00
protected CharacterBase user => card.user;
2025-11-15 12:17:34 -05:00
protected CombatTeam usingTeam => card.usingTeam;
2026-03-20 11:56:50 -04:00
2025-10-23 00:49:44 -04:00
public virtual void Initialize(CardLogicBase card)
{
2025-11-15 12:17:34 -05:00
this.mainLogic = card;
this.card.eventSubmodule.onTargeting += TargetingEffect;
this.card.eventSubmodule.onUntargeting += UntargetingEffect;
2025-10-23 00:49:44 -04:00
}
2026-03-20 11:56:50 -04:00
protected virtual void TargetingEffect(CharacterBase target) { }
2025-10-23 00:49:44 -04:00
2026-03-20 11:56:50 -04:00
protected virtual void UntargetingEffect() { }
2025-10-03 00:02:43 -04:00
}
2026-03-20 11:56:50 -04:00
}