346 lines
17 KiB
Go
346 lines
17 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
var _ = time.Now
|
||
|
||
//
|
||
// Vehicle ownership + direct summon (Phase 2). Ported from the proven newcityps
|
||
// (4091149) direct-scene-spawn path, with 4229938 wire deltas applied
|
||
// (LogicVehicleClientInfo.VehicleSpoonName, PlayerVehicleClientDetail.IsPersistent).
|
||
// No road-planner/AI-task leg: the vehicle materializes beside the player,
|
||
// already interactable, and the stock client seat/drive lifecycle takes over.
|
||
// Enter/exit/move notifies are logged + tracked; pushes for those come after
|
||
// live-traffic observation (see KNOWLEDGEBASE.md §4).
|
||
//
|
||
// Call when despawning NPCs or resetting state to abort any active interaction monitor.
|
||
func GameRouter_Vehicle_ResetInteractionState(ctx context.Context, ) any {
|
||
// Router implementation for GameRouter_Vehicle_ResetInteractionState
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_GetVehicleRuntimeState(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_Vehicle_GetVehicleRuntimeState
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_GetVehicleRuntime(ctx context.Context, rpcCtx *RpcContext) any {
|
||
// Router implementation for GameRouter_Vehicle_GetVehicleRuntime
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_VehicleOwnershipSnapshot(ctx context.Context, runtime any) []any {
|
||
// Router implementation for GameRouter_Vehicle_VehicleOwnershipSnapshot
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_Vehicle_PublishVehicleOwnership(ctx context.Context, rpcCtx *RpcContext) any {
|
||
// Router implementation for GameRouter_Vehicle_PublishVehicleOwnership
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_EnsureAetherVehicleInit(ctx context.Context, rpcCtx *RpcContext) any {
|
||
// Router implementation for GameRouter_Vehicle_EnsureAetherVehicleInit
|
||
var zero any; return zero
|
||
}
|
||
|
||
// --- Aether city simulation: C2S calls -------------------------------------------
|
||
// The client owns the simulation (pedestrians / static NPCs / traffic). These handlers
|
||
// exist so the tasks it awaits after SyncAetherAIInitDatas are answered instead of
|
||
// stalling the spawner.
|
||
func GameRouter_Vehicle_AskMassHideAreas(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskMassHideAreas
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskAetherChangeQuality(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskAetherChangeQuality
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskAetherAIBorrowVehicle(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskAetherAIBorrowVehicle
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskChangePedToVehicleNpc(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskChangePedToVehicleNpc
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskReportAetherLeaflet(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskReportAetherLeaflet
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskAetherAICreateRaidVehicleAndClaimSeatByAether(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskAetherAICreateRaidVehicleAndClaimSeatByAether
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskAetherAIRaidVehicleRegressAether(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskAetherAIRaidVehicleRegressAether
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskRaidVehicleConvertToAether(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskRaidVehicleConvertToAether
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_ParseHexBytes(ctx context.Context, hex any) []byte {
|
||
// Router implementation for GameRouter_Vehicle_ParseHexBytes
|
||
return nil
|
||
}
|
||
|
||
// ⛑ 2026-10-02 对齐捐赠端:原来这里的 `FireExamAuto`(cmd=33/34 ⇒ 自动开盘查)已随
|
||
// `GameRouter.PoliceExamAuto.cs` 一起移除 —— 捐赠端**没有**"准星对准/对话 ⇒ 自动盘查"这条链,
|
||
// 它的入口只有原生 F(`PoliceFRequest`)和 GM `/api/lixi/police`。
|
||
// cmd=33/34 现在只保留 uid 记录与日志,不再触发盘查。
|
||
//
|
||
// Interact heartbeat/sync channel. Decoded + logged so a future F-press via the
|
||
// interact system is visible; currently accepted without side effects.
|
||
//
|
||
func GameRouter_Vehicle_AskInteractCmd(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskInteractCmd
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Two-person interaction (双人 ACTION) invite channel. The client sends
|
||
// AskInvitePlayerInteractionAction as an Invoke (12b = inviteePid u64 + actionItemId u32)
|
||
// when the player picks an action. The NPC then shows "思考中" until the server acks.
|
||
// We auto-accept: push inviter+invitee state and SyncStartPlayerInteractionAction.
|
||
func GameRouter_Vehicle_AskInvitePlayerInteractionAction(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskInvitePlayerInteractionAction
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskStartPlayerInteractionAction(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskStartPlayerInteractionAction
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskReplyInvitePlayerInteractionAction(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskReplyInvitePlayerInteractionAction
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskGetUnlockedVehicles(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskGetUnlockedVehicles
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskSummonVehicle(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskSummonVehicle
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 召唤车「开过来」到达之后,把权威位置钉死在它现在真正的位置(2026-10-03 实机修瞬移)。
|
||
// <para>症状:车能开到身边,玩家一上车就瞬移回生成点。</para>
|
||
// <para>根因:服务端在生成时下发了 SyncAetherAIVehicleAddData(ClientVehicleInitData),
|
||
// 里面写的是生成点坐标且 LaneHandle=-1(不参与车道推进)⇒ 这份 Aether 坐标
|
||
// 之后再也没人更新。车是被 SyncAssignVehicleAITask 的 Cruise(Raid 通道)开过来的,
|
||
// 客户端两套表示就此分叉。玩家上车时客户端拿 Aether 那份数据对齐实体 ⇒ 车被拽回生成点。
|
||
// 服务端上车那几秒一条 S2C 位置包都没发(14:46:42–45 全是 C2S),所以不是我们回填错了,
|
||
// 是客户端自己的那份旧值。</para>
|
||
// <para>对策:SyncTeleportVehicle(68088911) 是 Raid 通道的权威位置包(赛车 R309 /
|
||
// 改装店钉车 / 传送都在用,Reset=true = 客户端直接落位不插值)。位置取
|
||
// AskVehicleMove 上报的最新值 —— AI 开车期间客户端每秒都在报,所以这一发
|
||
// 视觉上是「原地不动」,只是把两边强行对齐。</para>
|
||
//
|
||
func GameRouter_Vehicle_CompleteSummonDriveInAsync(ctx context.Context, conn any, runtime any, entityId uint64) any {
|
||
// Router implementation for GameRouter_Vehicle_CompleteSummonDriveInAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 召唤载具的「开过来」那一半(2026-10-03,SummonDriveInSettings)。
|
||
// <para>三步,顺序不能改:
|
||
// <list type="number">
|
||
// <item>SyncAetherAIChangeVehicleControl(2) —— 把车切回 RaidVehicle。
|
||
// 客户端会把有 VDE 的车交给 Aether 车道模拟,位移每帧被车道覆盖 ⇒ Cruise 变成
|
||
// 「轮胎在转、车在原地进行」(GameRouter.Taxi.cs:147-166,出租车踩过的)。</item>
|
||
// <item>SyncVehicleAutonomousDrivingState(true) —— 必须早于 AI 任务,
|
||
// 否则客户端把这条任务当成「别人的车被接管」渲染 ⇒ 车门消失 / 角色缺模
|
||
// (2026-09-30 §43.1)。</item>
|
||
// <item>SyncAssignVehicleAITask + Cruise,token 复用召唤回执那个。</item>
|
||
// </list>
|
||
// </para>
|
||
// <para>⛑ 终点是「车道上离玩家最近的点」(靠边停车),不是玩家脚下:自定义标点吸附失败时
|
||
// 客户端一条 Cruise 都不下发,表现是「叫了车没反应」。解不出车道路径就只给终点,
|
||
// 交给客户端 C# NavigationHelper 自己导航(自驾已验证能到任意 POI)。</para>
|
||
//
|
||
func GameRouter_Vehicle_StartSummonDriveInAsync(ctx context.Context, conn any, session *GameSession, entityId uint64, token uint64, spawn Vec3, stop Vec3, cfg any) any {
|
||
// Router implementation for GameRouter_Vehicle_StartSummonDriveInAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// Direct scene spawn shared by client summon and the admin panel:
|
||
// SyncLogicVehicleEnter + SyncSpawnVehicle (interactable) + SyncChangeVehicleInteractable.
|
||
// Returns the new entity id + task token. Replaces the previous summoned vehicle.
|
||
//
|
||
//
|
||
// 给了就原样落在这(只 +0.15m 抬高),不再做「玩家右手边 5.5m」的偏移。
|
||
// 改装店换展示车必须走这条:工位在地下(y<0),按玩家朝向偏移会把车摆进墙里
|
||
// (donor 13:38 实测「车在你看的地方/卡墙」)。
|
||
//
|
||
func GameRouter_Vehicle_SpawnDirectAsync(ctx context.Context, session *GameSession, vehicleId uint32, nearPos Vec3, facing float32, reason string, exactPos any, createSourceType int32, destroyPrevious bool) any {
|
||
// Router implementation for GameRouter_Vehicle_SpawnDirectAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_DestroyVehicleAsync(ctx context.Context, session *GameSession, runtime any, entityId uint64, reason string) any {
|
||
// Router implementation for GameRouter_Vehicle_DestroyVehicleAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// --- Enter / exit / move tracking (notifies; logged + recorded, no speculative pushes yet) ---
|
||
func GameRouter_Vehicle_AskPlayerStartEnterOrExitVehicle(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskPlayerStartEnterOrExitVehicle
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskPlayerFinishEnterOrExitVehicle(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskPlayerFinishEnterOrExitVehicle
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Vehicle_AskVehicleMove(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_AskVehicleMove
|
||
var zero any; return zero
|
||
}
|
||
|
||
// AskClaimVehicleSeat lives in GameRouter.VehicleStory.cs (story-aware seat re-pin).
|
||
// --- Drive-loop accepts (proven set from the sibling 08.05 build) ---
|
||
// The client keeps driving client-side; the server only has to accept the drive-loop
|
||
// notifies and answer the drive-state invokes with an empty OK so the loop never stalls.
|
||
func GameRouter_Vehicle_VehicleNotifyAccept(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_VehicleNotifyAccept
|
||
var zero any; return zero
|
||
}
|
||
|
||
// AskVehicleHorn / ReportDrivingVehicle / AskVehicleNitro already have logging/tracking
|
||
// handlers in GameRouter.UnhandledBatch.cs — do not re-register them here.
|
||
func GameRouter_Vehicle_VehicleInvokeAccept(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Vehicle_VehicleInvokeAccept
|
||
var zero any; return zero
|
||
}
|
||
|
||
type VehicleRecord struct {
|
||
EntityId uint64 `json:"entityId"`
|
||
ConfigId uint32 `json:"configId"`
|
||
SeatCount int32 `json:"seatCount"`
|
||
Position Vec3 `json:"position"`
|
||
Facing float32 `json:"facing"`
|
||
ControllerPid uint64 `json:"controllerPid"`
|
||
Interactable bool `json:"interactable"`
|
||
TaskToken uint64 `json:"taskToken"`
|
||
LastBits []byte `json:"lastBits"`
|
||
MoveLogged bool `json:"moveLogged"`
|
||
DistanceDriven float64 `json:"distanceDriven"`
|
||
IsDriving bool `json:"isDriving"`
|
||
LastNitroValue float32 `json:"lastNitroValue"`
|
||
TopSpeed float32 `json:"topSpeed"`
|
||
HasTopSpeed bool `json:"hasTopSpeed"`
|
||
SeatReservations map[byte]uint64 `json:"seatReservations"`
|
||
SeatOccupants map[byte]uint64 `json:"seatOccupants"`
|
||
// T1-16 载具骇入 (ported from donor_7k). AetherRegistered is what makes a spawned
|
||
// car selectable by HackTargetSelector: it iterates AetherVehicleManager's
|
||
// VehicleId2IndexDict, which SyncSpawnVehicle alone never populates.
|
||
AetherRegistered bool `json:"aetherRegistered"`
|
||
HackBusyUntilUtc time.Time `json:"hackBusyUntilUtc"`
|
||
HackTaskToken uint64 `json:"hackTaskToken"`
|
||
HackTaskStatus byte `json:"hackTaskStatus"`
|
||
// ★ 真实车流(T3-7):由 GameRouter.Traffic.cs 生成、每 tick 被车道流推着跑的车。
|
||
// true ⇒ 仍在车道流里,服务端每 tick 用 SyncAetherAIVehicleLaneDatas 覆盖它的里程。
|
||
// false ⇒ 已被玩家骇入接管,脱离车道流(由 DetachTrafficVehicleFromLaneAsync 翻转)。
|
||
//
|
||
// 为什么必须能翻转:骇入下发的 SyncAssignVehicleAITask 是 RaidVehicle 通道,
|
||
// 而车道流每 50ms 覆盖一次里程 —— 两套驱动源同时作用的话,
|
||
// 车会表现为「加速一瞬间就被拽回原处」。所以接管的第一件事是把车摘出车道流。
|
||
IsTraffic bool `json:"isTraffic"`
|
||
}
|
||
|
||
type VehicleRuntimeState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
Unlocked []uint32 `json:"unlocked"`
|
||
Vehicles map[uint64]VehicleRecord `json:"vehicles"`
|
||
SummonedEntity uint64 `json:"summonedEntity"`
|
||
CurrentVehicleId uint64 `json:"currentVehicleId"`
|
||
CurrentVehicleSeat int32 `json:"currentVehicleSeat"`
|
||
//
|
||
// 自驾状态(GameRouter.VehicleNav.cs,2026-09-27)。
|
||
// 客户端的自动驾驶是「一辆车」的开关:开关状态只由 S2C
|
||
// SyncVehicleAutonomousDrivingState 决定(DriveVehiclesManager.autoDrivingVehicleDict),
|
||
// 所以服务端必须记住「哪辆车正开着」,才能在 Stop / 下车时回推正确的 false。
|
||
// 存 session 而不是持久化:重进场景客户端会重新发 Start。
|
||
//
|
||
AutoDrivingVehicleId uint64 `json:"autoDrivingVehicleId"`
|
||
AutoDriving bool `json:"autoDriving"`
|
||
//
|
||
// 自驾下发的最后一个 AI 任务 token(2026-09-28 第三轮)。
|
||
// ⛑️ 必须记住它:客户端收到 SyncAssignVehicleAITask 后会回 AskUpdateVehicleAITaskStatus,
|
||
// 但**会一直把任务停在 Pending,直到服务端用 SyncChangeVehicleAITaskState 确认同一个
|
||
// entity + token**。不确认 ⇒ 任务永远不进 Running ⇒ 车纹丝不动。
|
||
//
|
||
AutoDriveTaskToken uint64 `json:"autoDriveTaskToken"`
|
||
AutoDriveTaskStatus byte `json:"autoDriveTaskStatus"`
|
||
AutoDriveHasTarget bool `json:"autoDriveHasTarget"`
|
||
AutoDriveTargetX float32 `json:"autoDriveTargetX"`
|
||
AutoDriveTargetY float32 `json:"autoDriveTargetY"`
|
||
AutoDriveTargetZ float32 `json:"autoDriveTargetZ"`
|
||
//
|
||
// 上次真正下发的巡航点集(2026-09-30,GM 调速用)。
|
||
// 改速度必须重发 Cruise 任务(速度写在任务参数里),而重发需要点集;
|
||
// 现算要走 SolveRoute,那是实例方法且要 Connection,GM 侧两者都没有。
|
||
// ⇒ 下发时缓存一份,热应用直接复用,顺带保证「换速度不换路线」。
|
||
//
|
||
AutoDriveLastPoints []UxVector3 `json:"autoDriveLastPoints"`
|
||
// 上次下发时用的速度(km/h)。值没变就不重发,避免 GM 手抖连点刷包。
|
||
AutoDriveSpeedKmh float32 `json:"autoDriveSpeedKmh"`
|
||
//
|
||
// 出租车行程(2026-09-30 新增,GameRouter.Taxi.cs)。null = 当前没有行程。
|
||
// <para>⛑ 只记「服务端自己派发的那一次」。客户端的原生出租车 UI(选目的地面板、
|
||
// 到达提示)状态全在客户端,服务端只负责:认单 → 派 Cruise → 判到达 → 收车费。</para>
|
||
// <para>⛑ 与 AutoDriveTaskToken 的关系:出租车复用自驾那条
|
||
// SyncAssignVehicleAITask 通道,所以行程的 token 就是 AutoDriveTaskToken;
|
||
// 本字段额外记住「这趟是出租车」+ 目的地 + 起始里程,用来算车费和判到达。</para>
|
||
//
|
||
TaxiTrip *TaxiTrip4229938 `json:"taxiTrip"`
|
||
//
|
||
// 玩家最近开过的车(上车时写,下车不清)。改装店事务要它:位移前客户端已经
|
||
// 把玩家自动请下车了(retail 的 SetSeatInteractOperation(0,6)),此时
|
||
// CurrentVehicleId 已被客户端的 exit 通知清零,只有这个还记得是谁。
|
||
//
|
||
LastDrivenVehicleId uint64 `json:"lastDrivenVehicleId"`
|
||
OwnershipSent bool `json:"ownershipSent"`
|
||
AetherInitSent bool `json:"aetherInitSent"`
|
||
// T1-16 载具骇入: serializes hack dispatch so a stale status notify cannot
|
||
// retarget a newer task while its SyncAssignVehicleAITask is still in flight.
|
||
HackDispatchGate *sync.Mutex `json:"hackDispatchGate"`
|
||
// S011 story/boarding state (single-player: one active enter/exit transaction).
|
||
StoryRootSent bool `json:"storyRootSent"`
|
||
StorySeenAnyRpc bool `json:"storySeenAnyRpc"`
|
||
StoryHighestConfirmedRpcId int64 `json:"storyHighestConfirmedRpcId"`
|
||
StoryPendingRpcIds any `json:"storyPendingRpcIds"`
|
||
StoryCommittedRpcIds []int64 `json:"storyCommittedRpcIds"`
|
||
StoryEnterVehicleId uint64 `json:"storyEnterVehicleId"`
|
||
StoryEnterSeat byte `json:"storyEnterSeat"`
|
||
StoryExitInProgress bool `json:"storyExitInProgress"`
|
||
StoryBoardingUnitId uint64 `json:"storyBoardingUnitId"`
|
||
}
|
||
|
||
func GameRouter_Vehicle_Clear(ctx context.Context, ) any {
|
||
// Router implementation for GameRouter_Vehicle_Clear
|
||
var zero any; return zero
|
||
}
|
||
|