Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.EnemyHpBar.cs
T

142 lines
6.2 KiB
C#
Raw Normal View History

using System.Threading;
using Ananta.SDK.Network;
using Ananta.SDK.Serialization;
using Ananta.Server.Protocol.Client4229938;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// 敌人<b>血条</b>的显示时机 —— <c>SyncAddUnitState</c> / <c>SyncRemoveUnitState</c> 推
/// <c>UnitStateConfig.HideHp</c>。
///
/// ⛑⛑ 与 <c>GameRouter.EnemyWarning.cs</c>(detectionState 那条)是<b>两条完全不同的路</b>,
/// 别再混为一谈。客户端判据在 <c>EnemyHUDCtrl.lua</c>:
/// · <c>RefreshHpVisible</c>(:551) 决定血条 —— 看 <c>cfg.ShowHPBar</c>(模板配置,
/// 服务端改不了)和 <c>shouldShowHp</c> 里的 <c>UnitStateConfig.HideHp</c>(<b>我们能写</b>)。
/// · <c>detectionState</c> 只在 :85 绑到 <c>OnShowDetectionEffect</c>,动的是<b>潜行警戒条</b>,
/// 跟血条无关。所以警戒状态机从 Idle 推到 Fight 血条也不会变。
///
/// 结论:想让血条"不打不显示、打了才显示",唯一 per-unit 且服务端可写的开关就是 HideHp。
/// 先例:<c>GameRouter.HackerSummon.cs:341</c> 用同一招关掉机械狗的敌人血条。
/// </summary>
internal sealed partial class GameRouter
{
/// <summary>
/// <c>UnitStateConfig.HideHp</c> = 10266(dump.cs:1448623 / 1449004)。
/// ⛑ 不是 <c>UnitStateAtlasConfig</c> 的图集 id,也不是 DeadS(2)。
/// </summary>
private const uint LocalEnemyUnitStateHideHp = 10266;
/// <summary>spawn 批之后补发 HideHp 的延迟(ms)。与 <c>ReEngageSpawnedEnemyAsync</c> 的 1500 对齐。</summary>
private const int LocalEnemyHpBarHideReassertMs = 1500;
/// <summary>
/// spawn 之后调用:<c>SyncAddUnitState</c>(68026128) 把血条收起。
/// 幂等 —— 已经挂着 HideHp 的单位不会再发一次。
/// </summary>
internal static async Task HideLocalEnemyHpBarAsync(TcpSession session, ulong uid)
{
if (!EnemyHit.HideHpBarUntilHit)
return;
var runtime = GetLocalEnemyRuntimeState(session);
lock (runtime.SyncRoot)
{
if (!runtime.Enemies.TryGetValue(uid, out var existing) || existing is null) return;
// 只有 server-spawned 的走这套。client-spawned 的本地单位客户端自己建好了血条,
// 我们第一次知道它存在的时候它已经在挨打了,没机会提前收。
if (!existing.ServerSpawned || existing.Dead || existing.HpBarHidden) return;
}
try
{
await session.NotifyAsync(MethodId.SyncAddUnitState,
UxSerializer.Serialize(new GameMethods.SyncAddUnitStateArgs4229938
{
Id = uid,
State = LocalEnemyUnitStateHideHp,
EffectFreezeState = 0u,
}), CancellationToken.None);
bool hidden;
lock (runtime.SyncRoot)
{
hidden = runtime.Enemies.TryGetValue(uid, out var rec) && rec is not null;
if (hidden) rec!.HpBarHidden = true;
}
if (!hidden) return;
session.Log.Info($"[ENEMY-HPBAR] hidden uid={uid} state=HideHp({LocalEnemyUnitStateHideHp}) — bar shows on first hit");
// 补一拍。spawn 批是几十个包,AddUnitState 可能比单位在客户端建好更早到,
// 那就白发了(engage 那五件套也吃这个亏,ReEngageSpawnedEnemyAsync 同样延 1.5 s)。
// 若这 1.5 s 里玩家已经打中,HpBarHidden 已置 false ⇒ 这里跳过,不会把血条又收回去。
_ = ReassertHiddenHpBarAsync(session, uid);
}
catch (Exception ex)
{
session.Log.Warn($"[ENEMY-HPBAR] hide failed uid={uid}: {ex.Message}");
}
}
private static async Task ReassertHiddenHpBarAsync(TcpSession session, ulong uid)
{
try
{
await Task.Delay(LocalEnemyHpBarHideReassertMs, CancellationToken.None);
var runtime = GetLocalEnemyRuntimeState(session);
lock (runtime.SyncRoot)
{
if (!runtime.Enemies.TryGetValue(uid, out var rec) || rec is null) return;
if (!rec.HpBarHidden || rec.Dead) return;
}
await session.NotifyAsync(MethodId.SyncAddUnitState,
UxSerializer.Serialize(new GameMethods.SyncAddUnitStateArgs4229938
{
Id = uid,
State = LocalEnemyUnitStateHideHp,
EffectFreezeState = 0u,
}), CancellationToken.None);
session.Log.Info($"[ENEMY-HPBAR] reassert uid={uid} state=HideHp({LocalEnemyUnitStateHideHp})");
}
catch (Exception ex)
{
session.Log.Warn($"[ENEMY-HPBAR] reassert failed uid={uid}: {ex.Message}");
}
}
/// <summary>
/// 第一次命中时调用:<c>SyncRemoveUnitState</c>(68566559) 撤掉 HideHp,血条出现。
/// 只在 <c>HpBarHidden</c> 为真时发,之后自动变 no-op(不会每一下都刷包)。
/// </summary>
internal static async Task RevealLocalEnemyHpBarAsync(TcpSession session, ulong uid)
{
var runtime = GetLocalEnemyRuntimeState(session);
lock (runtime.SyncRoot)
{
if (!runtime.Enemies.TryGetValue(uid, out var existing) || existing is null) return;
if (!existing.HpBarHidden) return;
existing.HpBarHidden = false;
}
try
{
await session.NotifyAsync(MethodId.SyncRemoveUnitState,
UxSerializer.Serialize(new SceneMethods.SyncRemoveUnitState
{
unitId = uid,
state = LocalEnemyUnitStateHideHp,
reason = 1,
sourceId = 0u,
}), CancellationToken.None);
session.Log.Info($"[ENEMY-HPBAR] revealed uid={uid} state=HideHp({LocalEnemyUnitStateHideHp}) removed");
}
catch (Exception ex)
{
session.Log.Warn($"[ENEMY-HPBAR] reveal failed uid={uid}: {ex.Message}");
}
}
}