using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.Server.Protocol.Client4229938;
namespace Ananta.Server.Handlers.Game;
///
/// 伪造匹配 —— 让客户端点「开始」就能进搜打撤图。
///
/// 为什么需要这一层:正式服的开局要过真实匹配服 / 队伍服,我们手上只有游戏服,
/// 补不出那条链路;而客户端点完开始就在等切图,没人应答它就卡在开始界面。
/// 捐赠端 handoff 的做法就是「把这四条请求劫到 RaidType 19 上」,本文件是同一思路的实现。
///
/// 四条通用开局请求(ClientToGameDelegate.lua 里都能找到):
/// AskStartSingleMatch 63347492 / StartBotMatch 63474603 /
/// StartMatchInTeam 63563208 / StartGameInTeam 63121362。
///
/// ⛑⛑ 这四条是通用请求,不是搜打撤专用:别的玩法开局也走它们。
/// 所以默认关(world.content.extraction.fakeMatch=false),
/// 只在面板勾上「伪造匹配」时生效,且改动只活在内存里 —— 重启一定回落到配置值。
/// 开着的时候任何一次点开始都会被劫到 PreferredRaid。
///
/// ⛑ 没有 Request 契约:这四条的参数在 dump 里是空壳,也没有可用的 lua writer 对照,
/// 所以一律裸接、只打 body 长度。把 midToReturnMessageReader 里没有 reader 的请求当成
/// 「可以随便回」是对的,但反过来不要因为这里没报错就以为参数结构对了 —— 我们一根字段都没读。
///
/// ⛑ [Handler] 写完必须把 methodId 加进 GameRouter.cs 的
/// Enabled4229938MethodIds,否则是空注册 —— 请求不报错,只拿 typed-default void。
///
internal sealed partial class GameRouter
{
[Handler(MethodId.AskStartSingleMatch, HandlerPacketKind.Both)]
private Task OnAskStartSingleMatch(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "AskStartSingleMatch");
[Handler(MethodId.StartBotMatch, HandlerPacketKind.Both)]
private Task OnStartBotMatch(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartBotMatch");
[Handler(MethodId.StartMatchInTeam, HandlerPacketKind.Both)]
private Task OnStartMatchInTeam(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartMatchInTeam");
[Handler(MethodId.StartGameInTeam, HandlerPacketKind.Both)]
private Task OnStartGameInTeam(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartGameInTeam");
///
/// 开局请求的公共收尾:先应答,再决定要不要把这次开局劫到搜打撤图。
///
/// ⛑ 应答必须无条件发:这四条是 await 型,不回会让客户端那条调用永久挂住,
/// 表现是「点开始没反应」,而且日志里什么都没有。代价为零 —— handoff §6.3 的请求表里
/// 没有这四条,说明 midToReturnMessageReader 里没有 reader,
/// 空 OK 不会像 SearchContainer 那样用 nil 覆盖客户端已有的好数据。
///
private async Task HijackMatchToExtractionAsync(Connection conn, UxRpcMessage msg, string label)
{
if (msg.IsInvoke)
await conn.ReturnEmptyOkAsync(msg);
var state = GetStateIfExists(msg.Context.Session);
if (state is null)
return;
var raidId = ExtractionPreferredRaid;
if (!ExtractionFakeMatch)
{
conn.Log.Info($"{ExtractionTag} {label} {msg.Body.Length}b ack-only (fake match off)");
return;
}
if (!IsExtractionRaid(raidId))
{
conn.Log.Warn($"{ExtractionTag} {label} dropped: preferred raid {raidId} " +
"不在搜打撤名单里(world.content.extraction.enabled / raidIds / preferredRaid)");
return;
}
// 已经在图里了不再重复切——客户端切图期间会把这四条重发几次。
uint activeRaid;
lock (state.SyncRoot)
activeRaid = state.ActiveRaidId;
if (activeRaid == raidId)
{
conn.Log.Info($"{ExtractionTag} {label} ignored: already in raid {raidId}");
return;
}
var (ok, message) = await EnterExtractionRaidAsync(msg.Context.Session, state, raidId);
conn.Log.Info($"{ExtractionTag} {label} hijack -> raid {raidId}: {message}");
if (!ok)
conn.Log.Warn($"{ExtractionTag} {label} hijack failed: {message}");
}
}