292 lines
16 KiB
Go
292 lines
16 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 车库 / 停车(House Garage)—— 2026-09-30。
|
||
// <para>用户报的症状(3 条):① 靠近车位按 F「调取载具」能开选车面板(纯客户端 UI),
|
||
// 选完车后按 F「驶离车库」毫无后续;② 车的模型不出现在车位;③ 车库大门不开。</para>
|
||
// <para>根因(客户端 GarageManager.lua 逐行读出来的):</para>
|
||
// <list type="number">
|
||
// <item>⛔⛔ 2026-09-30 追加:真正的第一道墙在客户端配置里。
|
||
// HouseConfig[11200003].GarageIndoorId 官方写成了 301(= 绿屿公馆主体),
|
||
// 真实车库 indoor 是 302(IndoorConfig:301 绿屿公馆 / 302 绿屿公馆车库)。
|
||
// ChangeIndoor 建的是 garageIndoorIdList[GarageIndoorId] = houseId,
|
||
// 而 LoadingFinish 用当前 gMapManager.IndoorId 查它 ⇒ 站在车库里
|
||
// (实测客户端只报 332 大厅 → 302 车库,从不经过 301)永远查不到 ⇒
|
||
// CurrentHouseId 恒为 0 ⇒ SwitchCarprotVehicle 第一行
|
||
// parkinginfolist[0] 为空直接 FatalLog("换车失败,该房屋未购买或数据异常, houseId=0")
|
||
// + return ⇒ 连 AskHouseMoveParkingSpace 都发不出去(车不出、也没有「驶离车库」)。
|
||
// 5 套房里另外 4 套的 GarageIndoorId 都等于各自车库 indoor(201/300/299/379),只有绿屿错。
|
||
// 服务端改不了(CurrentHouseId / garageIndoorIdList / parkinginfolist
|
||
// 全是纯客户端状态,没有任何 RPC 能写;HouseConfig 是客户端自带表,服务端那份不下发)
|
||
// ⇒ 走客户端 fastpatch tools/garage_indoor_fix.lua(rawset(getmetatable(row),...),
|
||
// 非 BepInEx)。本文件的所有 handler 只有在那一行修好之后才可能被调用。</item>
|
||
// <item>这一族 9 条 RPC 在服务端一直是 DefaultReturnCatalog4229938 的
|
||
// shape="void" 空 body 兜底 —— 客户端发出去的请求永远等不到有意义的回应。</item>
|
||
// <item>两个关键 S2C 服务端从来不发:
|
||
// SyncHouseParking(64277819)(车位→车的清单,触发进库演出 + 车辆生成)
|
||
// 和 SyncHouseParkingEntityBinding(64060781)(每辆车的 EntityId)。</item>
|
||
// <item>LeaveGarage(行 156) 第一步就读 currentParkingInfo.ParkingVehicleUid[parkingId],
|
||
// 而这个表只能由 SyncHouseParkingEntityBinding 填 ⇒ 为空 ⇒
|
||
// 行 164 FatalLog("离开车库","ParkingVehicleUid为空") + 行 165 return ⇒
|
||
// 连 AskTeleportFromHouseGarage 都没发出去 —— 这就是「按 F 没后续」。</item>
|
||
// </list>
|
||
// <para>本文件做的事:把 9 条 RPC 全部接起来,按客户端 GarageManager 的期望回包:</para>
|
||
// <list type="bullet">
|
||
// <item>AskHouseParking / AskHouseMoveParkingSpace / GmHouseParking:
|
||
// 记账(Progress.GarageParking)+ 落盘 + 回推 SyncHouseParking(addInfoList)。
|
||
// ⛔ AskHouseMoveParkingSpace(换车 / 调取载具)还要**自己 spawn 车 + 推
|
||
// SyncHouseParkingEntityBinding** —— 客户端在这条链路上 IsSwitchingCar=true,
|
||
// SyncHouseParking 的处理会跳过进库演出,因而永远不会自己发
|
||
// AskCreateHouseParkingVehicles(详见 EnsureParkingVehiclesAsync)。
|
||
// 而 AskHouseParking(开自己的车进库)不推 SyncHouseParking:客户端回调里自己会发
|
||
// AskCreateHouseParkingVehicles(GarageManager.lua:124),再推一次会把进库演出播两遍。</item>
|
||
// <item>AskCreateHouseParkingVehicles:命门 —— 为该房每个已指派车位
|
||
// SpawnDirectAsync 出一辆车(CreateSourceType = 10 HouseParking、
|
||
// destroyPrevious:false 以便多车位共存),再回推
|
||
// SyncHouseParkingEntityBinding(填 ParkingVehicleUid)。</item>
|
||
// <item>AskDestroyHouseParkingVehicles:出库销毁本房车辆。</item>
|
||
// <item>AskTeleportToHouseGarage:只回 OK(进库演出由客户端自己播)。</item>
|
||
// <item>AskTeleportFromHouseGarage:开门(复用 PushDoorOpen + 缓存的门 id)+ 回 OK。</item>
|
||
// <item>AskHouseCancelParking / GmHouseCancelParking:取消指派 + 回推 SyncHouseParking(cancelInfoList)。</item>
|
||
// </list>
|
||
// <para>车辆落点:HouseConfig.ParkingSpaceWaypoints 是 Spoon 航点 id
|
||
// (如 1893051),服务端没有 Spoon 位置表(E:\ConfigDump 里查不到),
|
||
// 所以无法把车精确摆到车位格。退而求其次:落在玩家当前位置(进库演出刚把玩家放到车位,
|
||
// 或玩家就站在车库里),前向偏移由 gameplay.houseGarage.spawnForwardOffset 热调。
|
||
// 车一旦生成,客户端就能按 F 上车 / 驶离。</para>
|
||
// <para>回滚:gameplay.houseGarage.enabled=false(全部回空 body 兜底),
|
||
// 或把 9 条 mid 从 Enabled4229938MethodIds 删掉。两者都与改动前逐字节一致。</para>
|
||
//
|
||
// 记下最近一次车库事务的 houseId(出库传送要用来查落点)。
|
||
func GameRouter_Garage_RememberGarageHouse(ctx context.Context, session *GameSession, houseId uint32) any {
|
||
// Router implementation for GameRouter_Garage_RememberGarageHouse
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 本会话最近一次车库事务的 houseId;没有则 0。
|
||
func GameRouter_Garage_LastGarageHouseId(ctx context.Context, session *GameSession) uint32 {
|
||
// Router implementation for GameRouter_Garage_LastGarageHouseId
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// 玩家此刻正在开的车(entityId)。出库销毁必须跳过它 ——
|
||
// 客户端一坐进车位上的车就会走「驶离车库」流程、发 AskDestroyHouseParkingVehicles,
|
||
// 无差别销毁会把玩家正坐着的车一起拆掉(2026-09-30 实测:23:38:20 上车,
|
||
// 同秒 SyncDestroyVehicle,模型当场消失、角色保持开车姿势悬空,4 秒后
|
||
// LeaveGarage 再也找不到车辆实例 ⇒ 出库演出卡死 ⇒ 黑屏)。
|
||
// <para>三个信号取并集,都在「当前」语义上:
|
||
// CurrentVehicleId(AskPlayerStartEnterOrExitVehicle 写)、
|
||
// StoryEnterVehicleId(剧情通道 S011PlayerNetEnterVehicleRequest 写,实测走的是这条)、
|
||
// Vehicles[id].ControllerPid == 本玩家 pid。
|
||
// 不用 LastDrivenVehicleId:它下车不清,会把早先停好的车误判成「正开着」。</para>
|
||
//
|
||
func GameRouter_Garage_ResolveDrivenVehicleIds(ctx context.Context, session *GameSession) []any {
|
||
// Router implementation for GameRouter_Garage_ResolveDrivenVehicleIds
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_Garage_GetGarageRuntime(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_Garage_GetGarageRuntime
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 1) AskHouseParking(63154177) —— 「开着自己的车进车库」
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskHouseParking(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskHouseParking
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 2) AskHouseMoveParkingSpace(63780060) —— 「调取载具 / 换车」(用户场景的主链路)
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskHouseMoveParkingSpace(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskHouseMoveParkingSpace
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 3) AskHouseCancelParking(63495236) / GmHouseCancelParking(65601170) —— 按车辆 id 取消
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskHouseCancelParking(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskHouseCancelParking
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Garage_OnGmHouseCancelParking(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnGmHouseCancelParking
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 4) GmHouseParking(65027125) —— GM:把 houseIdList[i] 的车 vehicleIdList[i] 停进去
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnGmHouseParking(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnGmHouseParking
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 5) AskCreateHouseParkingVehicles(63479109) —— 命门:spawn 车 + 回填 EntityId
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskCreateHouseParkingVehicles(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskCreateHouseParkingVehicles
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 6) AskDestroyHouseParkingVehicles(63266373) —— 出库销毁本房车辆
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskDestroyHouseParkingVehicles(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskDestroyHouseParkingVehicles
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 7) AskTeleportToHouseGarage(63099218) —— 进库(演出客户端自己播,回 OK 即可)
|
||
// =========================================================================================
|
||
func GameRouter_Garage_OnAskTeleportToHouseGarage(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskTeleportToHouseGarage
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Garage_OnAskTeleportFromHouseGarage(ctx context.Context, conn any, msg any) any {
|
||
// Router implementation for GameRouter_Garage_OnAskTeleportFromHouseGarage
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// 出库落点推导,优先级:配置 exitLandingOverrides >
|
||
// HouseConfig.VisitorExit(绝对坐标)> 该房 MapentranceConfig 那一行 >
|
||
// 玩家当前上报位置(原地,宁可不动也不能黑屏)。
|
||
// <para>为什么不能用客户端自己那份:出库落点客户端取的是
|
||
// HouseConfig.GarageOutWaypoint(Spoon 航点 id,服务端没有 Spoon 位置表),
|
||
// 而且 ExitTimeline 只有 1 个元素的房(素光小筑)连 Spoon 反查都不做 ⇒
|
||
// afterPosition 全零。</para>
|
||
//
|
||
func GameRouter_Garage_ResolveGarageExitLanding(ctx context.Context, houseId uint32, log any) any {
|
||
// Router implementation for GameRouter_Garage_ResolveGarageExitLanding
|
||
var zero any; return zero
|
||
}
|
||
|
||
// =========================================================================================
|
||
// helpers
|
||
// =========================================================================================
|
||
//
|
||
// 确保「本房这些车位」上各有一辆活着的车,并返回可直接推给客户端的
|
||
// SyncHouseParkingEntityBinding 列表。
|
||
// <para>复用顺序:① 同一辆车换车位(PreviousSpace >= 0)时把运行时里旧车位的实体搬过来,
|
||
// 不重新生成;② 目标车位已有活着的实体直接复用;③ 都没有才 SpawnDirectAsync。</para>
|
||
// <para>两条链路都用它:AskCreateHouseParkingVehicles(进库演出结束后客户端主动要)
|
||
// 和 AskHouseMoveParkingSpace(换车 / 调取载具 —— 那条链路上客户端因为
|
||
// IsSwitchingCar 不会自己发前者,所以服务端必须自己生成)。</para>
|
||
//
|
||
func GameRouter_Garage_EnsureParkingVehiclesAsync(ctx context.Context, conn any, houseId uint32, wanted []any) []any {
|
||
// Router implementation for GameRouter_Garage_EnsureParkingVehiclesAsync
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_Garage_TryReadGarageArgs(ctx context.Context, conn any, msg any, args any) bool {
|
||
// Router implementation for GameRouter_Garage_TryReadGarageArgs
|
||
return true
|
||
}
|
||
|
||
// 把 放进该房第一个空车位,返回车位号(0 基);无空位返回 -1。
|
||
// 该车已在本房某个车位时返回原位置(幂等)。调用方必须持有 world.SyncRoot。
|
||
func GameRouter_Garage_AssignFreeSpace(ctx context.Context, world any, houseId uint32, vehicleId uint32) int32 {
|
||
// Router implementation for GameRouter_Garage_AssignFreeSpace
|
||
return 0
|
||
}
|
||
|
||
// 按车辆 id 取消车位指派(跨所有房),落盘并回推 cancelInfoList。
|
||
func GameRouter_Garage_CancelVehicles(ctx context.Context, conn any, vehicleIds []any) []any {
|
||
// Router implementation for GameRouter_Garage_CancelVehicles
|
||
return nil
|
||
}
|
||
|
||
func GameRouter_Garage_PushSyncHouseParkingAsync(ctx context.Context, session *GameSession, cancel []any, add []any, log any) any {
|
||
// Router implementation for GameRouter_Garage_PushSyncHouseParkingAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_Garage_PushSyncHouseParkingEntityBindingAsync(ctx context.Context, session *GameSession, binding []any, log any) any {
|
||
// Router implementation for GameRouter_Garage_PushSyncHouseParkingEntityBindingAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
// 车辆落点:玩家当前位置(+ 配置的前向偏移)。
|
||
func GameRouter_Garage_GarageSpawnPoint(ctx context.Context, state any, forwardOffset float32) Vec3 {
|
||
// Router implementation for GameRouter_Garage_GarageSpawnPoint
|
||
var zero Vec3; return zero
|
||
}
|
||
|
||
func GameRouter_Garage_IsVehicleAlive(ctx context.Context, session *GameSession, entityId uint64) bool {
|
||
// Router implementation for GameRouter_Garage_IsVehicleAlive
|
||
return true
|
||
}
|
||
|
||
//
|
||
// 本玩家正开着的、VehicleRecord.ConfigId == vehicleConfigId 的那个实体;没有则 0。
|
||
// <para>用途:玩家把车库里的车开出去、又开回来入库时复用同一个实体
|
||
// (EnsureParkingVehiclesAsync 的步骤 ②b)。出库时刻意不销毁玩家正开的车,
|
||
// 所以它仍在世界里活着;不复用就会给同一辆车生成第二份实体。</para>
|
||
// <para>锁序:调用方持有 GarageRuntimeState.SyncRoot 时本方法会再取
|
||
// VehicleRuntimeState.SyncRoot ⇒ 顺序恒为 Garage → Vehicle(与
|
||
// OnAskDestroyHouseParkingVehicles 一致),不会反向嵌套。</para>
|
||
//
|
||
func GameRouter_Garage_FindDrivenVehicleEntity(ctx context.Context, session *GameSession, vehicleConfigId uint32) uint64 {
|
||
// Router implementation for GameRouter_Garage_FindDrivenVehicleEntity
|
||
return 0
|
||
}
|
||
|
||
// 本会话运行时的车库实体表:houseId → (车位号(0 基) → 车辆 entityId)。
|
||
type GarageRuntimeState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
EntitiesByHouse map[uint32]map[int32]uint64 `json:"entitiesByHouse"`
|
||
//
|
||
// 最近一次车库 RPC 里出现的 houseId。给「出库传送」用:AskTeleport(config=7)
|
||
// 的 PreTeleportOption 里没有 houseId(客户端只填了 timeline 资源名),
|
||
// 而它前面必定先来过一条带 houseId 的车库 RPC(AskDestroyHouseParkingVehicles 4B /
|
||
// AskCreateHouseParkingVehicles 4B / AskHouseMoveParkingSpace 的元素里),
|
||
// 所以在这里记一份。
|
||
//
|
||
LastHouseId uint32 `json:"lastHouseId"`
|
||
}
|
||
|
||
// =========================================================================================
|
||
// 8) AskTeleportFromHouseGarage(63203581) —— 出库:开门 + **推 SyncTeleport 落点** + 回 OK
|
||
// =========================================================================================
|
||
//
|
||
// 一趟出库事务待落的点(写于 OnAskTeleportFromHouseGarage,
|
||
// 消费于 ReportPostTeleportFinish 的硬设兜底 —— 与景点电梯四修同一个套路)。
|
||
//
|
||
type PendingGarageLanding struct {
|
||
HouseId uint32 `json:"houseId"`
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
Z float32 `json:"z"`
|
||
Facing float32 `json:"facing"`
|
||
VehicleId uint64 `json:"vehicleId"`
|
||
}
|
||
|
||
// 出库落点 + 该填进 SyncTeleport.MapEntranceId 的入口 id。
|
||
type GarageExitLanding struct {
|
||
X float32 `json:"x"`
|
||
Y float32 `json:"y"`
|
||
Z float32 `json:"z"`
|
||
Facing float32 `json:"facing"`
|
||
EntranceId uint32 `json:"entranceId"`
|
||
Source string `json:"source"`
|
||
}
|
||
|