Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.EnemyStance.cs

68 lines
4.3 KiB
C#
Raw Permalink 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 Ananta.SDK.Network;
using Ananta.SDK.Serialization;
using Ananta.Server.ClientData.Client4229938;
using Ananta.Server.Protocol.Client4229938;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// 敌人战斗姿态握手(2026-09-26 Wave 2,原生战斗管线复活)—— 移植 7k-v0.5
/// <c>GameRouter.EnemyAi.cs:21-49 SendEnemyCombatStanceAsync / ClearEnemyGunAim4229938</c>,
/// 逐行对齐,唯一差异:我们的 <c>TcpSession</c> 没有 <c>ClosedToken</c>,一律
/// <c>CancellationToken.None</c>(与 GameAdminBridge 既有风格一致)。
///
/// 这条握手是「客户端原生受击/战斗 locomotion」的激活钥匙之一:
/// <c>SyncBattleAnimSet</c> 让客户端把单位切到指定 AnimationSetConfig(空手 325/327、
/// 远程 337/338/340),原生 hurt/stagger 反应才有对应动画集可播;
/// <c>SyncAgentChangeWeapon</c> 负责卸/换武器(melee 空手 = weaponId 0)。
/// 7K 对 template 模式 / 无动画集可换的模板<b>整段跳过</b>,我们保持同样的判据,
/// 所以 spawnCombatMode=off / template 时本文件一个包都不会发。
/// </summary>
internal sealed partial class GameRouter
{
/// <summary>7K 原判据:melee 模式 + 已卸武器 + 有空手技能 = 空手单位。</summary>
private static bool IsUnarmedEnemyProfile4229938(EnemyAiProfile4229938? profile)
=> profile is { CombatMode: "melee", Weapon: 0, UnarmedAttacks.Count: > 0 };
/// <summary>
/// 清空上半身持枪瞄准(移植 7K <c>ClearEnemyGunAim4229938</c>,消费方注释原文保留)。
/// 两个包都是裸 u64(8B,无 marker)—— 7K 用 <c>new UxWriter().U64(id)</c>,照抄。
/// </summary>
private static async Task ClearEnemyGunAim4229938(TcpSession session, ulong id)
{
// Native 127C7F20 -> GunAimModule_Npc.Stop(pid): upper-body aim is
// independent of the weapon model and the selected battle locomotion set.
var payload = new UxWriter().U64(id).ToArray();
await session.NotifyAsync(MethodId.IGameSceneToClient_SyncEndShootingState, payload, CancellationToken.None);
// Native 127704B0 removes shooting tag 919, sends inward signal 1850,
// and exits the shooting skill transition when the state-tree is active.
await session.NotifyAsync(MethodId.IGameSceneToClient_SyncEndShootGameplayTag, payload, CancellationToken.None);
}
/// <summary>
/// 战斗姿态握手(移植 7K <c>SendEnemyCombatStanceAsync</c>)。
/// 判据与 7K 完全一致:profile null → 跳过;非空手且 BattleAnimSetTag==0 → 跳过
/// (template / 持武器近战模板 7K 本来一个包都不发)。
/// </summary>
internal static async Task SendEnemyCombatStanceAsync(TcpSession session, ulong id, EnemyAiProfile4229938? profile)
{
if (profile is null) return;
var unarmed = IsUnarmedEnemyProfile4229938(profile);
if (!unarmed && profile.BattleAnimSetTag == 0) return;
if (unarmed) await ClearEnemyGunAim4229938(session, id);
// Weapon removal alone does not replace the battle movement action set.
await session.NotifyAsync(MethodId.SyncAgentChangeWeapon, UxSerializer.Serialize(
new SceneMethods.EnemyAiWeapon4229938 { agentId = id, weaponId = profile.Weapon }), CancellationToken.None);
// ApplyBattleAnimSetTag returns immediately for the same cached tag.
// During the bounded loading warmups, remove/re-add the override so it
// cannot remain a cached no-op after a late weapon/module initialization.
if (unarmed && profile.BattleAnimSetTag != 0)
await session.NotifyAsync(MethodId.IGameSceneToClient_SyncBattleAnimSet, UxSerializer.Serialize(
new SceneMethods.EnemyAiBattleAnim4229938 { id = id, animSetTag = 0 }), CancellationToken.None);
await session.NotifyAsync(MethodId.IGameSceneToClient_SyncBattleAnimSet, UxSerializer.Serialize(
new SceneMethods.EnemyAiBattleAnim4229938 { id = id, animSetTag = profile.BattleAnimSetTag }), CancellationToken.None);
session.Log.Info($"[ENEMY-STANCE] uid={id} mode={profile.CombatMode} unarmed={unarmed} weapon={profile.Weapon} animSet={profile.BattleAnimSetTag} attacks={string.Join(',', profile.Attacks)}");
}
}