Files
SVSimServer/SVSim.BattleEngine/Engine/Wizard/SelectSkinCardDialog.cs
gamer147 2d9a6eea4b engine cleanup passes 4-7 + multi-instancing ambient rip
Squashes 146 commits from battle-engine-extraction. Net: 2,045 files changed,
+11,896 / -158,687 lines. Ships engine passes 4-7 (dead-code cull, view-layer
stub, receive-path shrink) plus the Phase-5 AsyncLocal ambient deletion that
turns concurrent battles into a type-system property rather than a scope
contract.

## What landed

**Passes 4-7 (chunks 1-34):** Extended the Phase-4 const-false collapse into a
cascading cull across the skill graph, view layer, and receive-path periphery.
Six mode flags (IsWatchBattle/IsReplayBattle/IsAdmin/IsAdminWatch/IsPuzzleQuest/
IsAINetwork) became `const false`, every guarded block deleted. Field*.cs
subclass ctors + BackGroundBase + ObjectChecker culled to no-ops. Mulligan
family reworked to take a mgr param through IMulliganMgr.InitMulligan.
Emotion/Recovery/Resource clusters null-stubbed. Prediction/OperationSimulator/
skill filters converted from static ambient reads to per-mgr reads via
SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr / ins.BattleMgr / this.BattleMgr.

**Phase-5 ambient rip (chunks 35-47):** Deleted BattleAmbient / BattleAmbient-
Context / TestBattleScope in full. Every per-battle mutable slot now lives on
the mgr instance itself:
  mgr.InstanceIsForecast / InstanceIsRandomDraw / InstanceRecoveryInfo /
  InstanceViewerId / InstanceNetworkAgent / GameMgr
BattleManagerBase.GetIns() returns null unconditionally; the residual static
flags + 3 façades (Certification.ViewerId, Data.BattleRecoveryInfo,
ToolboxGame.RealTimeNetworkAgent) are null-tolerant defaults kept for the
handful of engine-internal readers that still reference their types. Zero
BattleAmbient references anywhere in engine + node + tests.

Added pre-seeded GameMgr ctor overload threaded through the mgr chain
(BattleManagerBase → SingleBattleMgr / NetworkBattleManagerBase → NetworkStandard-
BattleMgr → HeadlessBattleMgr / HeadlessNetworkBattleMgr). Fixtures build a
GameMgr, seed it via HeadlessEngineEnv.SeedCharaIds/SeedNetUser, and pass it
to the mgr's ctor — no ambient reach.

Node side (SVSim.BattleNode/SessionBattleEngine): _ctx replaced with a plain
GameMgr field; 34 `using var _ambient = BattleAmbient.Enter(_ctx)` scope wraps
ripped from every accessor and mutator; EngineGlobalInit.WirePerSessionGameMgr
takes GameMgr as a param and runs from SessionBattleEngine.SetupInternal
BEFORE mgr construction.

Test side: TestBattleScope deleted; 18 fixture [SetUp]s migrated to
`HeadlessEngineEnv.EnsureProcessGlobals()`; MultiInstanceEngineTests rewritten
around per-mgr construction (GetIns() → null is the pinned invariant).

## Regression fixes

- **chunk-48** (MulliganCtrl): chunk-35's `= null` stubs on card lookups broke
  the live receive-driven Deal path (BattlePlayerBase.DrawCard NRE'd downstream
  of NetworkPlayerMulliganCtrl.StartMulliganVfx). Restored the three lookups
  via `_battlePlayer.BattleMgr.GetBattleCardIdx`. Engine tests were satisfied
  by the WireMulliganPhase seam; unit tests exposed the live-path gap.

## Ship state

- SVSim.BattleEngine.Tests: 56/56 pass, 2 skip
- SVSim.UnitTests: 1554/1554 pass (was 1523/31-fail before chunk 48)
- Solution build: 0 source warnings (40 pre-existing NU1902 MessagePack CVEs
  in SVSim.EmulatedEntrypoint, unrelated)
- Sequential PVP smoke: verified live (two back-to-back battles, no regression
  on cleanup/spinup)
- Concurrent PVP smoke: verified live

Adds tools/engine-port/ClosureAnalyzer/ — the Roslyn transitive-type-closure
analyzer needed to make future cascade cleanup safe (per feedback memory
"Engine cleanup needs closure tool" from the 2026-06-28 pass-3 failure).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 19:18:54 -04:00

380 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cute;
using UnityEngine;
using Wizard.UI.Common;
namespace Wizard;
public class SelectSkinCardDialog : MonoBehaviour
{
[SerializeField]
private SimpleScrollViewUI _cardListScrollView;
[SerializeField]
private TabList _tabListClass;
[SerializeField]
private UISprite _spriteClassTab;
private List<string> _loadedResourceList = new List<string>();
private UIAtlas _atlasProfile;
private List<UIBase_CardManager.CardObjData> _cardObjectList;
[SerializeField]
private GameObject _cardObjectRoot;
[SerializeField]
private GameObject _cardDetailRoot;
private CardDetailUI _cardDetail;
private List<string> _loadedCardResourceList = new List<string>();
private int _cardDetailIndex;
[SerializeField]
private UIButton _btnExclude;
private DialogBase _dialog;
private CardBasePrm.ClanType _displayClassType = CardBasePrm.ClanType.MAX;
private Dictionary<CardBasePrm.ClanType, List<SelectSkinCardInfo>> _selectSkinCardListInClassDic = new Dictionary<CardBasePrm.ClanType, List<SelectSkinCardInfo>>();
private Dictionary<int, bool> _selectCardStateDict;
private bool _isAcquireSkinCardPack;
private Action<int, bool> _onClickAcquireButton;
public static void Create(Dictionary<CardBasePrm.ClanType, List<SelectSkinCardInfo>> selectSkinCardListInClassDic, Action<Dictionary<int, bool>> onClickOk, bool isDefaultExclude, bool isAcquireSkinCardPack, Action<int, bool> onClickAcquireButton)
{
DialogBase dialog = UIManager.GetInstance().CreateDialogClose();
(UnityEngine.Object.Instantiate(Resources.Load("UI/layoutParts/Dialog/SelectSkinCardDialog")) as GameObject).GetComponent<SelectSkinCardDialog>().Initialize(dialog, selectSkinCardListInClassDic, onClickOk, isDefaultExclude, isAcquireSkinCardPack, onClickAcquireButton);
}
public void Initialize(DialogBase dialog, Dictionary<CardBasePrm.ClanType, List<SelectSkinCardInfo>> selectSkinCardListInClassDic, Action<Dictionary<int, bool>> onClickOk, bool isDefaultExclude, bool isAcquireSkinCardPack, Action<int, bool> onClickAcquireButton)
{
_dialog = dialog;
_selectSkinCardListInClassDic = selectSkinCardListInClassDic;
_isAcquireSkinCardPack = isAcquireSkinCardPack;
_onClickAcquireButton = onClickAcquireButton;
InitDialog(onClickOk);
InitCardDetail();
if (_isAcquireSkinCardPack)
{
_btnExclude.gameObject.SetActive(value: false);
UIUtil.SetPositionY(_tabListClass.transform, -255f);
_cardListScrollView.SetScrollViewLayoutByAcquireSkinCardPack();
}
else
{
InitializeSelectSkinCardState(isDefaultExclude);
_btnExclude.onClick.Add(new EventDelegate(delegate
{
ExcludeSkinCardAlreadyHave();
}));
}
StartCoroutine(LoadInitialResources(delegate
{
InitClassTab();
}));
}
private void InitDialog(Action<Dictionary<int, bool>> onClickOk)
{
_dialog.SetSize(DialogBase.Size.XL);
_dialog.SetLayer("MyPage");
_dialog.SetPanelSortingOrder(37);
_dialog.SetPanelDepth(900);
if (_isAcquireSkinCardPack)
{
_dialog.SetTitleLabel(Data.SystemText.Get("Shop_0258"));
_dialog.SetButtonLayout(DialogBase.ButtonLayout.NONE);
}
else
{
_dialog.SetTitleLabel(Data.SystemText.Get("Shop_0194"));
_dialog.SetButtonLayout(DialogBase.ButtonLayout.OkBtn);
_dialog.SetButtonDelegate(delegate
{
onClickOk.Call(_selectCardStateDict);
});
}
_dialog.SetObj(base.gameObject);
}
private IEnumerator LoadInitialResources(Action onFinish)
{
UIManager.GetInstance().createInSceneCenterLoading();
yield return StartCoroutine(LoadProfileAtlas());
UIManager.GetInstance().closeInSceneCenterLoading();
onFinish.Call();
}
private IEnumerator LoadProfileAtlas()
{
string profileAtlasName = UIManager.GetInstance().GetSceneAssetPath(UIAtlasManager.AssetBundleNames.Profile, null);
yield return StartCoroutine(Toolbox.ResourcesManager.LoadAssetAsync(profileAtlasName, null));
_loadedResourceList.Add(profileAtlasName);
profileAtlasName = UIManager.GetInstance().GetSceneAssetPath(UIAtlasManager.AssetBundleNames.Profile, null, isload: true);
_atlasProfile = Toolbox.ResourcesManager.LoadObject<GameObject>(profileAtlasName).GetComponent<UIAtlas>();
}
private void InitCardDetail()
{
_cardDetail = DialogCreator.CreateCardDetailDialog(_cardDetailRoot, "Detail");
_cardDetail.OnDragCard = CardDetailDragCallback;
_cardDetail.OnDetailCardUpdate = UpdateCardDetailArrowButtonVisible;
_cardDetail.gameObject.SetActive(value: false);
}
private void CardDetailDragCallback(Vector2 vec)
{
if (!_cardDetail.IsEnableShowDetail)
{
return;
}
float x = vec.x;
if (!(Mathf.Abs(x) < 70f))
{
int num = _cardDetailIndex + ((!(x > 0f)) ? 1 : (-1));
if (num >= 0 && _cardObjectList.Count > num)
{
_cardDetail.CloseDefault(playSe: false);
_cardDetail.ShowCardDetail(_cardObjectList[num].CardObj);
_cardDetailIndex = num;
UpdateCardDetailArrowButtonVisible();
}
}
}
private void UpdateCardDetailArrowButtonVisible()
{
_cardDetail.LeftButtonVisible = _cardDetailIndex > 0;
_cardDetail.RightButtonVisible = _cardDetailIndex < _cardObjectList.Count - 1;
}
private IEnumerator LoadCardObject(List<int> cardIdList, Action onFinish)
{
if (cardIdList.Count == 0)
{
onFinish.Call();
yield break;
}
UnloadCardObject();
UIManager uiMgr = UIManager.GetInstance();
uiMgr.createInSceneCenterLoading();
bool isLoaded = false;
uiMgr.CardLoadSelect(null, cardIdList, base.gameObject.layer, is2D: true, delegate
{
isLoaded = true;
});
while (!isLoaded)
{
yield return null;
}
InitCardObject();
uiMgr.closeInSceneCenterLoading();
onFinish.Call();
}
private void InitCardObject()
{
List<UIBase_CardManager.CardObjData> cardList2DObjs = UIManager.GetInstance().getCardList2DObjs();
_cardObjectList = new List<UIBase_CardManager.CardObjData>(cardList2DObjs);
cardList2DObjs.Clear();
List<string> cardListAssetPathList = Toolbox.ResourcesManager.CardListAssetPathList;
_loadedCardResourceList.AddRange(new List<string>(cardListAssetPathList));
cardListAssetPathList.Clear();
if (_cardObjectList == null)
{
return;
}
for (int i = 0; i < _cardObjectList.Count; i++)
{
GameObject cardObject = _cardObjectList[i].CardObj;
cardObject.SetActive(value: false);
UITexture[] componentsInChildren = cardObject.GetComponentsInChildren<UITexture>();
foreach (UITexture uITexture in componentsInChildren)
{
if (uITexture.name.Contains("CardTexture"))
{
UITexture component = uITexture.GetComponent<UITexture>();
Material material = component.material;
component.mainTexture = material.mainTexture;
component.material = null;
}
}
cardObject.transform.parent = _cardObjectRoot.transform;
CardListTemplate component2 = cardObject.GetComponent<CardListTemplate>();
component2.HideNum();
component2._newLabel.gameObject.SetActive(value: false);
component2.SetId(_cardObjectList[i].ids);
component2.SetScale(0.36f);
component2.AddDepth(20);
int tempIndex = i;
component2.AddColliderToFrame(0.9f).onClick = delegate
{
_cardDetailIndex = tempIndex;
_cardDetail.OnPushCardDetailOn(cardObject);
};
}
}
private void UnloadCardObject()
{
Toolbox.ResourcesManager.RemoveAssetGroup(_loadedCardResourceList);
_loadedCardResourceList.Clear();
if (_cardObjectList != null)
{
for (int i = 0; i < _cardObjectList.Count; i++)
{
UnityEngine.Object.Destroy(_cardObjectList[i].CardObj.gameObject);
}
_cardObjectList.Clear();
}
}
private void InitClassTab()
{
_spriteClassTab.atlas = _atlasProfile;
int num = 0;
for (int i = 1; i < 9; i++)
{
CardBasePrm.ClanType classType = (CardBasePrm.ClanType)i;
string spriteBaseName = "class_tab_" + i.ToString("00");
_tabListClass.AddTab(delegate
{
if (classType != _displayClassType)
{
ShowCardList(classType);
}
}, spriteBaseName).name = "Class_" + i + "(Clone)";
if (_selectSkinCardListInClassDic[classType].Count == 0)
{
int num2 = i - 1;
_tabListClass.SetTabToGrayByIndex(num2, disable: true);
if (num == num2)
{
num++;
}
}
}
_tabListClass.Reset();
_tabListClass.SelectTabByIndex(num, isForceSet: true);
}
private void ShowCardList(CardBasePrm.ClanType classType)
{
_displayClassType = classType;
List<SelectSkinCardInfo> cardList = _selectSkinCardListInClassDic[_displayClassType];
_cardListScrollView.SetVisiable(isVisiable: false);
List<int> list = new List<int>(cardList.Count);
CardMaster instance = CardMaster.GetInstance(CardMaster.CardMasterId.Default);
for (int i = 0; i < cardList.Count; i++)
{
CardParameter cardParameterFromId = instance.GetCardParameterFromId(cardList[i].CardId);
list.Add(cardParameterFromId.ResourceCardId);
}
StartCoroutine(LoadCardObject(list, delegate
{
_cardListScrollView.SetVisiable(isVisiable: true);
_cardListScrollView.CreateScrollView(cardList.Count, InitializePlate);
}));
}
private void InitializePlate(int index, GameObject plate)
{
List<SelectSkinCardInfo> list = _selectSkinCardListInClassDic[_displayClassType];
if (index >= list.Count)
{
plate.SetActive(value: false);
}
else
{
plate.GetComponent<SelectSkinCardPlate>().SetData(list[index], OnClickSelectCardToggle, _selectCardStateDict, _cardObjectList[index].CardObj, _cardObjectRoot, _isAcquireSkinCardPack ? new Action<int, bool>(OnClickAcquireButton) : null);
}
}
private void InitializeSelectSkinCardState(bool isDefaultExclude)
{
_selectCardStateDict = new Dictionary<int, bool>();
for (CardBasePrm.ClanType clanType = CardBasePrm.ClanType.MIN; clanType < CardBasePrm.ClanType.MAX; clanType++)
{
List<SelectSkinCardInfo> list = _selectSkinCardListInClassDic[clanType];
for (int i = 0; i < list.Count; i++)
{
bool value = true;
if (isDefaultExclude)
{
value = !list[i].HasSkin;
}
_selectCardStateDict.Add(list[i].CardId, value);
}
}
UpdateDialogOkBtn();
UpdateExcludeBtn();
}
private void ExcludeSkinCardAlreadyHave()
{
for (CardBasePrm.ClanType clanType = CardBasePrm.ClanType.MIN; clanType < CardBasePrm.ClanType.MAX; clanType++)
{
List<SelectSkinCardInfo> list = _selectSkinCardListInClassDic[clanType];
for (int i = 0; i < list.Count; i++)
{
_selectCardStateDict[list[i].CardId] = !list[i].HasSkin;
}
}
List<SelectSkinCardPlate> list2 = _cardListScrollView.ActivePlateList.Select((GameObject p) => p.GetComponent<SelectSkinCardPlate>()).ToList();
for (int num = 0; num < list2.Count; num++)
{
list2[num].SetSelectStatus(_selectCardStateDict, isOnClickToggle: false);
}
UpdateDialogOkBtn();
UpdateExcludeBtn();
}
private void OnClickSelectCardToggle(int cardId, bool isSelect, SelectSkinCardPlate plate)
{
_selectCardStateDict[cardId] = isSelect;
plate.SetSelectStatus(_selectCardStateDict, isOnClickToggle: true);
UpdateDialogOkBtn();
UpdateExcludeBtn();
}
private void UpdateDialogOkBtn()
{
UIManager.SetObjectToGrey(_dialog.button1.gameObject, _selectCardStateDict.All((KeyValuePair<int, bool> x) => !x.Value));
}
private void UpdateExcludeBtn()
{
bool b = true;
for (CardBasePrm.ClanType clanType = CardBasePrm.ClanType.MIN; clanType < CardBasePrm.ClanType.MAX; clanType++)
{
if (_selectSkinCardListInClassDic[clanType].Any((SelectSkinCardInfo x) => x.HasSkin && _selectCardStateDict[x.CardId]))
{
b = false;
break;
}
}
UIManager.SetObjectToGrey(_btnExclude.gameObject, b);
}
private void OnClickAcquireButton(int cardId, bool hasSkin)
{
_onClickAcquireButton.Call(cardId, hasSkin);
_dialog.Close();
}
}