152 lines
6.8 KiB
C#
152 lines
6.8 KiB
C#
using System.Buffers.Binary;
|
||
using Ananta.SDK.Rpc;
|
||
using Ananta.SDK.Serialization;
|
||
using Ananta.Server.ClientData.Client4229938;
|
||
using Ananta.Server.Protocol.Client4229938;
|
||
using Auto = Ananta.Server.RpcTypes.Client4229938.Auto;
|
||
|
||
namespace Ananta.Server.Handlers.Game;
|
||
|
||
/// <summary>
|
||
/// 挑战(Challenge / NewChallenge)记录 —— 桌面清单 **T0-2** 的运行时那一半。
|
||
///
|
||
/// 背景:客户端 2026-10-02 11:58:17Z 实机跑过一整轮挑战流程(`logs/unknown-methods.log`):
|
||
/// 11:58:17.276 AskNewChallengeRecordList(63830456) 74B = List7 18 个 id(1025..1042)
|
||
/// 11:58:17.291 StartNewChallenge(63092905) 4B = challengeId 1025
|
||
/// 11:58:26.091 SetNewChallengeData(63990449) 12B = 1025 + [false,false] + score 1.0
|
||
/// 11:58:26.106 FinishNewChallenge(63261145) 8B = 1025 + taskId
|
||
/// 四个调用当时**全部落在 typed-default 兜底** ⇒ 服务端一条成绩都没记下,玩家看到的永远是空面板。
|
||
///
|
||
/// 这里只接**记录读写**这一半(`SetNewChallengeData` / `AskNewChallengeRecord` / `AskNewChallengeRecordList`),
|
||
/// 理由:`StartNewChallenge` 与 `SetNewChallengeData` 的回包是 **void**,兜底给的就是空包,
|
||
/// 字节上与我方回包**完全等价**(接了只多一行日志);而 `FinishNewChallenge` 的回包是
|
||
/// `Complex(Reader[607])` = `ChallengeRecord(594) + u32 + RewardInfo(308)`,其中 `RewardInfo`
|
||
/// 还嵌套 `ExtraInfo(1428)` / `Reward(500)` ⇒ 要接就得新造 4 个契约类,
|
||
/// **而兜底那 88 字节(`DefaultReturnCatalog4229938.cs:384`)本来就是对的**,
|
||
/// 接了没有增益、只有错位风险。⇒ 那两条继续留兜底,注释在此备案。
|
||
///
|
||
/// 🔴 `UX.Game.ChallengeResult` 在 `dump.cs:1910662` 是**空类**,但 lua `Auto.Reader[607]` 有 3 个字段
|
||
/// —— 这正是硬规则 209(契约以 lua `Auto.Reader[n]` 为准,dump.cs 会丢字段)。
|
||
/// 第四个捐赠端(`_rollback_20260925_donor4/Challenge/ChallengeMethods.cs`)就信了 dump、
|
||
/// 把 `ChallengeResult` 写成空类 ⇒ `FinishNewChallenge` 回包会少 87 字节。**别照抄那个文件。**
|
||
///
|
||
/// 🔴 硬规则 94:下面每个 methodId 都必须同时登记进 `GameRouter.cs` 的 `Enabled4229938MethodIds`。
|
||
/// 🔴 硬规则 168:`[Handler]` 必须紧贴方法,中间不能插 helper —— helper 一律放在文件末尾。
|
||
///
|
||
/// 返回契约逐字段核过(lua `RPCDeserializeAuto.lua`):
|
||
/// AskNewChallengeRecord(63126760) uint challengeId -> Complex(Reader[594] NewChallengeRecord)
|
||
/// AskNewChallengeRecordList(63830456) List<uint> ids -> List7Bit(Complex(Reader[594]))
|
||
/// SetNewChallengeData(63990449) uint, List<bool>, float -> void
|
||
/// 请求形状取自 `ClientToGameDelegate.lua` 的 SerializerHelper。
|
||
/// </summary>
|
||
internal sealed partial class GameRouter
|
||
{
|
||
[Handler(MethodId.SetNewChallengeData, HandlerPacketKind.Invoke)]
|
||
private Task SetNewChallengeData(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var (challengeId, nodes, score) = ReadChallengeData(msg.Body);
|
||
ChallengeRecordStore4229938.Apply(challengeId, nodes, score);
|
||
|
||
var hit = 0;
|
||
foreach (var n in nodes)
|
||
if (n)
|
||
hit++;
|
||
|
||
conn.Log.Info($"[CHALLENGE] set-data challengeId={challengeId} nodes={nodes.Count} " +
|
||
$"hit={hit} score={score:0.###}");
|
||
return conn.ReturnEmptyOkAsync(msg);
|
||
}
|
||
|
||
[Handler(MethodId.AskNewChallengeRecord, HandlerPacketKind.Invoke)]
|
||
private Task AskNewChallengeRecord(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var challengeId = msg.Body.Length >= 4
|
||
? BinaryPrimitives.ReadUInt32LittleEndian(msg.Body)
|
||
: 0u;
|
||
var record = BuildNewChallengeRecord(challengeId);
|
||
|
||
conn.Log.Info($"[CHALLENGE] ask-record challengeId={challengeId} " +
|
||
$"highest={record.HighestLevel} best={record.BestScore:0.###}");
|
||
return conn.ReturnAsync(msg, record);
|
||
}
|
||
|
||
[Handler(MethodId.AskNewChallengeRecordList, HandlerPacketKind.Invoke)]
|
||
private Task AskNewChallengeRecordList(Connection conn, UxRpcMessage msg)
|
||
{
|
||
var ids = ReadU32List(msg.Body);
|
||
var records = new List<Auto.NewChallengeRecord>(ids.Count);
|
||
foreach (var id in ids)
|
||
records.Add(BuildNewChallengeRecord(id));
|
||
|
||
conn.Log.Info($"[CHALLENGE] ask-record-list count={ids.Count} " +
|
||
$"ids=[{string.Join(",", ids)}]");
|
||
return conn.ReturnAsync(msg, records);
|
||
}
|
||
|
||
// ------------------------------------------------------------------ helper(必须在所有 [Handler] 之后)
|
||
|
||
/// <summary>把存档快照拼成 `Auto.NewChallengeRecord`(lua Reader[594])。</summary>
|
||
private static Auto.NewChallengeRecord BuildNewChallengeRecord(uint challengeId)
|
||
{
|
||
var (highest, received, isNew, best, param) = ChallengeRecordStore4229938.Snapshot(challengeId);
|
||
return new Auto.NewChallengeRecord
|
||
{
|
||
ChallengeId = challengeId,
|
||
HighestLevel = highest,
|
||
ReceivedRewardLevel = received,
|
||
CurrentIsNewRewardLevel = isNew,
|
||
BestScore = best,
|
||
ParamData = param,
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// `uint challengeId + List<bool>(Int7 count,每元素 1 字节)+ float score`。
|
||
/// 宽容解码:布局意外也绝不许中断请求 —— 这条流程仍然需要它的 ack
|
||
/// (猜不准的 wire 形状,优先把包答回去)。
|
||
/// </summary>
|
||
private static (uint ChallengeId, List<bool> Nodes, float Score) ReadChallengeData(byte[] body)
|
||
{
|
||
var nodes = new List<bool>();
|
||
if (body.Length < 4)
|
||
return (0u, nodes, 0f);
|
||
|
||
try
|
||
{
|
||
var reader = new UxReader(body);
|
||
var challengeId = reader.U32();
|
||
var count = reader.Int7();
|
||
for (var i = 0; i < count && reader.Remaining > 4; i++)
|
||
nodes.Add(reader.Bool());
|
||
|
||
var score = reader.Remaining >= 4 ? reader.F32() : 0f;
|
||
return (challengeId, nodes, score);
|
||
}
|
||
catch (Exception)
|
||
{
|
||
return (body.Length >= 4 ? BinaryPrimitives.ReadUInt32LittleEndian(body) : 0u, nodes, 0f);
|
||
}
|
||
}
|
||
|
||
/// <summary>`List<uint>`(Int7 count + 每元素 4 字节)。</summary>
|
||
private static List<uint> ReadU32List(byte[] body)
|
||
{
|
||
var ids = new List<uint>();
|
||
if (body.Length == 0)
|
||
return ids;
|
||
|
||
try
|
||
{
|
||
var reader = new UxReader(body);
|
||
var count = reader.Int7();
|
||
for (var i = 0; i < count && reader.Remaining >= 4; i++)
|
||
ids.Add(reader.U32());
|
||
}
|
||
catch (Exception)
|
||
{
|
||
ids.Clear();
|
||
}
|
||
return ids;
|
||
}
|
||
}
|