220 lines
11 KiB
Go
220 lines
11 KiB
Go
package catalogs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
)
|
||
|
||
var _ = json.RawMessage{}
|
||
var _ = sync.Mutex{}
|
||
var _ = os.ReadFile
|
||
var _ = filepath.Join
|
||
|
||
//
|
||
// 一次景点 / 电梯传送(AskEnterRaidByMapEntrance)的目的地。
|
||
// Resolved = false 表示推导失败,X/Y/Z
|
||
// 退回成入口自己那一格(也就是「原地不动」),Note 写明原因。
|
||
// 退回而不是不推 SyncTeleport,是因为「不推」正是黑屏的根因(见下)。
|
||
// EntranceId = 落点那一层的那扇门的 MapentranceConfig.Id。
|
||
// ⛑⛑ 2026-09-30 二修:它就是「进电梯不黑屏、但落地还在原地」的根因所在 ——
|
||
// SyncTeleport 必须把目的地的门 id 填进 MapEntranceId,
|
||
// 不能填玩家按下的那扇起点门。见 TryResolve 上的长注释。
|
||
//
|
||
type JiguanDestination4229938 struct {
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
Z float32 `json:"z"`
|
||
Facing float32 `json:"facing"`
|
||
EntranceId uint32 `json:"entranceId"`
|
||
RaidId uint32 `json:"raidId"`
|
||
Resolved bool `json:"resolved"`
|
||
Note string `json:"note"`
|
||
}
|
||
|
||
//
|
||
// 景点 / 电梯传送门(MapentranceConfig.Type == 1)的目的地推导。
|
||
// 2026-09-30 新增,修「进电梯黑屏、ESC 返回不了」。
|
||
// <h3>症状与根因</h3>
|
||
// 客户端 RpcUtils.AskEnterRaidByMapEntrance(E:\lua\lua\LX6\Utils\RpcUtils.lua:88):
|
||
// <code>
|
||
// preTeleportOption.beforeResName = cfg.JiguanStartTimeline -- "loading_elevator02"
|
||
// preTeleportOption.beforePosition = cfg.Coordinate -- 电梯门自己的位置
|
||
// preTeleportOption.loadingResName = cfg.JiguanLoadingTimeline -- 337 是 ""
|
||
// preTeleportOption.afterResName = cfg.JiguanEndTimeline -- "loading_general_end"
|
||
// extParams.mapEntranceId = mapEntranceId
|
||
// gLoadingManager:AskTeleport(LoadingConfig.EnterExitRoomId=2, option, extParams, cb)
|
||
// -- cb 里(= ReportPreTeleportFinish 被 ack 之后):
|
||
// gClientToGameDelegate:AskEnterRaidByMapEntrance(mapEntranceId)
|
||
// </code>
|
||
// 注意 afterPosition 是全零、customAfterTrans=false —— 客户端不知道目的地,
|
||
// 它等的是服务端那一帧 SyncTeleport。而 2026-09-30 01:07:55 的实测日志里,
|
||
// 服务端 handler 只 ReturnEmptyOkAsync ⇒ 客户端永远停在 loading(黑屏,ESC 无效),
|
||
// 86 秒后 ReportPostTeleportFinish result=2 放弃。
|
||
// 对照 01:07:39 成功那次(地铁 / 大地图传送):客户端发 AskPlayerChangePositionByScenePortal,
|
||
// 服务端推 SyncTeleport ⇒ result=0。
|
||
// <h3>目的地从哪来</h3>
|
||
// 客户端只给 JiguanTeleportPos(Spoon 航点 id,如 1917305)与
|
||
// JiguanTeleportPosName(如 TeleportPos_337_338_339_target)。
|
||
// 本地没有 Spoon 图(SceneSpoonNames 只有 Chaos_bulbfly* 那几个),
|
||
// 所以拿不到航点坐标。但 MapentranceConfig 自己就把答案写全了:
|
||
// 电梯是「一个竖井、每层两个门」的结构,门的坐标就是那一层的坐标,
|
||
// 门的目标就是它去的那一层。以绿屿公馆(HouseConfig 11200003)为例:
|
||
// <code>
|
||
// 337/338/339 (2257.78, 0, 1448~1458) → 1917305 ┐ 一层(大厅)
|
||
// 340/341/342 (2257.78, 0, 1448~1458) → 1917531 ┘ 同坐标、目标不同
|
||
// 343 (2277.54, 160.6, 1412.50) → 1917575 高楼层(= VisitorEntrance 附近)
|
||
// 344 (2277.54, 160.6, 1412.50) → 1917531
|
||
// 345 (2257.81, -18.9, 1458.17) → 1917575 地下车库
|
||
// 346 (2257.81, -18.9, 1458.17) → 1917305
|
||
// </code>
|
||
// 于是:目标 T 所在的那一层 = 本组所有楼层里,唯一「没有门去往 T」的那一层。
|
||
// (337 的目标 1917305 被「大厅 + 车库」的门指向 ⇒ 剩下高楼层就是它。)
|
||
// 分组的连接规则三条(任一条成立即同组):
|
||
// ① 目标 id 相同(337 与 346 都去 1917305);
|
||
// ② 坐标几乎相同(337/338/339 与 340/341/342 同在大厅);
|
||
// ③ 三维距离 < gameplay.mapEntrance.jiguanGroupRadius(默认 60m,跨「大厅↔高楼层 168m」不够,
|
||
// 但靠 ① / ② 已经连通;60m 是为了兜住 317/318 这类只有两个门的短竖井)。
|
||
// <h3>已验证</h3>
|
||
// 2026-09-30 用 Python 把 93 行配置全跑了一遍(同一套规则):
|
||
// 45 个 Type==1 门里 41 个带 JiguanTeleportPos,全部推导出唯一/可质心化的目的地;
|
||
// 4 个只有 Spoon 名字没 id 的(326/327 AlanA、319/320/321 同一目标多门)落到退回分支。
|
||
// 337 → (2277.54, 160.60, 1412.50),与 HouseConfig 11200003.VisitorEntrance
|
||
// (2281.36, 160.73, 1411.68) 相距约 4m(同一个高楼层平台)。
|
||
// <h3>★★ 2026-09-30 二修:坐标对了,人还是没动 —— 缺的是「目的地门 id」</h3>
|
||
// 一修之后 packets 里看得到正确落点,客户端也回了
|
||
// ReportPostTeleportFinish result=0(黑屏解了),但玩家站在原地。
|
||
// 对照同一次会话里成功的那一帧(01:28:47 大地图传送):
|
||
// <code>
|
||
// 成功:SyncTeleport Position=(2287.65,0.01,1471.65) MapEntranceId=215 ← 215 就是目的地入口
|
||
// 失败:SyncTeleport Position=(2277.54,160.60,1412.50) MapEntranceId=337 ← 337 是玩家按的那扇门
|
||
// </code>
|
||
// 差别只有 MapEntranceId。客户端侧 LX6.GUI.CommonFuncTeleportFlowInfo
|
||
// (dump TypeDefIndex 9525)持有 preTeleportOption / teleportOption 两份,
|
||
// 并且有 public uint AfterTargetMapEntranceId 与
|
||
// InitAfterData() / ReParse_AfterData() —— ReParse_AfterData 不吃参数,
|
||
// 只能读 teleportOption(也就是服务端推下来的那一帧)去重算落地数据。
|
||
// 而本条链路里客户端自己的 afterPosition 是全零、customAfterTrans=false
|
||
// ⇒ 它只能靠 teleportOption 反推 ⇒ 拿到 337 的坐标 = 大厅 = 原地。
|
||
// (成功那条链路客户端自己填了 afterPosition,两个来源一致,所以从来没暴露过这个坑。)
|
||
// 修法:SyncTeleport.MapEntranceId 填落点那一层的门 id,Position 也换成
|
||
// 那扇门自己的坐标(不再用质心)—— 两个来源逐字节一致,走哪条分支都对。
|
||
// 绿屿公馆对照:337/338/339 → 343(高楼层);340/341/342 → 345(车库);346 → 343;344 → 345。
|
||
// 观澜北岸对照:347/348/354 → 351(高楼层);349/350/352 → 353(车库);351/353 → 347(大厅)。
|
||
// <h3>回滚</h3>
|
||
// gameplay.mapEntrance.jiguanElevator=false ⇒ 逐字节回到「只 ack」的旧行为。
|
||
// gameplay.mapEntrance.jiguanEntranceIdMode="source" ⇒ 回到一修(只推坐标、仍填起点 id)。
|
||
//
|
||
const (
|
||
// jiguanGroupRadius 缺省 / 非法时的兜底值(米)。
|
||
JiguanElevatorCatalog4229938_DefaultGroupRadius float32 = 60
|
||
// 「这两条门在同一层」的坐标容差(米)。
|
||
JiguanElevatorCatalog4229938_SameFloorEpsilon float32 = 1.0
|
||
)
|
||
|
||
var (
|
||
JiguanElevatorCatalog4229938_cacheLock sync.RWMutex
|
||
JiguanElevatorCatalog4229938_cached = false
|
||
JiguanElevatorCatalog4229938_entries []any
|
||
)
|
||
|
||
// LoadJiguanElevatorCatalog4229938 loads catalog records from client config directory
|
||
func LoadJiguanElevatorCatalog4229938(configDir string) ([]any, error) {
|
||
JiguanElevatorCatalog4229938_cacheLock.RLock()
|
||
if JiguanElevatorCatalog4229938_cached {
|
||
defer JiguanElevatorCatalog4229938_cacheLock.RUnlock()
|
||
return JiguanElevatorCatalog4229938_entries, nil
|
||
}
|
||
JiguanElevatorCatalog4229938_cacheLock.RUnlock()
|
||
|
||
JiguanElevatorCatalog4229938_cacheLock.Lock()
|
||
defer JiguanElevatorCatalog4229938_cacheLock.Unlock()
|
||
if JiguanElevatorCatalog4229938_cached { return JiguanElevatorCatalog4229938_entries, nil }
|
||
|
||
filePath := filepath.Join(configDir, "JiguanElevatorCatalog.json.json")
|
||
data, err := os.ReadFile(filePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var raw struct {
|
||
Records []json.RawMessage `json:"records"`
|
||
}
|
||
if err := json.Unmarshal(data, &raw); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var result []any
|
||
for _, item := range raw.Records {
|
||
var m map[string]any
|
||
if err := json.Unmarshal(item, &m); err == nil {
|
||
result = append(result, m)
|
||
}
|
||
}
|
||
|
||
JiguanElevatorCatalog4229938_entries = result
|
||
JiguanElevatorCatalog4229938_cached = true
|
||
return result, nil
|
||
}
|
||
|
||
//
|
||
// 推导 这台电梯的落点。
|
||
// 返回 false 只代表「入口 id 不在表里」;其余所有失败路径都会
|
||
// 返回 true + JiguanDestination4229938.Resolved=false
|
||
// 的「原地不动」兜底,好让调用方永远能推出一帧 SyncTeleport 解开客户端的 loading。
|
||
// = gameplay.mapEntrance.jiguanLandingOverrides(可空)。
|
||
//
|
||
func JiguanElevatorCatalog4229938_TryResolve(sourceEntranceId uint32, groupRadius float32, destination JiguanDestination4229938, note string) bool {
|
||
// Catalog helper JiguanElevatorCatalog4229938_TryResolve
|
||
return false
|
||
}
|
||
|
||
// <inheritdoc cref="TryResolve(uint, float, out JiguanDestination4229938, out string)"/>
|
||
func JiguanElevatorCatalog4229938_TryResolve_2(sourceEntranceId uint32, groupRadius float32, overrides []JiguanLandingOverride, destination JiguanDestination4229938, note string) bool {
|
||
// Catalog helper JiguanElevatorCatalog4229938_TryResolve_2
|
||
return false
|
||
}
|
||
|
||
//
|
||
// 纯几何推导(不含落点覆盖)。见 TryResolve(uint, float, out JiguanDestination4229938, out string)
|
||
// 的完整说明。
|
||
//
|
||
func JiguanElevatorCatalog4229938_TryResolveCore(sourceEntranceId uint32, groupRadius float32, destination JiguanDestination4229938, note string) bool {
|
||
// Catalog helper JiguanElevatorCatalog4229938_TryResolveCore
|
||
return false
|
||
}
|
||
|
||
//
|
||
// 把「门标记坐标」换成「人真正站得住的那一格」。
|
||
// 优先级:(配置,热重载)>
|
||
// MeasuredLandings(内置实测表)> 几何推导出来的门坐标。
|
||
// 键都是 落点门 id(SyncTeleport.MapEntranceId 填的那个);
|
||
// 「原地不动」兜底时它等于起点门 id,所以也能按起点门 id 覆盖。
|
||
//
|
||
func JiguanElevatorCatalog4229938_ApplyLanding(dest JiguanDestination4229938, overrides []JiguanLandingOverride) JiguanDestination4229938 {
|
||
// Catalog helper JiguanElevatorCatalog4229938_ApplyLanding
|
||
var zero JiguanDestination4229938; return zero
|
||
}
|
||
|
||
// 两条门是否属于同一台电梯(同目标 / 同坐标 / 距离小于分组半径)。
|
||
func JiguanElevatorCatalog4229938_Linked(a MapEntranceEntry4229938, b MapEntranceEntry4229938, radius float32) bool {
|
||
// Catalog helper JiguanElevatorCatalog4229938_Linked
|
||
return false
|
||
}
|
||
|
||
func JiguanElevatorCatalog4229938_Distance(a any, b any) float32 {
|
||
// Catalog helper JiguanElevatorCatalog4229938_Distance
|
||
return 0
|
||
}
|
||
|
||
func JiguanElevatorCatalog4229938_Find(parent []int32, i int32) int32 {
|
||
// Catalog helper JiguanElevatorCatalog4229938_Find
|
||
return 0
|
||
}
|
||
|
||
func JiguanElevatorCatalog4229938_Union(parent []int32, a int32, b int32) {
|
||
// Catalog helper JiguanElevatorCatalog4229938_Union
|
||
}
|
||
|