package routers import ( "context" "sync" ) var _ = sync.Mutex{} var _ = context.Background // // Playable characters standing in the city, each at whatever their own daily schedule says they // are doing right now — Richie on the treadmill, Garm at the claw machine, and so on. // Two reasons this file exists: // 1. Official has them roaming from the start (the 趴卜 schedule tab and the big-map markers exist // precisely so the player can go find them). We only ever materialised one at the moment the // player switched away from that character, so walking to a schedule coordinate found // nobody — which is exactly what the player reported on 09-25. // 2. The client unloads distant units and never re-adds them by itself: coming back to a spot from // far away shows an empty pavement until you switch the character away and back. So the server // has to own the distance bookkeeping — place on enter, retire on exit, place again on return. // That is not a new idea; it is the same follow-loop + hysteresis shape as // GameRouter.EnsureFunctionalNpcs, only with different coordinates. // Coordinates and the activity id come straight from the client's own tables // (see NpcActivityCatalog4229938): nothing here is invented. // func GameRouter_RosterNpc_RosterNpcStateOf(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_RosterNpc_RosterNpcStateOf var zero any; return zero } // World-ready hook: start the follow loop. Idempotent; no-op when disabled. func GameRouter_RosterNpc_EnsureRosterNpcs(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_RosterNpc_EnsureRosterNpcs var zero any; return zero } // Disconnect / scene switch: stop the loop. The client clears the entities itself. func GameRouter_RosterNpc_StopRosterNpcs(ctx context.Context, session *GameSession) any { // Router implementation for GameRouter_RosterNpc_StopRosterNpcs var zero any; return zero } // How many are currently standing. Panel display only. func GameRouter_RosterNpc_RosterNpcActiveCount(ctx context.Context, session *GameSession) int32 { // Router implementation for GameRouter_RosterNpc_RosterNpcActiveCount return 0 } // // ★ 切人接管时**立即**撤掉常驻摆在日程点上的那具身体(09-26 00:4x 加)。 // ⛑⛑ 为什么必须同步做:开了 SwitchToScheduleSpot 之后,玩家会直接瞬移到 // 角色的日程点位。而那个点位上常驻化很可能已经摆了一具同样的身体 // (他本来就「在城里过日子」)⇒ 瞬移过去就是「两个里希重叠」的老毛病(规则 166)。 // 常驻循环 1.5 秒才跳一次,等它自己撤(spirit == activeSpirit)中间那一秒玩家 // 就盯着两个自己 —— 所以切人时主动撤一次。 // ⛑ 撤不掉也不至于更糟:RemoveRosterNpcAsync 已经是 Leave+Aether 双发(规则 166)。 // func GameRouter_RosterNpc_RetireRosterNpcForSwitchAsync(ctx context.Context, session *GameSession, spiritId uint32) bool { // Router implementation for GameRouter_RosterNpc_RetireRosterNpcForSwitchAsync return true } // // spiritId -> unitId for everything currently on the field. The GM panel uses it to mark // which rows are actually standing in the world right now (as opposed to merely scheduled). // func GameRouter_RosterNpc_RosterNpcPlacements(ctx context.Context, session *GameSession) map[string]any { // Router implementation for GameRouter_RosterNpc_RosterNpcPlacements return nil } func GameRouter_RosterNpc_RunRosterNpcLoopAsync(ctx context.Context, session *GameSession, owner any, token context.Context) any { // Router implementation for GameRouter_RosterNpc_RunRosterNpcLoopAsync var zero any; return zero } func GameRouter_RosterNpc_TickRosterNpcsAsync(ctx context.Context, session *GameSession, owner any, cfg any) any { // Router implementation for GameRouter_RosterNpc_TickRosterNpcsAsync var zero any; return zero } func GameRouter_RosterNpc_PlaceRosterNpcAsync(ctx context.Context, session *GameSession, state any, owner any, spiritId uint32, choice any, cfg any, mode string) any { // Router implementation for GameRouter_RosterNpc_PlaceRosterNpcAsync var zero any; return zero } func GameRouter_RosterNpc_RemoveRosterNpcAsync(ctx context.Context, session *GameSession, state any, owner any, spiritId uint32) any { // Router implementation for GameRouter_RosterNpc_RemoveRosterNpcAsync var zero any; return zero } func GameRouter_RosterNpc_Dist2(ctx context.Context, x float32, z float32, px float32, pz float32) float32 { // Router implementation for GameRouter_RosterNpc_Dist2 return 0 } // // 目标点位 米内(3D)是否已经站着别的 NPC。 // 用 3D 是为了让「同一栋楼 1 楼和 2 楼各站一个」这种正常情况不误判 —— 只有真的贴在一起才算冲突。 // func GameRouter_RosterNpc_Taken(ctx context.Context, x float32, y float32, z float32, poses []any, separation float32) bool { // Router implementation for GameRouter_RosterNpc_Taken return true } type RosterNpcSessionState struct { SyncRoot any `json:"syncRoot"` Cts *context.CancelFunc `json:"cts"` Loop *any `json:"loop"` // spiritId -> what was placed for it. Doubles as the "already placed" set. Active map[uint32]RosterNpcPlacement `json:"active"` // Scene the Active entries belong to; a change wipes them. Raid uint32 `json:"raid"` // 是否已经打过一次「名册里有多少人没有可落地活动」的诊断行(只打一次,别刷屏)。 Diagnosed bool `json:"diagnosed"` } type RosterNpcPlacement struct { UnitId uint64 `json:"unitId"` X float32 `json:"x"` Y float32 `json:"y"` Z float32 `json:"z"` ActivityId uint32 `json:"activityId"` }