151 lines
6.8 KiB
C#
151 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;
|
|||
|
|
}
|
|||
|
|
}
|