- ThreatUtility: 공통 위협 생성 유틸리티 (OverlapSphere 기반 반경 내 적 탐색) - OverlapSphereNonAlloc 버퍼 32→256 확장으로 씬 콜라이더 누락 수정 - 위협 배율 체인: SkillGem × ThreatController × Passive - HealEffect: flatThreat + (actualHeal × threatPercent) 공식 적용 - ShieldEffect: flatThreat + (actualShield × threatPercent) 공식 적용 - AbnormalityEffect: flatThreat 고정 위협 생성 - EditMode 유닛 테스트 9/9 통과 (SupportThreatTests) - 테스트 씬 UI 레이아웃 수정 사항 포함
92 lines
3.6 KiB
C#
92 lines
3.6 KiB
C#
using UnityEngine;
|
||
|
||
using Colosseum.Abnormalities;
|
||
using Colosseum.Enemy;
|
||
using Colosseum.Player;
|
||
using Colosseum.Stats;
|
||
using Colosseum.Combat;
|
||
using Colosseum.Passives;
|
||
using Colosseum.Skills;
|
||
|
||
namespace Colosseum.Skills.Effects
|
||
{
|
||
/// <summary>
|
||
/// 보호막 효과입니다.
|
||
/// 대상에게 일정 시간 동안 피해를 흡수하는 보호막을 부여하며,
|
||
/// 보호막 수치에 비례하여 적에게 위협을 생성할 수 있습니다.
|
||
/// </summary>
|
||
[CreateAssetMenu(fileName = "ShieldEffect", menuName = "Colosseum/Skills/Effects/Shield")]
|
||
public class ShieldEffect : SkillEffect
|
||
{
|
||
[Header("Shield")]
|
||
[Tooltip("기본 보호막 수치")]
|
||
[Min(0f)] [SerializeField] private float baseShield = 100f;
|
||
|
||
[Tooltip("회복력 계수")]
|
||
[Min(0f)] [SerializeField] private float shieldScaling = 0.5f;
|
||
|
||
[Tooltip("보호막 지속 시간")]
|
||
[Min(0f)] [SerializeField] private float duration = 5f;
|
||
|
||
[Header("Abnormality")]
|
||
[Tooltip("보호막 활성 여부를 나타내는 이상상태 데이터")]
|
||
[SerializeField] private AbnormalityData shieldStateAbnormality;
|
||
|
||
[Header("Threat")]
|
||
[Tooltip("보호막 사용 시 항상 생성할 고정 위협 수치")]
|
||
[Min(0f)] [SerializeField] private float flatThreatAmount = 5f;
|
||
[Tooltip("실제 보호막 수치에 대한 위협 비율 (1.0 = 100%)")]
|
||
[Range(0f, 10f)] [SerializeField] private float threatPercentOfShield = 0.5f;
|
||
[Tooltip("위협을 생성할 반경 (시전자 기준)")]
|
||
[Min(0f)] [SerializeField] private float threatRadius = 50f;
|
||
|
||
protected override void ApplyEffect(GameObject caster, GameObject target)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
float totalShield = CalculateShield(caster);
|
||
float actualShield = 0f;
|
||
|
||
PlayerNetworkController playerNetworkController = target.GetComponent<PlayerNetworkController>();
|
||
if (playerNetworkController != null)
|
||
{
|
||
actualShield = playerNetworkController.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
|
||
CombatBalanceTracker.RecordShield(caster, target, actualShield);
|
||
}
|
||
else
|
||
{
|
||
EnemyBase enemyBase = target.GetComponent<EnemyBase>();
|
||
if (enemyBase != null)
|
||
{
|
||
actualShield = enemyBase.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
|
||
CombatBalanceTracker.RecordShield(caster, target, actualShield);
|
||
}
|
||
}
|
||
|
||
// 위협 생성: 고정 수치 + (실제 보호막량 × 비율)
|
||
float threat = flatThreatAmount + (actualShield * threatPercentOfShield);
|
||
if (threat > 0f)
|
||
{
|
||
ThreatUtility.GenerateThreatOnNearbyEnemies(caster, threat, threatRadius);
|
||
}
|
||
}
|
||
|
||
private float CalculateShield(GameObject caster)
|
||
{
|
||
CharacterStats stats = caster != null ? caster.GetComponent<CharacterStats>() : null;
|
||
if (stats == null)
|
||
{
|
||
return baseShield *
|
||
SkillRuntimeModifierUtility.GetShieldMultiplier(caster) *
|
||
PassiveRuntimeModifierUtility.GetShieldDoneMultiplier(caster);
|
||
}
|
||
|
||
float resolvedShield = baseShield + (stats.HealPower * shieldScaling);
|
||
return resolvedShield *
|
||
SkillRuntimeModifierUtility.GetShieldMultiplier(caster) *
|
||
PassiveRuntimeModifierUtility.GetShieldDoneMultiplier(caster);
|
||
}
|
||
}
|
||
}
|