This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/internal/gameserver/routers/game_router_mini_game_entry.go
T

278 lines
20 KiB
Go
Raw Normal View History

package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// ★★★ 龙栖村摊位小游戏「能玩」的入口 RPC 全集(第 90 轮,2026-09-27)。
// ── 为什么是这些(而不是别的)────────────────────────────────────────────
// 用户原话:「把能玩的小游戏都补全了,我一起测试,一次一次测太麻烦了」
// 上一轮(R89-b)已确认:摊位**布景**我方已能一次全刷(`GameRouter.MiniGameStall.cs`),
// 但「看得见摊位」≠「摊位能玩」。能玩要各自的小游戏入口 RPC。
// 对照包 Lyco-v0.2.1 的自我定性(原文):
// 「服务端不接 ⇒ 客户端 await 不到回执,模态界面永久等待 → 只能大退」
// 「龙栖村这些小游戏(套圈/五子棋/刮刮乐/气枪/跳格子/踢毽子/炒茶…)本质不是各自独立,
// 而是同一套 `PoiGame` 配置框架 + 统一的「组队对局」入口子系统」
// ⇒ 所以本文件把「缺失的入口」一次补齐,用户一轮测完。
// ── ★ 协议形状的第一现场(铁律 31;不要凭 dump.cs 猜)──────────────────────
// ① C2S 逐字段布局:`lua/lua/LuaGen/AutoGen/ClientToGameSceneDelegate.lua`
// (Party 那三条在 `ClientToGameDelegate.lua`,因为走的是 Game 而非 GameScene 连接)
// 形如 `SerializerHelper.<方法>_Serializer = function(writer, ...)`,逐行 `WritePrimitive/WriteStruct`。
// ② INVOKE 返回值:`RPCDeserializeAuto.lua` 的 `midToReturnMessageReader[mid]`
// —— **函数体为空 = void**;只有 `EnterRingTossZone` 回**裸 u64**、`QueryGomokuPlayerInfo` 回 complex。
// ── 逐个的判据与处置(全部实测自上面两个文件)───────────────────────────
// 【套圈 RingToss】mid = EnterRingTossZone **67108425** / RecordRingTossResult **67649660** / LeaveRingToss **67046402**
// C2S: EnterRingTossZone = u64 gadgetuid (无 0xFF 标记,裸 primitive)
// RecordRingTossResult = u64 gadgetuid + WriteStruct(RingTossThrowResult)
// LeaveRingToss = u64 gadgetuid + bool abort
// RET: EnterRingTossZone → **裸 u64 = pid**(客户端 `self.myPid = pid` 只在非 0 时才赋值)
// RecordRingTossResult → void ;LeaveRingToss → void
// ★ 套圈**不需要任何推送**:计分全在客户端本地
// (`LX6/MiniGame/RingToss/RingTossNetMgr.lua` 202 行,是该目录**唯一**文件
// ⇒ 纯本地玩法的指纹;`RING_TOSS_SINGLE_RESULT` → `OnLocalThrowSettled` → 本地算 rewardDropId,
// 再 `RecordRingTossResult` 上报;服务端若要「纠正」才回 `SyncGameGroundZoneInfo`)。
// ⇒ 三个 handler + 一个非零 pid 即可开玩。
// 【组队对局入口 PartyMiniGameMatch】mid = AskStart **63517279** / AskConfirm **63947581** / AskCancel **63953381**
// C2S: AskStart = u32 gameconfigid
// AskConfirm = u32 gameconfigid + bool confirmed
// AskCancel = u32 gameconfigid
// RET: 三条**全是 void**
// ★ 这是龙栖村所有摊位的**公共入口**(对照包唯一与我方有差异的三条),
// 缺了它 = 按 F 之后模态永久等待 = 只能大退。优先级最高。
// ⚠️ 提醒:回 void = 客户端以为「已受理」,但真正开局还需要服务端推
// `SyncPartyMiniGameMatchRoomReady(68130087)` 那一族(对照包也只做到「接住」,没推)。
// 本轮先解「硬卡死」;「对局内同步」是更深一层。
// 【刮刮乐 Scratch】mid = AskStart **67062589** / AskLose **67829420** / AskFinish **67996302**
// C2S: AskStart = u64 gadgetid + **byte** scratchsubtype(`CheckEnum(subtype, 52, 1)` ⇒ 枚举上限 52、默认 1)
// AskLose = u64 gadgetid
// AskFinish = u64 gadgetid + u32 rewardmoney
// RET: 三条**全是 void**
// ⛔ 这三个在 2349 个 `.lua` 里 **0 命中**(`AskStartScratchGame` 无调用者)
// ⇒ 入口不在 Lua 层(IL2CPP / Spoon),但**协议形状**在 AutoGen 里是确定的,照它实现。
// 【五子棋 Gomoku】mid = EnterGomokuZoneDoublePlayer **67377693** / PlaceGomokuPiece **67496058** / LeaveGomoku **67832026**
// C2S: EnterDoublePlayer = u64 gadgetuid + bool canuseskill
// PlaceGomokuPiece = u64 gadgetuid + u32 x + u32 y
// LeaveGomoku = u64 gadgetuid
// RET: 三条**全是 void**
// 另有 QueryGomokuPlayerInfo(67225667) = **无参**,RET 是 complex `Auto.Reader[676]`
// (两个 `Dictionary<uint,bool>`:DroppedEndGameIdSet / DroppedDoubleAIDifficultySet…)
// —— 本轮**不实现它**:返回值的字段序还没验,硬造一个空字典反而可能 deserialize failed。
// 留作 R91,且那时必须用「两次序列化字节一致 + 尾部探针」证明形状(铁律 39)。
// 【炒茶 / 炒栗 FryChestnut】mid = AskStartFryChestnutGame **67949200** / AskFinishChestnutGame **67543164** / AskFarmerFry **63258925**
// C2S: AskStartFryChestnutGame = u64 gadgetid + **byte** frytype(`CheckEnum(frytype, 91, 0)`)
// AskFinishChestnutGame = u64 gadgetid + **float** fryscore(`writer.WriteSingle`)
// AskFarmerFry = u32 ruleid + u32 count ← ★ 走 **ClientToGameDelegate**(Game 连接)
// RET: AskStartFryChestnutGame → **void**
// AskFinishChestnutGame → ★★ **裸 byte**(`local r0 = reader.ReadByte(reader)`)
// AskFarmerFry → ★★ **Dict7Bit&lt;u32,u32&gt;**(`Base.ReadDict7Bit(reader, ReadUInt32, ReadUInt32)`)
// ★ 这是本文件**唯一两组「不是 void」的返回**,必须 `ReturnRawAsync` 手拼载荷(不能泛型、不能空 ok)。
// ★ 炒茶/炒栗**确实需要服务端**:`FryTeaPanelStore.lua` 明调
// `gClientToGameSceneDelegate:AskFinishChestnutGame(gadgetid, result)` 与 `gClientToGameDelegate:AskFarmerFry(ruleId, count)`
// —— 与套圈(纯本地)/ 毽子(纯客户端,零 RPC)相反。
// 【被排除的(不要顺手加)】──────────────────────────────────────────────
// • 毽子 Shuttlecock:`ShuttlecockStartPanelStore.lua` 调 `game.StartGame(game)`,**零 RPC** ⇒ 不需要 handler。
// 面板齐全(StartPanel / PlayPanel / FinalPanel / QteBtn)。
// • `QueryGomokuPlayerInfo(67225667)`:RET 是 complex `Auto.Reader[676]`(两个 `Dictionary<uint,bool>`),
// 字段序未验 ⇒ 留 R91,本轮硬造可能 deserialize failed。
// • `SyncPartyMiniGameMatch*` 推送族:那是「首包 ACK 之后」的第二层,本轮只解硬卡死。
// ── 判空规律(本文件安全性依据)──────────────────────────────────────────
// ★★ 修正(R90 收尾)★★ 原先写的「除 EnterRingTossZone 外全是 void」**是错的**:
// 实测 `AskFinishChestnutGame` 回**裸 byte**、`AskFarmerFry` 回 **Dict7Bit**。
// ⇒ 现规律是:**只有这三个需要手拼返回载荷** —— `EnterRingTossZone`(裸 u64)、
// `AskFinishChestnutGame`(裸 byte)、`AskFarmerFry`(空字典 `FF 01`);
// 其余全部是 void,`ReturnEmptyOkAsync` 就是客户端期望的 `err=0` 空应答。
// ⛑ 回滚:把本文件涉及的 mid 从 `GameRouter.cs` 的 `Enabled4229938MethodIds` 删掉即可
// (handler 不注册 = 走 typed-default 兜底),不用删本文件。
// 更细的回滚粒度见 miniGameStalls.stallHandlers 三个开关(见下)。
//
// ─────────────────────────────────────────────────────────────────────
// ① 套圈 RingToss —— 三件套,其中 Enter 回裸 u64
// ─────────────────────────────────────────────────────────────────────
//
// EnterRingTossZone(67108425): C2S = u64 gadgetuid;RET = **裸 u64 pid**。
// ⛔ 必须用 `ReturnAsync(byte[])` 手工拼 8 字节小端 u64,**不能** `ReturnAsync(ulong)`:
// 泛型路径会把顶层再套一层 `0xFF + Int7(len)`,客户端按裸 u64 读就走位。
// 这与气球里 `NotifyRawAsync` 的理由同源(见 GameRouter.Balloon.cs)。
//
func GameRouter_MiniGameEntry_EnterRingTossZoneAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_EnterRingTossZoneAccept
var zero any; return zero
}
//
// RecordRingTossResult(67649660): C2S = u64 gadgetuid + WriteStruct(RingTossThrowResult);RET = void。
// 载荷:ThrowIndex(int) + RewardDropId(?) —— 只记日志,不参与判定(本地玩法,服务端只做记账)。
//
func GameRouter_MiniGameEntry_RecordRingTossResultAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_RecordRingTossResultAccept
var zero any; return zero
}
// LeaveRingToss(67046402): C2S = u64 gadgetuid + bool abort;RET = void。
func GameRouter_MiniGameEntry_LeaveRingTossAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_LeaveRingTossAccept
var zero any; return zero
}
// ─────────────────────────────────────────────────────────────────────
// ② 组队对局入口 PartyMiniGameMatch —— 龙栖村所有摊位的公共入口(优先级最高)
// ⚠️ 走 ClientToGameDelegate(Game 连接),不是 GameScene。
// ─────────────────────────────────────────────────────────────────────
//
// AskStartPartyMiniGameMatch(63517279): C2S = u32 gameconfigid;RET = void。
// 客户端 await 这个回执;不回 ⇒ 模态界面永久等待 ⇒ 只能大退。
//
func GameRouter_MiniGameEntry_AskStartPartyMiniGameMatchAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskStartPartyMiniGameMatchAccept
var zero any; return zero
}
// AskConfirmPartyMiniGameMatch(63947581): C2S = u32 gameconfigid + bool confirmed;RET = void。
func GameRouter_MiniGameEntry_AskConfirmPartyMiniGameMatchAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskConfirmPartyMiniGameMatchAccept
var zero any; return zero
}
// AskCancelPartyMiniGameMatch(63953381): C2S = u32 gameconfigid;RET = void。
func GameRouter_MiniGameEntry_AskCancelPartyMiniGameMatchAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskCancelPartyMiniGameMatchAccept
var zero any; return zero
}
// ─────────────────────────────────────────────────────────────────────
// ③ 刮刮乐 Scratch —— 入口在 IL2CPP/Spoon(Lua 0 命中),但协议形状确定
// ─────────────────────────────────────────────────────────────────────
//
// AskStartScratchGame(67062589): C2S = u64 gadgetid + **byte** scratchsubtype;RET = void。
// ⚠️ subtype 是 CheckEnum(subtype, 52, 1) ⇒ 编码宽度 1 字节、枚举上限 52、默认 1。
//
func GameRouter_MiniGameEntry_AskStartScratchGameAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskStartScratchGameAccept
var zero any; return zero
}
//
// ★★★ 把刮刮乐面板「开局数据」推给客户端(SyncStartScratchGameZone 68649573)。
// ── wire(RPCDeserializeAuto.lua:6475 的 midToReader[68649573])─────────
// u64 gadgetUid + byte scratchSubType + tag byte(Dispatch[510]) + 子类字段。
// 本方法只走 **LevelOne(subType=1 / tag=2)**:这是唯一我们把配表数据摸清的子类型。
// body 是三种异构字段的串联,**不能**用泛型 NotifyAsync&lt;T&gt;(那会给整个对象套一层
// 0xFF/complex 壳)⇒ 必须手拼再 NotifyAsync(mid, byte[], token)。
// ── 为什么收 TcpSession 而不是 Connection ────────────────────
// 两条调用路径都要用:① handler 里(有 conn)② 摊位摆位 tick 里(只有 TcpSession)。
//
func GameRouter_MiniGameEntry_PushScratchStartAsync(ctx context.Context, session *GameSession, gadgetUid uint64, subType byte) any {
// Router implementation for GameRouter_MiniGameEntry_PushScratchStartAsync
var zero any; return zero
}
// AskLoseScratchGame(67829420): C2S = u64 gadgetid;RET = void。
func GameRouter_MiniGameEntry_AskLoseScratchGameAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskLoseScratchGameAccept
var zero any; return zero
}
// AskFinishScratchGame(67996302): C2S = u64 gadgetid + u32 rewardmoney;RET = void。
func GameRouter_MiniGameEntry_AskFinishScratchGameAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskFinishScratchGameAccept
var zero any; return zero
}
// ─────────────────────────────────────────────────────────────────────
// ④ 五子棋 Gomoku —— 入口同样在 IL2CPP/Spoon
// ─────────────────────────────────────────────────────────────────────
//
// EnterGomokuZoneDoublePlayer(67377693): C2S = u64 gadgetuid + bool canuseskill;RET = void。
// 注意:**RET 是 void**(与套圈的裸 u64 不同)—— 对客户端来说回空 ok 即可。
//
func GameRouter_MiniGameEntry_EnterGomokuZoneDoublePlayerAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_EnterGomokuZoneDoublePlayerAccept
var zero any; return zero
}
// PlaceGomokuPiece(67496058): C2S = u64 gadgetuid + u32 x + u32 y;RET = void。
func GameRouter_MiniGameEntry_PlaceGomokuPieceAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_PlaceGomokuPieceAccept
var zero any; return zero
}
// LeaveGomoku(67832026): C2S = u64 gadgetuid;RET = void。
func GameRouter_MiniGameEntry_LeaveGomokuAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_LeaveGomokuAccept
var zero any; return zero
}
// ─────────────────────────────────────────────────────────────────────
// ⑤ 炒茶 / 炒栗 FryChestnut —— ★ 这一组**不是 void**,是本文件唯一两个需要手拼返回载荷的
//
// 客户端调用点(实测,不是推断):
// • `LX6/SGUI/StoreDefine/FryTeaPanelStore.lua` → `gClientToGameSceneDelegate:AskFinishChestnutGame(gadgetid, result)`
// (`result` 就是炒制得分,float)
// • 同一族 → `gClientToGameDelegate:AskFarmerFry(ruleId, count)`(Game 连接,不是 GameScene)
// ⇒ 炒茶/炒栗**确实需要服务端**(与套圈/毽子那种纯本地玩法相反)。
//
// ★★ 返回值形状(`RPCDeserializeAuto.lua` 逐行核过):
// AskStartFryChestnutGame(67949200) → `function(reader) end` **空体 = void** ✅
// AskFinishChestnutGame (67543164) → `local r0 = reader.ReadByte(reader)` **裸 byte** ⚠️
// AskFarmerFry (63258925) → `Base.ReadDict7Bit(reader, u32, u32)` **7bit 字典** ⚠️
// ⇒ 后两个如果照抄 `ReturnEmptyOkAsync`(0 字节)**必然**走位上崩:
// 裸 byte 会读到「没有字节」;字典会读到 echo 里的下一个字节当存在标记。
// ⇒ 更不能走 `ReturnAsync<T>`:泛型路径会加 `0xFF + Int7(len)` 外层框架(铁律:裸值不套壳)。
// 必须 `ReturnRawAsync(byte[])` 手拼。
//
// ★ 空字典的字节取 `FF 01`(**不是** `FF 00`):
// `RPCDeserializeBase.lua:173-187` 的 `ReadDict7Bit` = 读 1 字节存在标记(0=null / 非0=有值)
// → `reader:ReadInt7Bit()`。而 `ReadInt7Bit` 与 `ReadInt7Bit` 同源的那套(我方 UxReader.Int7 =
// `ReadVarInt() - 1`)带 **+1 偏置** ⇒ 0 个元素在线上是 `01`。
// 写侧 `RPCSerializeBase.lua:260-273` 打 `SerializeObjectMarkCommon = 255 = 0xFF`,
// 然后 `Write7BitEncodedInt(count)`(同偏置)。⇒ `FF 01`。
// ⛔ `FF 00` 解出来是 −1 ⇒ 循环不跑但值可疑;`00`(null) 会被当成「没有这个字典」。
// 本机既有结论同源(`GadgetMethods4229938.cs` 的 MobilePlatformSyncInfo.Players 注释:
// 「空字典是 `01 01`」「`FF 01` 是对的」「`01 00` 因偏置变 −1」)。
//
// ⛑ 为什么 AskFinishChestnutGame 回 byte 0 而不是别的:
// 客户端拿到的是**本次炒制的结算编号/结果码**,离线语义下我们没有派生规则。
// 0 = 「服务端不做纠正」,与套圈「回本地 pid」是同一种「让客户端自己算」的策略。
// 若实测发现 0 会让面板卡住,改 <c>miniGameStalls.chestnutFinishResult</c> 即可,不用改代码。
// ─────────────────────────────────────────────────────────────────────
//
// AskStartFryChestnutGame(67949200): C2S = u64 gadgetid + **byte** frytype;RET = **void**。
// ⚠️ frytype 是 CheckEnum(frytype, 91, 0) ⇒ 编码宽度 1 字节、枚举组 91、默认 0。
//
func GameRouter_MiniGameEntry_AskStartFryChestnutGameAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskStartFryChestnutGameAccept
var zero any; return zero
}
//
// AskFinishChestnutGame(67543164): C2S = u64 gadgetid + **float** fryscore;RET = **裸 byte**。
// ⛔ 返回值**不是 void**:`midToReturnMessageReader[67543164]` 的函数体是
// `local r0 = reader.ReadByte(reader); return r0`。
// ⇒ 必须回 1 字节;空的回执会让客户端在「读结算码」这一步读到缓冲区末尾。
//
func GameRouter_MiniGameEntry_AskFinishChestnutGameAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskFinishChestnutGameAccept
var zero any; return zero
}
//
// AskFarmerFry(63258925): C2S = u32 ruleid + u32 count;RET = **Dict7Bit&lt;u32,u32&gt;**。
// ⛔ 走的是 **Game 连接**(`ClientToGameDelegate.lua:2130`),不是 GameScene。
// ⛔ 返回值**不是 void**:`Base.ReadDict7Bit(reader, ReadUInt32, ReadUInt32)`。
// 本轮回**空字典**(`FF 01`)——离线语义下没有「炒制规则→产出」的权威映射,
// 空字典 = 「服务端不调整」,客户端用自带的本地产出表(与套圈同策略)。
// ⛔ 为什么手拼 2 字节而不是用 ReturnAsync(new Dictionary&lt;uint,uint&gt;()):
// 泛型路径会给顶层**再套一层** `0xFF + Int7(len)`,客户端按 ReadDict7Bit 读就会
// 把外层 0xFF 当成「字典存在」、把外层长度当成元素个数 ⇒ 走位。
// 这与 EnterRingTossZone 必须手拼 u64 是同一个理由。
//
func GameRouter_MiniGameEntry_AskFarmerFryAccept(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_MiniGameEntry_AskFarmerFryAccept
var zero any; return zero
}