Commit 87790664 authored by ElderLich's avatar ElderLich

Feature: Online Accessories Sync for MDPro3 Clients

Implemented client-side online accessory sync over existing YGOPro chat flow, so MDPro3 players can see each other’s field/protector/grave/stand/mate and avatar icon/frame in rooms and duels without server changes. Added safe parsing/validation, override-vs-deck source handling, DIY icon support, seat/side mapping, room/duel apply paths, and lifecycle fixes to avoid stale deck data and late UI updates.
parent 4fa42886
...@@ -96,6 +96,10 @@ namespace MDPro3.Duel ...@@ -96,6 +96,10 @@ namespace MDPro3.Duel
&& File.Exists(Program.PATH_DECK + deckName + Program.EXPANSION_YDK)) && File.Exists(Program.PATH_DECK + deckName + Program.EXPANSION_YDK))
deck = new Deck(Program.PATH_DECK + deckName + Program.EXPANSION_YDK); deck = new Deck(Program.PATH_DECK + deckName + Program.EXPANSION_YDK);
var hasSide0Appearance = RoomServant.TryGetOnlineAppearanceForSide(0, out var side0Appearance);
var hasSide1Appearance = RoomServant.TryGetOnlineAppearanceForSide(1, out var side1Appearance);
var overrideDeckAppearance = Config.GetBool("OverrideDeckAppearance", false);
UIManager.UIBlackIn(Core.TransitionTime); UIManager.UIBlackIn(Core.TransitionTime);
await UniTask.WaitForSeconds(Core.TransitionTime); await UniTask.WaitForSeconds(Core.TransitionTime);
await UniTask.WaitUntil(() => Appearance.loaded); await UniTask.WaitUntil(() => Appearance.loaded);
...@@ -142,16 +146,21 @@ namespace MDPro3.Duel ...@@ -142,16 +146,21 @@ namespace MDPro3.Duel
var path = Program.items.GetAssetPath( var path = Program.items.GetAssetPath(
Config.Get(condition.ToString() + "Field0", Config.Get(condition.ToString() + "Field0",
Program.items.mats[0].id.ToString()), Items.ItemType.Mat); Program.items.mats[0].id.ToString()), Items.ItemType.Mat);
if (deck != null && !Config.GetBool("OverrideDeckAppearance", false)) if (condition == Condition.Watch && hasSide0Appearance)
path = Program.items.GetAssetPath(side0Appearance.Field.ToString(), Items.ItemType.Mat);
else if (deck != null && !overrideDeckAppearance)
path = Program.items.GetAssetPath(deck.Field.ToString(), Items.ItemType.Mat); path = Program.items.GetAssetPath(deck.Field.ToString(), Items.ItemType.Mat);
path = "MasterDuel/" + path; path = "MasterDuel/" + path;
var field0 = await ABLoader.LoadFromFileAsync(path + "_near", false, true); var field0 = await ABLoader.LoadFromFileAsync(path + "_near", false, true);
field0.transform.SetParent(Program.instance.container_3D, false); field0.transform.SetParent(Program.instance.container_3D, false);
field0Manager = field0.GetComponent<BgEffectManager>(); field0Manager = field0.GetComponent<BgEffectManager>();
var field1Config = Config.Get(condition.ToString() + "Field1",
Program.items.mats[0].id.ToString());
if (hasSide1Appearance)
field1Config = side1Appearance.Field.ToString();
var field1 = await ABLoader.LoadFromFileAsync("MasterDuel/" + var field1 = await ABLoader.LoadFromFileAsync("MasterDuel/" +
Program.items.GetAssetPath(Config.Get(condition.ToString() + "Field1", Program.items.GetAssetPath(field1Config, Items.ItemType.Mat, 1) + "_far"
Program.items.mats[0].id.ToString()), Items.ItemType.Mat, 1) + "_far"
, false, true); , false, true);
field1.transform.SetParent(Program.instance.container_3D, false); field1.transform.SetParent(Program.instance.container_3D, false);
field1Manager = field1.GetComponent<BgEffectManager>(); field1Manager = field1.GetComponent<BgEffectManager>();
...@@ -180,7 +189,9 @@ namespace MDPro3.Duel ...@@ -180,7 +189,9 @@ namespace MDPro3.Duel
path = Program.items.GetAssetPath( path = Program.items.GetAssetPath(
Config.Get(condition.ToString() + "Grave0", Config.Get(condition.ToString() + "Grave0",
Program.items.graves[0].id.ToString()), Items.ItemType.Grave); Program.items.graves[0].id.ToString()), Items.ItemType.Grave);
if (deck != null && !Config.GetBool("OverrideDeckAppearance", false)) if (condition == Condition.Watch && hasSide0Appearance)
path = Program.items.GetAssetPath(side0Appearance.Grave.ToString(), Items.ItemType.Grave);
else if (deck != null && !overrideDeckAppearance)
path = Program.items.GetAssetPath(deck.Grave.ToString(), Items.ItemType.Grave); path = Program.items.GetAssetPath(deck.Grave.ToString(), Items.ItemType.Grave);
path = "MasterDuel/" + path; path = "MasterDuel/" + path;
...@@ -188,9 +199,12 @@ namespace MDPro3.Duel ...@@ -188,9 +199,12 @@ namespace MDPro3.Duel
grave0.transform.SetParent(pos_Grave_near, false); grave0.transform.SetParent(pos_Grave_near, false);
grave0Manager = grave0.GetComponent<BgEffectManager>(); grave0Manager = grave0.GetComponent<BgEffectManager>();
var grave1Config = Config.Get(condition.ToString() + "Grave1",
Program.items.graves[0].id.ToString());
if (hasSide1Appearance)
grave1Config = side1Appearance.Grave.ToString();
var grave1 = await ABLoader.LoadFromFileAsync("MasterDuel/" + var grave1 = await ABLoader.LoadFromFileAsync("MasterDuel/" +
Program.items.GetAssetPath(Config.Get(condition.ToString() + "Grave1", Program.items.GetAssetPath(grave1Config, Items.ItemType.Grave, 1) + "_far"
Program.items.graves[0].id.ToString()), Items.ItemType.Grave, 1) + "_far"
, false, true); , false, true);
grave1.transform.SetParent(pos_Grave_far, false); grave1.transform.SetParent(pos_Grave_far, false);
grave1Manager = grave1.GetComponent<BgEffectManager>(); grave1Manager = grave1.GetComponent<BgEffectManager>();
...@@ -211,10 +225,12 @@ namespace MDPro3.Duel ...@@ -211,10 +225,12 @@ namespace MDPro3.Duel
#region Stand #region Stand
var standConfig = Config.Get(condition.ToString() + "Stand0", Program.items.stands[0].id.ToString()); var standConfig = Config.Get(condition.ToString() + "Stand0", Program.items.stands[0].id.ToString());
if (condition == Condition.Watch && hasSide0Appearance)
standConfig = side0Appearance.Stand.ToString();
if (standConfig != Items.CODE_NONE.ToString() || deck != null) if (standConfig != Items.CODE_NONE.ToString() || deck != null)
{ {
path = Program.items.GetAssetPath(standConfig, Items.ItemType.Stand); path = Program.items.GetAssetPath(standConfig, Items.ItemType.Stand);
if (deck != null && !Config.GetBool("OverrideDeckAppearance", false)) if (deck != null && !overrideDeckAppearance)
path = Program.items.GetAssetPath(deck.Stand.ToString(), Items.ItemType.Stand); path = Program.items.GetAssetPath(deck.Stand.ToString(), Items.ItemType.Stand);
path = "MasterDuel/" + path; path = "MasterDuel/" + path;
var stand0 = await ABLoader.LoadFromFileAsync(path + "_near", false, true); var stand0 = await ABLoader.LoadFromFileAsync(path + "_near", false, true);
...@@ -226,6 +242,8 @@ namespace MDPro3.Duel ...@@ -226,6 +242,8 @@ namespace MDPro3.Duel
} }
standConfig = Config.Get(condition.ToString() + "Stand1", Program.items.stands[0].id.ToString()); standConfig = Config.Get(condition.ToString() + "Stand1", Program.items.stands[0].id.ToString());
if (hasSide1Appearance)
standConfig = side1Appearance.Stand.ToString();
if (standConfig != Items.CODE_NONE.ToString()) if (standConfig != Items.CODE_NONE.ToString())
{ {
var stand1 = await ABLoader.LoadFromFileAsync("MasterDuel/" + var stand1 = await ABLoader.LoadFromFileAsync("MasterDuel/" +
...@@ -243,27 +261,25 @@ namespace MDPro3.Duel ...@@ -243,27 +261,25 @@ namespace MDPro3.Duel
#region Mate #region Mate
var mateConfig = Config.Get(condition.ToString() + "Mate0", Program.items.mates[0].id.ToString()); var mateConfig = Config.Get(condition.ToString() + "Mate0", Program.items.mates[0].id.ToString());
var overrideDeckAppearance = Config.GetBool("OverrideDeckAppearance", false); if (condition == Condition.Watch && hasSide0Appearance)
var mateConfigIsNone = mateConfig == Items.CODE_NONE.ToString(); mateConfig = side0Appearance.Mate.ToString();
if (!mateConfigIsNone || (deck != null && !overrideDeckAppearance)) int mateCode = int.Parse(mateConfig);
{ if (deck != null && !overrideDeckAppearance)
int mateCode = int.Parse(mateConfig); mateCode = deck.Mate;
if (deck != null && !overrideDeckAppearance && !mateConfigIsNone) if (mateCode != Items.CODE_NONE)
mateCode = deck.Mate; {
var mate = await ABLoader.LoadMateAsync(mateCode);
if (mateCode != Items.CODE_NONE) if (mate != null)
{ {
var mate = await ABLoader.LoadMateAsync(mateCode); mate0 = mate;
if (mate != null) mate0.parent = pos_Avatar_near;
{ mate0.gameObject.SetActive(false);
mate0 = mate;
mate0.parent = pos_Avatar_near;
mate0.gameObject.SetActive(false);
}
} }
} }
mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString()); mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString());
if (hasSide1Appearance)
mateConfig = side1Appearance.Mate.ToString();
if (mateConfig != Items.CODE_NONE.ToString()) if (mateConfig != Items.CODE_NONE.ToString())
{ {
var mate = await ABLoader.LoadMateAsync(int.Parse(mateConfig)); var mate = await ABLoader.LoadMateAsync(int.Parse(mateConfig));
...@@ -343,20 +359,44 @@ namespace MDPro3.Duel ...@@ -343,20 +359,44 @@ namespace MDPro3.Duel
#region Deck Model #region Deck Model
var deckMat = Appearance.duelProtector0; var deckMat = Appearance.duelProtector0;
if (deck != null && !Config.GetBool("OverrideDeckAppearance", false)) if (deck != null && !overrideDeckAppearance)
deckMat = await ABLoader.LoadProtectorMaterial(deck.Protector.ToString(), Application.exitCancellationToken); deckMat = await ABLoader.LoadProtectorMaterial(deck.Protector.ToString(), Application.exitCancellationToken);
if (condition == Condition.Duel) if (condition == Condition.Duel)
myProtector = deckMat; myProtector = deckMat;
else if (condition == Condition.Watch) else if (condition == Condition.Watch)
{
myProtector = Appearance.watchProtector0; myProtector = Appearance.watchProtector0;
if (hasSide0Appearance)
{
var mat = await ABLoader.LoadProtectorMaterial(side0Appearance.Protector.ToString(), Application.exitCancellationToken);
if (mat != null)
myProtector = mat;
}
}
else if (condition == Condition.Replay) else if (condition == Condition.Replay)
myProtector = Appearance.replayProtector0; myProtector = Appearance.replayProtector0;
if (condition == Condition.Duel) if (condition == Condition.Duel)
{
opProtector = Appearance.duelProtector1; opProtector = Appearance.duelProtector1;
if (hasSide1Appearance)
{
var mat = await ABLoader.LoadProtectorMaterial(side1Appearance.Protector.ToString(), Application.exitCancellationToken);
if (mat != null)
opProtector = mat;
}
}
else if (condition == Condition.Watch) else if (condition == Condition.Watch)
{
opProtector = Appearance.watchProtector1; opProtector = Appearance.watchProtector1;
if (hasSide1Appearance)
{
var mat = await ABLoader.LoadProtectorMaterial(side1Appearance.Protector.ToString(), Application.exitCancellationToken);
if (mat != null)
opProtector = mat;
}
}
else if (condition == Condition.Replay) else if (condition == Condition.Replay)
opProtector = Appearance.replayProtector1; opProtector = Appearance.replayProtector1;
......
This diff is collapsed.
fileFormatVersion: 2
guid: 2cbd8637a7f820c4caacc6106ddc0dbb
\ No newline at end of file
...@@ -270,6 +270,8 @@ namespace MDPro3 ...@@ -270,6 +270,8 @@ namespace MDPro3
} }
onDisConnected = false; onDisConnected = false;
tcpClient = null; tcpClient = null;
deck = null;
deckStrings.Clear();
canJoin = true; canJoin = true;
if (Program.instance.ocgcore.showing) if (Program.instance.ocgcore.showing)
{ {
...@@ -375,6 +377,7 @@ namespace MDPro3 ...@@ -375,6 +377,7 @@ namespace MDPro3
for (var i = 0; i < deckFor.Extra.Count; i++) message.Data.writer.Write(deckFor.Extra[i]); for (var i = 0; i < deckFor.Extra.Count; i++) message.Data.writer.Write(deckFor.Extra[i]);
for (var i = 0; i < deckFor.Side.Count; i++) message.Data.writer.Write(deckFor.Side[i]); for (var i = 0; i < deckFor.Side.Count; i++) message.Data.writer.Write(deckFor.Side[i]);
Send(message); Send(message);
CtosMessage_UpdateAppearance(deckFor);
} }
public static void CtosMessage_HandResult(int res) public static void CtosMessage_HandResult(int res)
...@@ -420,6 +423,7 @@ namespace MDPro3 ...@@ -420,6 +423,7 @@ namespace MDPro3
public static void CtosMessage_JoinGame(string psw) public static void CtosMessage_JoinGame(string psw)
{ {
deckStrings.Clear(); deckStrings.Clear();
deck = null;
var message = new Package(); var message = new Package();
message.Function = (int)CtosMessage.JoinGame; message.Function = (int)CtosMessage.JoinGame;
message.Data.writer.Write((short)Config.ClientVersion); message.Data.writer.Write((short)Config.ClientVersion);
...@@ -432,6 +436,8 @@ namespace MDPro3 ...@@ -432,6 +436,8 @@ namespace MDPro3
public static void CtosMessage_LeaveGame() public static void CtosMessage_LeaveGame()
{ {
deck = null;
deckStrings.Clear();
var message = new Package(); var message = new Package();
message.Function = (int)CtosMessage.LeaveGame; message.Function = (int)CtosMessage.LeaveGame;
Send(message); Send(message);
...@@ -459,6 +465,30 @@ namespace MDPro3 ...@@ -459,6 +465,30 @@ namespace MDPro3
Send(message); Send(message);
} }
public static void CtosMessage_UpdateAppearance(Deck deckFor)
{
if (deckFor == null)
return;
var syncMessage = OnlineAppearanceSync.BuildMessageForLocalPlayer(deckFor);
CtosMessage_Chat(syncMessage);
Debug.Log($"[OnlineAppearance] Sent sync payload: {syncMessage}");
}
public static void CtosMessage_UpdateAppearanceFromCurrentDeck()
{
if (deck != null)
{
CtosMessage_UpdateAppearance(deck);
return;
}
var deckPath = Program.PATH_DECK + Config.GetConfigDeckName() + Program.EXPANSION_YDK;
if (!File.Exists(deckPath))
return;
CtosMessage_UpdateAppearance(new Deck(deckPath));
}
public static void CtosMessage_HsToDuelist() public static void CtosMessage_HsToDuelist()
{ {
var message = new Package(); var message = new Package();
......
...@@ -1498,6 +1498,8 @@ namespace MDPro3.Servant ...@@ -1498,6 +1498,8 @@ namespace MDPro3.Servant
GetUI<OcgCoreUI>().AvatarPlayer1.material = Appearance.duelFrameMat1Tag; GetUI<OcgCoreUI>().AvatarPlayer1.material = Appearance.duelFrameMat1Tag;
SetFaceWhenCharaOff(Appearance.duelFace1Tag, 1); SetFaceWhenCharaOff(Appearance.duelFace1Tag, 1);
} }
_ = ApplyOnlineOpponentFaceAsync();
} }
else if (condition == Condition.Watch) else if (condition == Condition.Watch)
{ {
...@@ -1549,6 +1551,49 @@ namespace MDPro3.Servant ...@@ -1549,6 +1551,49 @@ namespace MDPro3.Servant
_ = SetMyCardFace(); _ = SetMyCardFace();
} }
private async UniTask ApplyOnlineOpponentFaceAsync()
{
if (condition != Condition.Duel)
return;
var useTagOpponent = isTag && GetUI<OcgCoreUI>().TextPlayer1Name.text != name_1;
if (!RoomServant.TryGetOnlineAppearanceForOpponent(useTagOpponent, out var appearance))
{
if (!useTagOpponent || !RoomServant.TryGetOnlineAppearanceForOpponent(false, out appearance))
return;
}
var iconPlayer = useTagOpponent ? 3 : 1;
var frameCode = appearance.Frame.ToString();
var frameSprite = await Program.items.LoadConcreteItemIconAsync(frameCode, Items.ItemType.Frame, iconPlayer);
Material frameMaterial;
if (appearance.Frame == Items.CODE_DIY)
frameMaterial = Appearance.matForFace == null ? null : new Material(Appearance.matForFace);
else
frameMaterial = await ABLoader.LoadFrameMaterial(frameCode);
if (frameMaterial != null && frameSprite != null)
frameMaterial.SetTexture("_ProfileFrameTex", frameSprite.texture);
var faceSprite = await Program.items.LoadConcreteItemIconAsync(appearance.Face.ToString(), Items.ItemType.Face, iconPlayer);
var latestUseTagOpponent = isTag && GetUI<OcgCoreUI>().TextPlayer1Name.text != name_1;
if (latestUseTagOpponent != useTagOpponent)
return;
if (!RoomServant.TryGetOnlineAppearanceForOpponent(useTagOpponent, out var latest))
{
if (!useTagOpponent || !RoomServant.TryGetOnlineAppearanceForOpponent(false, out latest))
return;
}
if (latest.Face != appearance.Face || latest.Frame != appearance.Frame)
return;
if (frameMaterial != null)
GetUI<OcgCoreUI>().AvatarPlayer1.material = frameMaterial;
if (faceSprite != null)
SetFaceWhenCharaOff(faceSprite, 1);
}
private async UniTask SetMyCardFace() private async UniTask SetMyCardFace()
{ {
if (MyCard.account == null || !mycardDuel) if (MyCard.account == null || !mycardDuel)
......
...@@ -47,6 +47,7 @@ namespace MDPro3.Servant ...@@ -47,6 +47,7 @@ namespace MDPro3.Servant
public bool ready; public bool ready;
} }
public static Player[] players = new Player[32]; public static Player[] players = new Player[32];
private static readonly Dictionary<int, OnlineAppearanceData> onlineAppearances = new();
private Deck deck; private Deck deck;
...@@ -88,6 +89,7 @@ namespace MDPro3.Servant ...@@ -88,6 +89,7 @@ namespace MDPro3.Servant
if (FromLocalHost) if (FromLocalHost)
YgoServer.StopServer(); YgoServer.StopServer();
} }
onlineAppearances.Clear();
base.OnExit(); base.OnExit();
Program.instance.ocgcore.CloseConnection(); Program.instance.ocgcore.CloseConnection();
} }
...@@ -136,6 +138,77 @@ namespace MDPro3.Servant ...@@ -136,6 +138,77 @@ namespace MDPro3.Servant
return true; return true;
} }
public static bool TryGetOnlineAppearanceForSide(int side, out OnlineAppearanceData appearance)
{
appearance = default;
if (!TryGetTeamPlayer(side, false, out var player))
return false;
return onlineAppearances.TryGetValue(player, out appearance);
}
public static bool TryGetOnlineAppearanceForOpponent(bool tag, out OnlineAppearanceData appearance)
{
appearance = default;
if (!TryGetTeamPlayer(1, tag, out var player))
return false;
return onlineAppearances.TryGetValue(player, out appearance);
}
public static bool TryGetOnlineAppearanceForPlayer(int player, out OnlineAppearanceData appearance)
{
appearance = default;
if (player < 0 || player >= 4)
return false;
return onlineAppearances.TryGetValue(player, out appearance);
}
private static bool TryGetTeamPlayer(int side, bool tag, out int player)
{
player = -1;
if (side != 0 && side != 1)
return false;
if (Mode != 2)
{
if (tag)
return false;
if (SelfType == 7)
player = side;
else
player = side == 0 ? SelfType : 1 - SelfType;
return player >= 0 && player < 4;
}
var hostTeamAsSide0 = SelfType == 7 || SelfType < 2;
var useHostTeam = hostTeamAsSide0 ? side == 0 : side == 1;
if (useHostTeam)
player = tag ? 1 : 0;
else
player = tag ? 3 : 2;
return true;
}
private static void MoveOnlineAppearance(int from, int to)
{
if (onlineAppearances.TryGetValue(from, out var appearance))
{
onlineAppearances[to] = appearance;
onlineAppearances.Remove(from);
}
else
{
onlineAppearances.Remove(to);
}
}
private static void RemoveOnlineAppearance(int player)
{
onlineAppearances.Remove(player);
}
private void ShowOcgCore() private void ShowOcgCore()
{ {
if(CoreShowing == 0) if(CoreShowing == 0)
...@@ -415,6 +488,7 @@ namespace MDPro3.Servant ...@@ -415,6 +488,7 @@ namespace MDPro3.Servant
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
players[i] = null; players[i] = null;
onlineAppearances.Clear();
if(!FromHandTest) if(!FromHandTest)
Program.instance.ShiftToServant(Program.instance.room); Program.instance.ShiftToServant(Program.instance.room);
...@@ -427,6 +501,8 @@ namespace MDPro3.Servant ...@@ -427,6 +501,8 @@ namespace MDPro3.Servant
IsHost = ((type >> 4) & 0xF) != 0; IsHost = ((type >> 4) & 0xF) != 0;
if (SelfType < 4 && players[SelfType] != null) if (SelfType < 4 && players[SelfType] != null)
players[SelfType].ready = false; players[SelfType].ready = false;
if (SelfType < 4)
TcpHelper.CtosMessage_UpdateAppearanceFromCurrentDeck();
Realize(); Realize();
} }
...@@ -469,8 +545,23 @@ namespace MDPro3.Servant ...@@ -469,8 +545,23 @@ namespace MDPro3.Servant
public void StocMessage_Chat(BinaryReader r) public void StocMessage_Chat(BinaryReader r)
{ {
int player = r.ReadInt16(); int player = r.ReadInt16();
var length = r.BaseStream.Length - 3; var length = (int)((r.BaseStream.Length - r.BaseStream.Position) / 2);
var content = r.ReadUnicode((int)length); var content = r.ReadUnicode((int)length);
if (OnlineAppearanceSync.IsSyncMessage(content))
{
if (player >= 0 && player < 4 && OnlineAppearanceSync.TryParse(content, out var appearance))
{
onlineAppearances[player] = appearance;
if (showing)
Realize();
Debug.Log($"[OnlineAppearance] Received sync from seat {player}: {appearance.Case},{appearance.Protector},{appearance.Field},{appearance.Grave},{appearance.Stand},{appearance.Mate},{appearance.Face},{appearance.Frame}");
}
else
{
Debug.LogWarning($"[OnlineAppearance] Ignored sync chat. seat={player}, content='{content}'");
}
return;
}
//Debug.Log($"{player}: {content} {OcgCore.isFirst} {RoomServant.SelfType}"); //Debug.Log($"{player}: {content} {OcgCore.isFirst} {RoomServant.SelfType}");
Program.instance.ui_.chatPanel.AddChatItem(player, content); Program.instance.ui_.chatPanel.AddChatItem(player, content);
} }
...@@ -485,6 +576,9 @@ namespace MDPro3.Servant ...@@ -485,6 +576,9 @@ namespace MDPro3.Servant
player.name = name; player.name = name;
player.ready = false; player.ready = false;
players[pos] = player; players[pos] = player;
RemoveOnlineAppearance(pos);
if (SelfType < 4 && players[SelfType] != null && pos != SelfType)
TcpHelper.CtosMessage_UpdateAppearanceFromCurrentDeck();
Realize(); Realize();
} }
...@@ -499,16 +593,21 @@ namespace MDPro3.Servant ...@@ -499,16 +593,21 @@ namespace MDPro3.Servant
{ {
players[state] = players[pos]; players[state] = players[pos];
players[pos] = null; players[pos] = null;
MoveOnlineAppearance(pos, state);
} }
if (state == 0x9) if (state == 0x9)
players[pos].ready = true; players[pos].ready = true;
if (state == 0xA) if (state == 0xA)
players[pos].ready = false; players[pos].ready = false;
if (state == 0xB) if (state == 0xB)
{
players[pos] = null; players[pos] = null;
RemoveOnlineAppearance(pos);
}
if (state == 0x8) if (state == 0x8)
{ {
players[pos] = null; players[pos] = null;
RemoveOnlineAppearance(pos);
ObserverCount++; ObserverCount++;
} }
Realize(); Realize();
......
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
...@@ -62,63 +63,82 @@ namespace MDPro3.UI ...@@ -62,63 +63,82 @@ namespace MDPro3.UI
public void Refresh() public void Refresh()
{ {
_ = RefreshAsync(); _ = RefreshAsync(this.GetCancellationTokenOnDestroy());
} }
private async UniTask RefreshAsync() private async UniTask RefreshAsync(CancellationToken cancellationToken)
{ {
for (int i = 0; i < index; i++) try
await UniTask.Yield();
if (path.StartsWith("Protector"))
{
protectorMaterial = await ABLoader.LoadProtectorMaterial(itemID.ToString(), destroyCancellationToken);
if (protectorMaterial != null)
protectorMaterial.renderQueue = 3000;
// Use default UI material for list rendering so viewport/mask clipping works while scrolling.
Protector.texture = protectorMaterial == null ? null : protectorMaterial.mainTexture;
Protector.material = null;
Protector.color = Color.white;
Icon.gameObject.SetActive(false);
}
else if (path.Length > 0)
{ {
Icon.sprite = await Program.items.LoadItemIconAsync(itemID.ToString(), Items.ItemType.Unknown); for (int i = 0; i < index; i++)
if (Manager == null) await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken);
if (cancellationToken.IsCancellationRequested || this == null)
return; return;
Icon.color = Color.white;
if (path.StartsWith("ProfileFrame")) if (path.StartsWith("Protector"))
{ {
Icon.rectTransform.localScale = Vector3.one * 0.8f; protectorMaterial = await ABLoader.LoadProtectorMaterial(itemID.ToString(), cancellationToken);
Icon.material = await ABLoader.LoadFrameMaterial(itemID.ToString()); if (protectorMaterial != null)
Icon.material.SetTexture("_ProfileFrameTex", Icon.sprite.texture); protectorMaterial.renderQueue = 3000;
Icon.sprite = TextureManager.container.black;
Icon.color = Color.white; if (cancellationToken.IsCancellationRequested || this == null)
return;
// Use default UI material for list rendering so viewport/mask clipping works while scrolling.
Protector.texture = protectorMaterial == null ? null : protectorMaterial.mainTexture;
Protector.material = null;
Protector.color = Color.white;
Icon.gameObject.SetActive(false);
} }
else if (path.StartsWith("DeckCase")) else if (path.Length > 0)
{ {
Icon.transform.localPosition = new Vector3(0f, 15f, 0f); Icon.sprite = await Program.items.LoadItemIconAsync(itemID.ToString(), Items.ItemType.Unknown);
if (cancellationToken.IsCancellationRequested || this == null || Manager == null)
return;
Icon.color = Color.white;
if (path.StartsWith("ProfileFrame"))
{
Icon.rectTransform.localScale = Vector3.one * 0.8f;
Icon.material = await ABLoader.LoadFrameMaterial(itemID.ToString());
if (cancellationToken.IsCancellationRequested || this == null)
return;
Icon.material.SetTexture("_ProfileFrameTex", Icon.sprite.texture);
Icon.sprite = TextureManager.container.black;
Icon.color = Color.white;
}
else if (path.StartsWith("DeckCase"))
{
Icon.transform.localPosition = new Vector3(0f, 15f, 0f);
}
else if (path.StartsWith("WallPaperIcon"))
{
WallpaperBG.gameObject.SetActive(true);
}
Protector.gameObject.SetActive(false);
} }
else if (path.StartsWith("WallPaperIcon")) else //CrossDuel Mate
{ {
WallpaperBG.gameObject.SetActive(true); var art = await CardImageLoader.LoadArtAsync(itemID, true, cancellationToken);
if (cancellationToken.IsCancellationRequested || this == null)
return;
Icon.color = Color.white;
Icon.sprite = TextureManager.Texture2Sprite(art);
Protector.gameObject.SetActive(false);
} }
Protector.gameObject.SetActive(false);
if (path.StartsWith("ProfileIcon") && !cancellationToken.IsCancellationRequested && this != null)
Icon.material = Appearance.matForFace;
loaded = true;
} }
else //CrossDuel Mate catch (OperationCanceledException)
{ {
var art = await CardImageLoader.LoadArtAsync(itemID, true, destroyCancellationToken);
Icon.color = Color.white;
Icon.sprite = TextureManager.Texture2Sprite(art);
Protector.gameObject.SetActive(false);
} }
finally
if (path.StartsWith("ProfileIcon")) {
Icon.material = Appearance.matForFace; refreshCoroutine = null;
}
loaded = true;
refreshCoroutine = null;
} }
protected override void CallHoverOnEvent() protected override void CallHoverOnEvent()
...@@ -202,6 +222,14 @@ namespace MDPro3.UI ...@@ -202,6 +222,14 @@ namespace MDPro3.UI
Config.Set("Wallpaper", itemID.ToString()); Config.Set("Wallpaper", itemID.ToString());
else else
Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player, itemID.ToString()); Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player, itemID.ToString());
if (Appearance.condition == Appearance.Condition.Duel
&& Appearance.player == "0"
&& Config.GetBool("OverrideDeckAppearance", false)
&& Program.instance.room != null
&& Program.instance.room.showing
&& RoomServant.SelfType < 4)
TcpHelper.CtosMessage_UpdateAppearanceFromCurrentDeck();
} }
StartCoroutine(ConfigSetAsync()); StartCoroutine(ConfigSetAsync());
......
...@@ -3,8 +3,10 @@ using MDPro3.Servant; ...@@ -3,8 +3,10 @@ using MDPro3.Servant;
using MDPro3.Utility; using MDPro3.Utility;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Cysharp.Threading.Tasks;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
using static MDPro3.UI.ChatPanel; using static MDPro3.UI.ChatPanel;
namespace MDPro3.UI.ServantUI namespace MDPro3.UI.ServantUI
...@@ -83,6 +85,10 @@ namespace MDPro3.UI.ServantUI ...@@ -83,6 +85,10 @@ namespace MDPro3.UI.ServantUI
#endregion #endregion
private List<SelectionButton_RoomPlayer> roomPlayers; private List<SelectionButton_RoomPlayer> roomPlayers;
private readonly Dictionary<int, Sprite> syncedFaces = new();
private readonly Dictionary<int, Material> syncedFrames = new();
private readonly Dictionary<int, int> syncedFaceIds = new();
private readonly Dictionary<int, int> syncedFrameIds = new();
private void Awake() private void Awake()
{ {
...@@ -197,6 +203,7 @@ namespace MDPro3.UI.ServantUI ...@@ -197,6 +203,7 @@ namespace MDPro3.UI.ServantUI
roomPlayers[i].GetAvatar().sprite = Appearance.watchFace1Tag; roomPlayers[i].GetAvatar().sprite = Appearance.watchFace1Tag;
break; break;
} }
TryApplyOnlineAvatar(i);
} }
} }
if (RoomServant.IsHost) if (RoomServant.IsHost)
...@@ -285,5 +292,79 @@ namespace MDPro3.UI.ServantUI ...@@ -285,5 +292,79 @@ namespace MDPro3.UI.ServantUI
Program.instance.ShiftToServant(Program.instance.deckSelector); Program.instance.ShiftToServant(Program.instance.deckSelector);
} }
private void TryApplyOnlineAvatar(int player)
{
if (!RoomServant.TryGetOnlineAppearanceForPlayer(player, out var appearance))
return;
var iconPlayer = GetIconPlayerIndex(GetPlayerPosition(player));
if (syncedFaceIds.TryGetValue(player, out var cachedFaceId) &&
syncedFrameIds.TryGetValue(player, out var cachedFrameId) &&
cachedFaceId == appearance.Face &&
cachedFrameId == appearance.Frame &&
syncedFaces.TryGetValue(player, out var cachedFace) &&
syncedFrames.TryGetValue(player, out var cachedFrame) &&
cachedFace != null &&
cachedFrame != null)
{
roomPlayers[player].GetAvatar().sprite = cachedFace;
roomPlayers[player].GetAvatar().material = cachedFrame;
return;
}
_ = ApplyOnlineAvatarAsync(player, appearance, iconPlayer);
}
private async UniTask ApplyOnlineAvatarAsync(int player, OnlineAppearanceData appearance, int iconPlayer)
{
var frameCode = appearance.Frame.ToString();
var frameSprite = await Program.items.LoadConcreteItemIconAsync(frameCode, Items.ItemType.Frame, iconPlayer);
Material frameMaterial;
if (appearance.Frame == Items.CODE_DIY)
frameMaterial = Appearance.matForFace == null ? null : new Material(Appearance.matForFace);
else
frameMaterial = await ABLoader.LoadFrameMaterial(frameCode);
if (frameMaterial != null && frameSprite != null)
frameMaterial.SetTexture("_ProfileFrameTex", frameSprite.texture);
var faceSprite = await Program.items.LoadConcreteItemIconAsync(appearance.Face.ToString(), Items.ItemType.Face, iconPlayer);
if (!RoomServant.TryGetOnlineAppearanceForPlayer(player, out var latest))
return;
if (latest.Face != appearance.Face || latest.Frame != appearance.Frame)
return;
if (player < 0 || player >= roomPlayers.Count)
return;
if (!roomPlayers[player].gameObject.activeInHierarchy)
return;
if (frameMaterial != null)
roomPlayers[player].GetAvatar().material = frameMaterial;
if (faceSprite != null)
roomPlayers[player].GetAvatar().sprite = faceSprite;
syncedFaceIds[player] = appearance.Face;
syncedFrameIds[player] = appearance.Frame;
syncedFaces[player] = faceSprite;
syncedFrames[player] = frameMaterial;
}
private static int GetIconPlayerIndex(PlayerPosition position)
{
switch (position)
{
case PlayerPosition.Op:
case PlayerPosition.WatchOp:
return 1;
case PlayerPosition.MyTag:
case PlayerPosition.WatchMyTag:
return 2;
case PlayerPosition.OpTag:
case PlayerPosition.WatchOpTag:
return 3;
default:
return 0;
}
}
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment