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

305 lines
15 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 routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
//
// Teleport: inbound client/GM requests plus server-push primitives used by the admin panel.
// Lua (RPCSerializeAuto WriteTeleportOption/WritePreTeleportOption) is the wire truth;
// dump.cs TeleportOption/PreTeleportOption field order is mirrored 1:1 in TeleportMethods.
//
// 按「离玩家最近的那家店」选店(没有位置信息时兜底西门)。
func GameRouter_Teleport_NearestShop(ctx context.Context, playerPos any) any {
// Router implementation for GameRouter_Teleport_NearestShop
var zero any; return zero
}
// 入口与出口共用 config=8,靠 beforeResName 里的 in/out 区分方向。
func GameRouter_Teleport_IsExitCutscene(ctx context.Context, option any) bool {
// Router implementation for GameRouter_Teleport_IsExitCutscene
return true
}
//
// config=8 且 position 是全零时,照 donor 的做法用真实目标点填空:
// 进场填 Interior(工位)、离场填 Leave(街边),并同步 afterPosition / afterRot。
//
func GameRouter_Teleport_PrepareCarShopOption(ctx context.Context, conn any, option any, state any) any {
// Router implementation for GameRouter_Teleport_PrepareCarShopOption
var zero any; return zero
}
//
// ★★ 景点 / 电梯传送门(config == 2,MapentranceConfig.Type == 1)——
// 2026-09-30 三修:「进电梯不黑屏、但落地还在原地」的真正元凶在这里。
// <h3>前两修都改错了地方</h3>
// 一修:开始推 SyncTeleport ⇒ 黑屏解了(ReportPostTeleportFinish result=0)。
// 二修:SyncTeleport.MapEntranceId 从起点门改成落点门(343)⇒ 依旧原地
// (13:38:43 实机:下发门=343 落点=(2277.54,160.60,1412.50),玩家仍在
// [INDOOR] indoorId=332 绿屿公馆大厅)。
// <h3>真因(捐赠端在改装店上用实机日志证明过,见本文件顶部注释 ①)</h3>
// <blockquote>
// config=8 的 option.position / afterPosition 客户端填的是 (0,0,0),
// 过场结束时客户端会「应用 afterPosition」 ⇒ 不改写就把人和车甩到世界原点
// (donor 2026-09-22 18:28 实测,SyncChangeIndoor(4) 进店成功 2 秒后掉回家)。
// </blockquote>
// 电梯这条链路一模一样:RpcUtils.lua:88 只填了
// beforePosition = cfg.Coordinate、afterResName = JiguanEndTimeline,
// afterPosition 是全零、customAfterTrans = false。
// 而 SyncPreTeleportOption 我们一直是原样回显客户端那份 ⇒ 过场一结束,
// 客户端就把全零(或退化后的当前点)当成落点应用 —— 跟我们推的 SyncTeleport 无关。
// <h3>修法(照改装店 PrepareCarShopOption 的成型做法)</h3>
// 在 echo 之前原地改写 option:position / afterPosition 都填推导出来的
// 落点,customAfterTrans = true。目的地仍由
// JiguanElevatorCatalog4229938 从 MapentranceConfig 推。
// 起点门用 beforePosition 反查(客户端就是拿 cfg.Coordinate 填的,
// 不依赖 extParams 的解析 —— 那份契约我们还没有)。
// SyncTeleport 那一帧保留(它是解开 loading 的必要条件),但不再是落点来源。
// <h3>★★ 起点门从哪来:extParams 里的 JSON(2026-09-30 实机取证)</h3>
// 客户端 RpcUtils.lua:90-105 会把 extParams.mapEntranceId 一起塞进
// PreTeleportOption.extParams(一个 JSON 字符串)。13:38:37 实机报文:
// <code>
// {"configId":2, ..., "customAfterTrans":false, "afterPosition":{"X":0,"Y":0,"Z":0},
// "beforePosition":{"X":2257.781,"Y":0,"Z":1458.242}, "beforeRot":{"X":0,"Y":90,"Z":0},
// "extParams":"{\"mapEntranceId\":337}"}
// </code>
// ⛑ 光靠 beforePosition 是分不开的:337 与 340 的 Coordinate 完全相同
// (大厅同一个门位上下两个按钮),只有 EntranceFacing 差 180°(90 / -90)。
// 所以优先读 extParams;读不到才退回「位置 + beforeRot.Y 朝向」匹配。
// 回滚 = gameplay.mapEntrance.jiguanElevator=false。
//
// true = 这是电梯事务且已改写,调用方去 echo。
func GameRouter_Teleport_PrepareJiguanOption(ctx context.Context, conn any, option any) bool {
// Router implementation for GameRouter_Teleport_PrepareJiguanOption
return true
}
//
// 从 PreTeleportOption.extParams 里取 mapEntranceId。
// 客户端塞的是一个 JSON 字符串(实测 {"mapEntranceId":337};不是电梯事务时是 {})。
// 解析失败一律返回 0,由调用方退回坐标匹配。
//
func GameRouter_Teleport_TryReadMapEntranceIdFromExtParams(ctx context.Context, extParams any) uint32 {
// Router implementation for GameRouter_Teleport_TryReadMapEntranceIdFromExtParams
return 0
}
//
// 按坐标 + 朝向反查景点 / 电梯门(客户端把 cfg.Coordinate 填进 beforePosition、
// 把 cfg.EntranceFacing 填进 beforeRot.Y)。
// ⛑ 必须先按「位置 + 朝向」筛:绿屿大厅 337 与 340 的 Coordinate 完全相同
// (同一个门位的两个按钮),只有 EntranceFacing 差 180°(90 / -90),
// 而它俩的目的地一个是高楼层、一个是车库。只看坐标会挑到列表里靠后的那个。
//
func GameRouter_Teleport_FindJiguanDoorByPosition(ctx context.Context, p any, facing float32) any {
// Router implementation for GameRouter_Teleport_FindJiguanDoorByPosition
var zero any; return zero
}
//
// ★★ 车库出库事务(config == 7 且 beforeResName 含 out)的落点改写。
// 2026-09-30 —— 与景点电梯三修、改装店 PrepareCarShopOption 是同一个坑:
// 过场结束时客户端应用的是 afterPosition,而客户端自己那份算不出来。
// <h3>为什么客户端算不出来</h3>
// GarageManager.lua PlayLoadingCutscene 里(反编译后比较符方向已按语义还原):
// <code>
// afterWaypointName = houseCfg.GarageOutWaypoint -- Spoon 航点 id
// afterTLName = houseCfg.ExitTimeline[2]
// if afterWaypointName and afterTLName and afterTLName ~= "" then
// local afterPos = gSpoonMgr:GetWayPointPositionByNameOrId(afterWaypointName)
// if afterPos then ... preTeleportOption.afterPosition = afterPos ... end
// end
// </code>
// 两条都断:① 服务端没有 Spoon 位置表,客户端在私服里也查不到航点;
// ② 素光小筑(11200006)的 ExitTimeline 只有 1 个元素
// (["loading_garage_out03"],别的房都是 ["out01","out03"])⇒
// afterTLName = ExitTimeline[2] = nil ⇒ 整块跳过 ⇒
// afterPosition 留全零(实测 23:38:24 报文
// afterPosition:(0,0,0) customAfterTrans:false)⇒ 过场一结束就被甩到世界原点 / 黑屏。
// <h3>修法</h3>
// 原地改写 option,然后由调用方自己 echo 一份
// SyncPreTeleportOption(不走那个受 gameplay.mapEntrance 开关约束的
// 通用 echo 分支 —— 车库不该被景点配置牵连):position / afterPosition 都填
// ResolveGarageExitLanding 推出来的落点、customAfterTrans = true。
// 过场起点(beforePosition)客户端同样拿不到 Spoon 航点(也是全零),
// 用玩家最近上报的位置兜底 —— 出库时玩家正坐在车位上的车里,那就是车位格。
// <para>⚠️ PreTeleportOption 没有 MapEntranceId 字段(那是
// SyncTeleport 的 TeleportOption 才有),落点门 id 只在
// OnAskTeleportFromHouseGarage 推 SyncTeleport 那一步用。</para>
// 回滚 = gameplay.houseGarage.enabled=false(本方法直接返回,option 逐字节不变)。
//
// true = 这是车库出库事务且已改写,调用方去 echo。
func GameRouter_Teleport_PrepareGarageExitOption(ctx context.Context, conn any, option any) bool {
// Router implementation for GameRouter_Teleport_PrepareGarageExitOption
return true
}
// 把人 + 车一起挪到 dst/yaw(改装店进场/离场共用)。
func GameRouter_Teleport_PushPlayerAndVehicleAsync(ctx context.Context, conn any, state any, dst any, yaw float32, reason string) any {
// Router implementation for GameRouter_Teleport_PushPlayerAndVehicleAsync
var zero any; return zero
}
//
// 「这个 AskTeleport 事务要播 timeline」的判据:任一 timeline 资源名非空。
// 决定是否把客户端发来的 option 原样回显(见 MapEntranceSettings.EchoPreTeleport)。
// 实测数据点:地铁 config=5 → after=loading_metro_end_1;改装店
// config=8 → loading_garage_in02(后者走它自己的分支,不经过这里)。
//
func GameRouter_Teleport_HasTimeline(ctx context.Context, option any) bool {
// Router implementation for GameRouter_Teleport_HasTimeline
return true
}
// 玩家最近开过的那辆车(服务端 Proxy 生成/F1 召唤才有)。
func GameRouter_Teleport_LastDrivenVehicleFix(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_Teleport_LastDrivenVehicleFix
var zero any; return zero
}
func GameRouter_Teleport_ResolveShopVehicleId(ctx context.Context, session *GameSession) uint64 {
// Router implementation for GameRouter_Teleport_ResolveShopVehicleId
return 0
}
func GameRouter_Teleport_GetStateIfExists(ctx context.Context, session *GameSession) any {
// Router implementation for GameRouter_Teleport_GetStateIfExists
var zero any; return zero
}
// Client -> server: native loading-flow request. Accept it, remember the correlation
// ids, and let the client drive its own flow; admin pushes use the outbound path below.
func GameRouter_Teleport_AskTeleport(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_AskTeleport
var zero any; return zero
}
func GameRouter_Teleport_AskTeleportToParkingWaypoint(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_AskTeleportToParkingWaypoint
var zero any; return zero
}
func GameRouter_Teleport_AskTeleportToLeavingWaypoint(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_AskTeleportToLeavingWaypoint
var zero any; return zero
}
func GameRouter_Teleport_ReportPreTeleportFinish(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_ReportPreTeleportFinish
var zero any; return zero
}
func GameRouter_Teleport_ReportPostTeleportFinish(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_ReportPostTeleportFinish
var zero any; return zero
}
//
// 延迟补发一帧落点(jiguanHardSet 的第二发)。失败只记日志,不影响主流程。
// 走 TcpSession.NotifyAsync(uint, byte[], CancellationToken) +
// CancellationToken.None:本方法的调用者(handler)已经返回,请求上下文可能已失效。
//
func GameRouter_Teleport_ReassertJiguanLandingAsync(ctx context.Context, session *GameSession, unitId uint64, landing any) any {
// Router implementation for GameRouter_Teleport_ReassertJiguanLandingAsync
var zero any; return zero
}
//
// 延迟补发一帧车库出库落点(ReportPostTeleportFinish 硬设之后 500ms 的第二发)。
// 与景点电梯四修同一套路:loading 面板还在淡出时第一帧会被吞掉。
// 同坐标重复下发无副作用;失败只记日志,不影响主流程。
// 走 TcpSession.NotifyAsync(uint, byte[], CancellationToken) +
// CancellationToken.None:调用它的 handler 已经返回,请求上下文可能已失效。
//
func GameRouter_Teleport_ReassertGarageLandingAsync(ctx context.Context, session *GameSession, unitId uint64, landing any) any {
// Router implementation for GameRouter_Teleport_ReassertGarageLandingAsync
var zero any; return zero
}
//
// 当前该用哪个单位 id 发 SyncUnitPositionAndFacing。
// 与 TeleportPlayerInstantAsync 取同一个来源,保证补发那一帧打在同一实体上。
//
func GameRouter_Teleport_ResolveActiveUnitId(ctx context.Context, session *GameSession) uint64 {
// Router implementation for GameRouter_Teleport_ResolveActiveUnitId
return 0
}
// GM service Teleport(x, z): UXRPCMethodArgs62733679. Log it and treat it as a
// same-map XZ jump hint (Y stays at the last reported height).
func GameRouter_Teleport_GmTeleportXZ(ctx context.Context, conn any, msg any) any {
// Router implementation for GameRouter_Teleport_GmTeleportXZ
var zero any; return zero
}
//
// Instant move without a loading screen: SyncUnitPositionAndFacing (68030312) with the default
// SetPositionType.SwitchSpirit (3). Updates the tracked transform optimistically.
// ⚠️ Not SyncUnitPosition_P (68233969): that one is refused for the local avatar —
// NetworkGhostModule.ForceSetPositionAndFacing needs NetworkRoleType.Proxy, and the main
// character is not a proxy (Player.log: "Role(None)不可强设位置!!").
//
func GameRouter_Teleport_TeleportPlayerInstantAsync(ctx context.Context, rpcCtx *RpcContext, x float32, y float32, z float32, facing any, reason string) any {
// Router implementation for GameRouter_Teleport_TeleportPlayerInstantAsync
var zero any; return zero
}
//
// Loading-flow move: SyncPreTeleportOption (64167855) + SyncTeleport (64030755)
// with a fresh teleport id. The client is expected to answer with
// ReportPreTeleportFinish / ReportPostTeleportFinish (logged by handlers above).
//
func GameRouter_Teleport_TeleportPlayerWithLoadingAsync(ctx context.Context, rpcCtx *RpcContext, x float32, y float32, z float32, facing float32, reason string) uint64 {
// Router implementation for GameRouter_Teleport_TeleportPlayerWithLoadingAsync
return 0
}
func GameRouter_Teleport_GetWorldStateStatic(ctx context.Context, rpcCtx *RpcContext) any {
// Router implementation for GameRouter_Teleport_GetWorldStateStatic
var zero any; return zero
}
// 一趟电梯事务的落点(起终点门 id 只用于日志/排查)。
type PendingJiguanLanding struct {
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Facing float32 `json:"facing"`
SourceEntranceId uint32 `json:"sourceEntranceId"`
DestEntranceId uint32 `json:"destEntranceId"`
}
// ----------------------------------------------------------------------------------
// 改装店(ApplyCar / RepairShop)—— 2026-09-25 从俄语上游 donor 的 repair-shop.zip 移植。
//
// 入场链路(客户端 ApplyCarManager.lua:152 EnterRepairShop):
// 触发区(AskVehicleEnterArea) → CheckCanEnterCarShop → AskTeleport(config=8)
// → 服务端 echo SyncPreTeleportOption → 客户端播 beforeResName 时间轴
// → ReportPreTeleportFinish → **AskTeleportToParkingWaypoint**
// → 服务端推 SyncTeleport(人) + SyncTeleportVehicle(车) → ReportPostTeleportFinish。
//
// ⛑⛑ 两处 donor 用实机日志证明、不看代码绝对猜不出来的坑:
// ① config=8 的 option.position / afterPosition 客户端填的是 (0,0,0),
// 过场结束时客户端会「应用 afterPosition」⇒ 不改写就把人和车甩到世界原点
// (donor 2026-09-22 18:28 实测, SyncChangeIndoor(4) 进店成功 2 秒后掉回家)。
// ② 关键一步是分布 AskTeleportToParkingWaypoint / LeavingWaypoint 里推 *人的*
// SyncTeleport:只 ack 不推 ⇒ 客户端永远停在黑屏(17:11 / 17:31 实测),
// post-finish 永远不来。而且 pre-finish 里不能推(会把驾驶中的玩家拽到半空)。
// ----------------------------------------------------------------------------------
// 一间 Smokey Drift 的四个落成点(坐标见 ShopInteriors)。
type ShopAnchor struct {
Entrance TeleportVec3 `json:"entrance"`
Interior TeleportVec3 `json:"interior"`
Facing float32 `json:"facing"`
EnterPos TeleportVec3 `json:"enterPos"`
EnterYaw float32 `json:"enterYaw"`
Leave TeleportVec3 `json:"leave"`
LeaveYaw float32 `json:"leaveYaw"`
}