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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|