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