Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.BowlingPinGadget.cs

195 lines
11 KiB
C#
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.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.SDK.Serialization;
using Ananta.Server.Configuration;
using Ananta.Server.Protocol.Client4229938;
using Ananta.Server.RpcTypes.Client4229938;
using Auto = Ananta.Server.RpcTypes.Client4229938.Auto;
using SceneMethods = Ananta.Server.RpcTypes.Client4229938.Methods.GameScene;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// ★ 诊断实验(2026-09-24 深夜):把 10 个球瓶改走**健身房器材那条路**推。
///
/// 起因(客户端日志给出的硬证据):
/// [error] ,scene item,910000000015, hold not found,
/// BowlingGameManager.lua:670 in 'getItemsFromIdList'
/// BowlingGameManager.lua:680 in 'InitSceneItems'
/// ⇒ 客户端确实收到了我们推的 id(910000000001..15,见 BowlingZoneInfo 的两个 id 列表),
/// 但 `SceneItemMgr.Instance:GetSceneItemHold(id)` 查不到 hold,
/// 于是 `sceneItemPinList` 为空 → `Rent(101)` 报 "pool[101] is nil" →
/// `BowlingPinSetter.CreatePin` 直接 return → 官方那套带模型的球瓶根本没被建出来。
/// 我们走的 `DestructibleInfo` 只造出「物理 + 音效」的实体,不产生 hold、不产生渲染。
///
/// 而本项目里**唯一被实测证明能生成"看得见"的物体**的通道是 gadget:
/// `GameRouter.GymEquipment.cs` → GadgetEntityInfo(Pack.PathId 直接给资源 id)
/// → `GadgetGridAOIIncrease` → `SyncGadgetAOIAddAndRemove`(68169646)
/// 健身房器材就是这么生成出来的,肉眼可见。
///
/// 所以本实验:同样用 gadget AOI 把东西推到球瓶位置,看客户端认不认。
///
/// 配置(config/bowling_wire.json):
/// "pinAsGadget": 1 打开本实验(0 = 关)
/// "gadgetProbePathId": N 对照组的 pathId,默认 79300636 = MiniGameMachine_Bowling
/// (GadgetConfig 里保龄球机器,写 0 = 不放对照组)
///
/// 一次测试能读出三件事:
/// ① 对照机器的 gadget 现身了?→ 说明「服务端推 gadget + AOI 通道」在球馆里也成立
/// ② 10 个球瓶位置现出物体了?→ 说明客户端能把 `pinPathId` 当资源 id 解析出来 ⇒ 收工
/// ③ 都不现 → gadget 通道认不得场景物 id,得转客户端侧(frida)方案
///
/// ⚠ 本实验**不动**原来的 DestructibleInfo 推送(物理/计分/音效照旧),
/// 只是在同一位置上再叠一层"看得见"的 gadget。两者互不干扰。
///
/// ★★ 2026-09-24 A 方案移植说明(本文件与 PrivateServer修改 版的差异)
///
/// 本仓库(DRMK-PS_09_HyNB-v1.7)里,机关那一族类型在 `...Methods.GameScene` 命名空间下,
/// 且 `GadgetEntityInfo.Position/Facing` 的类型是 `Auto.UXVector3`:
/// GadgetEntityInfo → Methods.GameScene(本仓库,下方用 SceneMethods. 限定)
/// PackedGadgetInfo → Methods.GameScene(= 我们那边的 GadgetPackInfo,**字段顺序前 13 项完全一致**,
/// 本仓库末位多出 extractionItemContainerId / isStatic 两个字段,
/// 默认 0 即可,与 v1.7 自己的机关推送同形)
/// 字段名对照(我们 → 本仓库):Jnc/Jnb/Jna → posX/posY/posZ;EulerX/Y/Z → eulerX/eulerY/eulerZ;
/// IScale → iScale;UniqueId → uniqueId;PathId → pathId。
/// ⚠ iScale 保持 100:这是**机关族**的正确取值(本仓库 PackedGadgetInfo.iScale 的注释写明
/// "Uniform scale in percent (100 = authored size)",且 GameRouter.Gadget.cs / GymEquipment.cs
/// 的默认参数就是 100)。我们那条「iScale 必须写 0」的教训针对的是**另一个**结构
/// `SceneItemPlaceInfoData +0x3C`,两者不要混。
///
/// ★★★ 第二处、也是**唯一改变语义**的一处:68169646 的顶层 args 换成本仓库既有的
/// `SceneMethods.SyncGadgetAOIAddAndRemoveArgs4229938`(6 字段),不再用我们那边的
/// `GymGadgetContracts.GadgetGridAOIIncrease`(7 字段)。理由是**证据打架时选宿主仓**:
///
/// (a) 我们那边的 7 字段版本,来源是 Drmk.Proxy 的 proxy/rpc_schema.json 读端 schema
/// `433`(已实测抽取,字段 = addInfos / indexList / addUniqueIds / removeIds /
/// activeIds / inactiveIds / reason(ReadByte)),但本项目自己的
/// `Ananta.RpcTypes\Client4229938\BowlingWireContracts.cs:271-276` 的注释把同一个
/// DTO 判为 "uncalled rpc_schema-era gym DTO"(把 Destructible 与 Gadget 两族混用)。
/// (b) 本仓库对 68169646 的既有写法就是 6 字段(GameRouter.Gadget.cs:257、Blackout.cs:335),
/// 且 GameRouter.Gadget.cs:33-38 自述这条通道 "which one the client honours … is still
/// unknown" —— 也就是说**本仓也从未验证过它**。
/// (c) 本文件是**诊断旁路**,默认关闭(`_pinAsGadget = 0`),不参与保龄球主功能。
/// 保龄球能不能玩,取决于服务端推球瓶实体(GameRouter.GameGround.cs + 本仓
/// BowlingWireContracts.cs 的 DestructibleGridAOIIncrease,schema 396)**以及客户端
/// BF1 补丁(AnantaBowlingLoader.exe)**,与本文件无关。
///
/// ⇒ 结论:既然两种形状都未在本仓验证过,就**不引入第三种形状、不搬入被判可疑的 DTO**,
/// 直接用宿主仓既有类型,保持本仓内部完全同形。若日后要试 7 字段形状,请回
/// `PrivateServer修改\Ananta.Server\Ananta.RpcTypes\Client4229938\GymGadgetContracts.cs`
/// 整体搬入,本文件只需把 payload 类型与字段名换回大驼峰即可。
/// </summary>
internal sealed partial class GameRouter
{
/// <summary>0 = 关;1 = 开。见 BowlingWireNow()。</summary>
private static int _pinAsGadget = 0;
/// <summary>对照组 pathId(默认保龄球机器)。0 = 不放对照组。</summary>
private static int _gadgetProbePathId = 79300636;
/// <summary>推送前等多少毫秒(热改,不用重编译)。客户端还没准备好的话可以往后挪。</summary>
private static int _gadgetPushDelayMs = 0;
/// <summary>
/// gadget instance id 号段。**2026-09-24 移植时改过**:源版(PrivateServer修改)用
/// <c>920000000000</c>,但本仓库(DRMK-PS_09_HyNB-v1.7)已经把这个段占了 ——
/// · GameRouter.GmWallet.cs:21 <c>GmItemSeqFloor = 920000000000L</c>(GM 钱包发道具,
/// 经 IdCounterStore 持久化计数器,见 GameRouter.Economy.cs:246)
/// · GameAdminBridge.cs:1608-1611 的注释把 920 段明确称作「GameRouter 内部那个计数器」,
/// 并把管理面板发道具的 <c>PanelItemSeqFloor</c> 抬到 950000000000 来避开它
/// ⇒ 沿用 920 会和 GM 钱包发出同一个 <c>PlayerPackItem.UniqueId</c>。
///
/// 本仓库号段图(已逐个核过,930/940 空闲):
/// 320000000000 GM 实体 · 330000000000 GM 敌人 · 910000000000 背包弹药 uid
/// 920000000000 GM 钱包发道具 · 950000000000 管理面板发道具
/// ⇒ 取 <b>940000000000</b>:落在 920 与 950 之间,语义上不与任何一侧重叠。
/// </summary>
// ★ 规则 104 适配(v0.3):源版是 `private static long _bowlingGadgetSeq = 940000000000L;`
// 进程内自增。本仓同样改用 IdCounterStore 落盘,floor 仍取 940000000000
// (920 GM 钱包 与 950 面板发道具 之间;球瓶诊断旁路默认关闭,不会真发号)。
private const string BowlingGadgetIdCounter = "Bowling.GadgetProbeId";
private const long BowlingGadgetIdFloor = 940000000000L;
private async Task PushBowlingPinGadgetsAsync(Connection conn, BowlingSession session, Vec3 playerPos)
{
if (_pinAsGadget == 0) return;
if (_gadgetPushDelayMs > 0)
{
try { await Task.Delay(_gadgetPushDelayMs); }
catch { return; }
if (_pinAsGadget == 0) return;
}
var lane = BowlingPickLane(playerPos.Z);
var laneHeadX = lane.GameNodeX + 0.24f;
var addInfos = new List<SceneMethods.GadgetEntityInfo>();
void AddGadget(float x, float y, float z, float eulerY, int pathId, string tag)
{
var id = (ulong)IdCounterStore.Next(BowlingGadgetIdCounter, BowlingGadgetIdFloor);
addInfos.Add(new SceneMethods.GadgetEntityInfo
{
InstanceId = id,
Position = new Auto.UXVector3 { X = x, Y = y, Z = z },
Facing = new Auto.UXVector3 { X = 0, Y = eulerY, Z = 0 },
Pack = new SceneMethods.PackedGadgetInfo
{
posX = x,
posY = y,
posZ = z,
eulerX = 0,
eulerY = eulerY,
eulerZ = 0,
iScale = 100,
uniqueId = id,
pathId = pathId,
},
});
conn.Log.Info($"[BOWLING-GADGET] {tag} id={id} pathId={pathId} at=({x:F2},{y:F2},{z:F2}) yaw={eulerY:F1}");
}
// 10 个球瓶位置(1-2-3-4 三角阵),用球瓶的 pathId
for (int r = 0; r < 4; r++)
{
int n = r + 1;
for (int c = 0; c < n; c++)
{
float lateral = (c - (n - 1) * 0.5f) * BowlingPinLateralStep;
float depth = r * BowlingPinDepthStep;
AddGadget(lane.PinApexX + depth, BowlingPinBaseY, lane.LaneZ + lateral, 0f,
_pinPath, $"pin[{r},{c}]");
}
}
// 对照组:一个保龄球机器 gadget,放在犯规线后 3 米(玩家身后),明显不挡道
if (_gadgetProbePathId != 0)
{
AddGadget(laneHeadX - 3.0f, BowlingPinBaseY, lane.LaneZ, 180f, _gadgetProbePathId, "CONTROL-machine");
}
// ★ 移植差异:本仓库对 68169646 的既有写法 = SceneMethods.SyncGadgetAOIAddAndRemoveArgs4229938
// (6 字段:addInfos / addPackSyncInfos / removeIds / activeIds / inacticeIds / reason),
// 见 GameRouter.Gadget.cs:255-262 与 GameRouter.Blackout.cs:334-336。
// 我们那边的 7 字段 gym DTO 在此不引入 —— 详见文件顶部说明。
// reason 保持我们那条线的原值 0(AoiAddAndRemoveReason.Init),因为这是**首次下发**;
// 本仓 GameRouter.Gadget.cs 用的是 2(AOIMove),两者含义不同,此处不跟随。
var payload = new SceneMethods.SyncGadgetAOIAddAndRemoveArgs4229938
{
addInfos = addInfos,
reason = 0,
};
var bytes = UxSerializer.Serialize(payload);
await conn.NotifyAsync(MethodId.SyncGadgetAOIAddAndRemove, bytes);
conn.Log.Info(
$"[BOWLING-GADGET] pushed {addInfos.Count} gadgets " +
$"(10 pins pathId={_pinPath} + control={_gadgetProbePathId}) args={nameof(SceneMethods.SyncGadgetAOIAddAndRemoveArgs4229938)} bytes={bytes.Length}");
}
}