package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 龙栖村(新祈村)庙会摊位「布景」—— 服务端主动把摊位机关摆到官方世界坐标上。 // ★ 用户的原话:「现在能像别人服务那样把龙栖村所有的摊位布景都一起刷出来吗」 // ── 与对照包的关系(第 89 轮已核实,别再多花时间)──────────────────────────── // 对照包 `DRMK-PS_08.05_Lyco-v0.2.1` 里**没有任何摊位摆位代码**: // grep SyncGadgetAdd|GadgetEntityInfo|MiniGameStall 只命中它自己的 // GameRouter.cs / GameRouter.SceneDeviceState.cs / MethodId.cs。 // 它只做了**打气球一个**小游戏的完整链路,其余摊位(套圈/刮刮乐/五子棋…) // 连 wire 契约都没有(它的 Drmk.RpcTypes/Methods/Game/ 只有 BalloonZone + RacingZone)。 // ⇒ 「像别人那样把摊位刷出来」这件事,**我方是走在前面的一方**,不是补课。 // 对照包它自己也只有一句「入口空 ACK」,摊位本体照样看不见。 // ── 为什么必须服务端摆(与健身房器材 / 派对家具 / 补货物件完全同因)──────────── // 这批摊位机关(Gadget_DaQiQiu 912387408 / Gadget_RingToss 1937674505 / // ScratchGame 165071345 / Gadget_WuZiQi 2066229997 …)的真身分两半: // ① 客户端 Spoon 图里有一个 GADGET 节点(StartBalloonNode / StartRingTossNode …) // ② 关卡块(vfc_1479 / vfc_859)里有一份**官方世界摆位** // 而它**不在**客户端会自己实例化的清单里 —— grid-AOI 下发(68956797)**不驱动** // LX6.Item.GadgetBlob.OnAoiAddBlobCfgByGridIndex ⇒ 一件都不会自己出现。 // 唯一可行路径 = 服务端拿 pathId + 官方坐标显式走 SyncGadgetAdd。 // ── 数据来源(别再搞混两套 id 空间)──────────────────────────────────────── // pathId = ClientData/4229938/Configs/GadgetCatalog.json(2935 条,服务端用) // ⛔ 不是客户端 GadgetConfig.json 的 1367 条(另一套; // 同一件 Gadget_DaQiQiu 在那边是 79304020,在服务端 catalog 是 912387408) // 世界坐标 = Configs/MiniGameStalls.json(解自关卡块,工具 // gym_tools/_r87_minigame_gadget.py,记录结构 = 命中点-28 起头, // +0/+4/+8 = f32 x/y/z,+16 = yaw,+28 = pathId) // ── 判据(回答「是不是要按 F 才开始摆」)────────────────────────────────── // **不是。** 摆位是「玩家走进锚点半径」触发的常驻世界装点,一次会话每片区只摆一次。 // 与玩家有没有交互、摊位开没开、Lua 有没有跑到那里**都无关** —— 这是纯服务端行为。 // 反过来说:**看得见摊位 ≠ 摊位能玩**。能玩要各自的小游戏入口 RPC // (打气球 EnterBalloonZone 已实现;套圈 EnterRingTossZone 67108425 见下)。 // ⛔ PackedGadgetInfo.iScale 必须是 **0**(100 ⇒ transform scale (0,0,1) 零尺寸空壳, // 看得见实体、看不见模型)。与气球的 DestructibleInfo.IScale = 1000 是 // **同名不同尺度的两个字段**(铁律 39;本条在 R89 上真踩过一次,别再来)。 // Rollback: world.content.miniGameStalls.enabled = false(config 热重载,不需重编译)。 // func GameRouter_MiniGameStall_MiniGameStallStateOf(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_MiniGameStall_MiniGameStallStateOf var zero any; return zero } // 重连 / 切场景:忘掉已摆记录,下次进图重摆(客户端那边实例已随会话消失)。 func GameRouter_MiniGameStall_ResetMiniGameStall(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_MiniGameStall_ResetMiniGameStall var zero any; return zero } // // 由网格 AOI tick 调用(它已经知道玩家位置)。把玩家走进的每个摊位片区的机关摆一次。 // func GameRouter_MiniGameStall_TickMiniGameStallAsync(ctx context.Context, session *GameSession, state any, px float32, pz float32) any { // Router implementation for GameRouter_MiniGameStall_TickMiniGameStallAsync var zero any; return zero } // // ★★★ 玩家走近刮刮乐桌 ⇒ 推一条 SyncStartScratchGameZone(68649573),把面板叫出来。 // ── 为什么必须服务端「主动」推 ──────────────────────────────────────── // 客户端**不发** AskStartScratchGame:它在 2349 个 Lua 文件里 0 命中, // 9 份 packets-*.log 里也 0 命中(服务端 handler 已登记白名单、从未被调用过)。 // 而刮刮乐面板的**唯一**入口是服务端推 SyncStartScratchGameZone: // GameSceneToClientImpl.lua:1131 → gScratchGameManager:Show(gadgetUid, subType, result) // ScratchGameManager.lua:7:result == nil ⇒ return;否则 // GetEntitySearchByInstanceId(gadgetUid) → RegisterWaitLoad → // Find("ItemViewNode/UIPose") → CheckShow(SCRATCHCARD_PANEL, …)。 // ⇒ 服务端不推,「通用交互提示」永远只是提示、没有选项。 // 详细 wire 见 GameRouter.MiniGameEntry.PushScratchStartAsync。 // ── 迟滞(别在边界上连推)──────────────────────────────────────────── // 进圈(dist ≤ ScratchOfferRadius)推**一次**;出圈超过 ScratchRearmRadius // 才复位,允许再进来再推。玩家一直站在圈里 ⇒ 只推一条。 // ── 前提 ───────────────────────────────────────────────────────── // 桌子的 gadgetUid 必须是**服务端自己 mint 的那个**([GADGET] add id=…)—— // 配表里 UniqueId = 0,所以只有 SpawnGadgetAsync 的返回值可信。 // 没摆到(uid==0)或走 AddList 批量通道(那条路目前不回收 id)⇒ 直接返回,什么都不做。 // Rollback: world.content.miniGameStalls.scratchAutoOpen = false(配置热重载,不用重编译)。 // func GameRouter_MiniGameStall_MaybeOfferScratchAsync(ctx context.Context, session *GameSession, owner any, px float32, pz float32) any { // Router implementation for GameRouter_MiniGameStall_MaybeOfferScratchAsync var zero any; return zero } type MiniGameStallSessionState struct { SyncRoot any `json:"syncRoot"` // 摊位片区本轮已摆过的名字。一次会话每片区一次,和健身房 / 营地一样。 Placed []string `json:"placed"` PlacedCount int32 `json:"placedCount"` // 空表只抱怨一次,不每 tick 刷屏。 WarnedEmpty bool `json:"warnedEmpty"` // 只记一次「跳过了几件」的明细,避免每片区重复。 SkipLogged []int32 `json:"skipLogged"` // ── ★ 刮刮乐「走近自动叫面板」的状态 ────────────────────────────── // 服务端自己 mint 出来的 ScratchGame 桌 instanceId(`[GADGET] add id=…`)。 ScratchGadgetUid uint64 `json:"scratchGadgetUid"` ScratchX float32 `json:"scratchX"` ScratchZ float32 `json:"scratchZ"` // 上一刻玩家是否在圈内(迟滞用)。 ScratchNear bool `json:"scratchNear"` // 本轮是否已推过(进圈只推一次)。 ScratchOffered bool `json:"scratchOffered"` }