107 lines
5.6 KiB
Go
107 lines
5.6 KiB
Go
package routers
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
)
|
|
|
|
var _ = sync.Mutex{}
|
|
var _ = context.Background
|
|
|
|
//
|
|
// RACE AI OPPONENT VEHICLES (R190, ported from the Lyco fork's GameRouter.RacingAiVehicles.cs,
|
|
// adapted to v2.1's own vehicle/Aether helpers) — the entity half of "AI 车 + 小地图蓝点 + 真名次".
|
|
// 🔑 WHY REAL ENTITIES (§59.28): the zone packet carries RacingZoneInfo.AIVehicleInfos =
|
|
// a plain Dictionary<ulong,uint> of scene vehicle entity id → racing AI cfg id.
|
|
// Every client consumer resolves that key against a REAL scene entity:
|
|
// <code>
|
|
// CarRaceManager.lua AddEntity(id) -> LX6.Drive.DriveUtils.GetBaseVehicle(id) (miss = no entity)
|
|
// MapSubSystem_CarRace.lua -> element:BindVehicle(id) (the minimap blue dot)
|
|
// C# CarRaceManager.RaceSpeedManager -> rank / lap map keyed by that same id
|
|
// </code>
|
|
// Inventing ids (the legacy 0x6000… grid) therefore can never materialise. So each opponent is
|
|
// spawned as a real BaseVehicle + Aether registration + NPC driver + ForceGo, and the (entityId,
|
|
// aiCfgId, vehicleCfgId) triples are fed into AIVehicleInfos/ParticipantInfos verbatim.
|
|
// v2.1 ADAPTATION NOTES (vs the Lyco original):
|
|
// • Spawn = the SAME three-packet sequence as v2.1's own SpawnDirectAsync (SyncLogicVehicleEnter
|
|
// + SyncSpawnVehicle + SyncChangeVehicleInteractable), but with a GIVEN entity id, no
|
|
// summon-registry/replacement side effects and a free CreateSourceType (4 = Online; 2 = Summon
|
|
// would file the car as the PLAYER'S own — exactly what registerSummoned:false exists to avoid).
|
|
// • Aether entity registration reuses v2.1's PROVEN wire shape (bare ClientVehicleInitData4229938
|
|
// with LaneHandle = -1 / ControlType = 2, the shape RegisterHackVehicleAsync has been shipping
|
|
// since the 载具骇入 work) instead of the Lyco wrapper.
|
|
// • Aether init = SendDonorAetherInitAsync (v2.1's one proven SyncAetherAIInitDatas channel).
|
|
// • Player anchor = GetStateIfExists + LastReportedPlayerPosition / WorldEntryCreateHeroPosition.
|
|
// • The driver formwork pool: env override → known-good fallback (v2.1 has no Aether
|
|
// VehicleNpcFormworkIds config section).
|
|
// • Best-effort everywhere: empty world state / missing track row / empty roster ⇒ empty list,
|
|
// and RunRacingAutoStartAsync then sends an HONEST EMPTY grid (never invented ids again).
|
|
//
|
|
func GameRouter_RacingAiVehicles_ResolveRacingAiSpawnEnabled(ctx context.Context, ) bool {
|
|
// Router implementation for GameRouter_RacingAiVehicles_ResolveRacingAiSpawnEnabled
|
|
return true
|
|
}
|
|
|
|
func GameRouter_RacingAiVehicles_RacingAiDriverId(ctx context.Context, generation int32, slot int32) uint64 {
|
|
// Router implementation for GameRouter_RacingAiVehicles_RacingAiDriverId
|
|
return 0
|
|
}
|
|
|
|
func GameRouter_RacingAiVehicles_ResolveRacingAiDriverFormworks(ctx context.Context, ) []any {
|
|
// Router implementation for GameRouter_RacingAiVehicles_ResolveRacingAiDriverFormworks
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// Spawn the opponent cars of one race. Returns one entry per successfully created entity;
|
|
// callers must treat an empty list as "no AI this race" and send an empty grid.
|
|
//
|
|
func GameRouter_RacingAiVehicles_SpawnRacingAiVehiclesAsync(ctx context.Context, session *GameSession, trackCfgId uint32, count int32, token context.Context) []any {
|
|
// Router implementation for GameRouter_RacingAiVehicles_SpawnRacingAiVehiclesAsync
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// Direct scene spawn for ONE AI opponent: the same three-packet sequence as
|
|
// SpawnDirectAsync (SyncLogicVehicleEnter + SyncSpawnVehicle +
|
|
// SyncChangeVehicleInteractable) with three differences that matter:
|
|
// 1. the entity id is GIVEN (770_000_000_000 segment) — the zone grid must name exactly
|
|
// these ids;
|
|
// 2. NO summon-registry side effects — SummonedEntity/CurrentVehicleId are
|
|
// never touched (the record still lands in Vehicles so the teardown channel works);
|
|
// 3. CreateSourceType is free (4 = Online) instead of the hardcoded Summon(2).
|
|
//
|
|
func GameRouter_RacingAiVehicles_SpawnRaceAiVehicleAsync(ctx context.Context, session *GameSession, vehicleId uint32, exactPos Vec3, facing float32, lateral float32, sourceType byte, entityId uint64, reason string, token context.Context) bool {
|
|
// Router implementation for GameRouter_RacingAiVehicles_SpawnRaceAiVehicleAsync
|
|
return true
|
|
}
|
|
|
|
//
|
|
// The AETHER half of the opponent grid: registers each car with the client's vehicle-AI
|
|
// subsystem, binds an NPC driver to it and switches its AI on. Every step is best-effort and
|
|
// individually guarded: a wiring mistake can only degrade to the statue behaviour.
|
|
//
|
|
func GameRouter_RacingAiVehicles_AttachRaceAetherDriversAsync(ctx context.Context, session *GameSession, cars []any, generation int32, token context.Context) any {
|
|
// Router implementation for GameRouter_RacingAiVehicles_AttachRaceAetherDriversAsync
|
|
var zero any; return zero
|
|
}
|
|
|
|
// Remove this race's opponents (teardown). Best-effort, never throws.
|
|
func GameRouter_RacingAiVehicles_DestroyRacingAiVehiclesAsync(ctx context.Context, session *GameSession, vehicles []any, reason string) any {
|
|
// Router implementation for GameRouter_RacingAiVehicles_DestroyRacingAiVehiclesAsync
|
|
var zero any; return zero
|
|
}
|
|
|
|
// One spawned opponent: the scene entity, its racing-AI cfg id (the AIVehicleInfos
|
|
// value), the vehicle config it was built from, the VehicleCreateSourceType it was created
|
|
// with and the world transform it was placed at (re-used verbatim by the Aether packets).
|
|
type RacingAiVehicle struct {
|
|
EntityId uint64 `json:"entityId"`
|
|
AiCfgId uint32 `json:"aiCfgId"`
|
|
VehicleCfgId uint32 `json:"vehicleCfgId"`
|
|
SourceType byte `json:"sourceType"`
|
|
Position Vec3 `json:"position"`
|
|
Facing float32 `json:"facing"`
|
|
}
|
|
|