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
@@ -2,6 +2,7 @@ using Ananta.SDK.Rpc;
|
||||
using Ananta.Server.Handlers;
|
||||
using Ananta.Server.Gameplay;
|
||||
using Ananta.Server.Protocol.Client4229938;
|
||||
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
|
||||
|
||||
namespace Ananta.Server.Handlers.Game;
|
||||
|
||||
@@ -35,9 +36,22 @@ internal sealed partial class GameRouter
|
||||
ctx.Session.Log.Info($"[WEB] feisuo-enter ignored template={state.ActiveSpiritTemplateId} protagonist=false");
|
||||
return;
|
||||
}
|
||||
// ⛑⛑ 进蹲伏态的同一帧客户端会自己发 AskRemoveClientBuff(52853760) 摘掉飞索 buff。
|
||||
// 旧逻辑在这里 EnsureFeiSuoActionBuff 把它加回去,再加上 OnClientBuffRemove 里
|
||||
// 120ms 后的 persistent-gate-reassert,客户端立刻 AskLeaveFeiSuoCrouch
|
||||
// (实测 19:41:32.258 进 / .262 摘 / 同秒退出)⇒ 蹲伏撑不过 0.4s
|
||||
// ⇒ StealthManager.CrouchAssassinationUpdate 跑不满一轮
|
||||
// ⇒ ReportCanAssassinateEnemies 恒 0 ⇒ 暗杀 HUD 死在源头。
|
||||
state.FeiSuoCrouchActive = true;
|
||||
|
||||
// ★ 每次进蹲伏都重新播种(硬规则 192):AssassinationSeededAgents 记的是「本次蹲伏
|
||||
// 播过谁」,不清的话第二次蹲伏就再也不播了 —— 而 canAssassinate 会被上报路径撤销,
|
||||
// 于是第二次及以后永远没有候选。
|
||||
state.AssassinationSeededAgents.Clear();
|
||||
state.AssassinationSeedAtTicks = 0;
|
||||
|
||||
await PublishInitialCapabilityBuffs(ctx);
|
||||
await EnsureFeiSuoActionBuff(ctx);
|
||||
ctx.Session.Log.Info($"[WEB] feisuo-enter ack=true transient={WebTraversal.FeiSuoBuff}");
|
||||
ctx.Session.Log.Info($"[WEB] feisuo-enter crouch=true transient-suppressed={WebTraversal.FeiSuoBuff} seed-reset={WebTraversal.PushAssassinationSeed}");
|
||||
}
|
||||
|
||||
async Task OnFeiSuoSuccess(RpcContext ctx, int feiSuoId)
|
||||
@@ -55,6 +69,17 @@ internal sealed partial class GameRouter
|
||||
async Task OnLeaveFeiSuoCrouch(RpcContext ctx)
|
||||
{
|
||||
var state = GetWorldState(ctx);
|
||||
// ⛑ 必须在任何提前 return 之前清:蹲伏一结束,飞索 buff 的 reassert 就要恢复。
|
||||
state.FeiSuoCrouchActive = false;
|
||||
|
||||
// ★ 离开蹲伏 ⇒ 播种记录一起清,下次蹲伏重新播(硬规则 192)。
|
||||
// ⛑ 撤销也必须在提前 return 之前:原来它挂在函数末尾,一旦
|
||||
// ActiveFeiSuoBuffInstanceId == 0 就直接 return,敌人身上的 canAssassinate
|
||||
// 永远撤不掉(残留暗杀交互 + HUD 提示)。
|
||||
state.AssassinationSeededAgents.Clear();
|
||||
state.AssassinationSeedAtTicks = 0;
|
||||
await RevokeAllAssassinationGrants(ctx, "feisuo-leave");
|
||||
|
||||
if (state.ActiveFeiSuoBuffInstanceId == 0)
|
||||
return;
|
||||
|
||||
@@ -69,9 +94,92 @@ internal sealed partial class GameRouter
|
||||
ctx.Session.Log.Info($"[WEB] feisuo-leave transient-removed instance={instanceId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 蹲伏暗杀裁决:客户端上报「这几个在我背后」→ 服务端回 <c>SyncEnemyCanAssassinate</c> 授权。
|
||||
///
|
||||
/// ★ 为什么必须有这一段:客户端 <c>StealthManager</c> 只负责算候选(每 0.4s 一次),
|
||||
/// 真正的授权在服务端这一条回包上。本服 2026-09-26 之前<b>既没有 handler 也从不下发</b>
|
||||
/// (全仓只有 MethodId 常量)⇒ <c>canAssassinate</c> 恒 false ⇒
|
||||
/// <c>EnemyInteractCore.TrySetFeisuoCrouchAssassinInteract</c> 从不置 true ⇒
|
||||
/// <b>HUD 不出暗杀提示、ControlPower 按钮不切成暗杀态</b>。
|
||||
///
|
||||
/// ⛑ 只在集合变化时发包:候选名单蹲着不动时是完全一样的,不做差分会每 0.4s 推一批。
|
||||
/// ⛑ 两态都发:撤销(canAssassinate=false)同样重要,否则目标走远了提示还在。
|
||||
/// </summary>
|
||||
async Task OnReportCanAssassinateEnemies(RpcContext ctx, List<ulong> enemyIds)
|
||||
{
|
||||
var state = GetWorldState(ctx);
|
||||
if (!WebTraversal.AnswerCrouchAssassination)
|
||||
return;
|
||||
if (!IsActiveProtagonist(state))
|
||||
return;
|
||||
|
||||
var granted = state.AssassinationGrantedAgents;
|
||||
var incoming = new HashSet<ulong>();
|
||||
foreach (var id in enemyIds)
|
||||
{
|
||||
if (id != 0)
|
||||
incoming.Add(id);
|
||||
}
|
||||
|
||||
// ① 撤销:上次授权过、这次不在候选里
|
||||
var revoked = 0;
|
||||
foreach (var gone in granted.Where(x => !incoming.Contains(x)).ToArray())
|
||||
{
|
||||
granted.Remove(gone);
|
||||
revoked++;
|
||||
await ctx.NotifyAsync(MethodId.IGameSceneToClient_SyncEnemyCanAssassinate,
|
||||
new SceneMethods.SyncEnemyCanAssassinate4229938 { agentId = gone, canAssassinate = false });
|
||||
}
|
||||
|
||||
// ② 授权:这次在候选里、上次没给过
|
||||
var grantedNew = 0;
|
||||
foreach (var id in incoming)
|
||||
{
|
||||
if (!granted.Add(id))
|
||||
continue;
|
||||
grantedNew++;
|
||||
await ctx.NotifyAsync(MethodId.IGameSceneToClient_SyncEnemyCanAssassinate,
|
||||
new SceneMethods.SyncEnemyCanAssassinate4229938 { agentId = id, canAssassinate = true });
|
||||
}
|
||||
|
||||
if (revoked > 0 || grantedNew > 0)
|
||||
ctx.Session.Log.Info($"[ASSASS] grant=+{grantedNew} revoke=-{revoked} total={granted.Count} reported={enemyIds.Count}");
|
||||
}
|
||||
|
||||
/// <summary>清空全部暗杀授权(离开蹲伏态 / 退场景时调用),逐个发 false。</summary>
|
||||
async Task RevokeAllAssassinationGrants(RpcContext ctx, string phase)
|
||||
{
|
||||
var state = GetWorldState(ctx);
|
||||
var granted = state.AssassinationGrantedAgents;
|
||||
if (granted.Count == 0)
|
||||
return;
|
||||
|
||||
var all = granted.ToArray();
|
||||
granted.Clear();
|
||||
foreach (var id in all)
|
||||
await ctx.NotifyAsync(MethodId.IGameSceneToClient_SyncEnemyCanAssassinate,
|
||||
new SceneMethods.SyncEnemyCanAssassinate4229938 { agentId = id, canAssassinate = false });
|
||||
ctx.Session.Log.Info($"[ASSASS] revoke-all phase={phase} count={all.Length}");
|
||||
}
|
||||
|
||||
async Task OnClientBuffAdd(RpcContext ctx, ulong unitId, uint buffId)
|
||||
{
|
||||
var state = GetWorldState(ctx);
|
||||
|
||||
// ★ 白靖「斯安威斯坦」(子弹时间)—— 这是它的<b>原生触发通道</b>:
|
||||
// 客户端职业技能按钮按下后 AskAddClientBuff(unitId, 52959496) 到服务端,
|
||||
// 服务端才补上 SyncSandevistanStart + 5 秒 buff + 赛车手时缓次数结算。
|
||||
// ⛑ 必须早于下面所有通用增 buff 分支(包括 IsWebBuff 的拒绝),
|
||||
// 否则这一帧会被当成普通 buff 直接回了 Service 侧而过不了我们对时限的控制。
|
||||
// ⛔ 2026-09-27 用户拍板:不把它挂到当前流派 ActiveSkill(E)上 —— 那是职业技能,不是战技。
|
||||
if (buffId == CharacterCapabilities.BaiJingTimeScaleActiveBuff
|
||||
&& state.ActiveSpiritTemplateId == CharacterCapabilities.BaiJingSpiritId)
|
||||
{
|
||||
await TryStartBaiJingTime4229938(ctx.Session, unitId, "native-profession-button");
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebTraversal.IsWebBuff(buffId) && !IsActiveProtagonist(state, unitId))
|
||||
{
|
||||
ctx.Session.Log.Info($"[WEB] client-buff-add denied unit={unitId} template={state.ActiveSpiritTemplateId} buff={buffId} protagonist=false");
|
||||
@@ -109,6 +217,18 @@ internal sealed partial class GameRouter
|
||||
async Task OnClientBuffRemove(RpcContext ctx, ulong unitId, uint buffId)
|
||||
{
|
||||
var state = GetWorldState(ctx);
|
||||
|
||||
// ★ 白靖「斯安威斯坦」的收场:客户端技能结束 / buff 到点后摘除时上报。
|
||||
// 只有操控单位本人发出的这一次才收尾,别人的包不动当前状态。
|
||||
// ⛑ 一定要走到 TryStart 那一侧成对处理 —— 少了这条 End,玩家会永久卡在慢世界里。
|
||||
if (buffId == CharacterCapabilities.BaiJingTimeScaleActiveBuff
|
||||
&& state.ActiveSpiritTemplateId == CharacterCapabilities.BaiJingSpiritId)
|
||||
{
|
||||
if (unitId == state.ActiveSpiritUnitId)
|
||||
await StopBaiJingTime4229938(ctx.Session, "client-buff-remove");
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebTraversal.IsWebBuff(buffId) && !IsActiveProtagonist(state, unitId))
|
||||
return;
|
||||
if (WebTraversal.IsClientLifecycleBuff(buffId))
|
||||
@@ -145,6 +265,13 @@ internal sealed partial class GameRouter
|
||||
// after teardown for either protagonist. It is deliberately NOT stored as
|
||||
// ActiveFeiSuoBuffInstanceId; that slot is reserved for the next action copy only.
|
||||
await Task.Delay(WebTraversal.GrappleRearmDelayMs);
|
||||
// ⛑⛑ 蹲伏期间不加回:客户端是为了进蹲伏才摘的(Enter 与 Remove 相隔 4ms)。
|
||||
// 强行加回 ⇒ 客户端立刻退出蹲伏 ⇒ 暗杀检测永远跑不完一轮。
|
||||
if (state.FeiSuoCrouchActive)
|
||||
{
|
||||
ctx.Session.Log.Info($"[WEB] persistent-gate-reassert skipped unit={unitId} reason=feisuo-crouch-active");
|
||||
return;
|
||||
}
|
||||
if (IsActiveProtagonist(state, unitId))
|
||||
{
|
||||
var residentInstanceId = state.NextBuffInstanceId++;
|
||||
|
||||
Reference in new issue
Block a user