Files
Continentis/Assets/Scripts/MainGame/Character/CharacterMainFunctions.cs
SoulliesOfficial c3b1561375 更新
2026-04-01 12:23:27 -04:00

532 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<CardData>(cardDataID), this, initialPile);
}
foreach (EquipmentBase equipment in equipmentSubmodule.currentEquipments)
{
foreach (string cardDataID in equipment.equipmentData.belongingCardDataRefs)
{
CardInstance.GenerateCardInstance(ModManager.GetData<CardData>(cardDataID), this, initialPile);
}
}
}
}
public partial class CharacterBase
{
/// <summary>
/// 检查是否有足够的体力
/// </summary>
public bool CheckEnoughStamina(int staminaCost)
{
return GetAttribute("Stamina") >= staminaCost;
}
/// <summary>
/// 消耗体力
/// </summary>
public void ModifyStamina(int staminaValue)
{
ModifyAttribute("Stamina", staminaValue);
ClampAttribute("Stamina", 0, GetAttribute("MaximumStamina"));
if (this is PlayerHero)
{
CombatUIManager.Instance.combatMainPage.combatResourcesDisplayer.UpdateIcons();
}
}
/// <summary>
/// 检查是否有足够的魔法
/// </summary>
public bool CheckEnoughMana(int manaCost)
{
return GetAttribute("Mana") >= manaCost;
}
/// <summary>
/// 消耗魔法
/// </summary>
public void ModifyMana(int manaValue)
{
ModifyAttribute("Mana", manaValue);
ClampAttribute("Mana", 0, GetAttribute("MaximumMana"));
if (this is PlayerHero)
{
CombatUIManager.Instance.combatMainPage.combatResourcesDisplayer.UpdateIcons();
}
}
}
public partial class CharacterBase
{
/// <summary>
/// 攻击目标(新版本,使用 AttackContext 携带标签与来源信息)。
/// </summary>
/// <param name="target">攻击目标</param>
/// <param name="startDamage">初始伤害</param>
/// <param name="context">攻击上下文(包含来源卡牌、标签等);传 null 等价于默认上下文</param>
/// <returns>攻击结果</returns>
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;
}
/// <summary>
/// 攻击目标(兼容旧版 API内部转换为 AttackContext 调用)。
/// 新代码请优先使用 Attack(target, damage, AttackContext) 重载。
/// </summary>
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);
}
/// <summary>
/// 检查闪避(闪避失败后会清空闪避值)
/// </summary>
/// <param name="damage">即将受到的伤害</param>
/// <returns>是否闪避成功</returns>
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;
}
/// <summary>
/// 检查格挡(并且扣除格挡值)
/// </summary>
/// <param name="damage">即将受到的伤害</param>
/// <returns>格挡之后的剩余伤害</returns>
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;
}
/// <summary>
/// 检查临时生命(并且扣除临时生命值)
/// </summary>
/// <param name="damage">即将受到的伤害</param>
/// <returns>临时生命吸收后的剩余伤害</returns>
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
{
/// <summary>
/// 添加格挡(格挡每回合结束后会清空)
/// </summary>
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();
}
/// <summary>
/// 添加闪避(闪避在回合结束后或被击中后清空)
/// </summary>
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();
}
/// <summary>
/// 添加临时生命(不会自动清空)
/// </summary>
public void AddShield(int shield, CharacterBase target = null)
{
int baseShieldAfterOffset = shield + 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<IntentionBase> 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<CardInstance> availableCards = deckSubmodule.PoolPile;
List<IntendedCard> intended = new List<IntendedCard>();
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<CardInstance> forced = new List<CardInstance>();
List<CardInstance> normal = new List<CardInstance>();
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<CharacterBase> 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<CardInstance> 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<CharacterBase> 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<CharacterBase> targets)
{
card.DetectTargetsValidity(out List<CharacterBase> valid, out _, out _);
if (valid.Count == 0)
{
targets = null;
return false; // 无有效目标或无法使用则跳过
}
targets = card.SetRandomTargets(valid);
return true;
}
}
}