122 lines
6.7 KiB
Go
122 lines
6.7 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// 原版赛车图加载通道 — SyncPlayerSingleTask(64917604) + TaskSpoonViewInfo.
|
||
// WHY THIS FILE EXISTS (2026-09-23 evening, "还原大于一切")
|
||
// Until now the racing Spoon graph was force-merged by a CLIENT fastpatch that injected
|
||
// "<raidId>_RacingDriver" into EnterSceneInfo.SceneSpoonNames. That is NOT the original
|
||
// behaviour and we can now prove it: in the only 4 official-server captures we own
|
||
// (logs\packets-20260913-*.log; 09-14 onward is our own private server — the discriminator is
|
||
// that SyncAetherAIVehicleAddData first appears 09-14 15:41), the official server sends
|
||
// SceneSpoonNames: [] AND SceneSpoonMd5s: [] while the player stands inside the racing
|
||
// raid 23300888. RaidConfig carries no spoon field either. So the racing graph is NOT delivered
|
||
// as a scene spoon.
|
||
// The remaining native channel is the TASK graph:
|
||
// <code>
|
||
// SpoonTaskMgr (client, [ExportToLua])
|
||
// TASK_SPOON_FILE_PATH / TASK_RAID_FILE_PATH ← graphs are local files
|
||
// GetSpoonTaskFilePath(spRaidId, alias, out fileName) ← keyed by (spRaidId, alias)
|
||
// SyncPlayerSingleTask(taskId, TaskViewData, TaskSpoonViewInfo, TaskStateData) ← RPC entry
|
||
// LoadTaskWorkActionData(taskId, taskSpoon) → GetTaskWorkActionByTask
|
||
// </code>
|
||
// TaskSpoonViewInfo = { SpoonMd5, SpRaidId, StartTaskId, EndTaskId, Alias, EventId,
|
||
// EventStartTaskId } — and the racing track table authors exactly those two keys inside
|
||
// RacingDriverTrackInformationConfig.RaidSpoonName:
|
||
// <code>
|
||
// "23300888_RacingDriver" → SpRaidId 23300888, Alias "RacingDriver"
|
||
// "spoon23301289_RacingDriver" → SpRaidId 23301289, Alias "RacingDriver"
|
||
// </code>
|
||
// GameRouter.Task.cs already speaks this RPC, but ONLY for the demo story task and with a
|
||
// PLACEHOLDER SpoonMd5; the racing task 60000147 was never sent through it. That is the
|
||
// documented gap this file closes.
|
||
// WHAT IT SHOULD FIX
|
||
// The 2026-09-23 probes read task.workActions=0 and, in the client log,
|
||
// CarRaceManager.lua:291/417 nextRankPoint is nil + counter is nil spam at ~60/s — i.e.
|
||
// the racing task has NO work actions, which is exactly what LoadTaskWorkActionData fills in.
|
||
// Without them the client cannot build the checkpoint / rank-point list, so the arrow, the GPS track
|
||
// and the rank logic are all dead. Those checkpoints are also the only thing that could give the
|
||
// racing AI its route, which is why the AI opponents currently spawn but never drive.
|
||
// IS IT ORIGINAL?
|
||
// The RPC, the payload types and the key derivation are all native (client dump + client-generated
|
||
// readers). What is NOT original is the CONTENT: we synthesise the task-graph reference from
|
||
// RacingDriverTrackInformationConfig because the official server's task-Spoon config
|
||
// (TASK_SPOON_FILE_PATH / TASK_RAID_FILE_PATH source) is server-side and we do not have
|
||
// it. SpoonMd5 is therefore left empty, and the client then silently ignores the whole
|
||
// push (no error, no graph, workActions = 0). The real md5 is the one genuinely missing piece.
|
||
// ⛔ 已证伪的两个旧说法(别再用):
|
||
// 1. 「空 md5 客户端会容忍,只警告版本不一致、照旧跑图」—— 那是**场景**通道的行为
|
||
// (SpoonRaidMgr.cs:87),**任务图**通道不是。任务图这边是静默忽略。
|
||
// 2. 「填个非空假 md5 就能逼客户端走加载分支」—— 实测**硬崩**(R211b,2026-09-30 23:05)。
|
||
// 见 RacingTaskSpoonMd5。
|
||
// SAFETY
|
||
// Additive only: nothing already being sent changes, and a failure degrades to today's behaviour.
|
||
// Kill switch: RacingTaskSpoonLoadEnabled.
|
||
//
|
||
//
|
||
// Splits a RacingDriverTrackInformationConfig.RaidSpoonName into the two keys
|
||
// TaskSpoonViewInfo actually wants: SpRaidId (the numeric prefix, with an optional
|
||
// "spoon" word in front) and Alias (everything after the first underscore).
|
||
// Real values observed in the client config:
|
||
// <code>
|
||
// 23300888_RacingDriver → (23300888, "RacingDriver")
|
||
// 23300999_RacingDriver → (23300999, "RacingDriver")
|
||
// spoon23301289_RacingDriver → (23301289, "RacingDriver")
|
||
// spoon23301305_main → (23301305, "main")
|
||
// spoon23301294_23301294_Race_Chaos_Gadgets → (23301294, "23301294_Race_Chaos_Gadgets")
|
||
// </code>
|
||
// Returns false (and zero/empty) when the string cannot be parsed — the caller then skips the
|
||
// push rather than sending a nonsense graph key.
|
||
//
|
||
func GameRouter_RacingTaskGraph_TryParseRaidSpoonName(ctx context.Context, raidSpoonName any, spRaidId uint32, alias string) bool {
|
||
// Router implementation for GameRouter_RacingTaskGraph_TryParseRaidSpoonName
|
||
return true
|
||
}
|
||
|
||
//
|
||
// Racing task-graph reference for a track: SpRaidId + Alias parsed from the track's
|
||
// authored RaidSpoonName, plus the track's EventIds[0] for
|
||
// TaskSpoonViewInfo.EventId.
|
||
// The authored name is authoritative and self-contained (its numeric prefix IS the spoon raid),
|
||
// so is NOT used as a filter — the caller logs it for inspection only,
|
||
// because the client keys the graph on (spRaidId, alias) rather than on the player's raid.
|
||
// Alias precedence (highest first):
|
||
// 1. RacingTaskSpoonAliasOverride — manual A/B knob.
|
||
// 2. RacingTaskSpoonVerifiedAliases — alias literally read from a client asset.
|
||
// 3. parsed from RaidSpoonName (known to be the SCENE-spoon spelling, so it is only a
|
||
// last resort and is logged as such).
|
||
//
|
||
func GameRouter_RacingTaskGraph_TryResolveRacingTaskSpoon(ctx context.Context, trackId uint32, spRaidId uint32, alias string, eventId uint32, aliasSource string) bool {
|
||
// Router implementation for GameRouter_RacingTaskGraph_TryResolveRacingTaskSpoon
|
||
return true
|
||
}
|
||
|
||
// Back-compat overload without the alias-provenance output.
|
||
func GameRouter_RacingTaskGraph_TryResolveRacingTaskSpoon_2(ctx context.Context, trackId uint32, spRaidId uint32, alias string, eventId uint32) bool {
|
||
// Router implementation for GameRouter_RacingTaskGraph_TryResolveRacingTaskSpoon_2
|
||
return true
|
||
}
|
||
|
||
//
|
||
// Pushes SyncPlayerSingleTask(64917604) for one racing task so the client loads the racing
|
||
// TASK graph and populates its work actions (checkpoints / rank points).
|
||
// Wire (method-frame body, Inline — no root 0xFF marker; nested entities are Complex):
|
||
// TaskId = uint32
|
||
// TaskInfo = TaskViewData (0xFF)
|
||
// SpoonViewInfo = TaskSpoonViewInfo (0xFF)
|
||
// StateData = TaskStateData (0xFF)
|
||
// Best-effort: any failure is logged and swallowed so the race itself is unaffected.
|
||
//
|
||
func GameRouter_RacingTaskGraph_PublishRacingTaskSpoonAsync(ctx context.Context, rpcCtx *RpcContext, taskId uint32, trackId uint32, raidId uint32) any {
|
||
// Router implementation for GameRouter_RacingTaskGraph_PublishRacingTaskSpoonAsync
|
||
var zero any; return zero
|
||
}
|
||
|