This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

1759 lines
83 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routers
import (
"context"
"sync"
)
var _ = sync.Mutex{}
var _ = context.Background
// Public snapshot of the tracked game session for the admin panel (no internal types leak).
type AdminPlayerSnapshot struct {
Connected bool `json:"connected"`
SessionId string `json:"sessionId"`
LastSeenUtc *string `json:"lastSeenUtc"`
UnitId uint64 `json:"unitId"`
TemplateId uint32 `json:"templateId"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Facing float32 `json:"facing"`
HasTransform bool `json:"hasTransform"`
Ready bool `json:"ready"`
ActiveRaidId uint32 `json:"activeRaidId"`
ActiveInstanceId uint64 `json:"activeInstanceId"`
ActiveUniverseId uint32 `json:"activeUniverseId"`
PendingTeleportId uint64 `json:"pendingTeleportId"`
LastAdminVehicleId uint32 `json:"lastAdminVehicleId"`
LastSpawnedVehicleUid uint64 `json:"lastSpawnedVehicleUid"`
CurrentVehicleId uint64 `json:"currentVehicleId"`
CurrentVehicleSeat int32 `json:"currentVehicleSeat"`
LastVehicleDriveState byte `json:"lastVehicleDriveState"`
HasVehicleDriveState bool `json:"hasVehicleDriveState"`
TimeHour uint32 `json:"timeHour"`
TimeMinute uint32 `json:"timeMinute"`
TimeFixed bool `json:"timeFixed"`
HasExplicitTime bool `json:"hasExplicitTime"`
WeatherId uint32 `json:"weatherId"`
HasExplicitWeather bool `json:"hasExplicitWeather"`
}
// Public weapon catalog entry for the admin panel.
type AdminWeaponEntry struct {
TemplateId uint32 `json:"templateId"`
Name string `json:"name"`
InstanceId uint64 `json:"instanceId"`
}
// Public vehicle catalog entry for the admin panel (flying/water spawner).
type AdminVehicleEntry struct {
TemplateId uint32 `json:"templateId"`
Name string `json:"name"`
VehicleType int32 `json:"vehicleType"`
SeatCount int32 `json:"seatCount"`
Category string `json:"category"`
}
// Public scene preset (raid/instance/universe + arrival) for the admin panel.
type AdminScenePreset struct {
Key string `json:"key"`
Label string `json:"label"`
RaidId uint32 `json:"raidId"`
InstanceId uint64 `json:"instanceId"`
UniverseId uint32 `json:"universeId"`
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Facing float32 `json:"facing"`
}
// Public NPC (character) catalog entry for the admin panel.
type AdminNpcEntry struct {
NpcCultivationId uint32 `json:"npcCultivationId"`
Name string `json:"name"`
SpiritId uint32 `json:"spiritId"`
VariantCount int32 `json:"variantCount"`
Menus []string `json:"menus"`
}
// One row of the 生成敌人 / 生成NPC catalog (raw AgentConfig agent).
type AdminAgentEntry struct {
AgentId uint32 `json:"agentId"`
Name string `json:"name"`
EnemyClassType uint32 `json:"enemyClassType"`
AiSetting uint32 `json:"aiSetting"`
//
// Number of AgentConfig rows sharing this display name inside the same panel. AgentConfig
// holds hundreds of near-identical variants per character (same model, different AI /
// spawn-group), which used to flood the dropdown. Only the first variant is listed.
//
VariantCount int32 `json:"variantCount"`
//
// Every AgentConfig id that collapsed into this entry, ascending. Kept so the search box can
// still resolve an id that is no longer shown, and so any variant stays reachable.
//
VariantIds []uint32 `json:"variantIds"`
//
// Whether the client binds an enemy HP bar to this template. The bar is a property of
// AgentConfig (ShowHPBar, plus a non-zero InitWeaponId), not of anything the server pushes:
// no amount of SyncUnitHp / SyncUnitAttacked conjures one onto a template that has none.
// False means the enemy still fights, takes damage and dies — it just shows no bar, which
// reads as "this thing cannot be killed".
//
HasHpBar bool `json:"hasHpBar"`
}
//
// Bridge between the game session and the embedded admin web server.
// Fed by the per-frame hook in PrivateServerApplication (every login+game frame);
// only sessions carrying a 4229938 world state are treated as the player session.
// All pushes use the same DTOs + method ids as the in-game handlers.
//
type GameAdminBridge struct {
Sessions map[string]any `json:"sessions"`
S_adminTeleportCounter int64 `json:"s_adminTeleportCounter"`
// ------------------------------------------------------------------
// 本地创建扫描:业务 Lua(NpcInviteManager / DartsGame / MaJiang)生成可交互单位
// 用的是 gCS.LuaUtils.CreateClientAgentByCfg(cfgId, pos, angle, cb, suit),
// 读的就是 AgentConfig —— 纯客户端本地实例化,自带全套 AI / 受击 / 交互,
// 不产生任何 C2S(区别于 AddEnemy*:那些会转发给服务端 RMPC 由服务器生成)。
// GmUtils 暴露了同一批本地入口,反射调用它们即可复刻 ALT+F1 面板的效果。
// ------------------------------------------------------------------
LocalCreateCandidates []any `json:"localCreateCandidates"`
NpcCatalogCache []AdminNpcEntry `json:"npcCatalogCache"`
//
// Agent/spirit resolver: maps any of the three id forms to its 1502xxxx FightSpirit template.
// - 1502xxxx : the FightSpirit template id itself (direct)
// - 9105xxxx : the NPC cultivation id (NpcCultivationRelatedId)
// - 40xxxxx : an Agent id that has a FightSpirit (AgentId field)
// Enemy/BOSS agents that have NO FightSpirit (e.g. 40140183 = 柠檬/Lemon) are absent and
// must be reported as non-spawnable through this character-spirit panel.
//
SpawnResolver map[uint32]any `json:"spawnResolver"`
// FightSpiritConfig.Id -> AgentConfig.Id. FightSpiritConfig carries the English
// internal name ("Rin", "Taffy"); the readable Chinese name lives on the AgentConfig row the
// spirit points at ("凛", "塔菲"), so the panel has to hop through this map.
SpiritAgentMap map[uint32]uint32 `json:"spiritAgentMap"`
// Monotonic ordinal behind the synthetic agent uid/pid pair.
S_nextAgentOrdinal int32 `json:"s_nextAgentOrdinal"`
// ---------------------------------------------------------------------------------
// Pending GM spawn intent.
//
// The client mangles the id when it forwards a GM command back to the server: the panel
// asks for AddEnemy 40100010 (黑金节奏手) and the client's C2S GmAddEnemy arrives with
// EnemyId=40900579 (verified in packets-latest.log). The id the operator actually wants is
// therefore remembered here when the S2C command is sent, and the C2S forward reuses it.
// ---------------------------------------------------------------------------------
S_pendingGmEnemyId uint32 `json:"s_pendingGmEnemyId"`
S_pendingGmEnemyAtMs int64 `json:"s_pendingGmEnemyAtMs"`
//
// 面板下拉里**置顶**的名字(按 Name 精确匹配,按数组顺序排在最前)。
// ⛑ 2026-09-30 用户定:厕所 QTE 用的「马桶鸵鸟」要排在「生成敌人」下拉框第一位。
//
PinnedFirstNames []string `json:"pinnedFirstNames"`
//
// 置顶项用的**代表 id**。AgentConfig 里有 5 个同名同模「马桶鸵鸟」
// (40122028 / 40652007 / 40922011 / 42046401 / 45000446,同一具 GeneralModelId=86963029),
// 按「组内最小 id」会取到 40122028 —— 那只是 id 最小的偶然结果,不是游戏自己引用的那个。
// 客户端常量是 AgentConfig.ToiletOstrich = 42046401,骑乘 AI 走它的
// AISetting 40213146 + BehaviorActionConfigId 40214321 ⇒ 置顶项必须用它。
//
PinnedFirstIds map[string]uint32 `json:"pinnedFirstIds"`
//
// 面板「生成单位 / 切换角色(客户端 GM 通道)」的下拉数据源 —— SpawnIds.json。
// 该文件由工作区脚本 gen_id_tables.py 从客户端导出的配置表预生成,分三类:
// enemy → AgentConfig 中 Faction 属于 11 个敌对阵营(PlayerLockTargetType=1)的记录
// npc → AgentConfig 中非敌对且 EnemyClassType=9 的市民/物件
// spirit → FightSpiritConfig,即 AskSwitchPlayerSpirit / GMSwitchSpirit 用的可控角色
// 注意三类 ID 空间不同:同一个数字在 AddNpc 与 AddEnemy 里指向不同实体。
//
SpawnIdTable map[string][]AdminSpawnIdEntry `json:"spawnIdTable"`
AllAetherInitVariants []string `json:"allAetherInitVariants"`
//
// Unit states that stop the player from moving or acting, taken from UnitStateConfig
// (NoSkill=1) and hand-picked down to the ones that are clearly a negative control
// effect rather than a neutral/beneficial flag.
// Why this exists: enemy casts are faked by us, but the hit and the resulting stiffness
// are resolved entirely client-side (we never send damage). On a real server the same
// settlement pass that applies the hit also releases it; here nobody does, so the state
// sticks forever. Pressing E is the client's own break-out, which clears movement but
// not the cast lock — exactly the "can move again but only the first combo step fires"
// symptom.
// Deliberately NOT included: 313/314/315 (免疫硬直 — those are the cure, not the
// disease), 316 悬浮, 317 骑机车, 362-364 燃烧/减速/中毒, and the rest of the 321
// NoSkill rows that are ordinary gameplay flags.
//
PlayerControlStates []uint32 `json:"playerControlStates"`
//
// Unit states that keep the knocked-out HUD ("昏迷") up, from UnitStateConfig:
// 373 倒地, 10564 FallingDownKeepAlive, 10583 FallingDownRecoverHealthState.
//
PlayerDownStates []uint32 `json:"playerDownStates"`
}
const (
GameAdminBridge_LandingMeters float32 = 3
GameAdminBridge_LandingSearchMeters float32 = 8
GameAdminBridge_LandingMinMeters float32 = 1.2
GameAdminBridge_PanelItemSeqFloor int64 = 950000000000
GameAdminBridge_PanelItemSeqCounter string = "GameAdminBridge.PanelItemSeq"
GameAdminBridge_SummonedNpcUidBase uint64 = 0x300000000000
GameAdminBridge_SummonOrdinalCounter string = "GameAdminBridge.SummonOrdina"
GameAdminBridge_ServerSpawnUidBase uint64 = 0x200000000000
GameAdminBridge_ServerSpawnUidOrdinalOffset uint64 = 100000
GameAdminBridge_ServerSpawnUidFloor uint64 = GameAdminBridge_ServerSpawnUidBase + GameAdminBridge_ServerSpawnUidOrdinalOffset
GameAdminBridge_PendingGmEnemyTtlMs int64 = 20_000
)
func GameAdminBridge_Touch(ctx context.Context, session *GameSession) any {
// Router implementation for GameAdminBridge_Touch
var zero any; return zero
}
func GameAdminBridge_GetSnapshot(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_GetSnapshot
var zero any; return zero
}
func GameAdminBridge_CurrentVehicleOf(ctx context.Context, runtime any) uint64 {
// Router implementation for GameAdminBridge_CurrentVehicleOf
return 0
}
func GameAdminBridge_CurrentSeatOf(ctx context.Context, runtime any) int32 {
// Router implementation for GameAdminBridge_CurrentSeatOf
return 0
}
func GameAdminBridge_FleetIds(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_FleetIds
return nil
}
//
// Flying (Type 35) and water (Type 33/34) vehicles from the catalog. These are
// excluded from the regular fleet by default; the admin panel offers a separate
// spawner with a force flag so they can be materialized without editing config.
//
func GameAdminBridge_FlyingWaterVehicles(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_FlyingWaterVehicles
return nil
}
//
// Scene presets: curated world spawns first, then every map entrance from the
// game dump (SceneCatalog4229938: RaidConfig SceneId = instance id). Falls back
// to the two curated entries if the catalog fails to load.
//
func GameAdminBridge_ScenePresets(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_ScenePresets
return nil
}
//
// Resolve a client-supplied raid id to one of the curated arrivals.
// Several presets deliberately share a raid id (nova-spawn and tafei-apartment are both the
// main city 23300888), and the client only sends the raid id, so ties resolve to the first
// entry listed. Put the arrival you want the in-game map to use first in
// ScenePresets — the admin panel is unaffected because it picks by Key.
//
func GameAdminBridge_TryGetScenePreset(ctx context.Context, raidId uint32, preset any) bool {
// Router implementation for GameAdminBridge_TryGetScenePreset
return true
}
//
// 按 pathId + 世界坐标刷一个物件,走 SyncGadgetAddList(68925813)
// 的单件批量形式(与 PropRestock / 小游戏摊位同一条已验证通道)。
// <para>⛑⛑ iScale 必须 0:0 ⇒ 客户端原生 1:1;写 100 会建成
// localScale=(0,0,1) 的空壳(无模型、交互范围缩到原点)。09-23 健身房器材
// 就是栽在这上面,GymEquipmentSettings.IScale 已从 100 改 0。</para>
// <para>用途:给街头篮球 QTE 补篮筐。PoiGameConfig.Basket_BallStand
// (BasketballSceneNode.prefab)里没有篮筐模型 —— 只有
// PivotNode/LanWangCenterPoint(空 Transform,进球判定点)和场边 5 个球架
// (sc_fd_ty_wjz_lanqiujia01a_pc),玩家站在圆心投篮时目标点是个"虚空点"。
// 专用篮筐件 Gadgets_Common_BasketballRim = 1086190276 在
// GadgetCatalog.json 里(客户端认得 ⇒ 不会像 gym 那次变空壳),
// 世界摆位为 0,需要时由本方法刷出来。</para>
//
func GameAdminBridge_SpawnGadgetAtAsync(ctx context.Context, pathId int32, x float32, y float32, z float32, yaw float32, iScale int32) any {
// Router implementation for GameAdminBridge_SpawnGadgetAtAsync
var zero any; return zero
}
//
// ★★ 街头篮球:把对手(红铁桶 42071029)的身体网格重新点亮(2026-10-03 23:4x)。
// <para>⛑⛑ 根因(实测,别再猜"没加载"):BasketballNpcCharacter.LoadCharacterModel
// 走 gCS.LuaUtils.GetDialogModelByAgentId(args),模型确实加载出来了——
// 节点 monster_A321001_p003_gm(Clone) 在 baseUnit.ModelSlot.transform 下,
// active=true、scale=1、离玩家约 8.5m、animator 已挂 basketball_tin_mirror;
// 但 body / body01 两个 SkinnedMeshRenderer.enabled = false,
// 只有辅助体 TouchBendProxy(碰撞代理)亮着 ⇒ 玩家看不见对手。
// 怀疑是 args.TargetUnit = gCS.LocalUnitMgr:GetNpcByTemplateId(42071029) 为 nil
// (凭空开局 ⇒ 场景里没有该模板 NPC)导致 dialog 模型停在"未上场"态。</para>
// <para>⛑ 修法:只开 SkinnedMeshRenderer。不能开 Capsule AO Body /
// TouchBendProxy 这类 MeshRenderer 辅助体(AO / 弯曲代理,开了会出怪块)。
// 实测开启 40s 后仍为 true,引擎不会自动关回去。</para>
// <para>⛑ 由面板在开局脚本之后延时调用(Lua 环境里 wait / UpdateBeat 都是 nil,
// 延迟只能在浏览器 JS 的 setTimeout 里做)。</para>
//
func GameAdminBridge_FixBasketballNpcModelAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_FixBasketballNpcModelAsync
var zero any; return zero
}
// Instant move: SyncUnitPositionAndFacing (see GameRouter.TeleportPlayerInstantAsync).
func GameAdminBridge_TeleportInstantAsync(ctx context.Context, x float32, y float32, z float32, facing any) any {
// Router implementation for GameAdminBridge_TeleportInstantAsync
var zero any; return zero
}
// Loading-flow move: SyncPreTeleportOption + SyncTeleport with a fresh teleport id.
func GameAdminBridge_TeleportWithLoadingAsync(ctx context.Context, x float32, y float32, z float32, facing any) any {
// Router implementation for GameAdminBridge_TeleportWithLoadingAsync
var zero any; return zero
}
//
// Admin vehicle spawn: validates against the catalog/fleet, then runs the same
// direct-scene-spawn push as client summon (SyncLogicVehicleEnter + SyncSpawnVehicle,
// interactable). The car appears beside the player; walk up and press F to drive.
// Set force=true to bypass the fleetIds whitelist (used for flying/water vehicles).
//
func GameAdminBridge_SpawnVehicleAsync(ctx context.Context, vehicleId uint32, force bool) any {
// Router implementation for GameAdminBridge_SpawnVehicleAsync
var zero any; return zero
}
//
// Admin gadget spawn: places one interactive prop from GadgetCatalog.json a few metres in
// front of the player. The client owns the prefab and loads it itself — the server only
// supplies the prefab id and a transform, which is the whole reason this channel works at all
// (unlike DynamicGo, whose ids live inside the client's scene resources).
//
// 0 = SyncGadgetAdd, 1 = SyncGadgetAOIAddAndRemove, 2 = both.
func GameAdminBridge_SpawnGadgetAsync(ctx context.Context, pathId int32, distance float32, channel int32) any {
// Router implementation for GameAdminBridge_SpawnGadgetAsync
var zero any; return zero
}
// Admin gadget clear: removes the props this panel placed. One id, or all of them.
func GameAdminBridge_ClearGadgetsAsync(ctx context.Context, onlyId any) any {
// Router implementation for GameAdminBridge_ClearGadgetsAsync
var zero any; return zero
}
//
// T2-31 Seymour 大停电 — 把控制器 Gadgets_Hacker_Tetris(1166913245) 放进世界,然后停手。
// 与 SpawnGadgetAsync 的区别不是"换个 pathId"那么简单:那个对象是
// IsTask=true、带着 startTaskId/endTaskId、用 packed iScale 的任务图物件,
// 普通放置物件那条路径的参数一律不适合它。
// 成功后返回 ok waiting-for-interact:服务端什么都不用再做,接下来要看的是
// 客户端自己有没有因为这个物件而弹黑客交互(详见 GameRouter.Blackout.cs 头部说明)。
//
// Panel-triggered blackout. Pass all three coordinates to override the landing spot
// (otherwise Gameplay.BlackoutControllerAtBillboard decides).
func GameAdminBridge_StartBlackoutAsync(ctx context.Context, x any, y any, z any) any {
// Router implementation for GameAdminBridge_StartBlackoutAsync
var zero any; return zero
}
// Only tears down our own controller and resets the round bookkeeping. This does not
// restore the city lighting on the client — the client's own graph owns that.
func GameAdminBridge_StopBlackoutAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_StopBlackoutAsync
var zero any; return zero
}
// Diagnostics: what the server currently believes about the running round.
func GameAdminBridge_BlackoutStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_BlackoutStatus
var zero any; return zero
}
// Admin catalog: the Gadget prefab groups shown in the panel picker.
func GameAdminBridge_GadgetGroups(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_GadgetGroups
return nil
}
// Admin enter: seats the player in the last spawned vehicle (no F-press needed).
func GameAdminBridge_EnterVehicleAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_EnterVehicleAsync
var zero any; return zero
}
// Admin exit: ejects the player from the current vehicle.
func GameAdminBridge_ExitVehicleAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_ExitVehicleAsync
var zero any; return zero
}
func GameAdminBridge_AutonomousDriveSpeed(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_AutonomousDriveSpeed
var zero any; return zero
}
//
// 保存自动驾驶巡航速度(km/h)。有车正在自动驾驶时顺带热应用:
// 撤回旧 Cruise 任务 → 用新速度重建 → 再确认一次,路线不变。
// ⛑ 没有会话 / 没在开车也算成功 —— 速度已经落盘,下次开自动驾驶生效。
//
func GameAdminBridge_SetAutonomousDriveSpeedAsync(ctx context.Context, kmh float32) any {
// Router implementation for GameAdminBridge_SetAutonomousDriveSpeedAsync
var zero any; return zero
}
// Admin time set: records + pushes SyncPlayerCurrentTime.
func GameAdminBridge_SetTimeAsync(ctx context.Context, hour int32, minute int32, fix bool, transition int32) any {
// Router implementation for GameAdminBridge_SetTimeAsync
var zero any; return zero
}
// Admin weather set: records + pushes SyncPlayerWeather.
func GameAdminBridge_SetWeatherAsync(ctx context.Context, weatherId int32, transition int32) any {
// Router implementation for GameAdminBridge_SetWeatherAsync
var zero any; return zero
}
func GameAdminBridge_TimeWeatherStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_TimeWeatherStatus
var zero any; return zero
}
//
// GM 面板「角色日程」卡片:每个可玩角色此刻在干什么、在哪、以及一个已经算好偏移的传送落点。
// 数据全来自官方三张表(养成 AgentTag → 时刻表 → 活动表),与场上真正放人的
// GameRouter.RosterNpc.cs 走同一个 NpcActivityCatalog4229938.Resolve,
// 所以面板显示的坐标就是服务端实际会放人的坐标。
// ⛑ goto* 故意不落在角色身上:挪到身旁约 3 米,并算出朝向角色的 facing,
// 免得传送完贴在脸上(用户明确要求)。facing 约定与全项目一致:
// atan2(dx, dz) * 180 / π(见 GameRouter.EnemyAiActions.cs:454)。
// ⛑⛑ 落点必须踩在 CivilianDistricts 的官方人行道上 —— 按固定方向偏移会卡进墙里
// 或掉进海里(09-25 玩家实测)。
//
func GameAdminBridge_RosterSchedule(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_RosterSchedule
var zero any; return zero
}
func GameAdminBridge_TeleportLandingNear(ctx context.Context, x float32, y float32, z float32) any {
// Router implementation for GameAdminBridge_TeleportLandingNear
var zero any; return zero
}
// Admin weapon grant: pushes SyncArmoryAddWeapon for a catalog template.
func GameAdminBridge_GrantWeaponAsync(ctx context.Context, templateId uint32) any {
// Router implementation for GameAdminBridge_GrantWeaponAsync
var zero any; return zero
}
// Admin bulk weapon grant: pushes SyncArmoryAddWeapon for every catalog weapon.
func GameAdminBridge_GrantAllWeaponsAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_GrantAllWeaponsAsync
var zero any; return zero
}
func GameAdminBridge_WeaponCatalog(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_WeaponCatalog
return nil
}
// Admin scene ("raid") switch: re-push SyncEnterScene for the destination.
func GameAdminBridge_SwitchSceneAsync(ctx context.Context, raidId uint32, instanceId uint64, universeId uint32, x float32, y float32, z float32, facing float32) any {
// Router implementation for GameAdminBridge_SwitchSceneAsync
var zero any; return zero
}
// Timeline catalog: all named cutscenes from TimelineConfig.json.
func GameAdminBridge_TimelineCatalog(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_TimelineCatalog
return nil
}
//
// 台詞索引(DialogIndex.json,58932 条)。支持关键词(文案 / 说话人 / id 片段)与
// 「只看过场」过滤。客户端播不播不依赖这张表 —— 它只是给面板挑 id 用的清单。
//
func GameAdminBridge_StoryDialogCatalog(ctx context.Context, keyword any, cutsceneOnly any, limit int32) []any {
// Router implementation for GameAdminBridge_StoryDialogCatalog
return nil
}
// 面板用的统计(总数 / 过场数),给卡片标题显示。
func GameAdminBridge_StoryDialogStats(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_StoryDialogStats
var zero any; return zero
}
// 推一条台词(含过场镜头)。
func GameAdminBridge_PlayStoryDialogAsync(ctx context.Context, dialogId uint32) any {
// Router implementation for GameAdminBridge_PlayStoryDialogAsync
var zero any; return zero
}
// 只推语音。
func GameAdminBridge_PlayStoryDialogVoiceAsync(ctx context.Context, dialogId uint32) any {
// Router implementation for GameAdminBridge_PlayStoryDialogVoiceAsync
var zero any; return zero
}
//
// Play a timeline/cutscene by name. Sends a raw trigger payload to the client;
// the fastpatch-injected __DRMK_PlayTimeline handler picks it up and calls
// gTimelineManager:Timeline_LoadAndPlay(name, data). The client handles NPC
// spawning, camera work and video playback locally.
//
func GameAdminBridge_PlayTimelineAsync(ctx context.Context, timelineName string) any {
// Router implementation for GameAdminBridge_PlayTimelineAsync
var zero any; return zero
}
// 面板「切人演出」清单(一行 SwitchSpiritConfig = 一出演出)。
func GameAdminBridge_SwitchShowCatalog(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_SwitchShowCatalog
return nil
}
//
// 当前受控角色(0 = 还没有玩家会话)。前端靠它把目标角色下拉的默认值避开当前角色 ——
// 否则「选一条演出 → 默认目标正好是当前角色」⇒ 服务端静默 return ⇒ 点了没反应。
//
func GameAdminBridge_ActiveSpiritId(ctx context.Context, ) uint32 {
// Router implementation for GameAdminBridge_ActiveSpiritId
return 0
}
// 指定某条演出来播。
func GameAdminBridge_PlaySwitchShowAsync(ctx context.Context, spiritId uint32, switchShowId uint32) any {
// Router implementation for GameAdminBridge_PlaySwitchShowAsync
var zero any; return zero
}
//
// Switch the controlled character from the admin panel (server-driven, no client wheel).
// The Z wheel can only ever offer the 15 FightSpiritConfig rows with CanJoin=true, and the
// server cannot widen that list (any non-empty SyncTaskRoleTeam greys out everything it does
// not name). This drives GameRouter.OnSwitchSpirit straight from the panel instead, so every
// roster row is reachable — including CanJoin=false story characters and rows rescued from the
// placeholder-icon filter via content.roster.additionalTemplateIds.
//
func GameAdminBridge_SwitchSpiritAsync(ctx context.Context, spiritId uint32) any {
// Router implementation for GameAdminBridge_SwitchSpiritAsync
var zero any; return zero
}
//
// Stuck weapon state recovery for the admin panel. See GameRouter.WeaponDrop.cs for why this
// exists: the client drop is entirely local, so long-press-Q can wedge the unit in its absorb
// loop with nothing for the server to answer. Re-publishing the weapon state is the server-side
// equivalent of the character switch that currently clears it.
//
func GameAdminBridge_ResetWeaponStateAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_ResetWeaponStateAsync
var zero any; return zero
}
//
// Metro / light rail — read-only status snapshot for the admin panel.
// Ported from the donor build (GameRouter.Metro.cs) unchanged in behaviour: the client owns the
// whole carriage simulation, so this is nothing but "which lines does the dispatcher push and
// where does the timetable put every carriage right now". See GameRouter.Metro.cs for why no
// client patch is involved (AskGetAllMetroInfos + SyncRunningMetroInfos carry no coordinates).
//
func GameAdminBridge_MetroStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_MetroStatus
var zero any; return zero
}
// 通缉系统只读状态(犯罪值 / 星级 / 已派警车 / 结算统计)。
func GameAdminBridge_WantedStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_WantedStatus
var zero any; return zero
}
//
// 通缉系统动作:addviolation / setvalue / setlevel / escape / completeescape / arrest / clear /
// configure。需要玩家已进世界(否则返回 world state not ready)。
//
func GameAdminBridge_WantedActionAsync(ctx context.Context, cmd any) any {
// Router implementation for GameAdminBridge_WantedActionAsync
var zero any; return zero
}
// Master switch of the metro dispatcher.
func GameAdminBridge_SetMetroEnabled(ctx context.Context, enabled bool) any {
// Router implementation for GameAdminBridge_SetMetroEnabled
var zero any; return zero
}
//
// Retune the dispatcher. null keeps the current selection (that is how the
// panel changes only the push period or the time factor); an empty list turns every line off.
// Passing = true switches the selection back to "follow the current raid"
// (RaidConfig.LightRailLineIds) - that is also the state a fresh session starts in; sending an
// explicit list turns raid-following off until auto is sent again.
//
func GameAdminBridge_ConfigureMetroLines(ctx context.Context, lines []any, pushMs any, timeFactor any, auto any) any {
// Router implementation for GameAdminBridge_ConfigureMetroLines
var zero any; return zero
}
// Stop pushing. The client keeps whatever carriages it already built.
func GameAdminBridge_StopMetro(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_StopMetro
var zero any; return zero
}
//
// 远程 GM 工具命令:把一条命令推给客户端"本地执行"。
// IGameGMToClient.SyncRunGmUtils(String methodName, List&lt;String&gt; args) — methodId 66042045。
// 客户端原生层按名字反射解析并调用(L50Game.dll RunGmUtils_Public_Static_String_String_List_1_String_0
// / RunGmUtilsCommand_Private_Static_String_MethodInfo_List_1_String_0),执行全程发生在客户端进程内,
// 服务端只发一次 Notify。ALT+F1 面板"召唤NPC"就是这类本地调用(实证:包日志 0 条 GM 请求但单位带完整
// AI/受击/踉跄),这是把同等效果搬进 17888 的通道。
// 注意:有效命令名集合未知(GmAddEnemyByPlayer 是网络命令名,客户端本地不存在),需实测。
//
func GameAdminBridge_RunGmUtilsAsync(ctx context.Context, methodName string, args []any) any {
// Router implementation for GameAdminBridge_RunGmUtilsAsync
var zero any; return zero
}
//
// 第二条 GM 通道:IGameSceneToClient.SyncDoGmCommand(String command, String[] args) — 68373443。
// 客户端由 LX6.Service.GameSceneToClientImpl 实现(ParseSpoonLines 解析命令行后走本地 GM 系统)。
// command 可带参数整体填入(如 "AddNpc 40100010"),args 留空;或 command 只填命令名、参数放 args。
//
func GameAdminBridge_DoGmCommandAsync(ctx context.Context, command string, args []any) any {
// Router implementation for GameAdminBridge_DoGmCommandAsync
var zero any; return zero
}
//
// 诊断用:同一条 GM 命令用 4 种候选 wire 格式依次发送(间隔 1.2s),用来定位客户端到底认哪一种。
// C = RunGmUtils(methodName=方法名, args=[参数]) ← 语义最明确,优先怀疑对象
// B = DoGmCommand(command="方法名 参数1 参数2", args=[]) ← 命令行整行模式
// A = DoGmCommand(command=方法名, args=[参数]) ← 当前面板默认,实测无效
// D = RunGmUtils(methodName="方法名 参数", args=[])
// 现象判读:AddNpc 无视 ID 永远出同一个 NPC + 需要参数的命令无反应 == 客户端没收到 args(整条命令被当无参执行)。
//
func GameAdminBridge_GmProbeAsync(ctx context.Context, method string, args []any) any {
// Router implementation for GameAdminBridge_GmProbeAsync
var zero any; return zero
}
func GameAdminBridge_GmScanLocalCreateAsync(ctx context.Context, cfgId string) any {
// Router implementation for GameAdminBridge_GmScanLocalCreateAsync
var zero any; return zero
}
//
// Encoding probe: the same command sent six ways — 7-bit vs Int32 count prefix,
// across both S2C channels, plus a full-command-line variant of each.
// Whichever one produces the *requested* unit (rather than the client's fallback
// NPC) is the correct wire format.
//
func GameAdminBridge_GmEncodeProbeAsync(ctx context.Context, method string, cfgId string) any {
// Router implementation for GameAdminBridge_GmEncodeProbeAsync
var zero any; return zero
}
//
// NPC / character catalog: the distinct NpcCultivationRelatedId values
// (9105xxxx — the "character codes") from FightSpiritConfig.json, each linked
// to its 1502xxxx fight-spirit (侠客/战斗灵) template id, the number of spirit
// rows sharing the id, and the menu functions that reference it
// (MobileMenuSGuiConfig.json NpcCultivationIdList).
//
func GameAdminBridge_NpcCatalog(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_NpcCatalog
return nil
}
func GameAdminBridge_BuildSpawnResolver(ctx context.Context, ) map[string]any {
// Router implementation for GameAdminBridge_BuildSpawnResolver
return nil
}
func GameAdminBridge_BuildNpcCatalog(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_BuildNpcCatalog
return nil
}
//
// Admin "spawn NPC": materializes the character through the game's PROVEN spirit-entity
// channel (the same RaidBattleUnitSpirit projection the world-entry and spirit-switch
// handoffs use — no fastpatch Lua involved, so it works even though the client never
// executes fastpatch Lua). The NPC's 1502xxxx spirit template is a playable roster
// character (FightSpiritConfig.json); we project it as a remote (ManagedPid=0) entity
// so it stands in the world without hijacking the player's controlled spirit.
//
func GameAdminBridge_SpawnNpcAsync(ctx context.Context, npcCultivationId uint32, x float32, y float32, z float32, facing float32) any {
// Router implementation for GameAdminBridge_SpawnNpcAsync
var zero any; return zero
}
//
// Admin "道具": pushes every backpack item in ItemCatalog.json into the player's pack.
// The wallet currencies (36920001 RewardGold / 36920002 RewardMoney / 36920003 RewardBindingGold)
// live inside the same id range but are wallet state, not pack stacks — granting them here would
// make the HUD wallet and the pack disagree, so they are skipped.
// The whole catalogue is several thousand rows, so the push is chunked: one
// SyncBackpackItemChanged per batch keeps any single packet to a sane size.
//
func GameAdminBridge_GrantAllItemsAsync(ctx context.Context, count uint32) any {
// Router implementation for GameAdminBridge_GrantAllItemsAsync
var zero any; return zero
}
//
// 把整批道具分包推给客户端。摊平 + 断线即停,理由同武器发放:
// 3783 个道具 / 300 = 13 个几百 KB 级的大包,一口气发完会把客户端主线程砸死
// (Player.log: [Davinci] type=TimeScaleAnomaly 紧跟「与服务器的网络连接已断开」)。
//
func GameAdminBridge_PushItemChunksAsync(ctx context.Context, session *GameSession, batch []any) int32 {
// Router implementation for GameAdminBridge_PushItemChunksAsync
return 0
}
// GM 面板「发放全货币」:三种钱包货币各自补到 。
func GameAdminBridge_GrantAllCurrencyAsync(ctx context.Context, amount float64) any {
// Router implementation for GameAdminBridge_GrantAllCurrencyAsync
var zero any; return zero
}
//
// ★ 分配一个**能被 SyncLogicAgentLeave 真正回收**的 NPC unitId(硬规则 165)。
// ⛑⛑ 为什么必须走这一段:100000000000+ 是客户端预分配的切人身份
// (玩家单位 100000000000、各角色 1000000000xx),
// `SyncLogicAgentLeave` 对这一段是**静默 no-op** —— 客户端根本不回收那一具身体。
// 09-25 21:5x 实机:每次切走里希都新分配一个 `100000000103 / 100000000105 …`
// 的留场身体,切回时发的 Leave 全部落空 ⇒ **跑步机上每切一回就多一个里希**。
// `0x300000000000` 这一段已在 GM 召唤上验证过「建得出、也收得掉」。
// ⛑ 序数必须走 IdCounterStore(高水位落盘):重启后若从 0 重发,
// 客户端还认得上一轮那个 unitId ⇒ Leave 又变 no-op。
//
func GameAdminBridge_NextRetirableNpcUid(ctx context.Context, ) uint64 {
// Router implementation for GameAdminBridge_NextRetirableNpcUid
return 0
}
func GameAdminBridge_BuildSpiritAgentMap(ctx context.Context, ) map[string]any {
// Router implementation for GameAdminBridge_BuildSpiritAgentMap
return nil
}
//
// FightSpiritConfig.Id -&gt; AgentConfig.Id(FightSpiritConfig.AgentId)。拿不到返回 0。
// ★ 这个 id 就是「这个可玩角色在 NPC 世界里的那一面」:
// 15020967 辰宿 → 40140030,15021021 里希 → 40140040,15021044 李扶风 → 40155013。
// 它就是 AgentConfig 表里的行 id(NpcCatalog.json 9345 行,15/15 CanJoin 全部命中,
// 带 AgentPersonaId / AiSettingId),所以可以直接当 NpcFormworkId 走市民通道刷 NPC ——
// 这就是官方「切人后老角色留在大世界里过自己的日子」的原生实现。
//
func GameAdminBridge_AgentIdForSpirit(ctx context.Context, spiritId uint32) uint32 {
// Router implementation for GameAdminBridge_AgentIdForSpirit
return 0
}
// Readable display name for a spirit: the Chinese AgentConfig name when the spirit
// maps to one, otherwise FightSpiritConfig's own name.
func GameAdminBridge_SpiritDisplayName(ctx context.Context, spiritId uint32, spiritName string) string {
// Router implementation for GameAdminBridge_SpiritDisplayName
return ""
}
// The playable roster — the only characters the spirit-projection channel can
// materialise. Exposed so the panel can render the exact same list instead of a hand-written
// one that drifts out of sync with FightSpiritConfig.
//
// GM 面板 / POST /api/police/arrest:对指定单位执行一次正向「逮捕」演出
// (SyncPoliceBeginArrest 68839124)。
// ⛑ 这是验证通道,不是玩法入口 —— 官方的逮捕挂在盘查面板(C# 侧
// gPoliceJobManager.cs)上,客户端什么时候发 AskPoliceExamInteract(63233964)
// 我们还没拿到实据。先用 GM 这一发确认"帧能不能出演出",再决定要不要投玩法。
// 目标单位 uid;传 0 ⇒ 用最近一次 GM 召唤的 NPC。
//
//
// 里希**牢房收押**(2026-09-30)。把 移进牢房内、停 AI、改写账本。
// ⛑ 玩家必须站在牢房门口 gameplay.policePrison.nearMeters(默认 4 m)内,且在主城。
// 详见 GameRouter.PolicePrison.cs。
//
func GameAdminBridge_PolicePrisonCommitAsync(ctx context.Context, uid uint64) any {
// Router implementation for GameAdminBridge_PolicePrisonCommitAsync
var zero any; return zero
}
// 里希**牢房带出**:把当前收押对象移回门口并恢复 AI。
func GameAdminBridge_PolicePrisonReleaseAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_PolicePrisonReleaseAsync
var zero any; return zero
}
//
// 里希**押送:把犯人押上警车**(2026-09-30)。
// 直接发 SyncUnitVehicleStatus(Status=4) + SyncUnitBoardingInfo 绑定座位,
// 没有原生押送过场(那个要 GATE Lua 通道)。详见 GameRouter.PoliceEscort.cs。
//
func GameAdminBridge_PoliceEscortBoardAsync(ctx context.Context, uid uint64, vehicle uint64, seat int32) any {
// Router implementation for GameAdminBridge_PoliceEscortBoardAsync
var zero any; return zero
}
// 里希**押送:把犯人从车上押下来**(落在车旁 2 m)。
func GameAdminBridge_PoliceEscortAlightAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_PoliceEscortAlightAsync
var zero any; return zero
}
//
// ★★ GATE Lua 通道:把一段 Lua 下发到客户端进程里执行
// (SyncDoToLuaMaster(45437825) → 客户端内置 gCsToLuaHandler.GmRunLua)。
// 详见 GameRouter.GateLua.cs。
//
func GameAdminBridge_RunClientLuaAsync(ctx context.Context, script string) any {
// Router implementation for GameAdminBridge_RunClientLuaAsync
var zero any; return zero
}
//
// ★ 回执探针:下发一段「执行了就往服务端发 DrmkLuaRx」的 Lua。
// 服务端日志出现 [VEHICLE-STORY] cmd … name=DrmkLuaRx … ⇒ 客户端真的执行了。
//
func GameAdminBridge_GateLuaProbeAsync(ctx context.Context, tag string) any {
// Router implementation for GameAdminBridge_GateLuaProbeAsync
var zero any; return zero
}
// ★ 给账本里的一个 NPC 挂 / 摘客户端 F 按钮(自动把 uid 拆成 low32/high32 再生成 Lua)。
func GameAdminBridge_GateLuaUnitButtonAsync(ctx context.Context, uidText string, text string, add bool) any {
// Router implementation for GameAdminBridge_GateLuaUnitButtonAsync
var zero any; return zero
}
// 服务端账本里可操作的 NPC(供 F 按钮下拉)。
func GameAdminBridge_GateLuaUnits(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_GateLuaUnits
var zero any; return zero
}
// ★ 一步到位:在玩家面前刷一个市民 + 给它挂「盘查」F 按钮(点了才开盘查)。
func GameAdminBridge_ExamSpawnWithFButtonAsync(ctx context.Context, agentId uint32) any {
// Router implementation for GameAdminBridge_ExamSpawnWithFButtonAsync
var zero any; return zero
}
//
// ★ 捐赠端里希总入口(2026-10-02 逐字移植捐赠端 GameAdminBridge.cs:227-255)。
// 面板按钮直接打这个;/api/lixi/police 的 action 与捐赠端完全一致。
// ⛑ 机械狗(dog)与武装支援(armed)本次没搬(依赖 HackerDevices / 友方随从子系统),
// 故这两个 action 返回"未接通"而不是伪造成功。
//
//
// 生成测试动作 NPC 并把它登记进警察账本。
// ⛑ 逐字对齐捐赠端 GameAdminBridge.cs:249-252:
// SpawnStaticNpcsAsync([new AdminNpcSpawnItem(4065053, 0)], 2, 2, 1)。
// ⛑⛑ 测试模板不要改,必须是 4065053(2026-10-02 事故复盘)。
// 客户端原生 SNpcBtnQuery.GetButtons / PoliceJobUtils 按
// AgentConfig.json → ExamType 判定一个模板是否可被巡卫盘查,
// 只有 ExamType=2 才行(捐赠端 GameRouter.Npc.cs:200-203 的
// PoliceExamType != 2 判据,提示语原文就是「可用模板 4065053 测试 F 互动」)。
// 实测数据:4065053(藤原一马 Fujiwara Kazuma)ExamType=2;
// 42031099(摩天轮售票员 Ferris Wheel Ticket Seller)ExamType=0。
// 10-02 曾误换成 42031099(理由「4065053 没有原生交互位、按 F 不弹菜单」),
// 该理由是错的:ExamType=0 时面板能被服务端硬推 S002 打开,
// 但客户端不授予盘查资格 ⇒ 按键点了恒无反应,且菜单里压根不给
// 「押送上车」。捐赠端 v0.6 全部日志里测试模板 100% 只有 4065053
// (ACTION_NPC ... template=4065053 examType=2),从未用过 42031099。
// ★ 2026-10-02 修正:此前主线走自己的 SpawnAgentAsync(战斗 agent 管线),
// 与捐赠端的静态 NPC 管线不是同一条 —— 静态管线才会发
// SyncBattleAiS=false(原生 PoliceJobUtils.CanUnitInteract 拒绝 IsBattleAiS
// 的目标 ⇒ 不发就永远盘查不了),并自带
// RegisterPoliceNpc4229938 + RefreshSpawnedPoliceNpcs4229938
// (已补进 GameRouter.DonorNpc.cs,对应捐赠端 Npc.cs:141/199)。
// 主线等价物 = SpawnDonorStaticNpcsAsync(GameRouter.Npc.cs 的逐字移植版)。
// ⇒ 因此这里不再手动补登记 / 刷新(捐赠端也没有),交给生成管线内部做。
//
func GameAdminBridge_SpawnPoliceTestNpcAsync(ctx context.Context, session *GameSession) any {
// Router implementation for GameAdminBridge_SpawnPoliceTestNpcAsync
var zero any; return zero
}
func GameAdminBridge_LixiPoliceAsync(ctx context.Context, action string) any {
// Router implementation for GameAdminBridge_LixiPoliceAsync
var zero any; return zero
}
// GATE 会话是否已绑定(面板用来提示「要不要重登」)。
func GameAdminBridge_GateLuaStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_GateLuaStatus
var zero any; return zero
}
// 牢房 + 押送 当前状态(收押了谁、车上押着谁、玩家离门口多远、坐标、场景是否 OK)。
func GameAdminBridge_PolicePrisonStatus(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_PolicePrisonStatus
var zero any; return zero
}
func GameAdminBridge_ArrestNpcAsync(ctx context.Context, uid uint64, teleport bool, pull bool) any {
// Router implementation for GameAdminBridge_ArrestNpcAsync
var zero any; return zero
}
//
// GET /api/police/targets:当前会话里服务端能点名的全部可抓单位,按距离升序。
// 存在的理由见 GameRouter.CollectPoliceTargets 的注释 —— 城里大多数路人是客户端本地
// 实体(id 形如 8300000000000000188),服务端抓不到;所以必须先把「抓得到的」摊开,
// 否则面板上一个 uid 都填不出来。
//
func GameAdminBridge_PoliceTargets(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_PoliceTargets
return nil
}
//
// GM 面板 / POST /api/plate/push:手动推一个 Plate(盘)下去。
// 用途:验证第五条管线(硬规则 157)。客户端收到盘才会建 Spoon 图并把 NPC 绑上去播表演,
// 否则角色只会站在跑步机旁边发呆。线格式目前是猜的(68554224 的 reader 在 C# 侧),
// 所以所有「有没有 0xFF / count 怎么编码」都做成了可覆盖项。
// 判据:服务端日志出现 [PLATE] ★★★ AskPlateLoadComplete /
// AskPlateResourceReady,或客户端 Player.log 报 68554224 反序列化失败。
//
func GameAdminBridge_PushPlateAsync(ctx context.Context, opt any) any {
// Router implementation for GameAdminBridge_PushPlateAsync
var zero any; return zero
}
//
// GM 面板 / POST /api/npclife/spoon:手动推一条
// SyncFavorNpcSpoonAgentId(64003213)。
// 这是「让角色真的去用器材」的正式通道(硬规则 160):客户端
// LifeScheduleNpcModule.AttachNet(activityId, spoonAgentId, hasAmbientLoopPose,
// ambientLoopPos, ambientLoopFacing) 吃的正是这一条,而我们以前一次都没发过
// —— 出场包里只有活动 id,所以角色只会播 ambient(站着玩手机)。
// 判据:客户端 Player.log 里出现 [日程表NPC]
// (LifeScheduleNpcModule.LOG_PREFIX)开头的行,或角色真的上跑步机;
// 若报 64003213 反序列化失败 ⇒ 把 busyInfoNullMarker 改成 false 再推。
//
func GameAdminBridge_PushNpcSpoonAsync(ctx context.Context, opt any) any {
// Router implementation for GameAdminBridge_PushNpcSpoonAsync
var zero any; return zero
}
//
// GM 面板 / POST /api/npclife/timetable:手动推时刻表。
// ★★ 这是 SyncFavorNpcSpoonAgentId 的硬前提:客户端
// NpcDaliyManager.lua:143 里 OnSyncFavorNpcSpoonAgentId 一进门就判
// table.isNilOrEmpty(self.NpcTimeTableInfos[agentTag]),空则整帧丢弃 ——
// 09-25 19:36 那次「推了没反应」就是这个原因。
// 登录帧虽然也带了一份 FavorNpcDailyScheduleInfos,但那一份的
// SpoonAgentId / CurrentSpoonAgentId / SpoonPosition 全是 0,
// 等于「有日程、没人占坑」。这里重推一份带 spoon 号的覆盖上去。
// 判据:推完再推 /api/npclife/spoon,角色是否开始播那条活动的表演;
// 客户端报 64219939 / 64400736 反序列化失败 ⇒ 改 single 再试(排掉字典线格式的变量)。
//
func GameAdminBridge_PushNpcTimeTableAsync(ctx context.Context, opt any) any {
// Router implementation for GameAdminBridge_PushNpcTimeTableAsync
var zero any; return zero
}
//
// GM 面板 / GET /api/player/pos:读玩家此刻的坐标。
// 为什么需要它:朝向没有表可查 —— 跑步机那台 gadget 在 dump 里只在 prefab
// 注册表出现(有 GraphId/pathId,没有世界摆放与旋转),`GadgetCatalog.json` 同样只有
// prefab 条目,`MapentranceConfig` 里离跑步机最近的入口也在 56m 外(未必是健身房那扇门)。
// 于是最靠谱的取法是实测:**站到目标点(比如健身房大门)上,读玩家坐标,反算朝向**。
// 用法:走到想要 NPC 面向的那个点 → 打这个接口拿到 (x,z) → 用
// atan2(dx, dz) 换成度填进 gameplay.rosterNpc.poiOverrides[].facing。
//
func GameAdminBridge_PlayerPosition(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_PlayerPosition
var zero any; return zero
}
//
// GM 面板 / POST /api/npclife/poi:给某个单位直接下发
// SyncAgentPlayPOIAction(68459398)。
// 这是已验证过的原生通道(GameRouter.Ambient.cs:892 同款),
// 跑步机那条是 UrbanDiversityPOIActionConfig 的
// 198「站姿_运动_跑步机跑步」(MainActionGroup 400 / MainActions [86190])。
// ⛑ 生活日程开着(AgentDataSetsActivityCfgId 非 0)时客户端走生活日程状态,
// 这一帧会被盖掉 ⇒ 想看效果先把 gameplay.rosterNpc.dailyLife 设 false。
//
func GameAdminBridge_PushNpcPoiActionAsync(ctx context.Context, opt any) any {
// Router implementation for GameAdminBridge_PushNpcPoiActionAsync
var zero any; return zero
}
//
// GM 面板 / POST /api/police/examine:给指定 NPC 推一次「案底」
// (SyncAgentPoliceExamData 68635920,LuaOnly)。
// ⛑ 这是服务端唯一能把盘查 UI 叫出来的通道。盘查面板本体要 NPC 挂
// MultiInteractTypeConfig.Examine 双人交互位才开得出来(规则 153),按 F 永远出不来;
// 但这一帧是 LuaOnly(midToExportOption[68635920])⇒ 客户端 lua
// PoliceJobManager.OnSyncAgentPoliceExamData 一定收得到,
// 我们注入的 tools/police_examine.lua 就钩在那里,收到即开盘查面板。
// 目标 uid;0 ⇒ 自动挑最近(同 ArrestNpcAsync)。
// 塞几条罪名(从 PoliceFineCatalog 的 IsShow 行里取)。
// 先把玩家挪到目标跟前,免得面板开了但人不在视野里。
//
func GameAdminBridge_ExamineNpcAsync(ctx context.Context, uid uint64, fineCount uint32, teleport bool, close bool, pull bool, crimesWire string) any {
// Router implementation for GameAdminBridge_ExamineNpcAsync
var zero any; return zero
}
//
// 给一个 NPC 下发「案底」—— SyncAgentCrimes(68251759, CSharpOnly)。
// ⛑⛑ 这条是盘查链路的开关:PoliceExamineManager 的
// TryAddFine / 搜身 / 酒测 / 右上「执法建议」全部读
// module.Crimes,而 Crimes 只有这一帧能填。
// 不发 ⇒ 面板开得出来但点什么都是空转。
//
func GameAdminBridge_SetAgentCrimesAsync(ctx context.Context, session *GameSession, uid uint64, fineIds []any, wire string) any {
// Router implementation for GameAdminBridge_SetAgentCrimesAsync
var zero any; return zero
}
//
// 手搓 SyncAgentCrimes 的字节, 用来现场换线格式。
// ⛑ 为什么不用 DTO 序列化:这一帧极性是 CSharpOnly,客户端 lua 没有 reader 可抄,
// count 到底怎么编码只能靠 GmCreatePedData.Crimes 那个 writer 类比推断 ——
// 猜错一次就要重启一次服务端。做成旋钮后,POST /api/police/crimes {"wire":"b"}
// 当场换格式重发,不重启。
// a = [FF][Int7 count+1] (默认,照 WriteList7Bit 的推断)
// b = [FF][Raw7 count] (gadget 系:peek byte + 原始 count,不 +1)
// c = [Int7 count+1] (无 marker)
// d = [FF][u32 count] (定长 count)
//
func GameAdminBridge_BuildAgentCrimesWire(ctx context.Context, id uint64, fines []any, wire any) []byte {
// Router implementation for GameAdminBridge_BuildAgentCrimesWire
return nil
}
// GM 面板 / POST /api/police/crimes 的入口(uid=0 ⇒ 挑最近的)。
func GameAdminBridge_PushAgentCrimesAsync(ctx context.Context, uid uint64, fineIds []any, wire string) any {
// Router implementation for GameAdminBridge_PushAgentCrimesAsync
var zero any; return zero
}
//
// 推一次 SyncPoliceExamReaction(64480222, LuaOnly) —— 「你刚才那个盘查动作,对方的反应」。
// ⛑⛑ 这是盘查里**唯一能让面板"有反应"的服务端帧**,2026-09-25 才挖出来。
// 客户端链路:PoliceJobManager.OnReceiveReaction_Story(reactionId)
// → PoliceExamineManager.OnReceiveReaction
// → 用 self.PreOptionId 取 optionCfg + PoliceExamReactionConfig.GetConfig(reactionId)
// → 两者都在 ⇒ OnSuccessClickOption(加罪名、弹 AI 对话、可能发 AskPoliceEffectiveExam)。
// ⇒ 所以**必须先让玩家点一下选项**(PreOptionId 才有值),再推这一帧。
// ⛑ 载荷就是一个 u32 reactionId(RPCDeserializeAuto.lua:3281),极性 LuaOnly ⇒ 只有 lua 收。
// ⛑ 为什么需要它:官方链路里客户端点选项后 C# 会发 C2S,服务端回这一帧。
// 但我们这边 C# 那条 story 跑不起来(街上的人没有 Examine 多人交互位)⇒ C2S 根本没发
// (unknown-methods.log 里一条都没有)⇒ 只能由 GM/服务端主动推。
//
func GameAdminBridge_PushPoliceExamReactionAsync(ctx context.Context, reactionId uint32) any {
// Router implementation for GameAdminBridge_PushPoliceExamReactionAsync
var zero any; return zero
}
//
// GM 面板 / POST /api/police/fine 的入口:手动推一次罚款结果
// (ShowReceiveRewardDetail 64145515,Reason = PoliceFine 180001)。
// 正常链路不需要它 —— 玩家点「开罚单」⇒ C2S AskPoliceFineNpc(63548238)
// ⇒ GameRouter.PoliceFine.cs 自动回。留着是为了调线格式不用重启。
//
func GameAdminBridge_PushPoliceFineRewardAsync(ctx context.Context, money float64, jobExp uint32) any {
// Router implementation for GameAdminBridge_PushPoliceFineRewardAsync
var zero any; return zero
}
func GameAdminBridge_SummonRoster(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_SummonRoster
return nil
}
//
// Admin "生成敌人" / "生成NPC": materialises a raw AgentConfig agent through the
// SyncRaidBattleUnitAgent channel (the channel the dbnb-0.2 branch was verified with),
// which is independent of the 生成NPC(角色)spirit-projection path above.
// Sequence and why each packet is required (all established in-game on dbnb-0.2):
// SyncLogicAgentEnter — registers the logic agent id.
// SyncManagedLogicAgent — hands the agent to a pid so the client keeps it alive.
// SyncRaidBattleUnitAgent — the unit itself; carries AgentSyncClientInfo.treeName and
// .AISetting. An empty tree name or AISetting 0 leaves the
// client unable to resolve a behaviour tree -> no animation.
// SyncAgentCampInfo — 4 = UnitCamp.AllEnemy (hostile), 0 = Unknown (civilian).
// SyncAttachBattleModule — binds the skeleton; without it the agent renders in T-pose.
// SyncUnitAttrs / SyncUnitHp — attribute + HP tables so the unit data set is complete.
// Deliberately omitted: hurt feedback, chase/movement, death and removal packets. This port
// only has to spawn a correctly-posed agent; dbnb-0.2 proved the rest unusable for
// server-spawned agents and they are out of scope here.
//
//
// false = 只把实体放进世界,不把它登记进敌人账本、也不下令开战。黑客召唤物(蜘蛛机器人 /
// 送货无人机)走这条路:它们要被玩家接管操控,而不是反过来打玩家。模板仍在 Hostile 桶里
// (entry.Hostile 校验拦着,必须 hostile:true 才让生成),所以 camp 依旧是 4,只是没有
// 行为树下达攻击指令。客户端 SyncSwitchControl 不看 camp,只看 ClientData.AgentId。
//
//
// 必须与 AgentSummonCatalog 的 EnemyClassType 一致(1/2/4/6/7 ⇒ true,9 ⇒ false),
// 否则 entry.Hostile != hostile 直接拒绝生成。
//
//
// false = 只建单位、不注册进敌人账本(打不死、也不会被服务端拉去战斗)。召唤物一律 false。
//
//
// 是否推 SyncForceEnemyRelation(把玩家和这个单位显式标成敌对)。
// 默认 true = 保持 2026-09-25 之前的现状,所有既有调用点行为不变。
// 2026-09-25 战术支援新增:友方支援 NPC 必须传 false,否则玩家能锁定并"打"自己人
// (又因为 engage=false 打不死,表现就是「对着友军挥刀,血条不掉」)。
//
//
// 覆盖 SyncAgentCampInfo.camp。默认 null = 按 hostile 取 4(敌人)/ 0(平民)。
// 友方支援 NPC 传 0:玩家的本地战斗 AI 按阵营挑目标,camp=4 会把他们当成可攻击对象。
//
//
// 覆盖 AgentSyncClientInfo.treeName。默认 null = 沿用模板解析出来的那棵
// (EnemyAiCatalog 有行就用它的 Tree,否则 hostile ? Main_Common : Main_Common_NPC)。
// ⛑ 2026-09-27 为梅卡羊加的:40980002 在 EnemyAiCatalog.json 里没有行,
// 只能退化成 Main_Common;而它原生那棵是 MeikaShip。
//
//
// 覆盖 SyncUnitOccur.occurId。默认 null = aiProfile?.Occur ?? 0。
// ⛑ 同上:40980002 的 AgentConfig.EnemyOccurId = 70000302,但没行 ⇒ 只能填 0,
// 实机结果是「单位在、能出攻击特效、模型透明」⇒ 出场演出没跑。
//
func GameAdminBridge_SpawnAgentAsync(ctx context.Context, agentId uint32, x float32, y float32, z float32, facing float32, hostile bool, engage bool, forceEnemyRelation bool, campOverride any, treeNameOverride any, occurOverride any) any {
// Router implementation for GameAdminBridge_SpawnAgentAsync
var zero any; return zero
}
// Remembers the enemy id the admin panel just asked for.
func GameAdminBridge_SetPendingGmEnemyId(ctx context.Context, id uint32) any {
// Router implementation for GameAdminBridge_SetPendingGmEnemyId
var zero any; return zero
}
// Consumes the remembered id, or 0 when none is fresh enough.
func GameAdminBridge_TakePendingGmEnemyId(ctx context.Context, ) uint32 {
// Router implementation for GameAdminBridge_TakePendingGmEnemyId
return 0
}
//
// Records the intent when the panel issues an AddEnemy* GM command. The client forwards
// these to the server as C2S GmAddEnemy* invokes with a mangled id, so the server keeps
// the authoritative one.
//
func GameAdminBridge_NoteGmCommandIntent(ctx context.Context, method string, args []any) any {
// Router implementation for GameAdminBridge_NoteGmCommandIntent
var zero any; return zero
}
//
// Catalog for the 生成敌人 / 生成NPC panel sections: raw AgentConfig agents split by
// EnemyClassType (1/2/4/6/7 hostile, 9 civilian).
//
//
// Agent dropdown rows for one panel. AgentConfig carries many same-name rows per character —
// same model, different AI / spawn-group — which used to flood the list (one enemy had 552
// rows). Each name is therefore listed once, using its lowest id; the remaining ids stay
// reachable through AdminAgentEntry.VariantIds and the free-id input.
//
func GameAdminBridge_AgentSummonCatalog(ctx context.Context, hostile bool) []any {
// Router implementation for GameAdminBridge_AgentSummonCatalog
return nil
}
// 置顶次序;未命中的名字返回 int.MaxValue ⇒ 排到置顶项之后。
func GameAdminBridge_PinnedRank(ctx context.Context, name string) int32 {
// Router implementation for GameAdminBridge_PinnedRank
return 0
}
func GameAdminBridge_BuildSpawnIdTable(ctx context.Context, ) map[string]any {
// Router implementation for GameAdminBridge_BuildSpawnIdTable
return nil
}
func GameAdminBridge_SpawnIdCatalog(ctx context.Context, kind string) []any {
// Router implementation for GameAdminBridge_SpawnIdCatalog
return nil
}
// --- World placement ------------------------------------------------------------
// Feed the proven friend-NPC actor path (SpawnAgentAsync) a list of explicit world
// coordinates, so "put NPCs into the city at fixed spots and go look" is testable.
// Encodes one SyncAetherAIInitDatas (68704070) framing variant. null = unknown name.
//
// 给带 -isec-(不含 -real)的变体用:发多少条**合成探测**路口(0 = 空表)。
// -real 变体忽略它,改由 aether.initIntersectionsReal / initRealIntersectionCount 控制。
//
func GameAdminBridge_BuildAetherInitWire(ctx context.Context, variant string, raidId uint32, hasZoneGraph bool, handle int32, intersectionCount int32) []byte {
// Router implementation for GameAdminBridge_BuildAetherInitWire
return nil
}
//
// 探测用路口表(派生先):InstanceId / ZoneIndex 递增,状态取默认相位。
// 真实 InstanceId 语义未知,这里只是「让客户端有东西可解析」的最小样本。
//
func GameAdminBridge_BuildProbeIntersectionsDerived(ctx context.Context, count int32) []any {
// Router implementation for GameAdminBridge_BuildProbeIntersectionsDerived
return nil
}
// 同 BuildProbeIntersectionsDerived,基类字段在前。
func GameAdminBridge_BuildProbeIntersectionsBaseFirst(ctx context.Context, count int32) []any {
// Router implementation for GameAdminBridge_BuildProbeIntersectionsBaseFirst
return nil
}
// --- 真路口(捐赠端相位表 zonegraph_intersections.csv,171 行)---------------------
//
// ★★ 2026-10-02:捐赠端 PrivateServerPor 那张路口相位表(IntersectionTable)。
// ⛑⛑ InstanceId 填 CSV 第一列(= 主线 intersections.csv 的第一列,59/105/…);
// ZoneIndex **默认填真 zone index**(查 data/intersection_zone_map.csv,
// 59 ⇒ 184),这是 2026-10-02 21:2x 修的 —— 捐赠端原写法把两个字段都填成第一列,
// 客户端拿它查 _zoneIndexToBlobIndex 查不到 ⇒ 灯柱没人接管、**永远不变色**。
// 开关 aether.intersectionUseTrueZoneIndex=false 可回到捐赠端原填法。
// ⛑ 周期三 byte 取表里**真值**,不是旧探测变体那样写死 0/1/2。
// ⛑ 一键回滚:aether.intersectionsEnabled=false ⇒ 这里返回空表(与 top-int7 等价)。
//
func GameAdminBridge_BuildDonorIntersectionsDerived(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_BuildDonorIntersectionsDerived
return nil
}
//
// 取 init 里该填的 ZoneIndex:useTrueZone 时查映射表,查不到(0)就回退成 id 本身。
//
func GameAdminBridge_ZoneIndexFor(ctx context.Context, instanceId int32, useTrueZone bool) int32 {
// Router implementation for GameAdminBridge_ZoneIndexFor
return 0
}
// 同 BuildDonorIntersectionsDerived,基类字段在前(对照组)。
func GameAdminBridge_BuildDonorIntersectionsBaseFirst(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_BuildDonorIntersectionsBaseFirst
return nil
}
// --- 真路口(world dump) -------------------------------------------------------
//
// -real 变体实际会发出去的路口行(已应用开关与条数上限)。
// 关掉 aether.initIntersectionsReal 即退化为「空表」—— 与现状 top-int7 完全等价,
// 这是本次改动的一键回滚点。
//
func GameAdminBridge_RealIntersectionRows(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_RealIntersectionRows
return nil
}
// 真路口(派生字段先 —— lua 确认的 wire 顺序)。
func GameAdminBridge_BuildRealIntersectionsDerived(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_BuildRealIntersectionsDerived
return nil
}
// 把任意 (InstanceId,ZoneIndex) 行转成 init 元素(派生字段先)。
func GameAdminBridge_BuildIntersectionsFrom(ctx context.Context, rows []any) []any {
// Router implementation for GameAdminBridge_BuildIntersectionsFrom
return nil
}
// 真路口(基类字段先 —— 万一 lua 那侧的 Autogen 与实际 C++ reader 不一致时的对照组)。
func GameAdminBridge_BuildRealIntersectionsBaseFirst(ctx context.Context, ) []any {
// Router implementation for GameAdminBridge_BuildRealIntersectionsBaseFirst
return nil
}
// Replays every Aether init framing (800 ms apart) so one client run identifies the
// one whose RaidId the client reads back as the real raid id in Player.log.
func GameAdminBridge_AetherInitVariantsAsync(ctx context.Context, variants []any) any {
// Router implementation for GameAdminBridge_AetherInitVariantsAsync
var zero any; return zero
}
//
// 重发一次 Aether init,内容 = data/intersections_probe.csv 里的
// (InstanceId, ZoneIndex) 组合(允许同一个 InstanceId 出现多行 = 同时试多个候选 zone)。
// ★ 用途:客户端对每个「在 blob 里找不到」的 zone 打一行
// cannot find static zone info for zone index: Z, instanceId: I,
// 没打错的就是有效组合 ⇒ 反查每个路口真正属于哪个 zone(世界转储里推不出来)。
// 走 HTTP POST /api/aether/isec-probe,不用重启客户端。
//
func GameAdminBridge_AetherIsecProbeAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_AetherIsecProbeAsync
var zero any; return zero
}
//
// Places NPCs at explicit world coordinates and returns every point (including its final
// coordinates) so the result can be walked/teleported to and inspected.
// origin=null → the tracked player's last reported position.
//
func GameAdminBridge_SpawnAgentGridAsync(ctx context.Context, agentId uint32, hostile bool, originX any, originY any, originZ any, distance float32, facingDeg float32, rows int32, cols int32, spacing float32) any {
// Router implementation for GameAdminBridge_SpawnAgentGridAsync
var zero any; return zero
}
// ---- Citizens (Aether crowd simulation) -------------------------------------------------
// These entities belong to the client's crowd manager: the server announces them once and the
// client owns them from then on — no roster, no persistence. tier 0 means "every preset",
// 1 = street service (vendors / clerks / couriers), 2 = pure background pedestrians.
// Group list only — 199 groups × 20 items would be a megabyte of JSON on every
// panel refresh, so the items are fetched per group on demand.
func GameAdminBridge_CivilianPanelState(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_CivilianPanelState
var zero any; return zero
}
func GameAdminBridge_CivilianGroupDetail(ctx context.Context, id int32) any {
// Router implementation for GameAdminBridge_CivilianGroupDetail
var zero any; return zero
}
func GameAdminBridge_SpawnCivilianGroupAsync(ctx context.Context, groupId int32, spacing float32) any {
// Router implementation for GameAdminBridge_SpawnCivilianGroupAsync
var zero any; return zero
}
//
// The land-plot seeding card (T0-8). Returns what is loaded plus the clock the seeding will
// use, so the panel can show both without a second round trip.
//
func GameAdminBridge_CivilianDistrictState(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_CivilianDistrictState
var zero any; return zero
}
//
// 面板下拉用:列出有坐标的可入侵点。
// 官方数据里有几条 type=1(大本营)的 CenterPos 全是 0 —— 它们不能当传送目标,
// 所以这里只回 FactionInvasionSpotCatalog4229938.Placeable。
//
func GameAdminBridge_ListFactionInvasionSpots(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_ListFactionInvasionSpots
var zero any; return zero
}
//
// 一次嘉姆入侵:传送 → 放两个随从 → 推占领包。三步各自独立开关。
// ⛑ 不代切角色:玩家要先在角色界面切到嘉姆(spiritId 15020989)。
//
func GameAdminBridge_RunFactionInvasionAsync(ctx context.Context, eventId int32, teleport bool, summon bool, occupy bool) any {
// Router implementation for GameAdminBridge_RunFactionInvasionAsync
var zero any; return zero
}
func GameAdminBridge_SpawnCiviliansByDistrictAsync(ctx context.Context, radius float32, maxTotal int32, hour int32) any {
// Router implementation for GameAdminBridge_SpawnCiviliansByDistrictAsync
var zero any; return zero
}
//
// The functional-NPC card. Reports how many fixed spots exist for the player's current scene
// so the panel can show "this scene has 18" before anything is spawned.
//
func GameAdminBridge_FunctionalNpcState(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_FunctionalNpcState
var zero any; return zero
}
func GameAdminBridge_SpawnFunctionalNpcsAsync(ctx context.Context, radius float32, includeSemantic bool) any {
// Router implementation for GameAdminBridge_SpawnFunctionalNpcsAsync
var zero any; return zero
}
//
// The gang-territory card. Same shape as FunctionalNpcState: how many authored
// turf corners the current scene has, how far the nearest one is, and whether the follow loop
// is running — "85 spots" means nothing when the closest one is half a map away.
//
func GameAdminBridge_EnemyTerritoryState(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_EnemyTerritoryState
var zero any; return zero
}
func GameAdminBridge_SpawnEnemyTerritoryAsync(ctx context.Context, radius float32) any {
// Router implementation for GameAdminBridge_SpawnEnemyTerritoryAsync
var zero any; return zero
}
//
// Server-driven walk test for the citizens spawned this session. See GameRouter.WalkCiviliansAsync:
// the point is to compare moveId=0 (what every call site used to send) with a real gait on the
// same trajectory, because only the screen can settle which one animates.
//
func GameAdminBridge_WalkCiviliansAsync(ctx context.Context, moveId byte, distance float32, durationSeconds float32) any {
// Router implementation for GameAdminBridge_WalkCiviliansAsync
var zero any; return zero
}
// ---- Donor-build static NPC spawn (verbatim port) ----------------------------------------
// Kept separate from the civilian card above: this is the donor's exact sequence, including
// the per-spawn Aether init, used to prove the fourth entity channel works before layering
// persistence / presets on top.
func GameAdminBridge_SpawnDonorStaticNpcsAsync(ctx context.Context, items []any, xSpacing float32, zSpacing float32, maxPerRow int32) any {
// Router implementation for GameAdminBridge_SpawnDonorStaticNpcsAsync
var zero any; return zero
}
//
// Releases control states on the player unit. mode "curated" sends the hand-picked list
// above; mode "sweep" brute-forces every state id in 1..500, which is the fallback for
// when the stuck state is one we did not guess.
//
func GameAdminBridge_ClearPlayerControlStatesAsync(ctx context.Context, mode string) any {
// Router implementation for GameAdminBridge_ClearPlayerControlStatesAsync
var zero any; return zero
}
//
// Clears a stuck "昏迷 100%" HUD: re-publishes the player's HP table and explicitly exits
// the FallingDown state.
// Why this is needed: a spawned agent once shared the player's own entity id
// (see ServerSpawnUidOrdinalOffset), so the enemy HP settlement ran on the player and
// drove its HP to zero. With EnableFallingDownOnDeath2 on, HP=0 switches the HUD to the
// knocked-out bar and nothing writes a full HP value back afterwards — the bar stays up
// even though control is unaffected.
// unitId defaults to the tracked player unit. Pass the client-side entity id explicitly
// when that is the one driving the bar (packets log: AskUnitMoveActionSimple.UnitId).
//
func GameAdminBridge_RestorePlayerHealthAsync(ctx context.Context, unitId uint64) any {
// Router implementation for GameAdminBridge_RestorePlayerHealthAsync
var zero any; return zero
}
//
// Locks or releases the player's life.
// The bar always drains — HP is server-authoritative now, so a hit always shows. What the lock
// decides is only whether it can reach zero:
// * locked (default): the drain stops at GameRouter.PlayerVitalsLockFloorHp,
// the player gets hurt but is never knocked out;
// * released: zero is lethal — knock-out, then an automatic revive after
// GameRouter.PlayerDownRespawnDelayMs.
//
func GameAdminBridge_SetPlayerLifeLock(ctx context.Context, locked bool) any {
// Router implementation for GameAdminBridge_SetPlayerLifeLock
var zero any; return zero
}
// Current life-lock state plus the authoritative HP, for the panel toggle.
func GameAdminBridge_GetPlayerLifeLock(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_GetPlayerLifeLock
var zero any; return zero
}
// Turns the attack AI's automatic control-state release on or off.
func GameAdminBridge_SetAutoUnlock(ctx context.Context, enabled bool) any {
// Router implementation for GameAdminBridge_SetAutoUnlock
var zero any; return zero
}
// Session-scoped core, so the attack AI can schedule its own releases.
func GameAdminBridge_ClearControlStatesAsync(ctx context.Context, session *GameSession, unit uint64, ids []any, label string) any {
// Router implementation for GameAdminBridge_ClearControlStatesAsync
var zero any; return zero
}
func GameAdminBridge_RequirePlayerSession(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_RequirePlayerSession
var zero any; return zero
}
func GameAdminBridge_PickPlayerSession(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_PickPlayerSession
var zero any; return zero
}
//
// Retires every character summoned through SpawnNpcAsync, for the wheel's "disband all" button
// (AskDestroyAllGangMember, 63243016).
// This only works because summoned characters are given their own uid segment
// (SummonedNpcUidBase). The earlier build reused the roster's canonical unit ids, which double
// as the spirit-switch identities the client pre-allocates, and SyncLogicAgentLeave was a
// silent no-op on those — the client never retired the copy. A dedicated segment retires fine.
//
func GameAdminBridge_DismissSummonedNpcsAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_DismissSummonedNpcsAsync
var zero any; return zero
}
// ---------- 人流密度(T0-8 调参卡) ----------
//
// 立刻按当前所在地块重算并推一次人群密度。
// 为什么必须要有这个入口:人群没有 tick 循环,只在「进世界」和「移动 &gt;35m」推包,
// 原地改配置是不会生效的 — 必须手动推一次。
//
func GameAdminBridge_RefreshCrowdAsync(ctx context.Context, overrideDensity any) any {
// Router implementation for GameAdminBridge_RefreshCrowdAsync
var zero any; return zero
}
//
// 只算不推,给面板显示「玩家脚下当前会算出什么密度」。
// 改完参数不用真的走 35 米,页面上就能看到目标值的变化。
//
func GameAdminBridge_CrowdProbe(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_CrowdProbe
var zero any; return zero
}
// ============================================================ 搜打撤(Extraction Shooter)
// 面板「🎒 搜打撤」的状态体。GameRouter.ExtractionStateForPanel。
func GameAdminBridge_ExtractionState(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_ExtractionState
var zero any; return zero
}
//
// 面板开关的总入口。所有参数都是「null = 不动」,前端可以每次只提交改过的那一个。
// ⛑ 面板改动只在内存里:刻意不落盘 —— 这是排障工具,重启就该回到配置文件的默认值,
// 免得「昨天为了测试开的开关今天还在生效」这种幽灵行为。
//
func GameAdminBridge_SetExtractionOptions(ctx context.Context, fakeMatch any, preferredRaid any, exitToMainCity any, exitSettlement any, wheelSync any, wheelWeaponData any) any {
// Router implementation for GameAdminBridge_SetExtractionOptions
var zero any; return zero
}
// 面板「进图」。
func GameAdminBridge_StartExtractionAsync(ctx context.Context, raidId uint32) any {
// Router implementation for GameAdminBridge_StartExtractionAsync
var zero any; return zero
}
// 面板「退出副本回主城」—— 与游戏里点退出走同一条 ExitExtractionAsync。
func GameAdminBridge_ExitExtractionAsync(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_ExitExtractionAsync
var zero any; return zero
}
// 面板「⟳ 重推武器轮盘」:强制整表下发一次 SyncSpiritWeaponDetail。现场排障用。
func GameAdminBridge_RepublishExtractionWheel(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_RepublishExtractionWheel
var zero any; return zero
}
// 面板「⟳ 重推登录帧」:把含 PlayerExtractionShooterInfo 的那一帧再发一次。
func GameAdminBridge_RepublishExtractionLoginFrame(ctx context.Context, ) any {
// Router implementation for GameAdminBridge_RepublishExtractionLoginFrame
var zero any; return zero
}
//
// 面板「任意 notify 探针」:把一条裸包按指定 methodId 推给客户端(取证用,见
// GameRouter.PushExtractionNotifyAsync 的说明)。
//
func GameAdminBridge_PushExtractionNotify(ctx context.Context, methodId uint32, hex any) any {
// Router implementation for GameAdminBridge_PushExtractionNotify
var zero any; return zero
}
//
// GM 面板 / POST /api/kesi/control:科斯机甲的原生控制切换(2026-09-26,
// 移植 7k-v0.5 GameRouter.Kesi.cs,实现在 GameRouter.Kesi.cs)。
// 科斯 = FightSpiritConfig 15022030 "特摄战原型-科斯",隐藏可控原型 &lt;=&gt;
// IsPublish=false / CanJoin=false / Invisible=true ⇒ 不在 roster 里,GM 面板换人下拉里也选不到。
// 怎么用:进游戏 → 下车 → 站在室外空地 → 点「控制科斯机甲」→ 看模板有无出现。
// 判据:服务端日志 [KESI] enter=True ok=True;失败则看 [SWITCH-MIN]。
// ⛑ 服务端协议侧齐了 ≠ 画面上能动:模型异步加载、镜头、机甲技能面板都要实机确认
// (捐赠端自己也标了"尚需游戏内验证")。
//
func GameAdminBridge_KesiControlAsync(ctx context.Context, opt any) any {
// Router implementation for GameAdminBridge_KesiControlAsync
var zero any; return zero
}
// Panel-facing projection (rule 12: public methods cannot return internal types).
// Named "...Snapshot" because a nested type cannot share its parent's member name (CS0102).
type BlackoutStatusSnapshot struct {
Online bool `json:"online"`
Running bool `json:"running"`
ControllerId uint64 `json:"controllerId"`
RemainingSeconds int32 `json:"remainingSeconds"`
EndsAt *string `json:"endsAt"`
}
//
// 自动驾驶巡航速度的面板投影(2026-09-30)。
// SavedKmh 为 null = config/autonomous-driving.json 还不存在,
// 也就是「没在 GM 里调过速」,此时生效值就是主配置 cruiseSpeed × 3.6。
// ActiveAutoDrive = 当前有没有正在自驾的车(决定保存时能不能热应用)。
//
type AutonomousDriveSpeedStatus struct {
SavedKmh *float32 `json:"savedKmh"`
EffectiveKmh float32 `json:"effectiveKmh"`
ActiveAutoDrive bool `json:"activeAutoDrive"`
MinKmh float32 `json:"minKmh"`
MaxKmh float32 `json:"maxKmh"`
}
// Public timeline/cutscene entry for the admin panel.
type AdminTimelineEntry struct {
TimelineId uint32 `json:"timelineId"`
TimelineName string `json:"timelineName"`
Group string `json:"group"`
PGOType int32 `json:"pGOType"`
PlayerControlType int32 `json:"playerControlType"`
BanSkip bool `json:"banSkip"`
BanPause bool `json:"banPause"`
NeedPreload bool `json:"needPreload"`
PauseAI bool `json:"pauseAI"`
ClosePanel bool `json:"closePanel"`
}
// 一条台词,供面板「剧情对话」子页列表渲染。
type AdminDialogEntry struct {
DialogId uint32 `json:"dialogId"`
Speaker string `json:"speaker"`
Message string `json:"message"`
Cutscene bool `json:"cutscene"`
Type int32 `json:"type"`
}
// ================= 切人演出:按**动画**分(2026-10-03 纠正)=================
// 捐赠端 GM 面板的「切人动画演出」子页主下拉是 SwitchSpiritConfig 的 201 行(一条 = 一出演出),
// 第二个下拉才是「切成哪个角色」。我 10-02 第一版按角色做是错的:
// 同一角色有十几条演出,按角色只能触发服务端替他挑的那一条(受 DisperseAnchorsByCharacter /
// 距离 / 权重影响),玩家想看「送人下车」那条根本选不到。
// 一条切人演出,供面板「切人演出」子页列表渲染。
type AdminSwitchShowEntry struct {
SwitchShowId uint32 `json:"switchShowId"`
Timeline string `json:"timeline"`
Description string `json:"description"`
TimelineDescription string `json:"timelineDescription"`
Label string `json:"label"`
SwitchType int32 `json:"switchType"`
RaidId uint32 `json:"raidId"`
Weight float32 `json:"weight"`
Invalid bool `json:"invalid"`
// timeline 命中 CommonTimelinePrefix —— 这类行允许「FightSpiritId 空 = 人人可用」。
IsCommon bool `json:"isCommon"`
Playable bool `json:"playable"`
HasCompanionAgents bool `json:"hasCompanionAgents"`
Position []float32 `json:"position"`
// 能演这条演出的角色(已按 IdsFor 判据算好,并扣掉 Forbid)。
FightSpiritIds []uint32 `json:"fightSpiritIds"`
// 与 FightSpiritIds 一一对应的显示名。
SpiritNames []string `json:"spiritNames"`
// 默认选中的目标角色(清单第一个)。
DefaultSpiritId uint32 `json:"defaultSpiritId"`
AgentIds []uint32 `json:"agentIds"`
}
// ── 通缉系统(Wanted · 警车版,2026-09-23 自捐赠端 miyuki_fix 移植)────────────────────
// Body of POST /api/wanted/action。action 为空串时只回状态。
type WantedActionRequest struct {
Action string `json:"action"`
Level uint32 `json:"level"`
CrimeValue uint32 `json:"crimeValue"`
Points uint32 `json:"points"`
Seconds int32 `json:"seconds"`
Amount *float64 `json:"amount"`
Enabled *bool `json:"enabled"`
NpcHitPoints uint32 `json:"npcHitPoints"`
NpcKillPoints uint32 `json:"npcKillPoints"`
VehicleHitPoints uint32 `json:"vehicleHitPoints"`
VehicleDestroyPoints uint32 `json:"vehicleDestroyPoints"`
AutoEscapeEnabled *bool `json:"autoEscapeEnabled"`
ArrestOnPlayerDown *bool `json:"arrestOnPlayerDown"`
PursuitTimeoutSeconds int32 `json:"pursuitTimeoutSeconds"`
VehiclesPerLevel int32 `json:"vehiclesPerLevel"`
RewardPerLevel float64 `json:"rewardPerLevel"`
PenaltyPerLevel float64 `json:"penaltyPerLevel"`
}
//
// POST /api/plate/push 的可选覆盖项。null ⇒ 沿用 gameplay.plate 配置里的值。
// 存在的意义:线格式还没试出来,改配置再推一次比重新编译快得多。
//
type PlatePushOptions struct {
// 手动推时默认无视总开关(配置 enabled=false 也能推)。
Force bool `json:"force"`
UniqueId *uint64 `json:"uniqueId"`
GraphId *int32 `json:"graphId"`
X *float32 `json:"x"`
Y *float32 `json:"y"`
Z *float32 `json:"z"`
RotX *float32 `json:"rotX"`
RotY *float32 `json:"rotY"`
RotZ *float32 `json:"rotZ"`
FavorNpcActivityId *uint32 `json:"favorNpcActivityId"`
TaskId *uint32 `json:"taskId"`
NpcBind *string `json:"npcBind"`
SlotIndex *int32 `json:"slotIndex"`
SpiritId *uint32 `json:"spiritId"`
// 线格式旋钮(对应 PlatePilotSettings 同名项)
RootMarker *bool `json:"rootMarker"`
ListMarker *bool `json:"listMarker"`
ListCountEncoding *string `json:"listCountEncoding"`
ItemMarker *bool `json:"itemMarker"`
DictMarker *bool `json:"dictMarker"`
DictCountEncoding *string `json:"dictCountEncoding"`
VectorMarker *bool `json:"vectorMarker"`
}
// POST /api/npclife/spoon 的可选覆盖项。
type NpcSpoonOptions struct {
// 手动推时无视总开关。
Force bool `json:"force"`
// 好感 NPC 的 AgentTag(里希 = 12)。0 ⇒ 用 SpiritId 反查。
AgentTag *uint32 `json:"agentTag"`
// 由哪个角色反查 AgentTag / AgentId(默认里希 15021021)。
SpiritId *uint32 `json:"spiritId"`
// 直接指定 spoon 位号;不填就按 gameplay.npcLife.spoonAgentIdMode 取。
SpoonAgentId *int32 `json:"spoonAgentId"`
X *float32 `json:"x"`
Y *float32 `json:"y"`
Z *float32 `json:"z"`
// 线格式旋钮:包尾可空的 busyInfo 写哪个字节(0 / 255 / -1 = 不写)。默认沿用配置。
BusyInfoMarkerByte *int32 `json:"busyInfoMarkerByte"`
}
// POST /api/npclife/timetable 的可选覆盖项。
type NpcTimeTableOptions struct {
// 手动推时无视总开关。
Force bool `json:"force"`
// 好感 NPC 的 AgentTag(里希 = 12)。0 ⇒ 用 SpiritId 反查。
AgentTag *uint32 `json:"agentTag"`
// 由哪个角色反查 AgentTag / AgentId(默认里希 15021021)。
SpiritId *uint32 `json:"spiritId"`
// 直接指定 spoon 位号;不填就按 gameplay.npcLife.spoonAgentIdMode 取。
SpoonAgentId *int32 `json:"spoonAgentId"`
// true ⇒ 走单条 SyncAgentTagTimeTableInfo(64400736);false ⇒ 全量字典 64219939。
SingleTag *bool `json:"singleTag"`
}
// POST /api/npclife/poi 的可选覆盖项。
type NpcPoiOptions struct {
// 目标单位 id。0 ⇒ 从常驻 NPC 账本里按 SpiritId 查它此刻的实体。
UnitId *uint64 `json:"unitId"`
// 由哪个角色反查单位(默认里希 15021021)。
SpiritId *uint32 `json:"spiritId"`
// POI 动作 id。不填就用 gameplay.npcLife.poiActionId,再不行 198(跑步机跑步)。
PoiActionId *uint32 `json:"poiActionId"`
}
type AdminSpawnIdEntry struct {
Id uint32 `json:"id"`
Name string `json:"name"`
FactionName string `json:"factionName"`
}
type SpawnPoint struct {
X float32 `json:"x"`
Y float32 `json:"y"`
Z float32 `json:"z"`
Uid uint64 `json:"uid"`
Ok bool `json:"ok"`
Message string `json:"message"`
}
type KesiControlOptions struct {
// true = 接管科斯机甲;false = 切回进入机甲前的角色。
Enter bool `json:"enter"`
}