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;
///
/// 敌人血条的显示时机 —— SyncAddUnitState / SyncRemoveUnitState 推
/// UnitStateConfig.HideHp。
///
/// ⛑⛑ 与 GameRouter.EnemyWarning.cs(detectionState 那条)是两条完全不同的路,
/// 别再混为一谈。客户端判据在 EnemyHUDCtrl.lua:
/// · RefreshHpVisible(:551) 决定血条 —— 看 cfg.ShowHPBar(模板配置,
/// 服务端改不了)和 shouldShowHp 里的 UnitStateConfig.HideHp(我们能写)。
/// · detectionState 只在 :85 绑到 OnShowDetectionEffect,动的是潜行警戒条,
/// 跟血条无关。所以警戒状态机从 Idle 推到 Fight 血条也不会变。
///
/// 结论:想让血条"不打不显示、打了才显示",唯一 per-unit 且服务端可写的开关就是 HideHp。
/// 先例:GameRouter.HackerSummon.cs:341 用同一招关掉机械狗的敌人血条。
///
internal sealed partial class GameRouter
{
///
/// UnitStateConfig.HideHp = 10266(dump.cs:1448623 / 1449004)。
/// ⛑ 不是 UnitStateAtlasConfig 的图集 id,也不是 DeadS(2)。
///
private const uint LocalEnemyUnitStateHideHp = 10266;
/// spawn 批之后补发 HideHp 的延迟(ms)。与 ReEngageSpawnedEnemyAsync 的 1500 对齐。
private const int LocalEnemyHpBarHideReassertMs = 1500;
///
/// spawn 之后调用:SyncAddUnitState(68026128) 把血条收起。
/// 幂等 —— 已经挂着 HideHp 的单位不会再发一次。
///
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}");
}
}
///
/// 第一次命中时调用:SyncRemoveUnitState(68566559) 撤掉 HideHp,血条出现。
/// 只在 HpBarHidden 为真时发,之后自动变 no-op(不会每一下都刷包)。
///
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}");
}
}
}