package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // 出租车(2026-09-30 新增)。逐字读过 v0.5-2 捐赠端 // GameRouter.Taxi.cs(357 行) + GameRouter.TaxiNative.cs(210 行) 后, // 按主线自己的车辆栈重写 —— 不是照抄。 // ★ 从捐赠端确认的四条硬事实(都写进了注释,别推翻): // // 目的地是配置 uint,不是车辆 uid:TaxiSystemManager 传的是 // TaxiNavigationConfig.json 的 Id(共 18 条),所以必须查表, // 而且要校验 RaidId == 当前场景(表里混着 23300888 与 23300999 两个 raid)。 // 只有 VehicleType == 8 的车能开出租车 UI。主线 VehicleConfig.json 里 // 正好 4 辆:81004001 / 81004004 / 81004015 / 81004038(都是 4 座)。 // 请求是裸定长字节:Start=8B(u32 目的地 + u32 UI 距离)、End=6B(u8 传送 + u8 + u32)、 // Accelerate=0B、Status=11B(u64 + u8 + i16 停靠原因)。 // ⛑ UI 那个「距离」不信任,车费只按服务端自己的里程算。 // 计费:Ceiling(起步价 + Max(0, 里程 - 免费里程) * 单价), // 捐赠端原值 300 / 200m / 0.4 每米。 // // ★ 与捐赠端的故意差异(照抄会踩坑): // // 不自己拼 Cruise 包:主线 GameRouter.VehicleNav.AssignAutonomousCruiseAsync // 发的就是同一个 AssignVehicleControl<VehicleCruiseParameters>,而且它还负责 // ① 抢主控权 ② 记住 token ③ 主动 Acknowledge 一次(不确认车就不动,2026-09-28 第三轮的坑)。 // ⇒ 直接复用,只多传一个速度覆盖参数。 // 里程不自己挂钩:主线 GameRouter.UnhandledBatch.cs:41 已经在累加 // VehicleRecord.DistanceDriven。捐赠端的 RecordTaxiMovement 不需要搬。 // 路线用 SolveRoute(车道图 Dijkstra),不是捐赠端的 // FindNavigation —— 主线本来就有,且返回 DrivePoints(车→终点正向)。 // 不做「长按跳过 → 传送」(阶段一)。那要接主线 GameRouter.Teleport.cs // 的 ReportPreTeleportFinish 链,耦合深;AskEndTaxiTeleport 暂留在 // GameRouter.MiscAcks.cs 当占位。 // // ⛑ 回滚:gameplay.taxi.enabled=false ⇒ 四个 handler 全部直接回失败码 // (注册与日志仍在,可观测)。要彻底回到占位 AckVoid,把 // GameRouter.MiscAcks.cs 里那两条注释掉的 AckVoid 补回来并删掉本文件。 // // // 能不能开出租车。判据逐条来自捐赠端 CanTakeTaxi4229938: // 人在车上的乘客座(Seat > 0,主驾是 0)、不在下车流程中、 // 车可交互且已注册 Aether、车型是出租车。 // ⛑⛑ 最后一条(VehicleSpawnCatalog4229938.IsTrafficVehicle)是 // 主线自己加的,捐赠端没有,但不加就一定踩坑(2026-09-30 实锤): // 客户端 AetherVehicleManager.AddVehicleDataEntity 按 cfgId 在 // VehicleSpawnConfig 里查车辆模板,查不到就整台丢掉 —— // Player.log 三连:VehicleInit_Drop_NoConfig: vid=… cfgId=81004001 not found in VehicleSpawn // → Attach failed: VDE is null → Flush failed to create VDE。 // 后果是 没有 VehicleDataEntity ⇒ 车不会动、上下车没有动画 // (同一次实测:[TAXI] settled meters=0.0,车一米没走)。 // 4 辆 VehicleType == 8 里 81004001 不在那张表,只有 // 81004004 / 81004015 / 81004038 能用。 // func GameRouter_Taxi_CanTakeTaxi(ctx context.Context, runtime any, v any) bool { // Router implementation for GameRouter_Taxi_CanTakeTaxi return true } // // 客户端认得的出租车车型(VehicleType == 8 且在 VehicleSpawnConfig 里)。 // 只用于日志/报错提示,让玩家知道该召哪一辆。 // func GameRouter_Taxi_UsableTaxiConfigIds(ctx context.Context, ) string { // Router implementation for GameRouter_Taxi_UsableTaxiConfigIds return "" } // 车费。沿用捐赠端 TaxiFare4229938:Ceiling(Base + Max(0, d-Free) * PerMeter)。 func GameRouter_Taxi_TaxiFare(ctx context.Context, meters float64) float64 { // Router implementation for GameRouter_Taxi_TaxiFare return 0 } func GameRouter_Taxi_TaxiSpeedMs(ctx context.Context, accelerated bool) float32 { // Router implementation for GameRouter_Taxi_TaxiSpeedMs return 0 } // ------------------------------------------------------------------ 派单 / 收车 // // ⛑⛑ 最关键的一步(2026-09-30 第二轮补上,之前漏了 ⇒ 所有出租车原地不动): // 玩家在出租车 UI 里确认目的地时,客户端走 // TaxiSystemManager.StartTaxiOrder → SetTaxiVehicleControl(true) → City.SetAetherVehicleControl // 把车切成 AetherVehicle=1;而 Cruise 与 SyncTeleportVehicle 都要求 // RaidVehicle=2 —— 捐赠端 GameRouter.TaxiNative.cs:33-35 的原话: // Cruise and SyncTeleportVehicle need RaidVehicle=2; a controller PID alone is insufficient. // 只发 SyncChangeVehicleController 不够:车仍归 Aether 车道模拟管, // 位移每帧被车道覆盖 ⇒ 表现就是「轮胎在转,车在原地进行」。 // 反向验证:81004001 之前反而能开到目的地 —— 因为它不在客户端 // VehicleSpawnConfig 里、压根没建成 VDE,客户端切不动 Aether, // 车一直留在 Raid 控制下 ⇒ Cruise 生效(代价是没有 VDE ⇒ 没有上下车动画)。 // 两台车一个能跑没动画、一个有动画不能跑,正好各缺一半,两边证据互相印证。 // func GameRouter_Taxi_SetTaxiRaidControlAsync(ctx context.Context, session *GameSession, vehicleEntityId uint64) any { // Router implementation for GameRouter_Taxi_SetTaxiRaidControlAsync var zero any; return zero } // // 派一次 Cruise。⛑ 顺序与 GameRouter.VehicleNav.cs 的自动驾驶一致: // 状态包 → AI 任务(2026-09-30 §43.1 的血泪教训:顺序反了就是「车门消失/角色缺模」)。 // 派单前还要先 SetTaxiRaidControlAsync 把车从 Aether 切回 Raid。 // func GameRouter_Taxi_DispatchTaxiAsync(ctx context.Context, session *GameSession, conn any, runtime any, trip any, path []any) any { // Router implementation for GameRouter_Taxi_DispatchTaxiAsync var zero any; return zero } func GameRouter_Taxi_StopTaxiTaskAsync(ctx context.Context, session *GameSession, runtime any, trip any) any { // Router implementation for GameRouter_Taxi_StopTaxiTaskAsync var zero any; return zero } // 取这台车的停靠原因位(0 = 没有停靠原因,可以开车)。 func GameRouter_Taxi_TaxiStopReasons(ctx context.Context, runtime any, vehicleId uint64) uint16 { // Router implementation for GameRouter_Taxi_TaxiStopReasons return 0 } // // PushVehicleAutonomousStateAsync 需要 Connection,而收车路径常常只有 // TcpSession。这里用 session 直发同一条包(IGameSceneToClient_SyncVehicleAutonomousDrivingState)。 // func GameRouter_Taxi_PushVehicleAutonomousStateAsync2(ctx context.Context, session *GameSession, vehicleEntityId uint64, isStart bool) any { // Router implementation for GameRouter_Taxi_PushVehicleAutonomousStateAsync2 var zero any; return zero } // 到达:停任务 → 推 SyncTaxiReachDestination → 结算。 func GameRouter_Taxi_TaxiArrivedAsync(ctx context.Context, session *GameSession, conn any, runtime any, trip any) any { // Router implementation for GameRouter_Taxi_TaxiArrivedAsync var zero any; return zero } // // 扣车费。⛑ FareSettled 必须先置位再动钱包: // 到达/手动结束/异常三条路径可能并发,重复扣钱比少扣钱难查。 // func GameRouter_Taxi_SettleTaxiAsync(ctx context.Context, conn any, session *GameSession, runtime any, trip any, reason string) any { // Router implementation for GameRouter_Taxi_SettleTaxiAsync var zero any; return zero } // 结束行程(手动/异常/到达都走这里)。 func GameRouter_Taxi_FinishTaxiAsync(ctx context.Context, session *GameSession, conn any, runtime any, trip any, reason string) any { // Router implementation for GameRouter_Taxi_FinishTaxiAsync var zero any; return zero } // ======================================================================================== // AskStartTaxiNavigate(67363555) —— C2S Invoke,8B:u32 目的地 + u32 UI 距离(不信任) // ======================================================================================== func GameRouter_Taxi_StartTaxi4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Taxi_StartTaxi4229938 var zero any; return zero } // ======================================================================================== // AskEndTaxiNavigate(67960304) —— C2S Invoke,6B:u8 传送 + u8 + u32 // ======================================================================================== func GameRouter_Taxi_EndTaxi4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Taxi_EndTaxi4229938 var zero any; return zero } // ======================================================================================== // AskTaxiAccelerate(67753061) —— C2S Invoke,0B // ======================================================================================== func GameRouter_Taxi_AccelerateTaxi4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Taxi_AccelerateTaxi4229938 var zero any; return zero } func GameRouter_Taxi_TaxiVehicleStatus4229938(ctx context.Context, conn any, msg any) any { // Router implementation for GameRouter_Taxi_TaxiVehicleStatus4229938 var zero any; return zero } // ------------------------------------------------------------------ 监控 // // 每秒看一次:里程、是否到达、是否卡住。 // ⛑ 到不了就结束行程而不是无限等 —— 捐赠端是「保留行程让玩家换目的地」, // 但那要配合跳过传送;阶段一没有跳过,卡住就按当前里程结算收工,免得钱一直挂着。 // func GameRouter_Taxi_WatchTaxiAsync(ctx context.Context, conn any, runtime any, trip any) any { // Router implementation for GameRouter_Taxi_WatchTaxiAsync var zero any; return zero } // ------------------------------------------------------------------ 行程 type TaxiTrip4229938 struct { VehicleId uint64 `json:"vehicleId"` DestinationId uint32 `json:"destinationId"` Raid uint32 `json:"raid"` TargetX float32 `json:"targetX"` TargetY float32 `json:"targetY"` TargetZ float32 `json:"targetZ"` // 起点里程(VehicleRecord.DistanceDriven)。车费 = 当前里程 - 它。 StartOdometer float64 `json:"startOdometer"` // 上次观察到的里程,用来判断「还在不在动」。 LastOdometer float64 `json:"lastOdometer"` Accelerated bool `json:"accelerated"` Arrived bool `json:"arrived"` Ended bool `json:"ended"` FareSettled bool `json:"fareSettled"` StallNotified bool `json:"stallNotified"` // // 当前是否已经下发过 Cruise 任务。对应捐赠端的 trip.Token != 0 // (主线不自己管 token,由 AssignAutonomousCruiseAsync 内部 mint)。 // ⛑ 捐赠端靠它决定「什么时候该重派」:Token == 0(从没派过/刚被停) // 或收到 Resume(TaxiInteract=64) 才派。 // Dispatched bool `json:"dispatched"` StartedTick int64 `json:"startedTick"` LastMovementTick int64 `json:"lastMovementTick"` } // ======================================================================================== // AskAetherAISetVehicleStatus(67541089) —— C2S **Notify**,11B:u64 uid + u8 status + i16 停靠原因 // ======================================================================================== type TaxiStopState struct { Reasons uint16 `json:"reasons"` }