Files

157 lines
8.1 KiB
C#
Raw Permalink Normal View History

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)}]");
}
}
}