170 lines
9.3 KiB
Go
170 lines
9.3 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 赛车检查点门(R247 · 2026-10-01)—— 服务端显式摆放。
|
||
// 症状:整条赛道上一道绿框门都没有(用户:「开始赛车了,没有看到绿框门」)。
|
||
// 为什么没有:门是 gadget 对
|
||
// Racing_challenge01_CheckPoint / Racing_challenge01_CheckPointEnd,
|
||
// 官方**按检查点位置在运行时 spawn**;而检查点来自
|
||
// RacingDriverTrackInformationConfig.RaidSpoonName 指向的 Spoon 图 ——
|
||
// 这张图**从未合并进客户端**(SpoonMd5="" ⇒ SpoonRaidMgr 走
|
||
// #NoCreateIssue),所以官方那条链一次都没执行过,门**从来没人 spawn**。
|
||
// 不是坏了,是没装。
|
||
// 为什么必须服务端摆:R244 已经试过「客户端 Lua 自己
|
||
// gResourceManager:LoadAssetWithCallBack + GameObject.Instantiate」,
|
||
// 实机证据是 16/16 全部实例化成功、go0.active=true、scale=1/1/1、
|
||
// children=2、全程**零** Load asset FAILED —— 但**肉眼一道都看不见**。
|
||
// 这与「家具全无 / 健身器材 / 摊位补给」是同一个病根:**客户端不会自己实例化这类物件**,
|
||
// 必须由服务端经 gadget 通道下发。
|
||
// 通道:SpawnGadgetAsync(健身器材 / PropRestock / 摊位补给都走它,实测可见)。
|
||
// ⛔ iScale 必须传 0:100(GM 面板默认)出来是 transform scale (0,0,1)
|
||
// 的**零缩放空壳、没有 prefab** —— 正是保龄球球瓶「有物理有音效但看不见」的根因。
|
||
// 坐标:客户端 R225 已用 LoadRaceTrackData 采出 16 个检查点(沿弧长等分),
|
||
// R247 把它们与**服务端那套 pathId** 一起落到 config/racing-gates.json。
|
||
// 服务端读表摆放 —— 与健身器材 _gym_capture.py 的「离线采样 → 落配置 → 服务端摆」完全同构。
|
||
// 拿不到表时:在玩家四周摆 4 道「验证信标」。它的价值是把
|
||
// 「通道对不对」和「坐标对不对」两个变量解耦:看得见信标 ⇒ 通道没问题,只差坐标。
|
||
// 回滚:world.content.racingGates.enabled = false(不用重编译)。
|
||
//
|
||
// R255:把内置官方表变成摆位计划(含 YawOffset 修正)。
|
||
//
|
||
// ★★★ R256 —— **默认改用官方自己的检查点 prefab**(RacingGateCheckPointPathId),
|
||
// 不再用 R253 选的 Jobrace_gongmen(拱门)。
|
||
// <para>为什么换回去:config/racing-gates.json 里(客户端探针写的)
|
||
// 每个检查点本来就是 pathId 884611825 / 终点 271608686 ——
|
||
// 也就是 Res/Prefab/Gadget/Racing_challenge01_CheckPoint.prefab。
|
||
// 是 R253 起用 PathIdOverride=64493331 把它**覆盖**成了拱门。</para>
|
||
// <para>R254 的注释写过「884611825 资源从未随包 ⇒ 隐形」,**该判断已被推翻**:
|
||
// 关卡块 vfc_1479 里官方**实实在在地摆了 16 个该 prefab**
|
||
// (+2 个 ...End,见 _r256_gate_instances.py / _r190_racing_checkpoints.json)
|
||
// ⇒ 客户端一定能渲染它,资源必然随包。
|
||
// 当轮"看不见"的真因是 **yaw 算错**(见下)把门转到了错误方向/位置。</para>
|
||
// <para>切换开关:racingGates.pathIdOverride。
|
||
// 0(默认)= 官方检查点 prefab(终点自动用 ...End);
|
||
// 64493331 = 回退到 R253~R255 的拱门(此时终点同 prefab)。</para>
|
||
//
|
||
func GameRouter_RacingGates_BuildOfficialTrackGates(ctx context.Context, cfg any) []any {
|
||
// Router implementation for GameRouter_RacingGates_BuildOfficialTrackGates
|
||
return nil
|
||
}
|
||
|
||
//
|
||
// ⛔⛔ R258 定案:这个开关 = 「我们自己造的门宽」,不是官方数据 ⇒ 默认必须关。
|
||
// 用户的两条纠正(R258,决定了本开关的命运):
|
||
// 「<i>路宽不一定代表门宽啊,不要查错方向了</i>」+「<i>赛道的宽度也是根据检查点的宽度来计算的</i>」
|
||
// ⇒ 门宽是「门」这件物件自带的,赛道按它建;所以**没有**需要我们填的宽度,
|
||
// 也就不该有 pairMirror / pairOffset 这两个旋钮。
|
||
// R257 把它们打开,等于「我们自己在给门调宽」—— 已被明确叫停。
|
||
// R258 的官方摆位判据(这才是「一个实例够不够」的正确证法):
|
||
// gym_tools/_r190_racing_checkpoints.json 里官方静态摆了 **14 个**
|
||
// Racing_challenge01_CheckPoint,两两**最近距离 131.69 m、完全不成对**
|
||
// ⇒ 官方就是「**一个检查点摆一个实例**」。
|
||
// 再加上 gadget 注册表(vfc_907)里带「拱门」语义的物件**只有一个**
|
||
// (Jobrace_gongmen 64493331,其 prefab 自带 _left/_right/_up)
|
||
// ⇒ **一扇门 = 一个实例**,宽度由 prefab 自身决定,我们没有可填的数。
|
||
// R254 的假设(已推翻):看到画面只有一根立柱 + 顶端向内弯折 ⇒ 认为
|
||
// 一个 Jobrace_gongmen 实例只是**半扇拱门**,要同坐标摆两个(yaw 差 180°)才成整扇。
|
||
// R255d 的实测证据(推翻它):
|
||
// ① 资产扫描(`gym_tools/_r255c_gate_prefab_scan.py`,全库 1682 块)——
|
||
// vfc_1583 里 **同一个 prefab** 自带三件套:
|
||
// gp_arch_gongmen_blue_left(+`_left_lod2`)、
|
||
// gp_arch_gongmen_blue_right(+`_right_lod12`)、
|
||
// gp_arch_gongmen_blue_up(+`_up_lod`)⇒ **一个实例就是完整一扇门**。
|
||
// R254 当时看到的「只有右半」,最可能是**左半被棕榈树 / 那栋黑楼挡住**。
|
||
// ② 打开本开关后用户实测原话:「**门叠在一块了**」——
|
||
// 因为两个完整门被摆在**同一坐标**,只能互相 z-fighting 重叠。
|
||
// 结论:`pairMirror` 保持 **false**(配置 world.content.racingGates.pairMirror)。
|
||
// <para>⛔ R258 更正本条:R257 把 A、B 拆到对侧(-ox/-oz)本身没错,
|
||
// 但**整个「成对」模型是自造的**(见上)。官方每检查点只摆 1 个实例
|
||
// ⇒ 一扇门 = 一个 prefab,不需要第二个实例,也就没有「柱到中线的距离」要调。
|
||
// 所以 pairOffset 的真实语义只是「把整道门横向平移」,与「门宽」无关。</para>
|
||
//
|
||
func GameRouter_RacingGates_ExpandGatePairs(ctx context.Context, plan []any, cfg any) []any {
|
||
// Router implementation for GameRouter_RacingGates_ExpandGatePairs
|
||
return nil
|
||
}
|
||
|
||
// 同一个 session 只提醒一次「表还没有」——缺采集文件是设置步骤,不是运行错误。
|
||
func GameRouter_RacingGates_RacingGateNoticed(ctx context.Context, session *GameSession) bool {
|
||
// Router implementation for GameRouter_RacingGates_RacingGateNoticed
|
||
return true
|
||
}
|
||
|
||
//
|
||
// 在一局赛车开始时摆门。由 RunRacingAutoStartAsync 在 DISPLAY 推出去之后调用
|
||
// (倒计时 3 秒期间门就该在了)。
|
||
//
|
||
func GameRouter_RacingGates_SpawnRacingGatesAsync(ctx context.Context, session *GameSession, state any, trackId uint32, taskId uint32) any {
|
||
// Router implementation for GameRouter_RacingGates_SpawnRacingGatesAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// R294:撤这一局可能摆过的**所有**赛车门 pathId。
|
||
func GameRouter_RacingGates_RacingGatePathIdSet(ctx context.Context, cfg any) []any {
|
||
// Router implementation for GameRouter_RacingGates_RacingGatePathIdSet
|
||
return nil
|
||
}
|
||
|
||
//
|
||
// R294:撤掉**第 个检查点**那对门(A/B 两半)。
|
||
// 返回撤掉的个数(0 = 这个点本来就没摆 / 已经撤过)。
|
||
//
|
||
func GameRouter_RacingGates_RemoveCheckpointGatesAsync(ctx context.Context, session *GameSession, state any, cpIndex int32) int32 {
|
||
// Router implementation for GameRouter_RacingGates_RemoveCheckpointGatesAsync
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// R312:**真正的按坐标撤门**(纯执行,不含任何策略/宽限期/挂起逻辑)。
|
||
// <para>抽出来是为了让「R311 撤门宽限期」和「R312 前 N 个门延迟到第 N+1 个通过再撤」
|
||
// 两条策略都能调它,而**不会互相重入** —— 否则 `cpIndex < keepFirstN` 的那两个点
|
||
// 会在递归里被**再次挂起** ⇒ 永远撤不掉(自己给自己下套)。</para>
|
||
//
|
||
func GameRouter_RacingGates_RemoveCheckpointGatesCoreAsync(ctx context.Context, session *GameSession, state any, cpIndex int32, gateCfg any) int32 {
|
||
// Router implementation for GameRouter_RacingGates_RemoveCheckpointGatesCoreAsync
|
||
return 0
|
||
}
|
||
|
||
// R294:撤掉这一局摆过的**全部**赛车门(比赛结束 / 完成上报时调)。
|
||
func GameRouter_RacingGates_RemoveAllRacingGatesAsync(ctx context.Context, session *GameSession, state any) int32 {
|
||
// Router implementation for GameRouter_RacingGates_RemoveAllRacingGatesAsync
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// 读客户端 R247 落下的检查点表。文件不存在 / 解析失败都只是「还没有坐标」,
|
||
// 退回信标模式,绝不抛。
|
||
//
|
||
func GameRouter_RacingGates_LoadRacingGateTable(ctx context.Context, session *GameSession, cfg any) []any {
|
||
// Router implementation for GameRouter_RacingGates_LoadRacingGateTable
|
||
return nil
|
||
}
|
||
|
||
//
|
||
// 「验证信标」:在玩家四周各摆一道门。用途只有一个 ——
|
||
// 把「通道对不对」与「坐标对不对」解耦。看得见 ⇒ 通道没问题,只差那张坐标表。
|
||
//
|
||
func GameRouter_RacingGates_BuildRacingGateBeacons(ctx context.Context, state any, cfg any) []any {
|
||
// Router implementation for GameRouter_RacingGates_BuildRacingGateBeacons
|
||
return nil
|
||
}
|
||
|
||
// 一条待摆放的门。
|
||
type RacingGatePlacement struct {
|
||
PathId int32 `json:"pathId"`
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
Z float32 `json:"z"`
|
||
Yaw float32 `json:"yaw"`
|
||
Label string `json:"label"`
|
||
}
|
||
|