using System.Collections.Generic; using System.Linq; using Continentis.MainGame.Card; using Continentis.MainGame.Equipment; using SLSFramework.General; using SLSFramework.UModAssistance; using UnityEngine; namespace Continentis.MainGame.Character { public partial class CharacterBase { public virtual void InitializeCards() { string initialPile = this is PlayerHero ? "Draw" : "Pool"; foreach (string cardDataID in data.initialDeckRef) { CardInstance.GenerateCardInstance(ModManager.GetData(cardDataID), this, initialPile); } foreach (EquipmentBase equipment in equipmentSubmodule.currentEquipments) { foreach (string cardDataID in equipment.equipmentData.belongingCardDataRefs) { CardInstance.GenerateCardInstance(ModManager.GetData(cardDataID), this, initialPile); } } } } public partial class CharacterBase { /// /// 检查是否有足够的体力 /// public bool CheckEnoughStamina(int staminaCost) { return GetAttribute("Stamina") >= staminaCost; } /// /// 修改体力 /// public void ModifyStamina(int staminaValue, bool clampMax = false) { ModifyAttribute("Stamina", staminaValue); ClampAttribute("Stamina", 0, clampMax ? GetAttribute("MaximumStamina") : int.MaxValue); if (this is PlayerHero) { CombatUIManager.Instance.combatMainPage.combatResourcesDisplayer.UpdateIcons(); } } /// /// 检查是否有足够的魔法 /// public bool CheckEnoughMana(int manaCost) { return GetAttribute("Mana") >= manaCost; } /// /// 消耗魔法 /// public void ModifyMana(int manaValue, bool clampMax = false) { ModifyAttribute("Mana", manaValue); ClampAttribute("Mana", 0, clampMax ? GetAttribute("MaximumMana") : int.MaxValue); if (this is PlayerHero) { CombatUIManager.Instance.combatMainPage.combatResourcesDisplayer.UpdateIcons(); } } } public partial class CharacterBase { /// /// 攻击目标(新版本,使用 AttackContext 携带标签与来源信息)。 /// /// 攻击目标 /// 初始伤害 /// 攻击上下文(包含来源卡牌、标签等);传 null 等价于默认上下文 /// 攻击结果 public AttackResult Attack(CharacterBase target, int startDamage, AttackContext context) { context ??= new AttackContext(); bool isSilent = context.HasTag(AttackTags.Silent); bool isHpRemoval = context.HasTag(AttackTags.HpRemoval); bool isReactive = context.HasTag(AttackTags.Reactive); bool ignoreDodge = context.HasAnyTag(AttackTags.GuaranteedHit, AttackTags.HpRemoval); bool ignoreBlock = context.HasAnyTag(AttackTags.Penetrating, AttackTags.HpRemoval); bool ignoreShield = context.HasAnyTag(AttackTags.HpRemoval); // 静默和生命移除均不触发 onStartAttack if (!isSilent && !isHpRemoval) { eventSubmodule.onStartAttack.Invoke(target); } // 生命移除:直接扣血,跳过所有防御和事件 if (isHpRemoval) { target.HealthRemoval(startDamage, context); target.characterView.hudContainer.enablingHUDs["MainAttributesBar"].UpdateHud(); return new AttackResult(this, target, startDamage, context, false, 0, 0, startDamage); } // 闪避检测 int modifiedStartDamageForDodge = Mathf.RoundToInt(startDamage * GetRawAttribute("DodgeCheckStartDamageMultiplier", 1)); bool dodged = !ignoreDodge && target.CheckDodge(modifiedStartDamageForDodge); int hurt = 0; int blocked = 0; int shielded = 0; if (!dodged) { int remainingDamageAfterBlock = ignoreBlock ? startDamage : target.CheckBlock(startDamage); if (remainingDamageAfterBlock > 0) { blocked = startDamage - remainingDamageAfterBlock; int remainingDamageAfterShield = ignoreShield ? remainingDamageAfterBlock : target.CheckShield(remainingDamageAfterBlock); if (remainingDamageAfterShield > 0) { shielded = remainingDamageAfterBlock - remainingDamageAfterShield; hurt = remainingDamageAfterShield; target.HealthRemoval(remainingDamageAfterShield, context); } } } target.characterView.hudContainer.enablingHUDs["MainAttributesBar"].UpdateHud(); AttackResult attackResult = new AttackResult(this, target, startDamage, context, dodged, blocked, shielded, hurt); if (!isSilent) { // 角色 EventSubmodule 级别事件(始终触发,用于日志等) eventSubmodule.onFinishAttack.Invoke(target, attackResult); target.eventSubmodule.onGetAttacked.Invoke(this, attackResult); // Buff 层事件:响应式攻击不触发,防止无限递归 if (!isReactive) { combatBuffSubmodule.buffList.For(buff => { buff.eventSubmodule?.onDealAttack.Invoke(attackResult); }); target.combatBuffSubmodule.buffList.For(buff => { buff.eventSubmodule?.onGetAttacked.Invoke(attackResult); }); } } return attackResult; } /// /// 攻击目标(兼容旧版 API,内部转换为 AttackContext 调用)。 /// 新代码请优先使用 Attack(target, damage, AttackContext) 重载。 /// public AttackResult Attack(CharacterBase target, int startDamage, CardInstance attackCard = null, bool triggerAttackEvent = true, bool ignoreDodge = false, bool ignoreBlock = false, bool ignoreShield = false) { var context = new AttackContext(attackCard); if (!triggerAttackEvent) context.WithTag(AttackTags.Silent); if (ignoreDodge) context.WithTag(AttackTags.GuaranteedHit); if (ignoreBlock) context.WithTag(AttackTags.Penetrating); return Attack(target, startDamage, context); } /// /// 检查闪避(闪避失败后会清空闪避值) /// /// 即将受到的伤害 /// 是否闪避成功 public bool CheckDodge(int damage) { int dodge = attributeSubmodule.GetGeneralAttribute("Dodge"); if (dodge > 0) { bool success = damage <= dodge; if (success) { MainGameManager.Instance.basePrefabs.GenerateInfoText("Dodged!", characterView); return true; } else { attributeSubmodule.generalAttributeGroup.current["Dodge"] = 0; return false; } } return false; } /// /// 检查格挡(并且扣除格挡值) /// /// 即将受到的伤害 /// 格挡之后的剩余伤害 public int CheckBlock(int damage) { int block = attributeSubmodule.GetGeneralAttribute("Block"); if (block > 0) { bool success = damage <= block; int remainingDamage = 0; int blockedDamage = block; if (!success) { attributeSubmodule.generalAttributeGroup.current["Block"] = 0; remainingDamage = damage - block; } else { attributeSubmodule.generalAttributeGroup.current["Block"] = block - damage; blockedDamage = damage; } MainGameManager.Instance.basePrefabs.GenerateBlockedText(blockedDamage, characterView); return remainingDamage; } return damage; } /// /// 检查临时生命(并且扣除临时生命值) /// /// 即将受到的伤害 /// 临时生命吸收后的剩余伤害 public int CheckShield(int damage) { int shield = attributeSubmodule.GetGeneralAttribute("TemporaryHealth"); if (shield > 0) { bool success = damage <= shield; int remainingDamage = 0; int blockedDamage = shield; if (!success) { attributeSubmodule.generalAttributeGroup.current["TemporaryHealth"] = 0; remainingDamage = damage - shield; } else { attributeSubmodule.generalAttributeGroup.current["TemporaryHealth"] = shield - damage; blockedDamage = damage; } MainGameManager.Instance.basePrefabs.GenerateBlockedText(blockedDamage, characterView); return remainingDamage; } return damage; } public void HealthRemoval(int damage, AttackContext context = null) { int healthBefore = GetAttribute("Health"); ModifyAttribute("Health", -damage); int healthAfter = GetAttribute("Health"); int maxHealth = GetAttribute("MaximumHealth"); Color dmgTextColor = Color.white; if (context is { damageKeywords: { Count: > 0 } }) { foreach (string elementTag in MainGameManager.Instance.elementTags) { if (context.damageKeywords.Contains(elementTag)) { switch (elementTag) { case "Fire": dmgTextColor = Color.red; break; case "Ice": dmgTextColor = Color.cyan; break; case "Wind": dmgTextColor = Color.lightGreen; break; case "Earth": dmgTextColor = Color.darkGoldenRod; break; case "Storm": dmgTextColor = Color.magenta; break; case "Light": dmgTextColor = Color.yellowNice; break; case "Darkness": dmgTextColor = Color.rebeccaPurple; break; default: dmgTextColor = Color.white; break; } } } } MainGameManager.Instance.basePrefabs.GenerateHurtText(damage, characterView, dmgTextColor); // 血量百分比阈值检查:穿越整十档时通知 LogicBase(如 Boss 阶段切换) if (maxHealth > 0 && logicBase != null) { float percentBefore = (float)healthBefore / maxHealth; float percentAfter = (float)healthAfter / maxHealth; // 找出所有被穿越的整十档(从高到低依次触发) for (int threshold = 9; threshold >= 0; threshold--) { float t = threshold * 0.1f; if (percentBefore > t && percentAfter <= t) { logicBase.OnHealthThreshold(t); } } } if (GetAttribute("Health") <= 0) { Die(); } } public void Heal(int heal) { if (heal <= 0) return; ModifyAttribute("Health", heal); ClampAttribute("Health", 0, GetAttribute("MaximumHealth")); MainGameManager.Instance.basePrefabs.GenerateHealText(heal, characterView); characterView.hudContainer.UpdateAllHUD(); } } public partial class CharacterBase { /// /// 添加格挡(格挡每回合结束后会清空) /// public void AddBlock(int baseBlock, bool applyOffsetAndModifier = true, CharacterBase target = null) { target ??= this; if (!applyOffsetAndModifier) { target.ModifyAttribute("Block", baseBlock); } else { int baseBlockAfterOffset = baseBlock + GetAttribute("BlockGainOffset"); int finalBlock = Mathf.RoundToInt(baseBlockAfterOffset * GetRawAttribute("BlockGainMultiplier", 1)); target.ModifyAttribute("Block", finalBlock); } target.characterView.hudContainer.UpdateAllHUD(); } /// /// 添加闪避(闪避在回合结束后或被击中后清空) /// public void AddDodge(int dodge, CharacterBase target = null) { int baseDodgeAfterOffset = dodge + GetAttribute("DodgeGainOffset"); int finalDodge = Mathf.RoundToInt(baseDodgeAfterOffset * GetRawAttribute("DodgeGainMultiplier", 1)); target ??= this; target.ModifyAttribute("Dodge", finalDodge); target.characterView.hudContainer.UpdateAllHUD(); } /// /// 添加临时生命(不会自动清空) /// public void AddTemporaryHealth(int tempHealth, CharacterBase target = null) { int baseShieldAfterOffset = tempHealth + GetAttribute("TemporaryHealthGainOffset"); int finalShield = Mathf.RoundToInt(baseShieldAfterOffset * GetRawAttribute("TemporaryHealthGainMultiplier", 1)); target ??= this; target.ModifyAttribute("TemporaryHealth", finalShield); target.characterView.hudContainer.UpdateAllHUD(); } } public partial class CharacterBase { public virtual void RegisterIntention(params IntentionBase[] intentions) { intentionSubmodule.allIntentions.AddRange(intentions); } public virtual void IntentionBrain() { List availableIntentions = intentionSubmodule.allIntentions.Where(intention => intention.Condition()).ToList(); availableIntentions.Sort(); intentionSubmodule.currentIntention = availableIntentions.FirstOrDefault() ?? new IntentionBase(intentionSubmodule); intentionSubmodule.currentIntention.RefreshCardWeights(); intentionSubmodule.currentIntention.RefreshTargets(); } public virtual void GetIntendedCards() { IntentionBase currentIntention = intentionSubmodule.currentIntention; List availableCards = deckSubmodule.PoolPile; List intended = new List(); int predictedStamina = Mathf.Min(GetAttribute("MaximumStamina"), GetAttribute("Stamina") + GetAttribute("StaminaRecoverPerAction")); int remainingStamina = predictedStamina - currentIntention.guaranteedStamina; int predictedMana = Mathf.Min(GetAttribute("MaximumMana"), GetAttribute("Mana") + GetAttribute("ManaRecoverPerAction")); int remainingMana = predictedMana - currentIntention.guaranteedMana; List forced = new List(); List normal = new List(); foreach (CardInstance card in availableCards) { if (card.weightSubmodule.forceUse) { forced.Add(card); } else { normal.Add(card); } } intentionSubmodule.intendedCards.Clear(); //(characterView.hudContainer.enablingHUDs["Intention"] as Intention)?.Clear(); // 1. 优先处理强制选择卡牌 foreach (CardInstance card in forced) { if (currentIntention.maxCardCount > 0 && intended.Count >= currentIntention.maxCardCount) { break; // 已达数量上限 } if (CanAfford(card, remainingStamina, remainingMana)) { if(!CheckAvailabilityAndSetTargets(card, out List targets)) { continue; // 无有效目标或无法使用则跳过 } intended.Add(new IntendedCard(card, targets)); remainingStamina -= card.GetAttribute(CardAttributes.StaminaCost); remainingMana -= card.GetAttribute(CardAttributes.ManaCost); } // 行动力不足则跳过该卡 } // 2. 在剩余普通卡牌中基于权重随机选取 while (intended.Count < currentIntention.maxCardCount) { // 筛选出当前资源下还能出的牌 List affordableCards = normal.FindAll(card => CanAfford(card, remainingStamina, remainingMana)); if (affordableCards.Count == 0) { break; } float totalWeight = affordableCards.Sum(card => card.weightSubmodule.currentWeight); if (totalWeight <= 0f) break; float r = Random.value * totalWeight; float accum = 0f; CardInstance chosen = null; foreach (CardInstance card in affordableCards) { accum += card.weightSubmodule.currentWeight; if (r <= accum) { chosen = card; break; } } if (chosen != null) { if (!CheckAvailabilityAndSetTargets(chosen, out List targets)) { normal.Remove(chosen); continue; // 无有效目标或无法使用则跳过 } intended.Add(new IntendedCard(chosen, targets)); normal.Remove(chosen); remainingStamina -= chosen.GetAttribute(CardAttributes.StaminaCost); remainingMana -= chosen.GetAttribute(CardAttributes.ManaCost); } } intentionSubmodule.intendedCards.AddRange(intended); } bool CanAfford(CardInstance card, int stamina, int mana) { return card.GetAttribute(CardAttributes.StaminaCost) <= stamina && card.GetAttribute(CardAttributes.ManaCost) <= mana; } public bool CheckAvailabilityAndSetTargets(CardInstance card, out List targets) { card.DetectTargetsValidity(out List valid, out _, out _); if (valid.Count == 0) { targets = null; return false; // 无有效目标或无法使用则跳过 } targets = card.SetRandomTargets(valid); return true; } } }