280 lines
16 KiB
Go
280 lines
16 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 载具路线规划 / 自驾(2026-09-24 移植自第九份捐赠端 GameRouter.VehicleNav.cs,但图换成我们自己的)。
|
||
// ★★ 为什么值得做:`logs/unknown-methods.log` 里今天 06:05 UTC 连着 **89 次**
|
||
// `AskVehicleNavigationPathPointsFromPos`(每秒一次,起点在动、终点固定)——玩家正在开车跑导航,
|
||
// 而我们每条都回的是 `DefaultReturnCatalog` 的 typed default:**空点集 + 长度 0**。
|
||
// 也就是客户端在问「怎么开过去」,服务端一直答「不知道」。
|
||
// ★★ 为什么不搬捐赠端的 `vehicle_nav_graph.bin`(2.36MB):那份是他们自己 generator 从另一份
|
||
// 数据打的私有格式(magic `ANGV` / ver 2 / 每车道 40B),provenance 不可考;我们手上已经有
|
||
// `vehicle_lanes.csv`(14171 条) + `vehicle_lane_links.csv`(多值后继覆盖 98.2%,见
|
||
// `GameRouter.VehicleLanes.cs` 头注),用同一份数据算出来的路线才是自洽的。
|
||
// ⛑️ 我们这份表的几何是「弦」不是真实样条 —— `_gen_vehicle_lanes.py` 压掉了中间点。
|
||
// 所以画出来的导航线是折线、切内弯;想提高精度要把 lane 折线点补回来,不是改这里。
|
||
// ⛑️ 只走**前向**后继(本帧生成车道的方向),与 T3-7 车流 `Advance` 的规则一致;
|
||
// 客户端这次带的是 `IgnoreDirection=true`,它接受单方向绕行,不构成违约。
|
||
// ⛑️ 回滚开关 = `gameplay.vehicleNavigation.enabled=false` ⇒ 逐字节回到空结果(见 EmptyResult)。
|
||
//
|
||
func GameRouter_VehicleNav_EmptyResult(ctx context.Context, navReqId uint32) any {
|
||
// Router implementation for GameRouter_VehicleNav_EmptyResult
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 当前生效的巡航速度(米/秒)。
|
||
// 优先级:config/autonomous-driving.json(GM 面板写的) > gameplay.vehicleNavigation.cruiseSpeed。
|
||
// 前者不存在 = 没在 GM 里调过速 ⇒ 完全用主配置的值,与 2026-09-30 之前逐字节一致。
|
||
// ⛑ 单位换算(km/h → m/s)只在这里做;AutonomousDriveSpeedSettings 对外一律 km/h。
|
||
// ⛑ 文件被写坏时不能让玩家开不了车:吞掉异常退回主配置,GM 重新保存即可恢复。
|
||
//
|
||
func GameRouter_VehicleNav_EffectiveCruiseSpeedMs(ctx context.Context, ) float32 {
|
||
// Router implementation for GameRouter_VehicleNav_EffectiveCruiseSpeedMs
|
||
return 0
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------------------
|
||
// AskVehicleNavigationPathPoints (67317431) — 起点由服务端自己找(最后一次上报的位置)
|
||
// -----------------------------------------------------------------------------------------
|
||
func GameRouter_VehicleNav_OnAskVehicleNavigationPathPoints(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleNavigationPathPoints
|
||
var zero any; return zero
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------------------
|
||
// AskVehicleNavigationPathPointsFromPos (67650626) — 客户端自带起点(今天实际在用的就是这条)
|
||
// -----------------------------------------------------------------------------------------
|
||
func GameRouter_VehicleNav_OnAskVehicleNavigationPathPointsFromPos(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleNavigationPathPointsFromPos
|
||
var zero any; return zero
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------------------
|
||
// AskVehicleNavigationPathLength (67519267) — 只要里程(float)
|
||
// -----------------------------------------------------------------------------------------
|
||
func GameRouter_VehicleNav_OnAskVehicleNavigationPathLength(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleNavigationPathLength
|
||
var zero any; return zero
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------------------
|
||
// 自驾开关 Start / Stop / ChangeTarget / CancelTarget
|
||
//
|
||
// ★★ 2026-09-27 修「点了自动驾驶车不动」。根因不在 Start 的回执 —— 客户端的自动驾驶
|
||
// 开关是 **服务端推下来的 S2C**,不是本地状态:
|
||
// BaseVehicleControllerStore.OnAutoDriveBtnClick → AskVehicleStartAutonomousDriving
|
||
// GameSceneToClientImpl:1438 SyncVehicleAutonomousDrivingState(u64 车, bool isStart)
|
||
// → DriveVehiclesManager.autoDrivingVehicleDict[车] = true / nil
|
||
// → BaseVehicleControllerStore.SetAutoDriving(GetVehicleInAutonomousDriving(车))
|
||
// 真正的循迹 AI 在客户端 C# 的 LX6.Drive.DriveManager(包是 LuaAndCSharp,两边都收)。
|
||
// ⇒ 只要服务端不发 68349264,UI 上那个按钮按下去会立刻弹回来、车纹丝不动。
|
||
// ⇒ 反过来也解释了为什么 previously「挂了 Start/Stop 回执」毫无效果:回执只影响
|
||
// `AutoDrive.AskWait`,不影响 Driving 状态。
|
||
//
|
||
// ⛑️ 78636393(CancelTarget) 之前压根没注册 ⇒ 玩家在地图上删掉导航点时会 INVOKE 无回执、await 卡死(规则 84)。
|
||
// ⛑️ 服务端**不负责每帧开车**:车还是客户端本地物理。服务端只管「给不给自动驾驶许可」。
|
||
// 真要让另一端的观众也看到车在动,是另一件事(要服务端推位置环回),本次不做。
|
||
// -----------------------------------------------------------------------------------------
|
||
func GameRouter_VehicleNav_OnAskVehicleStartAutonomousDriving(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleStartAutonomousDriving
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_OnAskVehicleStopAutonomousDriving(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleStopAutonomousDriving
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_OnAskVehicleChangeAutonomousDrivingTarget(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleChangeAutonomousDrivingTarget
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 把正在进行的自动驾驶改道到新终点(MethodId.AskVehicleChangeAutonomousDrivingTarget 用)。
|
||
// ⛑ 只重算 Cruise,**不重推** SyncVehicleAutonomousDrivingState:捐赠端那句
|
||
// "Keep the native autonomous HUD active; replace only our own Cruise." 就是这个意思 ——
|
||
// 重推会闪一次 UI,且客户端本来就在自动驾驶状态里。
|
||
// ⛑ 改道起点用车当前位置(VehiclePositionFor),不是原来的出发位置。
|
||
// ⛑ 解不出新路时保持现状(继续开向老终点)而不是停手:中途抛锚比开到旧目的地更难受,
|
||
// 玩家可以自己按 Stop。这与捐赠端不同(他们 no-route 就 Stop),是他们明确的选择差异。
|
||
//
|
||
func GameRouter_VehicleNav_RetargetAutonomousDrivingAsync(ctx context.Context, conn any, runtime any, msg any, tx float32, ty float32, tz float32) any {
|
||
// Router implementation for GameRouter_VehicleNav_RetargetAutonomousDrivingAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// AskVehicleCancelAutonomousDrivingTarget(67836393)—— 2026-09-27 补,之前漏注册。
|
||
// 客户端在地图上删掉导航点时发,是 INVOKE ⇒ 不回执会让 lua 的 await 卡住。
|
||
// 语义只是「忘记导航目标」:车照开,只是没了终点(客户端会自己决定何时停下)。
|
||
//
|
||
func GameRouter_VehicleNav_OnAskVehicleCancelAutonomousDrivingTarget(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_OnAskVehicleCancelAutonomousDrivingTarget
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 自驾起点:优先用车自己上报的位置(AskVehicleMove 写进 VehicleRecord),
|
||
// 退化才用玩家位置。Start 请求不带起点,而玩家坐在车里时这两个坐标差着车长,
|
||
// 用错了会把起点吸到旁边的车道上。
|
||
//
|
||
func GameRouter_VehicleNav_VehiclePositionFor(ctx context.Context, runtime any, vehicleId uint64, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_VehiclePositionFor
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 下发「沿路径巡航」的 AI 任务(SyncAssignVehicleAITask + VehicleCruiseParameters)。
|
||
// 取证:GameRouter.VehicleControls.cs 的 action 4 就是用这个参数类型(带
|
||
// TargetPointList)让空车倒车挪位;车流/T3-7 与载具骇入都走同一条
|
||
// SyncAssignVehicleAITask 通道。 ⇒ 这就是服务端驱动车动的唯一入口。
|
||
// ⛑️ Count 填的是点数量(语义未实测:若车只开到第一个点,改成 1 再试)。
|
||
// ⛑️⛑️ 2026-09-30 两处按捐赠端 v0.5-2 对齐(都有回滚开关,见 PrivateServerConfig):
|
||
// ① DrivingFlags:以前只留 DFStopForCars(1) ⇒ 车不会绕开静止车辆/行人/物件,
|
||
// 前面一停就顶死,也不等红灯。现在默认 VehicleNavigationSettings.CruiseDrivingFlags
|
||
// = 1|2|8|16|32|128|16384(枚举语义见 ux_rpc/payload_types.dump.cs:193)。
|
||
// ② TargetPointList:默认只给终点一个点(VehicleNavigationSettings.CruiseSingleTarget),
|
||
// 让客户端 C# 的 NavigationHelper 自己导航 —— 我们这份车道几何是「弦」不是样条,
|
||
// 喂整串点会切内弯。PathFindFlags=0(正向,按道路方向)。
|
||
// 而不是 Connection:GM 面板没有 Connection,
|
||
// 热应用调速(ApplyAutonomousDriveSpeedAsync)要复用同一条下发路径。
|
||
//
|
||
//
|
||
// 下发 Cruise(沿一串点巡航)任务。自驾与出租车共用这一条通道
|
||
// (2026-09-30:出租车接入)。
|
||
//
|
||
// 速度覆盖(m/s)。null = 用自驾配置的速度(EffectiveCruiseSpeedMs)。
|
||
// 出租车必须传自己的值:正常 15、加速 25(捐赠端 BuildTaxiDriveTask4229938
|
||
// 原值),不能跟着 GM 的自驾调速走 —— 那会让「加速」按钮和 GM 滑条互相打架。
|
||
//
|
||
// 复用一个已经有了的 token,而不是自己 mint 一个新的(2026-10-03,召唤载具开过来)。
|
||
// 客户端 SummonVehicleManager 在 AskSummonVehicle 回执里拿到
|
||
// SummonVehicleResult.TaskToken 后就用它 RegisterTaskAIStartedEvent(token)
|
||
// —— 两边对不上,原生那套「召唤演出」永远不会触发。⇒ 那台车的到达任务必须带同一个 token。
|
||
//
|
||
//
|
||
func GameRouter_VehicleNav_AssignAutonomousCruiseAsync(ctx context.Context, session *GameSession, runtime any, vehicleId uint64, path []any, speedMsOverride any, tokenOverride any) any {
|
||
// Router implementation for GameRouter_VehicleNav_AssignAutonomousCruiseAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// GM 实时调速(2026-09-30,/api/vehicle/autodrive-speed)。
|
||
// ① 先落盘 config/autonomous-driving.json(一定执行,与有没有在开车无关);
|
||
// ② 再对正在自动驾驶的那辆车热应用:撤回旧任务 → 用新速度重建 → 再确认一次。
|
||
// ⛑ 返回 false 只代表「这次没法热应用」(没在车上 / 没在自驾 / 上次点集没缓存),
|
||
// 速度仍然已经保存,下一次按自动驾驶就会生效 —— 别把它当失败提示给用户。
|
||
// ⛑⛑ 为什么复用 AutoDriveLastPoints 而不是重算路线:SolveRoute 是实例方法
|
||
// (要 Connection + LogNavOnce),GM 这条路径上根本没有 Connection。
|
||
// 缓存还有个好处:换速度时路线逐点不变,不会因为重算而突然改道。
|
||
//
|
||
func GameRouter_VehicleNav_ApplyAutonomousDriveSpeedAsync(ctx context.Context, session *GameSession, kmh float32) bool {
|
||
// Router implementation for GameRouter_VehicleNav_ApplyAutonomousDriveSpeedAsync
|
||
return true
|
||
}
|
||
|
||
//
|
||
// 回推 SyncChangeVehicleAITaskState 设置自驾任务状态。客户端靠这个包把任务从 Pending
|
||
// 推进到 Running —— **没有这一步车就不会动**。
|
||
// VehicleAIStatus:0 Pending / 1 Running / 2 Success / 3 Pause / 4 Abort / 5 Override
|
||
// (客户端枚举,ux_rpc/ux_game_all.dump.cs:181155)。
|
||
//
|
||
func GameRouter_VehicleNav_AcknowledgeAutoDriveTaskAsync(ctx context.Context, session *GameSession, vehicleId uint64, token uint64, status byte) any {
|
||
// Router implementation for GameRouter_VehicleNav_AcknowledgeAutoDriveTaskAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 自驾任务的状态机:只要求 token 匹配。客户端会连发 Running(1),
|
||
// 所以 1→1 必须被接受(骇入那套 IsAllowedHackTaskTransition 会拒掉重复,自驾不能照抄)。
|
||
//
|
||
func GameRouter_VehicleNav_TryApplyAutoDriveTaskStatus(ctx context.Context, runtime any, token uint64, next byte) bool {
|
||
// Router implementation for GameRouter_VehicleNav_TryApplyAutoDriveTaskStatus
|
||
return true
|
||
}
|
||
|
||
// 停止 AI 任务(action 0)。与 AssignAutonomousCruiseAsync 成对。
|
||
func GameRouter_VehicleNav_AssignAutonomousStopAsync(ctx context.Context, session *GameSession, runtime any, vehicleId uint64) any {
|
||
// Router implementation for GameRouter_VehicleNav_AssignAutonomousStopAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_PushVehicleAutonomousStateAsync(ctx context.Context, conn any, vehicleEntityId uint64, isStart bool) any {
|
||
// Router implementation for GameRouter_VehicleNav_PushVehicleAutonomousStateAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_PushPlayerAutonomousStateAsync(ctx context.Context, conn any) any {
|
||
// Router implementation for GameRouter_VehicleNav_PushPlayerAutonomousStateAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_SolveRoute(ctx context.Context, conn any, sx float32, sy float32, sz float32, tx float32, ty float32, tz float32, needCenter bool, tag string) any {
|
||
// Router implementation for GameRouter_VehicleNav_SolveRoute
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_AnswerNavAsync(ctx context.Context, conn any, msg any, fallback any, route any) any {
|
||
// Router implementation for GameRouter_VehicleNav_AnswerNavAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_LogNavOnce(ctx context.Context, conn any, message string, bytes int32) any {
|
||
// Router implementation for GameRouter_VehicleNav_LogNavOnce
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 服务端最后一次看到的玩家位置,退化时退回世界出生点。
|
||
func GameRouter_VehicleNav_CurrentVehiclePosition(ctx context.Context, msg any) any {
|
||
// Router implementation for GameRouter_VehicleNav_CurrentVehiclePosition
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_VehicleNav_AddPoint(ctx context.Context, list []any, prevX float32, prevZ float32, x float32, y float32, z float32, minSpacing float32) any {
|
||
// Router implementation for GameRouter_VehicleNav_AddPoint
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 点数超过上限时按等距抽稀,首位必有。
|
||
func GameRouter_VehicleNav_Decimate(ctx context.Context, source []any, limit int32) []any {
|
||
// Router implementation for GameRouter_VehicleNav_Decimate
|
||
return nil
|
||
}
|
||
|
||
//
|
||
// 车道图上的前向 Dijkstra(代价 = 米)。返回跳数 0 表示不通, 是
|
||
// 起点→终点的车道序列。第一跳的代价是「起点所在车道剩余里程」,这样同车道起步也算得准。
|
||
//
|
||
func GameRouter_VehicleNav_Dijkstra(ctx context.Context, startLane int32, targetLane int32, maxExpansions int32, lanes []any) int32 {
|
||
// Router implementation for GameRouter_VehicleNav_Dijkstra
|
||
return 0
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------------------
|
||
// 求解:把两张点吸到车道上,再在车道图上跑一次 Dijkstra(代价 = 米)
|
||
// -----------------------------------------------------------------------------------------
|
||
//
|
||
// = 给导航线的点(可能被 ReversePoints 翻成「终点 → 车」);
|
||
// = 给 AI 巡航任务的点,恒为「车 → 终点」正向
|
||
// (2026-09-28:反转只对画线的箭头有意义,喂给 AI 会让车掉头)。
|
||
//
|
||
type NavRoute struct {
|
||
Ok bool `json:"ok"`
|
||
Points []UxVector3 `json:"points"`
|
||
Center []UxVector3 `json:"center"`
|
||
DrivePoints []UxVector3 `json:"drivePoints"`
|
||
Length float32 `json:"length"`
|
||
Hops int32 `json:"hops"`
|
||
Note string `json:"note"`
|
||
}
|
||
|