Files

105 lines
5.0 KiB
C#

using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.Server.Protocol.Client4229938;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// EonBug / hacker phone app — the interactive half of the BBS.
///
/// The data half lives in <c>GameRouter.Profiles.cs</c> (SyncHackerJobInfo 64189208: handle, post
/// list, rank, daily counters). This file is what happens when the player actually types in the
/// app, and the whole reason it exists is one symptom: open 千年虫 (EonBug), type a name, and the
/// UI freezes in the dialog with no way out.
///
/// Root cause (matches the reference branch's own logs): the client keeps asking
/// AskChangeHackerName(63975191) and never gets an answer, so its local "this account has no handle
/// yet" state never clears and the dialog re-arms every couple of seconds. Our tree used to have no
/// handler registered for it at all, which is the same failure with extra steps — the request
/// landed, nothing answered it, and the client stayed stuck.
/// </summary>
internal sealed partial class GameRouter
{
/// <summary>Per-session EonBug handle. Survives character switches within one login.</summary>
const string HackerHandleKey = "hacker.handle";
/// <summary>The handle used when the player never picked one.</summary>
const string DefaultHackerHandle = "Ananta";
[Handler(MethodId.AskChangeHackerName, HandlerPacketKind.Invoke)]
private async Task AskChangeHackerName(Connection conn, UxRpcMessage msg)
{
var name = string.Empty;
try
{
name = msg.GetArgs<GameMethods.AskChangeHackerName4229938>()?.name ?? string.Empty;
}
catch (Exception ex)
{
// Still answer: an unanswered INVOKE leaves the awaited dialog armed forever, which is
// the exact symptom this handler exists to remove. A parse failure only costs us the
// new handle, so fall back to whatever we already published.
msg.Context.Session.Log.Warn($"[HACK-JOB] AskChangeHackerName 解析失败(沿用旧代号): {ex.Message}");
}
name = name.Trim();
if (name.Length > 32)
name = name[..32];
// ⛑️ Always ack, even for an empty name — see the catch above. Without the empty OK the
// client's await never lands and the dialog stays up.
await conn.ReturnEmptyOkAsync(msg);
if (name.Length == 0)
{
msg.Context.Session.Log.Warn("[HACK-JOB] 收到空的黑客代号(忽略)");
return;
}
msg.Context.Session.Items[HackerHandleKey] = name;
msg.Context.Session.Log.Info($"[HACK-JOB] 黑客代号改为 '{name}' — 立即回推 SyncHackerJobInfo");
await RepublishHackerHandleAsync(msg.Context);
}
/// <summary>
/// Re-push the BBS backbone with whatever handle is stored. Without this the change only exists
/// server-side: nothing tells the client, so <c>HackerSpiritInfo[cardId]</c> stays empty and the
/// app reopens as a new user — back to the same dialog.
/// </summary>
async Task RepublishHackerHandleAsync(RpcContext ctx)
{
var templateId = GetWorldState(ctx).ActiveSpiritTemplateId;
await PublishHackerJobRuntime4229938(ctx, templateId, "hacker-rename",
ctx.Session.Items.TryGetValue(HackerHandleKey, out var stored) ? stored as string : null);
}
/// <summary>The EonBug handle to publish: the one the player picked, else the default.</summary>
static string HackerHandle4229938(TcpSession session)
=> session.Items.TryGetValue(HackerHandleKey, out var stored) && stored is string s && s.Length > 0
? s
: DefaultHackerHandle;
/// <summary>
/// The two remaining EonBug forum actions: read a new post (63150736) and accept a job from one
/// (63036377). Both return void per <c>DefaultReturnCatalog4229938</c>, so an empty OK is the
/// whole contract.
///
/// ⛑ The point of routing them is not content — the forum's post list already rides
/// SyncHackerJobInfo (64189208) from <c>GameRouter.Profiles.cs</c>. Both are INVOKEs, so with no
/// handler the client awaits a reply that never comes: tapping a post or 接差事 leaves the UI
/// armed forever, the same failure mode AskChangeHackerName used to have.
///
/// Nothing is persisted: the client commits the read/accept locally, and there is no server-side
/// job simulation to advance.
/// </summary>
[Handler(MethodId.AskReadHackerNewPost, HandlerPacketKind.Invoke)]
[Handler(MethodId.AskAcceptHackerPostTask, HandlerPacketKind.Invoke)]
private async Task EonBugForumAction(Connection conn, UxRpcMessage msg)
{
var action = msg.MethodId == MethodId.AskAcceptHackerPostTask ? "accept-post-task" : "read-new-post";
conn.Log.Info($"[HACK-JOB] {action} bytes={msg.Body.Length}");
await conn.ReturnEmptyOkAsync(msg);
}
}