Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.Kesi.cs
T

116 lines
6.7 KiB
C#
Raw 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.Runtime.CompilerServices;
using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.Server.ClientData.Client4229938;
using Ananta.Server.Protocol.Client4229938;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// 里希(Garm) 之外的另一条「可控非角色原型」:<b>科斯机甲</b>(2026-09-26,移植捐赠端 7k-v0.5
/// <c>GameRouter.Kesi.cs</c>)。
///
/// 根因(客户端配置侧,已在本服 ClientData 上复核):
/// - FightSpiritConfig <c>15022030</c> = "特摄战原型-科斯",<c>IsPublish=false / CanJoin=false /
/// Invisible=true</c> ⇒ 它<b>永远不在 Characters() 花名册里</b>,roster 上点了也不会出现。
/// - SceneitemConfig <c>80003500</c> = "科斯冲锋枪",<c>WeaponBelong=15022030</c>、
/// <c>FixedFightSkill=39010048</c>,且<b>没有 WheelIcon</b> ⇒ 常规军械库过滤器会把它判掉,
/// 所以它连「随便塞把枪」都做不到,必须走 CombatCatalogRepository 里那份孤立装配。
/// - 客户端 <c>CheckIsKesi</c> 拿 cardId <c>15022030</c> 认机甲(捐赠端注释)。
///
/// 供给链三条,缺一不可:
/// 1. <c>SyncPlayerAddNewSpirit</c>(64691088) 增量补 SpiritViewData —— 登录包里没有它,
/// 客户端 SpiritManager.Id_to_Tid 查不到 tid,后面所有和它相关的包都会被静默丢掉;
/// 2. CombatCatalogRepository 里那份科斯 loadout(slot0 科斯冲锋枪 80003500 + 风格 39010048);
/// 3. 走本服已在用的 <see cref="OnSwitchSpirit"/> 做身份移交 —— 不另发明一套切人。
///
/// ⛑ 这是<b>需实机验证</b>的实验入口:捐赠端自己也标注"尚需游戏内验证"。
/// 能确认的只有服务端协议侧齐了;模型加载、镜头、机甲技能面板和按键都得进游戏看。
/// </summary>
internal sealed partial class GameRouter
{
private static readonly ConditionalWeakTable<TcpSession, KesiControlRuntime4229938> KesiControls4229938 = new();
private sealed class KesiControlRuntime4229938
{
// 与普通角色切换共用一把锁:GM 切机甲和游戏内主动切人不能互相打断,
// 否则两份 PlayerInfo/SpiritViewData 会交错下发,客户端 roster 直接错位。
internal readonly SemaphoreSlim Gate = new(1, 1);
/// <summary>进入机甲前的角色 template;return 时切回它。</summary>
internal uint ReturnTemplate;
/// <summary>只有走过 ControlKesi 才允许 <see cref="OnSwitchSpirit"/> 接受 Kesi 作为目标。</summary>
internal bool Unlocked;
}
internal static async Task<(bool Ok, string Message)> ControlKesi4229938(TcpSession session, bool enter)
{
var runtime = KesiControls4229938.GetValue(session, _ => new());
await runtime.Gate.WaitAsync(CancellationToken.None);
try
{
var state = GetStateIfExists(session);
if (state is not { Ready: true, CombatProfilePublished: true, HasLastReportedPlayerTransform: true }
|| state.ActiveSpiritUnitId == 0 || state.PendingSwitchTemplateId != 0 || state.PendingTeleportId != 0
|| GetVehicleRuntimeState(session).CurrentVehicleId != 0)
return (false, "请进入游戏,离开载具并等待角色和场景加载完成。");
var p = state.LastReportedPlayerPosition;
if (!float.IsFinite(p.X) || !float.IsFinite(p.Y) || !float.IsFinite(p.Z)
|| !float.IsFinite(state.LastReportedPlayerRotation.Y))
return (false, "角色坐标尚未就绪,请移动几步后重试。");
var kesi = ClientConfigRepository.Kesi;
if (enter && state.ActiveSpiritTemplateId == kesi.TemplateId)
return (true, "当前已经是科斯机甲,不重复生成。");
if (!enter && state.ActiveSpiritTemplateId != kesi.TemplateId)
return (true, "当前不在科斯机甲中,无需返回。");
var target = enter ? kesi.TemplateId : runtime.ReturnTemplate;
if (!enter && ClientConfigRepository.ControlCharacter(target) is null)
target = Profile.InitialSpiritTemplateId;
var ctx = new RpcContext(
session,
new RpcPacket(RpcPacketKind.Invoke, MethodId.AskSwitchSpirit, 0, Array.Empty<byte>()),
CancellationToken.None);
if (enter)
{
// ⛑ 移交归属之前先自检:本服这份 client-data 抓包要是缺了科斯的原生武器/技能行,
// 切过去会得到一个空手且技能全空的单位,比直接拒绝难排查。
// Sceneitem 80003500 / FightSkill 39010048 已在本服 ClientData 上核到。
var weapon = CombatCodec.DefaultWeapon(kesi.TemplateId);
if (weapon.TemplateId != 80003500 || weapon.Style.Id != 39010048)
return (false, "缺少科斯原生武器/技能配置,已取消切换。");
// 记得来路,否则永远回不去 —— return 时 Target 就是它。
runtime.ReturnTemplate = state.ActiveSpiritTemplateId;
runtime.Unlocked = true;
// ⛑ 防御:旧版本会在 LeftBehindCharacters 里留一具科斯(T-pose 残骸)。
// 进来前先把那条记录抹掉,否则 OnSwitchSpirit 会把它当成「已在大世界」而走
// recreate 分支,反而又把那具旧身体重建一次。
state.LeftBehindCharacters.Remove(kesi.TemplateId);
await ctx.NotifyAsync(MethodId.SyncPlayerAddNewSpirit, RuntimePayloadFactory.KesiSpirit4229938(state));
}
await new GameRouter().OnSwitchSpirit(ctx, target, allowShow: false);
var ok = state.ActiveSpiritTemplateId == target && state.PendingSwitchTemplateId == 0;
session.Log.Info($"[KESI] enter={enter} ok={ok} active={state.ActiveSpiritTemplateId}/{state.ActiveSpiritUnitId} " +
$"return={runtime.ReturnTemplate} reasonWeapon={CombatCodec.DefaultWeapon(kesi.TemplateId).TemplateId}");
return (ok, ok
? enter
? "已下发科斯原生角色、控制切换与技能。请检查模型、移动和机甲技能面板;可点“返回原角色”退出。"
: "已切回进入机甲前的角色,保留当前位置。"
: "切换未完成,请查看服务端 [KESI]/[SWITCH-MIN] 日志。");
}
catch (Exception ex)
{
session.Log.Warn($"[KESI] command failed: {ex.Message}");
return (false, "机甲指令未完成,请查看服务端 [KESI] 日志。");
}
finally { runtime.Gate.Release(); }
}
}