122 lines
5.9 KiB
Go
122 lines
5.9 KiB
Go
package routers
|
||
|
||
import (
|
||
"context"
|
||
"sync"
|
||
)
|
||
|
||
var _ = sync.Mutex{}
|
||
var _ = context.Background
|
||
|
||
//
|
||
// Fifth entity channel: functional NPCs at fixed city spots (shop clerks, ticket sellers, ...).
|
||
// Same four-step announce sequence as the citizen channels — this file does not invent a spawn
|
||
// path, it only supplies different coordinates. Instead of "a plot's pavement spots, how many
|
||
// decided by the hour" the positions are固定的: one per venue, already snapped onto pavement by
|
||
// the generator, so a shop clerk ends up where the shop actually is.
|
||
// What the NPC *does* is not our problem here: F-key behaviour (open a shop panel, buy a ticket,
|
||
// start a mahjong match) is driven entirely by the client's own lua through
|
||
// AgentConfig.InteractSetting. The server just has to put a body there. That is deliberate — it
|
||
// is the whole reason this card is cheap.
|
||
// Two ways in, sharing one placement helper so they can never disagree:
|
||
// • the follow loop (EnsureFunctionalNpcs) — how they normally appear: walk up to
|
||
// a shop and its clerk is standing there;
|
||
// • the panel button (SpawnFunctionalNpcsAsync) — "put them all down now".
|
||
//
|
||
func GameRouter_FunctionalNpc_FunctionalNpcStateOf(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_FunctionalNpcStateOf
|
||
var zero any; return zero
|
||
}
|
||
|
||
// World-ready hook: start the follow loop. Idempotent; no-op when disabled.
|
||
func GameRouter_FunctionalNpc_EnsureFunctionalNpcs(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_EnsureFunctionalNpcs
|
||
var zero any; return zero
|
||
}
|
||
|
||
// Disconnect / scene switch: stop the loop. The client clears the entities itself.
|
||
func GameRouter_FunctionalNpc_StopFunctionalNpcs(ctx context.Context, session *GameSession) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_StopFunctionalNpcs
|
||
var zero any; return zero
|
||
}
|
||
|
||
// How many the follow loop currently has standing. Panel display only.
|
||
func GameRouter_FunctionalNpc_FunctionalNpcActiveCount(ctx context.Context, session *GameSession) int32 {
|
||
// Router implementation for GameRouter_FunctionalNpc_FunctionalNpcActiveCount
|
||
return 0
|
||
}
|
||
|
||
func GameRouter_FunctionalNpc_RunFunctionalNpcLoopAsync(ctx context.Context, session *GameSession, owner any, token context.Context) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_RunFunctionalNpcLoopAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_FunctionalNpc_TickFunctionalNpcsAsync(ctx context.Context, session *GameSession, owner any, cfg any) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_TickFunctionalNpcsAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_FunctionalNpc_RemoveFunctionalNpcAsync(ctx context.Context, session *GameSession, state any, owner any, id uint64, spot any) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_RemoveFunctionalNpcAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
//
|
||
// ★★★ R93(2026-09-27)—— **落脚高度吸附**:把点位声明的 Y 换成「该 XZ 上真实地面/物件的高度」。
|
||
// 为什么必须做:FunctionalNpcSpots.json 的 Y 是用 CivilianDistricts 人行道点
|
||
// 反向找的(见该文件 note)。那套人行道点只在**主城平地**有数据,一旦点位的 XZ 落在
|
||
// **高台/庙会/室内**,找出来的就是别处(常常是 y≈0/2 那一层)的 Y。
|
||
// 实测标本(2026-09-27):Tea Shop Owner(40954014)/Tea Stall Owner(40954022) 声明 Y=2.00,
|
||
// 而同 XZ 上 2528 件场景物一律 **y≈166~172** ⇒ NPC 被生成在**地下 167 m**,
|
||
// 现象 = 用户所说的「摊位的npc呢」(看着像根本没刷,其实刷了、埋了)。
|
||
// 数据源 = config/scene_item_placements.json(mtime 热重载,4877 件带真实 y)。
|
||
// 取点位 XZ **半径 内最近的若干件**的 y 中位数当落脚高度:
|
||
// 中位数对「同一点位旁边既有台面又有地面」的情况比最近邻稳。
|
||
// 命中不到(荒地/新点位)⇒ **原样返回**,绝不猜。
|
||
//
|
||
func GameRouter_FunctionalNpc_SnapGroundY(ctx context.Context, x float32, y float32, z float32, samples int32, snapped float32, radius float32) float32 {
|
||
// Router implementation for GameRouter_FunctionalNpc_SnapGroundY
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// One placement: announce + remember. Returns the entity id, or 0 when the spot's ids are not
|
||
// in the catalogs (a bad row in the json must not kill the batch).
|
||
//
|
||
func GameRouter_FunctionalNpc_PlaceFunctionalNpcAsync(ctx context.Context, session *GameSession, state any, owner any, spot any) uint64 {
|
||
// Router implementation for GameRouter_FunctionalNpc_PlaceFunctionalNpcAsync
|
||
return 0
|
||
}
|
||
|
||
//
|
||
// Panel button: place every functional NPC of the current scene (nearest first when a radius
|
||
// is given, radius <= 0 = the whole scene). Anything already standing is skipped.
|
||
//
|
||
func GameRouter_FunctionalNpc_SpawnFunctionalNpcsAsync(ctx context.Context, session *GameSession, radius float32, includeSemantic bool) any {
|
||
// Router implementation for GameRouter_FunctionalNpc_SpawnFunctionalNpcsAsync
|
||
var zero any; return zero
|
||
}
|
||
|
||
func GameRouter_FunctionalNpc_Distance2(ctx context.Context, spot any, x float32, z float32) float32 {
|
||
// Router implementation for GameRouter_FunctionalNpc_Distance2
|
||
return 0
|
||
}
|
||
|
||
type FunctionalNpcSessionState struct {
|
||
SyncRoot any `json:"syncRoot"`
|
||
Cts *context.CancelFunc `json:"cts"`
|
||
Loop *any `json:"loop"`
|
||
// entityId -> the spot it was placed for. Doubles as the "already placed" set.
|
||
Active map[uint64]FunctionalNpcSpot4229938 `json:"active"`
|
||
//
|
||
// Formwork ids whose placement has been started but not finished yet.
|
||
// ⛑ Placement awaits several network writes and happens OUTSIDE the lock, so a tick that
|
||
// starts while the previous batch is still in flight cannot see it in Active
|
||
// and would place the same clerk again — two identical NPCs stacked on one spot.
|
||
//
|
||
Pending []uint32 `json:"pending"`
|
||
// Scene the Active entries belong to; a change wipes them.
|
||
Raid uint32 `json:"raid"`
|
||
}
|
||
|