Files

143 lines
6.2 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 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}");
}
}
}