Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.Compatibility4229938.cs

158 lines
8.1 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 Ananta.SDK.Rpc;
using Ananta.Server.Gameplay;
using Ananta.Server.Protocol.Client4229938;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Build-4229938 RPC-surface compatibility. Semantic handlers still win. Any other Invoke uses
/// the exact generated return reader shape from the matching client Lua so callbacks receive a
/// valid default value (empty list/dict, zero primitive, empty string, or non-null empty object)
/// instead of a zero-byte body.
/// </summary>
internal sealed partial class GameRouter
{
private Task DefaultUnknownInvoke4229938(RpcContext ctx)
{
UnknownMethodLogger.Log(ctx.Session.Log.Scope, "INVOKE", ctx.MethodId, ctx.Body);
if (DefaultReturnCatalog4229938.TryGet(ctx.MethodId, out var body, out var shape))
{
ctx.Session.Log.Warn($"[RPC4229938] unimplemented {Ananta.SDK.Logging.RpcMethodNames.Display(ctx.MethodId)} -> typed-default {shape} {body.Length}b");
return ctx.ReturnAsync(body);
}
ctx.Session.Log.Warn($"[RPC4229938] no generated return reader for {Ananta.SDK.Logging.RpcMethodNames.Display(ctx.MethodId)} -> empty OK");
return ctx.ReturnEmptyOkAsync();
}
private Task DefaultUnknownNotify4229938(RpcContext ctx)
{
UnknownMethodLogger.Log(ctx.Session.Log.Scope, "NOTIFY", ctx.MethodId, ctx.Body);
// Notifies have no callback body. Accepting an unimplemented generated 4229938 notify is
// safer than treating it as a protocol error; stateful methods get semantic handlers as
// they are implemented, while telemetry/client-local signals remain harmless no-ops.
ctx.Session.Log.Warn($"[RPC4229938] unimplemented notify {Ananta.SDK.Logging.RpcMethodNames.Display(ctx.MethodId)} -> accepted");
return Task.CompletedTask;
}
// High-frequency/UI RPCs seen in stock 4229938 are made explicit so they no longer appear as
// unknown traffic. The server does not emulate production economy/social services here; it
// returns the exact neutral value expected by the generated client callback.
// AskTradeGetMarketList is handled for real by GameRouter.Trade.cs (synthesised market buckets);
// it must not stay in this neutral list or registration reports a duplicate invoke handler.
[Handler(MethodId.AskTradeGetHistoryPage, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskPlayerRankingSummary, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskPopularityPhoneFirstOpened, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskPopularityUIOpened, HandlerPacketKind.Invoke)]
[Handler(MethodId.SyncOpenInspireHub, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskQueryInspireHubAllGamePlayRecommendData, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskQueryInspireHubAllGamePlayRankData, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskLinkInfos, HandlerPacketKind.Invoke)]
[Handler(MethodId.GetLastMode, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskSwitchLinkMode, HandlerPacketKind.Invoke)]
private Task KnownNeutralInvoke4229938(Connection conn, UxRpcMessage msg)
{
if (DefaultReturnCatalog4229938.TryGet(msg.MethodId, out var body, out var shape))
{
conn.Log.Info($"[RPC4229938] neutral {Ananta.SDK.Logging.RpcMethodNames.Display(msg.MethodId)} -> {shape} {body.Length}b");
return msg.Context.ReturnAsync(body);
}
return conn.ReturnEmptyOkAsync(msg);
}
[Handler(MethodId.OnParkourStateChange, HandlerPacketKind.Invoke)]
private async Task OnParkourStateChange4229938(Connection conn, UxRpcMessage msg)
{
var ctx = msg.Context;
// ⛑ 哨兵用 uint? 而不是 0:ParkourState 10000「空状态」是合法值,0 会把它吞掉(硬规则 240)。
uint? parkourState = null;
var payload = msg.Body;
if (payload != null && payload.Length >= 4)
{
parkourState = BitConverter.ToUInt32(payload, 0);
conn.Log.Info($"[PARKOUR] state={parkourState} len={payload.Length} hex={Convert.ToHexString(payload)}");
}
await conn.ReturnEmptyOkAsync(msg);
// 跑酷状态 → 临时能力门(严格跟状态:进才发、离开立刻撤)。未配置 = 整层跳过,零开销。
if (parkourState is { } s && WebTraversal.HasParkourStateGates)
await SyncParkourStateGates4229938(ctx, s);
}
/// <summary>
/// ★ <c>gameplay.webTraversal.parkourStateGates</c>:**严格跟状态**的临时能力门。
///
/// 客户端每上报一次 <c>OnParkourStateChange</c>(67897801) 就进一次,但只在
/// <b>0→1 / 1→0 的边沿</b>各发一个 <c>SyncPaokuLimitChange</c>(68974941):
/// 进入配置里的 ParkourState ⇒ <c>isAdd=true</c>;一旦离开 ⇒ 立刻 <c>isAdd=false</c> 撤回,
/// <b>没有宽限期</b>(用户 2026-10-01 23:3x 定:大跳 5052 走这条,连跳 5040 走常驻)。
///
/// 当前配置:<c>"10081": [5052]</c> —— ParkourState 10081「八步赶蝉jump」即璎珑的虐杀原形跑酷
/// (TOPIC §11.10:全量 604 份日志里只在下发 3040 门之后的两个会话出现过)。
///
/// ⛑ 高频 RPC(实测 ~1.2 次/秒),靠 <c>WorldEntryState.ActiveParkourGateStates</c> 去重,
/// 否则每次上报都 add/remove 抖动。规则 238:这里不做 per-item 日志,只在边沿打一行。
/// </summary>
async Task SyncParkourStateGates4229938(RpcContext ctx, uint parkourState)
{
var state = GetWorldState(ctx);
var unitId = state.ActiveSpiritUnitId != 0 ? state.ActiveSpiritUnitId : Profile.InitialUnitId;
if (unitId == 0)
return;
var active = state.ActiveParkourGateStates;
// 换了单位(切角色)⇒ 旧单位上的门随之消失,记账重置;不补发 isAdd=false(旧单位已不在场)。
if (state.ActiveParkourGateUnitId != unitId)
{
active.Clear();
state.ActiveParkourGateUnitId = unitId;
}
// ① 已挂上、但与当前 state 不符的:撤回。
// ⛑ 只有 parkourStateGateRelease == "onLeave" 才撤。默认 "session" 模式跳过这一步 ——
// 10081「八步赶蝉jump」是瞬时态(实机 add→remove 间隔 ~1s,同秒内还会来回),
// 一撤就导致下一次触发前门已经没了(用户实测「第二次触发大跳就没了」)。
if (WebTraversal.ReleaseParkourGatesOnLeave)
{
List<uint>? revoked = null;
foreach (var prev in active)
{
if (prev == parkourState)
continue;
var prevIds = WebTraversal.ParkourStateGateIds(prev);
if (prevIds.Count == 0)
continue;
await ctx.NotifyAsync(MethodId.IGameSceneToClient_SyncPaokuLimitChange,
new SceneMethods.SyncPaokuLimitChange4229938
{
unitId = unitId,
limitIds = prevIds.ToList(),
isAdd = false,
});
(revoked ??= []).Add(prev);
ctx.Session.Log.Info($"[PARKOUR-GATE] unit={unitId} state={prev} remove=[{string.Join(",", prevIds)}] nowState={parkourState}");
}
if (revoked != null)
foreach (var prev in revoked)
active.Remove(prev);
}
// ② 当前 state 命中配置且还没挂上:下发(Add 返回 true 才是新边沿)
var addIds = WebTraversal.ParkourStateGateIds(parkourState);
if (addIds.Count > 0 && active.Add(parkourState))
{
await ctx.NotifyAsync(MethodId.IGameSceneToClient_SyncPaokuLimitChange,
new SceneMethods.SyncPaokuLimitChange4229938
{
unitId = unitId,
limitIds = addIds.ToList(),
isAdd = true,
});
ctx.Session.Log.Info($"[PARKOUR-GATE] unit={unitId} state={parkourState} add=[{string.Join(",", addIds)}]");
}
}
}