Files
SVSimServer/SVSim.BattleEngine/Engine/Wizard/PurchaseRewardDialog.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

405 lines
13 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using Cute;
using UnityEngine;
namespace Wizard;
public class PurchaseRewardDialog : MonoBehaviour
{
public enum Layout
{
NORMAL,
TITLE_BOTTOM,
TITLE_BOTTOM_LARGE_FONT
}
private readonly Vector3 CARDOBJECT_COLLIDER_SIZE = new Vector3(175f, 230f, 1f);
[SerializeField]
private UIGrid _itemGrid;
[SerializeField]
private GameObject _purchaseRewardItemOriginal;
[SerializeField]
private int _rewardNumParPage;
[SerializeField]
private UIButton _btnPrevPage;
[SerializeField]
private UIButton _btnNextPage;
[SerializeField]
private BoxCollider _flickCollider;
[SerializeField]
private UIPageIndicator _indicator;
[SerializeField]
private GameObject _centerLine;
[SerializeField]
private GameObject _attachPointBottom;
private int _currentPageIndex = 1;
private int _lastPageIndex = 1;
private bool _flickStart;
private List<GameObject> _rewardObjectsList = new List<GameObject>();
private List<UIBase_CardManager.CardObjData> _cardObjList;
private CardDetailUI _cardDetail;
protected List<string> _loadedResourceList = new List<string>();
private string _detailDialogTitleOverride;
private DialogBase _dialog;
private Layout _layout;
private bool IsFirstPage => _currentPageIndex <= 1;
private bool IsLastPage => _currentPageIndex >= _lastPageIndex;
private static string GetPrefabPath(List<PurchaseRewardInfo> purchaseRewardsList, Layout layout)
{
switch (layout)
{
case Layout.TITLE_BOTTOM_LARGE_FONT:
return "UI/layoutParts/Dialog/PurchaseRewardDialogTitleBottomLargeFont";
case Layout.TITLE_BOTTOM:
if (purchaseRewardsList.Count < 10)
{
return "UI/layoutParts/Dialog/PurchaseRewardDialogTitleBottom";
}
return "UI/layoutParts/Dialog/PurchaseRewardDialog10TitleBottom";
default:
if (purchaseRewardsList.Count < 10)
{
return "UI/layoutParts/Dialog/PurchaseRewardDialog";
}
return "UI/layoutParts/Dialog/PurchaseRewardDialog10";
}
}
public static DialogBase Create(List<PurchaseRewardInfo> purchaseRewardsList, CardDetailUI cardDetail, bool useLargeDetailDialog, Layout layout, GameObject attachBottomObj = null, string detailDialogTitleOverride = null, bool isPaging = false)
{
PurchaseRewardDialog component = (UnityEngine.Object.Instantiate(Resources.Load(GetPrefabPath(purchaseRewardsList, layout))) as GameObject).GetComponent<PurchaseRewardDialog>();
component.CreateDialog();
component.Initialize(purchaseRewardsList, cardDetail, useLargeDetailDialog, attachBottomObj, detailDialogTitleOverride, isPaging, layout);
return component._dialog;
}
private void CreateDialog()
{
_dialog = UIManager.GetInstance().CreateDialogClose();
_dialog.SetSize(DialogBase.Size.M);
_dialog.SetTitleLabel(Data.SystemText.Get("Shop_0142"));
_dialog.SetButtonLayout(DialogBase.ButtonLayout.CloseBtn);
_dialog.SetObj(base.gameObject);
}
private void Initialize(List<PurchaseRewardInfo> purchaseRewardsList, CardDetailUI cardDetail, bool useLargeDetailDialog, GameObject attachBottomObj, string detailDialogTitleOverride, bool isPaging, Layout layout)
{
_detailDialogTitleOverride = detailDialogTitleOverride;
_layout = layout;
if (attachBottomObj != null)
{
attachBottomObj.transform.parent = _attachPointBottom.transform;
attachBottomObj.transform.localPosition = Vector3.zero;
attachBottomObj.transform.localScale = Vector3.one;
}
_cardDetail = cardDetail;
InitializePage(purchaseRewardsList);
UIManager.GetInstance().createInSceneCenterLoading();
StartCoroutine(LoadResources(purchaseRewardsList, delegate
{
SetRewards(purchaseRewardsList, useLargeDetailDialog, isPaging);
ShowPage(_currentPageIndex);
UIManager.GetInstance().closeInSceneCenterLoading();
}));
}
private void SetRewards(List<PurchaseRewardInfo> purchaseRewardsList, bool useLargeDetailDialog, bool isPaging)
{
int count = purchaseRewardsList.Count;
for (int i = 0; i < count; i++)
{
GameObject gameObject = NGUITools.AddChild(_itemGrid.gameObject, _purchaseRewardItemOriginal.gameObject);
PurchaseRewardInfo purchaseRewardInfo = purchaseRewardsList[i];
ShopCommonRewardInfo shopCommonRewardInfo = purchaseRewardInfo.RewardInfoList[0];
PurchaseRewardItem component = gameObject.GetComponent<PurchaseRewardItem>();
if (shopCommonRewardInfo.Type == 5)
{
GameObject cardObj = GetCardObj(shopCommonRewardInfo.UserGoodsId);
component.SetUserGoods(purchaseRewardInfo, useLargeDetailDialog, cardObj, isPaging, isEnableItemNumber: true);
}
else
{
component.SetUserGoods(purchaseRewardInfo, useLargeDetailDialog, null, isPaging, _layout != Layout.TITLE_BOTTOM_LARGE_FONT);
}
component.DetailDialogTitleOverride = _detailDialogTitleOverride;
_rewardObjectsList.Add(gameObject);
}
if (count <= 4)
{
float scale = ((count <= 3) ? 1.5f : 1.25f);
foreach (GameObject rewardObjects in _rewardObjectsList)
{
rewardObjects.GetComponent<PurchaseRewardItem>().SetScale(scale);
}
if (count <= 3)
{
float num = ((count == 3) ? 1.25f : 1.5f);
_itemGrid.cellWidth *= num;
_itemGrid.cellHeight *= num;
}
}
if (_layout == Layout.TITLE_BOTTOM_LARGE_FONT)
{
_itemGrid.cellWidth = 275f;
}
}
private void InitializePage(List<PurchaseRewardInfo> rewardsList)
{
_lastPageIndex = (rewardsList.Count - 1) / _rewardNumParPage + 1;
_currentPageIndex = _lastPageIndex;
for (int i = 0; i < rewardsList.Count; i++)
{
if (!rewardsList[i].IsGet)
{
_currentPageIndex = i / _rewardNumParPage + 1;
break;
}
}
_indicator.Init(_lastPageIndex);
if (_lastPageIndex > 1)
{
_flickCollider.gameObject.SetActive(value: true);
UIEventListener uIEventListener = UIEventListener.Get(_flickCollider.gameObject);
uIEventListener.onDragStart = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener.onDragStart, new UIEventListener.VoidDelegate(OnDragStart));
UIEventListener uIEventListener2 = UIEventListener.Get(_flickCollider.gameObject);
uIEventListener2.onDrag = (UIEventListener.VectorDelegate)Delegate.Combine(uIEventListener2.onDrag, new UIEventListener.VectorDelegate(OnDrag));
UIEventListener uIEventListener3 = UIEventListener.Get(_btnPrevPage.gameObject);
uIEventListener3.onClick = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener3.onClick, (UIEventListener.VoidDelegate)delegate
{
ShowPrevPage();
});
UIEventListener uIEventListener4 = UIEventListener.Get(_btnNextPage.gameObject);
uIEventListener4.onClick = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener4.onClick, (UIEventListener.VoidDelegate)delegate
{
ShowNextPage();
});
}
if (rewardsList.Count <= 4)
{
_itemGrid.transform.localPosition = Vector3.zero;
_itemGrid.pivot = UIWidget.Pivot.Center;
_centerLine.SetActive(value: false);
}
}
private void ShowPage(int pageIndex)
{
_currentPageIndex = pageIndex;
for (int i = 0; i < _rewardObjectsList.Count; i++)
{
_rewardObjectsList[i].gameObject.SetActive(value: false);
}
int num = (_currentPageIndex - 1) * _rewardNumParPage;
int num2 = Mathf.Min(num + _rewardNumParPage, _rewardObjectsList.Count);
for (int j = num; j < num2; j++)
{
_rewardObjectsList[j].gameObject.SetActive(value: true);
}
_itemGrid.Reposition();
_btnPrevPage.gameObject.SetActive(!IsFirstPage);
_btnNextPage.gameObject.SetActive(!IsLastPage);
_indicator.UpdateIndicator(_currentPageIndex);
}
private void ShowPrevPage()
{
if (!IsFirstPage)
{
ShowPage(_currentPageIndex - 1);
}
}
private void ShowNextPage()
{
if (!IsLastPage)
{
ShowPage(_currentPageIndex + 1);
}
}
private void OnDragStart(GameObject obj)
{
_flickStart = true;
}
private void OnDrag(GameObject obj, Vector2 dir)
{
if (_flickStart)
{
if (dir.x >= 70f)
{
_flickStart = false;
ShowPrevPage();
}
else if (dir.x <= -70f)
{
_flickStart = false;
ShowNextPage();
}
}
}
private IEnumerator LoadResources(List<PurchaseRewardInfo> purchaseRewardsList, Action onFinish)
{
List<string> rewardPathList = new List<string>();
for (int i = 0; i < purchaseRewardsList.Count; i++)
{
for (int j = 0; j < purchaseRewardsList[i].RewardInfoList.Count; j++)
{
ShopCommonRewardInfo shopCommonRewardInfo = purchaseRewardsList[i].RewardInfoList[j];
string text = string.Empty;
switch ((UserGoods.Type)shopCommonRewardInfo.Type)
{
case UserGoods.Type.Item:
{
string userGoodsImageName = UserGoods.GetUserGoodsImageName(UserGoods.Type.Item, shopCommonRewardInfo.UserGoodsId);
text = Toolbox.ResourcesManager.GetAssetTypePath(userGoodsImageName, ResourcesManager.AssetLoadPathType.Item);
break;
}
case UserGoods.Type.Degree:
text = Toolbox.ResourcesManager.GetAssetTypePath(UserGoods.GetUserGoodsImageName(UserGoods.Type.Degree, 0L), ResourcesManager.AssetLoadPathType.Item);
break;
case UserGoods.Type.Sleeve:
{
long existingSleeveId = Toolbox.ResourcesManager.GetExistingSleeveId(shopCommonRewardInfo.UserGoodsId);
text = Toolbox.ResourcesManager.GetAssetTypePath(existingSleeveId.ToString(), ResourcesManager.AssetLoadPathType.SleeveTexture);
Sleeve sleeve = Data.Master.SleeveMgr.Get(existingSleeveId);
if (sleeve.IsPremiumSleeve)
{
UIManager.GetInstance().getUIBase_CardManager().AddPremireSleevePath(ref rewardPathList, sleeve);
}
break;
}
case UserGoods.Type.Emblem:
text = Toolbox.ResourcesManager.GetAssetTypePath(shopCommonRewardInfo.UserGoodsId.ToString(), ResourcesManager.AssetLoadPathType.Emblem_M);
break;
}
if (text != string.Empty && !rewardPathList.Contains(text) && !_loadedResourceList.Contains(text))
{
rewardPathList.Add(text);
}
}
}
yield return StartCoroutine(Toolbox.ResourcesManager.LoadAssetGroupAsync(rewardPathList, null));
_loadedResourceList.AddRange(rewardPathList);
yield return StartCoroutine(LoadCardObject(purchaseRewardsList));
onFinish.Call();
}
private IEnumerator LoadCardObject(List<PurchaseRewardInfo> purchaseRewardsList)
{
List<int> cardIdList = GetCardIdList(purchaseRewardsList);
if (cardIdList.Count != 0)
{
UIBase_CardManager uiCardMgr = UIManager.GetInstance().getUIBase_CardManager();
int layer = LayerMask.NameToLayer("MyPage");
UIManager.GetInstance().CardLoadSelect(base.gameObject, cardIdList, layer, is2D: true);
while (!uiCardMgr.getCreateEndFlag() || !uiCardMgr.isAssetAllReady)
{
yield return null;
}
SetCardObj();
}
}
private List<int> GetCardIdList(List<PurchaseRewardInfo> purchaseRewardsList)
{
List<int> list = new List<int>();
for (int i = 0; i < purchaseRewardsList.Count; i++)
{
PurchaseRewardInfo purchaseRewardInfo = purchaseRewardsList[i];
for (int j = 0; j < purchaseRewardInfo.RewardInfoList.Count; j++)
{
ShopCommonRewardInfo shopCommonRewardInfo = purchaseRewardInfo.RewardInfoList[j];
if (shopCommonRewardInfo.Type == 5 && !list.Contains((int)shopCommonRewardInfo.UserGoodsId))
{
list.Add((int)shopCommonRewardInfo.UserGoodsId);
}
}
}
return list;
}
private void SetCardObj()
{
_cardObjList = UIManager.GetInstance().getCardList2DObjs();
if (_cardObjList == null)
{
return;
}
for (int i = 0; i < _cardObjList.Count; i++)
{
GameObject cardObj = _cardObjList[i].CardObj;
if (!(null == cardObj))
{
cardObj.SetActive(value: false);
CardListTemplate component = cardObj.GetComponent<CardListTemplate>();
component.HideNum();
component._newLabel.gameObject.SetActive(value: false);
UITexture[] componentsInChildren = cardObj.GetComponentsInChildren<UITexture>(includeInactive: true);
for (int j = 0; j < componentsInChildren.Length; j++)
{
componentsInChildren[j].depth += 23;
}
UILabel[] componentsInChildren2 = cardObj.GetComponentsInChildren<UILabel>(includeInactive: true);
for (int k = 0; k < componentsInChildren2.Length; k++)
{
componentsInChildren2[k].depth += 23;
}
component._frameSprite.depth = 23;
component._cardTexture.depth = 21;
cardObj.AddComponent<BoxCollider>().size = CARDOBJECT_COLLIDER_SIZE;
UIEventListener.Get(cardObj).onClick = _cardDetail.OnPushCardDetailOn;
if (_lastPageIndex > 1)
{
UIEventListener uIEventListener = UIEventListener.Get(cardObj);
uIEventListener.onDragStart = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener.onDragStart, new UIEventListener.VoidDelegate(OnDragStart));
UIEventListener uIEventListener2 = UIEventListener.Get(cardObj);
uIEventListener2.onDrag = (UIEventListener.VectorDelegate)Delegate.Combine(uIEventListener2.onDrag, new UIEventListener.VectorDelegate(OnDrag));
}
}
}
}
private GameObject GetCardObj(long userGoodsId)
{
GameObject result = null;
for (int i = 0; i < _cardObjList.Count; i++)
{
if (_cardObjList[i] != null && userGoodsId == _cardObjList[i].ids)
{
result = _cardObjList[i].CardObj;
break;
}
}
return result;
}
}