232 lines
12 KiB
Go
232 lines
12 KiB
Go
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 行) 后,
|
||
// 按主线自己的车辆栈重写 —— 不是照抄。
|
||
// <para>★ 从捐赠端确认的四条硬事实(都写进了注释,别推翻):</para>
|
||
// <list type="number">
|
||
// <item>目的地是配置 uint,不是车辆 uid:TaxiSystemManager 传的是
|
||
// TaxiNavigationConfig.json 的 Id(共 18 条),所以必须查表,
|
||
// 而且要校验 RaidId == 当前场景(表里混着 23300888 与 23300999 两个 raid)。</item>
|
||
// <item>只有 VehicleType == 8 的车能开出租车 UI。主线 VehicleConfig.json 里
|
||
// 正好 4 辆:81004001 / 81004004 / 81004015 / 81004038(都是 4 座)。</item>
|
||
// <item>请求是裸定长字节:Start=8B(u32 目的地 + u32 UI 距离)、End=6B(u8 传送 + u8 + u32)、
|
||
// Accelerate=0B、Status=11B(u64 + u8 + i16 停靠原因)。
|
||
// ⛑ UI 那个「距离」不信任,车费只按服务端自己的里程算。</item>
|
||
// <item>计费:Ceiling(起步价 + Max(0, 里程 - 免费里程) * 单价),
|
||
// 捐赠端原值 300 / 200m / 0.4 每米。</item>
|
||
// </list>
|
||
// <para>★ 与捐赠端的故意差异(照抄会踩坑):</para>
|
||
// <list type="bullet">
|
||
// <item>不自己拼 Cruise 包:主线 GameRouter.VehicleNav.AssignAutonomousCruiseAsync
|
||
// 发的就是同一个 AssignVehicleControl<VehicleCruiseParameters>,而且它还负责
|
||
// ① 抢主控权 ② 记住 token ③ 主动 Acknowledge 一次(不确认车就不动,2026-09-28 第三轮的坑)。
|
||
// ⇒ 直接复用,只多传一个速度覆盖参数。</item>
|
||
// <item>里程不自己挂钩:主线 GameRouter.UnhandledBatch.cs:41 已经在累加
|
||
// VehicleRecord.DistanceDriven。捐赠端的 RecordTaxiMovement 不需要搬。</item>
|
||
// <item>路线用 SolveRoute(车道图 Dijkstra),不是捐赠端的
|
||
// FindNavigation —— 主线本来就有,且返回 DrivePoints(车→终点正向)。</item>
|
||
// <item>不做「长按跳过 → 传送」(阶段一)。那要接主线 GameRouter.Teleport.cs
|
||
// 的 ReportPreTeleportFinish 链,耦合深;AskEndTaxiTeleport 暂留在
|
||
// GameRouter.MiscAcks.cs 当占位。</item>
|
||
// </list>
|
||
// <para>⛑ 回滚:gameplay.taxi.enabled=false ⇒ 四个 handler 全部直接回失败码
|
||
// (注册与日志仍在,可观测)。要彻底回到占位 AckVoid,把
|
||
// GameRouter.MiscAcks.cs 里那两条注释掉的 AckVoid 补回来并删掉本文件。</para>
|
||
//
|
||
//
|
||
// 能不能开出租车。判据逐条来自捐赠端 CanTakeTaxi4229938:
|
||
// 人在车上的乘客座(Seat > 0,主驾是 0)、不在下车流程中、
|
||
// 车可交互且已注册 Aether、车型是出租车。
|
||
// <para>⛑⛑ 最后一条(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 能用。</para>
|
||
//
|
||
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"`
|
||
}
|
||
|