This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.MobileSkin.cs
T

112 lines
4.7 KiB
C#

using Ananta.SDK.Rpc;
using Ananta.Server.ClientData.Client4229938;
using Ananta.Server.Handlers;
using Ananta.Server.Protocol.Client4229938;
using Auto = Ananta.Server.RpcTypes.Client4229938.Auto;
using GameMethods = Ananta.Server.RpcTypes.Client4229938.Methods.Game;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// Phone menu skin parts (手机壁纸 / 装饰 / 挂件).
///
/// The client's "apply" button only sends AskSetMobileSkinPart and then waits for the answer.
/// It never writes the worn suit locally: GameToClientImpl.SyncSpiritMobileSkinPartInfo is the
/// sole writer of SpiritInfo.MobileSkinInfo, and it is also what fires
/// ON_SYNC_SPIRIT_SKIN_PART_INFO_CHANGE so MainPhonePanelStore.OnSpiritSkinPartChange re-reads the
/// value. Because this method previously fell through to the typed-neutral fallback, the whole
/// sequence ended with "applied successfully" and an unchanged wallpaper: the preview closed, the
/// panel re-read the untouched login struct, and the wallpaper snapped back.
///
/// This handler stores the choice per character (and mirrors it to PlayerProgressStore) and replays
/// it as SyncSpiritMobileSkinPartInfo so the wallpaper actually changes — and stays changed across
/// re-logins, because the login payload now carries the stored suit.
///
/// The reply stays an empty 0-error body, byte-identical to what the fallback produced, so the
/// client's existing "close preview / show result toast" branch is untouched.
/// </summary>
internal sealed partial class GameRouter
{
[Handler(MethodId.AskSetMobileSkinPart, HandlerPacketKind.Invoke)]
private async Task AskSetMobileSkinPart4229938(Connection conn, UxRpcMessage msg)
{
if (!msg.TryGetArgs<GameMethods.AskSetMobileSkinPart>(out var args) || args is null)
{
conn.Log.Warn($"[PHONE-SKIN] AskSetMobileSkinPart undecodable bytes={msg.Body.Length}");
await conn.ReturnEmptyOkAsync(msg);
return;
}
await ApplyPhoneSkin4229938Async(
conn, msg,
args.wallpaper, args.decoration, args.pendant,
"set");
}
[Handler(MethodId.AskResetMobileSkinPart, HandlerPacketKind.Invoke)]
private Task AskResetMobileSkinPart4229938(Connection conn, UxRpcMessage msg)
=> ApplyPhoneSkin4229938Async(
conn, msg,
NpcCultivationCatalog4229938.DefaultWallpaperPart,
NpcCultivationCatalog4229938.DefaultDecorationPart,
NpcCultivationCatalog4229938.DefaultPendantPart,
"reset");
/// <summary>
/// Stores the worn suit for the character currently in play and echoes it back.
///
/// The spirit id must be a FightSpiritConfig template id, not a unit instance id: the client
/// looks the character up through SpiritManager.spiritViewDatas, which SyncPlayerInfoSpirit /
/// SyncPlayerAllSpirits key by SpiritInfo.TemplateId, and GetCurrentSpiritSkinInfo resolves the
/// same way through gBattleSpiritMgr.currentSpiritTemplateId (= InfoSpirit.ActiveSpirit).
/// </summary>
private async Task ApplyPhoneSkin4229938Async(
Connection conn,
UxRpcMessage msg,
uint wallpaper,
uint decoration,
uint pendant,
string reason)
{
var state = GetWorldState(msg.Context);
var spiritId = state.ActiveSpiritTemplateId;
lock (state.SyncRoot)
{
state.PhoneSkinBySpirit[spiritId] = new Auto.SpiritMobileSkinInfo
{
Wallpaper = wallpaper,
Decoration = decoration,
Pendant = pendant,
};
}
// Re-state the full part list so the packet is self-contained. It must NOT be empty: an
// empty Lua list is truthy and would wipe the phone's skin list. When the catalog is
// unavailable (no config folder) we pass null instead, which the client reads as
// "leave my list alone".
var parts = NpcCultivationCatalog4229938.SkinParts;
List<uint>? available = parts.Count > 0 ? parts.Select(part => part.Id).ToList() : null;
await conn.NotifyAsync(MethodId.SyncSpiritMobileSkinPartInfo, new GameMethods.SyncSpiritMobileSkinPartInfo
{
spiritId = spiritId,
mobileSkinInfo = new Auto.SpiritMobileSkinInfo
{
Wallpaper = wallpaper,
Decoration = decoration,
Pendant = pendant,
},
availableSkinParts = available,
});
PlayerProgressStore.Save(state, conn.Log);
conn.Log.Info(
$"[PHONE-SKIN] {reason} spirit={spiritId} wallpaper={wallpaper} " +
$"decoration={decoration} pendant={pendant} parts={available?.Count ?? -1}");
await conn.ReturnEmptyOkAsync(msg);
}
}