244 lines
11 KiB
C#
244 lines
11 KiB
C#
using System.Collections.Generic;
|
||
using Cielonos.MainGame.AttackArea;
|
||
using Cielonos.MainGame.Buffs.Character;
|
||
using Lean.Pool;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using SLSUtilities.General;
|
||
using SLSUtilities.WwiseAssistance;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame.Characters
|
||
{
|
||
public partial class C1_Axe : Enemy
|
||
{
|
||
protected override void InitializeSubmodules()
|
||
{
|
||
base.InitializeSubmodules();
|
||
|
||
// Boss 怯战惩罚机制:5秒未受玩家攻击且未被玩家格挡,每秒快速恢复20点能量
|
||
new CowardicePenalty().Apply(this);
|
||
|
||
eventSm.onGetBreakthrough.Add("GetBreakthrough_Weak", new PrioritizedAction<AttackAreaBase>(attackArea =>
|
||
{
|
||
new Weak(5).Apply(this);
|
||
GetHit(Breakthrough.Type.Forced, out _, DisruptionType.ForcedExternal, funcAnimName: "GetBreakthrough");
|
||
movementSc.impulseSm.ApplyKnockback(-transform.forward, 10f, 1f, null, true);
|
||
}));
|
||
}
|
||
}
|
||
|
||
public partial class C1_Axe
|
||
{
|
||
private void FAPF_GenerateSlash(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringString p = rtFuncAnim.GetParams<CustomFunction.PC_StringString>();
|
||
GenerateSlash(p.str0, attackData[p.str1]);
|
||
}
|
||
|
||
private void FAPF_GenerateSmash(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringString p = rtFuncAnim.GetParams<CustomFunction.PC_StringString>();
|
||
GenerateSmash(p.str0, attackData[p.str1]);
|
||
}
|
||
|
||
private void FAPF_GenerateTripleSmashes(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringString p = rtFuncAnim.GetParams<CustomFunction.PC_StringString>();
|
||
float timeInterval = 0.2f;
|
||
Vector3 fwd = transform.forward;
|
||
for (int i = 0; i < 3; i++)
|
||
{
|
||
float delay = i * timeInterval;
|
||
int index = i;
|
||
selfTimeSm.AddLocalTimer(delay, () => GenerateSmash(p.str0, attackData[p.str1], fwd * (index * 5f), fwd));
|
||
}
|
||
}
|
||
|
||
private void FAPF_GenerateWhirlWind(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringStringFloat p = rtFuncAnim.GetParams<CustomFunction.PC_StringStringFloat>();
|
||
GenerateWhirlWind(p.str0, attackData[p.str1], p.float0);
|
||
}
|
||
|
||
private void FAPF_GenerateMeteorStorm(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringStringFloat p = rtFuncAnim.GetParams<CustomFunction.PC_StringStringFloat>();
|
||
string vfxName = p.str0;
|
||
string attackKey = p.str1;
|
||
float radius = p.float0;
|
||
AttackUnit attackUnit = attackData[attackKey];
|
||
|
||
for (int i = 0; i < 15; i++)
|
||
{
|
||
float delay = i * 1.0f; // 每秒生成一个,持续 15 秒
|
||
int index = i;
|
||
selfTimeSm.AddLocalTimer(delay, () =>
|
||
{
|
||
// 动态获取玩家当前状态
|
||
if (player == null || player.statusSm.isDead) return;
|
||
|
||
Vector3 targetPos;
|
||
if ((index + 2) % 5 == 0) // 每5个陨石中的第4个直接落在玩家当前位置,增加难度
|
||
{
|
||
targetPos = player.transform.position;
|
||
}
|
||
else if ((index + 1) % 5 == 0) //每5个陨石的第5个预测玩家位置,增加难度
|
||
{
|
||
targetPos = PredictPlayerPosition(0.85f);
|
||
}
|
||
else
|
||
{
|
||
// 随机在玩家周身一定半径内
|
||
Vector2 randomOffset = Random.insideUnitCircle * radius;
|
||
targetPos = player.transform.position + new Vector3(randomOffset.x, 0f, randomOffset.y);
|
||
}
|
||
|
||
// 利用 NavMesh 进行投影对齐,防止指示器浮空或生成在墙壁外部
|
||
if (UnityEngine.AI.NavMesh.SamplePosition(targetPos, out UnityEngine.AI.NavMeshHit hit, radius, UnityEngine.AI.NavMesh.AllAreas))
|
||
{
|
||
targetPos = hit.position;
|
||
}
|
||
|
||
GenerateMeteor(vfxName, attackUnit, targetPos);
|
||
|
||
// 额外生成第二枚陨石,避免与第一枚重合
|
||
List<Vector3> extraMeteors = AttackAreaDistributionHelper.GenerateNonOverlappingInCircle(
|
||
player.transform.position, radius, 4f, 1, new List<Vector3> { targetPos });
|
||
|
||
foreach (Vector3 pos in extraMeteors)
|
||
{
|
||
GenerateMeteor(vfxName, attackUnit, pos);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
private void FAPF_GenerateMovingSlashes(RuntimeFuncAnim rtFuncAnim)
|
||
{
|
||
CustomFunction.PC_StringStringInt p = rtFuncAnim.GetParams<CustomFunction.PC_StringStringInt>();
|
||
string vfxName = p.str0;
|
||
string attackKey = p.str1;
|
||
int count = p.int0;
|
||
|
||
if (count <= 0) return;
|
||
|
||
Vector3 baseDir = transform.forward;
|
||
if (player != null && !player.statusSm.isDead)
|
||
{
|
||
baseDir = (player.transform.position - transform.position).normalized;
|
||
baseDir.y = 0;
|
||
if (baseDir == Vector3.zero) baseDir = transform.forward;
|
||
baseDir.Normalize();
|
||
}
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
float angle = (i - (count - 1) / 2.0f) * 30f;
|
||
Vector3 slashDir = Quaternion.Euler(0, angle, 0) * baseDir;
|
||
Vector3 upFix = Vector3.up * 1.5f;
|
||
GenerateMovingSlash(vfxName, attackData[attackKey], transform.position + upFix + slashDir * 2f, slashDir, 10f, 10f);
|
||
}
|
||
}
|
||
}
|
||
|
||
public partial class C1_Axe
|
||
{
|
||
private NormalArea GenerateSlash(string vfxName, AttackUnit attackUnit)
|
||
{
|
||
NormalArea slash = vfxData.SpawnVFX(vfxName, this).GetComponentInChildren<NormalArea>();
|
||
|
||
slash.Initialize<NormalArea>(this, Fraction.Player)
|
||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||
.SetTimeSubmodule<NormalArea>(3f)
|
||
.SetHitSubmodule<NormalArea>();
|
||
slash.SetImpulseSubmodule().WithRepulsion(3);
|
||
slash.hitSm.AddHitSound(AK.EVENTS.C1_AXE_SWINGHIT);
|
||
return slash;
|
||
}
|
||
|
||
private NormalArea GenerateSmash(string vfxName, AttackUnit attackUnit,
|
||
Vector3 positionOffset = default, Vector3 force = default)
|
||
{
|
||
NormalArea slash = vfxData.SpawnVFX(vfxName, this).GetComponentInChildren<NormalArea>();
|
||
slash.Initialize<NormalArea>(this, Fraction.Player)
|
||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||
.SetTimeSubmodule<NormalArea>(5f)
|
||
.SetHitSubmodule<NormalArea>();
|
||
slash.SetImpulseSubmodule();
|
||
|
||
if (force == default)
|
||
{
|
||
slash.impulseSm.WithRepulsion(8f);
|
||
}
|
||
else
|
||
{
|
||
slash.impulseSm.WithCustomForce(force.normalized * 8f);
|
||
}
|
||
|
||
slash.topParent.position += positionOffset;
|
||
AudioManager.Post(AK.EVENTS.C1_AXE_SMASH, slash.gameObject);
|
||
return slash;
|
||
}
|
||
|
||
private NormalArea GenerateWhirlWind(string vfxName, AttackUnit attackUnit, float duration)
|
||
{
|
||
NormalArea slash = vfxData.SpawnVFX(vfxName, this).GetComponentInChildren<NormalArea>();
|
||
|
||
slash.Initialize<NormalArea>(this, Fraction.Player)
|
||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||
.SetTimeSubmodule<NormalArea>(1.3f, 0.04f, 0.86f)
|
||
.SetHitSubmodule<NormalArea>(0.2f, 4);
|
||
slash.SetImpulseSubmodule().WithSuction(2);
|
||
slash.hitSm.AddHitSound(AK.EVENTS.C1_AXE_SWINGHIT);
|
||
|
||
var topParticle = slash.topParent.GetComponent<ParticleSystem>();
|
||
topParticle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||
foreach (ParticleSystem particle in slash.topParent.GetComponent<VFXObject>().particles)
|
||
{
|
||
var main = particle.main;
|
||
main.duration = duration;
|
||
}
|
||
topParticle.Play();
|
||
return slash;
|
||
}
|
||
|
||
private void GenerateMeteor(string vfxName, AttackUnit attackUnit, Vector3 targetPosition)
|
||
{
|
||
// 直接生成包含指示器与陨石的单个特效
|
||
NormalArea meteor = vfxData.SpawnVFX(vfxName, this, targetPosition, Quaternion.identity).GetComponentInChildren<NormalArea>();
|
||
meteor.Initialize<NormalArea>(this, Fraction.Player)
|
||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||
.SetTimeSubmodule<NormalArea>(2, 0.85f, enableAction: ()=>
|
||
{
|
||
AudioManager.Post(AK.EVENTS.NEXUSCRAB_CLAWSTABBLAST, meteor.gameObject);
|
||
feedbackSc.PlayFeedback("MeteorImpact");
|
||
})
|
||
.SetHitSubmodule<NormalArea>();
|
||
|
||
// 施加冲击力与 repulsion
|
||
meteor.SetImpulseSubmodule().WithRepulsion(8);
|
||
}
|
||
|
||
private NormalArea GenerateMovingSlash(string vfxName, AttackUnit attackUnit, Vector3 position,
|
||
Vector3 direction, float duration, float speed)
|
||
{
|
||
NormalArea slash = vfxData.SpawnVFX(vfxName, this, position, Quaternion.LookRotation(direction)).GetComponentInChildren<NormalArea>();
|
||
GameObject blockVFX = vfxData.Get(vfxName).hitVFX;
|
||
slash.Initialize<NormalArea>(this, Fraction.Player)
|
||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||
.SetTimeSubmodule<NormalArea>(duration, 0.2f, duration - 0.2f)
|
||
.SetLinearDirectionMoveModule<NormalArea>(direction, speed, 5f, true, true, false, 1f)
|
||
.SetHitSubmodule<NormalArea>(); // 默认设置0.5秒判定间隔或单次判定
|
||
slash.hitSm.AddHitSound(AK.EVENTS.C1_AXE_SWINGHIT);
|
||
slash.SetImpulseSubmodule().WithDynamicForce(3f);
|
||
slash.reactionSm.hasPerfectWindowTime = false;
|
||
slash.reactionSm.generalBlockAction = blocker =>
|
||
{
|
||
var blockTransform = VFXObject.Spawn(blockVFX, this, slash.topParent.transform).transform;
|
||
blockTransform.SetParent(null);
|
||
LeanPool.Despawn(slash.topParent.gameObject);
|
||
};
|
||
return slash;
|
||
}
|
||
}
|
||
} |