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/catalogs/jiguan_elevator_catalog4229938.go

220 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 同在大厅);
// ③ 三维距离 &lt; 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
}
//
// 把「门标记坐标」换成「人真正站得住的那一格」。
// 优先级:(配置,热重载)&gt;
// MeasuredLandings(内置实测表)&gt; 几何推导出来的门坐标。
// 键都是 落点门 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
}