112 lines
4.7 KiB
C#
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);
|
|
}
|
|
}
|