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/gameserver/routers/game_router_vehicle_lanes.go
T

289 lines
15 KiB
Go
Raw 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
//
// 车辆路网(服务端侧 ZoneGraph 简化版)—— 给 T3-7 车流提供「车道 handle + 沿车道里程」。
// 数据来自捐赠端 `zonegraph_lanes.csv`(WorldMap_Release,14171 条车道),
// 由 `_gen_vehicle_lanes.py` 压成两份运行时表:
// `Drmk.Server/Drmk.App/data/vehicle_lanes.csv` = handle;bx;by;bz;ex;ey;ez;length;speed
// `Drmk.Server/Drmk.App/data/vehicle_lane_links.csv` = handle;to1;to2;...
// ★ 为什么用捐赠端那份而不是 `_ref/lanes/vehicle-lane-links.csv`:
// 后者 Type==1 前向边只有 10911 条、只覆盖 56.9% 的车道,且是捐赠端 next 的**真子集**;
// 捐赠端 next 是多值后继,覆盖 13910/14171 = 98.2%,换道接缝中位 0.00m。
// ★ 与 PedLaneGraph 的关键差别:客户端按 DistanceAlongLane 在**它本地的真实车道曲线**上
// 插值(MassTrafficInterpolationUtils),所以服务端不需要车道折线,只要里程单调推进。
// 这里的 bx/ez 只用于「玩家附近挑车道」和 despawn 判定。
//
type VehicleLaneGraph struct {
Cache Table `json:"cache"`
Available bool `json:"available"`
LaneCount int32 `json:"laneCount"`
// 最大强连通分量里的车道数(导航只会吸附到这些车道上)。排障用。
RoutableLaneCount int32 `json:"routableLaneCount"`
// 强连通分量个数。理想值 1;2026-09-30 实测 286(见 MarkRoutableLanes)。
ComponentCount int32 `json:"componentCount"`
NavCache NavIndex `json:"navCache"`
}
const (
VehicleLaneGraph_CellSize float32 = 100
)
//
// 算一条车道的弓高(弦中点相对真实曲线的最大横向偏离,米)。见 Lane.Sagitta。
// 直车道(弧长≈弦长)返回 0。
//
func GameRouter_VehicleLanes_SagittaOf(ctx context.Context, lane any) float32 {
// Router implementation for GameRouter_VehicleLanes_SagittaOf
return 0
}
//
// 弦上参数 t(0..1)处的横向偏离(米)。t=0/1 落在端点 ⇒ 偏离 0;t=0.5 最大 ⇒ 等于弓高。
//
func GameRouter_VehicleLanes_LateralDeviationAt(ctx context.Context, sagitta float32, t float32) float32 {
// Router implementation for GameRouter_VehicleLanes_LateralDeviationAt
return 0
}
func GameRouter_VehicleLanes_Load(ctx context.Context, ) any {
// Router implementation for GameRouter_VehicleLanes_Load
var zero any; return zero
}
// -----------------------------------------------------------------------------------------
// ⛑⛑ 2026-09-30:车道图有 **285 条孤岛车道**,这是「自动驾驶开不到自定义标点」的真因。
//
// 体检脚本 `_vehicle_lane_graph_check.py` 的结果(可直接复跑):
// 车道 14171 条、链接表只覆盖 13910 条 ⇒ **261 条没有任何后继、255 条没有任何前驱**,
// 两者的样例 id 完全重合(89/93/135/140/232/236/410/417…)⇒ 它们是<b>双向断开的孤儿车道</b>。
// Tarjan 的结果更狠:全图 **286 个强连通分量**,最大的那个有 13886 条(97%),
// 剩下 285 条各自成形单影只的分量。
//
// 后果:日志里 `车道图不通 start=1795 target=1972` 反复出现且**永远不解开** —— 因为 lane 1972 /
// 6901 / 13108 的「能到它的车道数」= 1(只有它自己)。而 <c>SolveRoute</c> 的 Dijkstra 只走前向
// 后继,玩家把目的地标点落到这种车道附近时,**永远解不出路** ⇒ Start 之后根本没有 cruise 下发
// ⇒ 现象就是「按了自动驾驶没反应」。
// 商店/POI 之所以大多没事:它们的 endPos 是走 `GpsAreaConnectMgr` 算出来的**路口/出入口点**,
// 天然落在主干道上;自定义标点给的是玩家自己点的原始坐标,撞上孤儿车道就是纯运气。
//
// ⇒ 治法:**导航吸附时只认最大强连通分量里的车道**。孤儿车道本来就没法参与路径,
// 拿它当起点/终点必然失败;跳过它去找次近的正常车道,代价是终点可能偏移几米。
// (真要根治得重跑 `_gen_vehicle_lanes.py` 把这 261 条接上去,本次不动数据。)
// -----------------------------------------------------------------------------------------
func GameRouter_VehicleLanes_MarkRoutableLanes(ctx context.Context, table any) any {
// Router implementation for GameRouter_VehicleLanes_MarkRoutableLanes
var zero any; return zero
}
func GameRouter_VehicleLanes_Next(ctx context.Context, table any, handle int32) any {
// Router implementation for GameRouter_VehicleLanes_Next
var zero any; return zero
}
func GameRouter_VehicleLanes_Num(ctx context.Context, s string) float32 {
// Router implementation for GameRouter_VehicleLanes_Num
return 0
}
func GameRouter_VehicleLanes_CellOf(ctx context.Context, x float32, z float32) any {
// Router implementation for GameRouter_VehicleLanes_CellOf
var zero any; return zero
}
//
// 在 (px,pz) 的**环形**区间内随机挑一条车道当作出生点:距玩家
// [minDistance, radius]。
// ⛑ 为什么必须有 minDistance(2026-09-29 修):原来只筛「≤ radius」,
// 于是车会直接刷在玩家脚边几米处;而且出生点固定取车道起点(Distance=0),
// 而车道起点大量落在路口里 ⇒ 观感就是「车刷在我身边,还不在车道上」。
// 更糟的是客户端 AetherVehicleViewManager 会在
// CANDIDATE_GO_DISTANCE_SQ=400(20m)内把车流车提升成 GO(完整载具实体),
// 提升后不再吃我们的 Aether 车道插值 ⇒ 冻在原地,于是玩家身边越积越多不动的车。
//
func GameRouter_VehicleLanes_TryPickSpawn(ctx context.Context, px float32, pz float32, radius float32, minDistance float32, rng any, state any, x float32, y float32, z float32, speed float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryPickSpawn
return true
}
//
// 「召唤载具从车道开过来」的生成点(2026-10-03):在 (px,pz) 的环形区间
// [minDistance, maxDistance] 内,挑一条车道上距离合适的点,并返回车道朝向。
// ⛑⛑ 与 TryPickSpawn(T3-7 车流出生)的三点差别,别混用:
// ① 只挑 Lane.Routable 的车道。吸附到那 285 条孤岛车道上 ⇒
// Dijkstra 解不出路 ⇒ 一条 Cruise 都不发 ⇒ 「叫了车但没反应」(2026-09-30 血泪)。
// ② 不只取车道起点:沿弦采样 5 个 t,用落在环内的那一段 —— 车道起点大量落在路口里,
// 刷在路口的车开出来会横穿。
// ③ 返回车道朝向(FacingOf 同一套角度约定),车头顺着路,
// 不然车要先原地掉头,观感是「车卡了一下才走」。
// :允许的横向偏离(米),0 = 不过滤(旧行为)。
// 车道 CSV 只有弦端点,弯道时弦中点在真实路面之外 ⇒ 车会刷到人行道/花坛里。
// 给 1.5 之类的值后,弯车道只保留靠近端点的那些采样点(偏离随 t 两端趋 0)。
// :多台车连叫时不想每次都刷在同一个位置,交给调用方给随机源。
//
func GameRouter_VehicleLanes_TryPickDriveInSpawn(ctx context.Context, px float32, pz float32, minDistance float32, maxDistance float32, maxLateralMeters float32, rng any, x float32, y float32, z float32, facingDegrees float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryPickDriveInSpawn
return true
}
func GameRouter_VehicleLanes_BuildNavIndex(ctx context.Context, ) any {
// Router implementation for GameRouter_VehicleLanes_BuildNavIndex
var zero any; return zero
}
//
// 找 (px,pz) 半径内最近的车道,并把点投影到它的弦上(T = 归一化里程)。
// 只看 XZ 平面:竖直方向(y) 对「哪条路最近」没有意义,而我们本来也只有端点。
// ⛑⛑ 2026-09-30:只返回 Lane.Routable 的车道(最大强连通分量内的)。
// 吸附到孤儿车道上 ⇒ Dijkstra 永远解不出路 ⇒ 按自动驾驶没反应。详见 MarkRoutableLanes。
// 车流的 TryPickSpawn 不受影响(那条路径不走这里)。
//
func GameRouter_VehicleLanes_TrySnapLane(ctx context.Context, px float32, pz float32, maxRadius float32, snap any) bool {
// Router implementation for GameRouter_VehicleLanes_TrySnapLane
return true
}
//
// 同 TrySnapLane(float, float, float, out LaneSnap),多一个横向偏离过滤。
// ⛑⛑ 召唤车「停在人行道 / 花坛里」的另一半根因:停车点是玩家坐标投影到最近车道弦上
// 得到的,弯车道时这个点同样在路面之外。给 (如 1.5)
// 即可只接受贴近真实路面的投影位置。
// ⛑ 传 0(= 上面那个重载)保持旧行为,自动驾驶导航走的就是那条,别动它。
//
func GameRouter_VehicleLanes_TrySnapLane_2(ctx context.Context, px float32, pz float32, maxRadius float32, maxLateralMeters float32, snap any) bool {
// Router implementation for GameRouter_VehicleLanes_TrySnapLane_2
return true
}
// 一条车道的弦与长度。找不到返回 false。
func GameRouter_VehicleLanes_TryLaneGeometry(ctx context.Context, handle int32, bx float32, by float32, bz float32, ex float32, ey float32, ez float32, length float32) bool {
// Router implementation for GameRouter_VehicleLanes_TryLaneGeometry
return true
}
// 后继车道(多值)。没有后继返回空数组。
func GameRouter_VehicleLanes_NextOf(ctx context.Context, handle int32) []any {
// Router implementation for GameRouter_VehicleLanes_NextOf
return nil
}
//
// 规划一条**直行优先**的路径:从起始车道出发,每一步都挑「方向最贴合当前朝向」的后继。
// 返回接下来要走的车道序列(不含起始车道本身)。
// ★★ 为什么需要它(2026-09-28):车流原来是每到一条车道就**随机挑后继**(PickNext),
// 于是车会乱拐、掉头、钻死路、很快被回收 —— 用户看到的就是「开很短距离就停下/消失」。
// 有了预规划路径,车沿道路连续跑 1~2 段甚至更远,观感从"随机乱窜"变成"正常行驶"。
// ★ 不直接用导航那套 Dijkstra:Dijkstra 求的是**到指定目标**的最短路,
// 而车流没有目的地,要的是"顺着路往前开"。直行优先恰好表达这个意图,
// 且天然排除掉头(dot 为负的后继排最后)。
// ★ 接缝超过 的后继直接跳过 —— 与 Advance 的硬上限一致,
// 否则规划出来的路径里会混进几百米的瞬移跳。
//
func GameRouter_VehicleLanes_PlanRoute(ctx context.Context, startLane int32, maxHops int32, maxHop float32) []any {
// Router implementation for GameRouter_VehicleLanes_PlanRoute
return nil
}
//
// 沿车道推进指定距离(米)。换道时把 切到下一条并把里程归零。
// 返回 false = 走到路网死路,调用方应当回收这辆车。
// ★ :只在接缝 ≤ 该值的后继里挑。捐赠端 next 是多值后继,
// 实测 5.4% 的后继接缝 &gt; 12m(最大 340m),直接随机挑会让车「瞬移一段甚至倒退」。
// 找不到合格后继时退回「接缝最小」的那条,并如实把 Gap 报给调用方。
//
//
// 换道接缝的**绝对硬上限**(米)。超过就判定"开到尽头",返回 false 让调用方回收。
// 为什么需要它:PickNext 在所有后继都被"方向反折"过滤掉时会退回 fallback
// (全局最小接缝)—— 那条可能远在天边。实测日志里出现过 worstGap=394.1m,
// 而车道数据本身 p99 接缝就有 347m、max 2965m(>30m 的占 13.92%)。
// 跳过去就是几百米的瞬移,比车在路口消失难看得多 —— 宁可回收。
//
func GameRouter_VehicleLanes_Advance(ctx context.Context, s any, meters float32, rng any, maxGap float32, hop any, maxHop float32, preferredNext int32) bool {
// Router implementation for GameRouter_VehicleLanes_Advance
return true
}
//
// 挑下一条车道:在「接缝最小」的基础上放宽 米的容差再随机选,
// 并排除方向反折的后继(那会让车掉头,视觉上像倒退瞬移)。
// ★ 为什么是**相对容差**而不是「接缝 ≤ N 米」的绝对阈值:
// 实测 400 起点 × 600 跳,绝对阈值 8m 会让 15.05% 的衔接 &gt; 12m、p99 达 607m;
// 而「总选最小缝」只有 2.55% &gt; 12m、p99 50m。原因是绝对阈值一旦命中就随机选,
// 容易拐进后继质量差的车道;贪心取最近则倾向留在连贯的主干道上。
// 所以 maxGap=0 = 严格取接缝最小(并列最小里随机),是实测最优。
//
func GameRouter_VehicleLanes_PickNext(ctx context.Context, from any, table any, rng any, maxGap float32, gap float32) int32 {
// Router implementation for GameRouter_VehicleLanes_PickNext
return 0
}
// 服务端侧的线性插值的粗略世界坐标(只用于 AOI 判定 / 日志,客户端自己有真实曲线)。
func GameRouter_VehicleLanes_PositionOf(ctx context.Context, s any, x float32, y float32, z float32) any {
// Router implementation for GameRouter_VehicleLanes_PositionOf
var zero any; return zero
}
// 距当前车道末端还有多少米(用于「接近路口就减速」)。
func GameRouter_VehicleLanes_RemainOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_RemainOf
return 0
}
// 当前车道的限速(m/s)。车换道后会变,所以每 tick 都要重取。
func GameRouter_VehicleLanes_LimitOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_LimitOf
return 0
}
//
// 朝向 弧度(0 = +Z,顺时针为正),由车道端点方向推出。
// ★ 2026-09-21 修正:原先这里返回「度」却被直接填进 ClientVehicleInitData.Facing,
// 实测线上包里 Facing = -88.5 —— 那是度数当弧度用了。Facing 是弧度,
// EulerAngles 才是度(调用处各自换算)。
//
func GameRouter_VehicleLanes_FacingOf(ctx context.Context, s any) float32 {
// Router implementation for GameRouter_VehicleLanes_FacingOf
return 0
}
type DriveState struct {
Lane int32 `json:"lane"`
Distance float32 `json:"distance"`
}
// ---------------------------------------------------------------------------------------------
// 路线规划(GameRouter.VehicleNav.cs)用的取值口。数据仍然只有这一份:
// 只为「最近车道」另建了一份索引(按起点/中点/终点三个关键 cell 挂号),因为 try 9 是长车道
// 只按起点挂号会漏掉「从路中间上车」的情形,而我们不能动 TryPickSpawn 那张 begin-only 索引
// (改了会改变 T3-7 车流的随机出生分布)。
// ---------------------------------------------------------------------------------------------
type NavIndex struct {
Cells map[any][]int32 `json:"cells"`
}
// 一次 TrySnapLane 的结果。
type LaneSnap struct {
Handle int32 `json:"handle"`
T float32 `json:"t"`
Distance float32 `json:"distance"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
}
// 一次车道衔接的细节(诊断用)。
type LaneHop struct {
FromLane int32 `json:"fromLane"`
ToLane int32 `json:"toLane"`
// 旧车道终点 → 新车道起点的接缝距离(米)。越大越像瞬移。
Gap float32 `json:"gap"`
}