feat: Upgrade server with HyNB v2.4 modular architecture, minigames, admin GM web panel, companion summons, and car radio
This commit is contained in:
1 parent
74e2963a12
commit
3e5adf1dc5
1571 files changed
+336468
-6348
No files matched your search
@@ -0,0 +1,142 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user