1759 lines
83 KiB
Go
1759 lines
83 KiB
Go
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<String> 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 -> 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 循环,只在「进世界」和「移动 >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 "特摄战原型-科斯",隐藏可控原型 <=>
|
||
// 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"`
|
||
}
|
||
|