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>
312 lines
8.1 KiB
C#
312 lines
8.1 KiB
C#
using System;
|
|
using System.Collections;
|
|
using Cute;
|
|
using UnityEngine;
|
|
|
|
namespace Wizard.DeckCardEdit;
|
|
|
|
[RequireComponent(typeof(CardSelectListUI_Positioning))]
|
|
public class CardSelectListUI_State_Edit : MecanimStateBase
|
|
{
|
|
|
|
[SerializeField]
|
|
private CardSelectListUIBase m_scene;
|
|
|
|
[SerializeField]
|
|
private CardSelectListUI_State_CardDrag m_stateDragMode;
|
|
|
|
[SerializeField]
|
|
private UIScrollView m_scrollView;
|
|
|
|
private UIPanel _scrollViewPanel;
|
|
|
|
private SpringPanel _springPanel;
|
|
|
|
[SerializeField]
|
|
private UICenterOnChild m_scrollViewCenterOnChild;
|
|
|
|
[SerializeField]
|
|
private CardSelectListUI_Positioning _selectionAreaCardPositioning;
|
|
|
|
[SerializeField]
|
|
private CardSelectListUI_Positioning _pagingAreaCardPositioning;
|
|
|
|
[SerializeField]
|
|
private UIPanel _maskPanel;
|
|
|
|
private GameObject _pressObj;
|
|
|
|
private float _pressTime;
|
|
|
|
private Vector2 _pressPoint = Vector2.zero;
|
|
|
|
private Coroutine _pagingCoroutine;
|
|
|
|
private bool _requestImmediateMove;
|
|
|
|
public bool IsClick { get; private set; }
|
|
|
|
public override void onOpen()
|
|
{
|
|
base.onOpen();
|
|
RefreshSelectionArea(isImmediate: false);
|
|
RefreshPage(isImmediate: false);
|
|
m_scrollView.enabled = true;
|
|
_maskPanel.clipping = UIDrawCall.Clipping.None;
|
|
}
|
|
|
|
public override void onClose()
|
|
{
|
|
base.onClose();
|
|
RefreshSelectionArea(isImmediate: false);
|
|
RefreshPage(isImmediate: false);
|
|
}
|
|
|
|
public void RefreshSelectionArea(bool isImmediate)
|
|
{
|
|
RefreshBase(m_scene.SelectionAreaList, onPressSelectionAreaCard, delegate(GameObject o)
|
|
{
|
|
onDoubleClick(o, CardSelectListUI_State_CardDrag.Mode.InToOut);
|
|
}, _selectionAreaCardPositioning, isMerge: false, isImmediate);
|
|
}
|
|
|
|
public void RefreshPage(bool isImmediate)
|
|
{
|
|
RefreshBase(m_scene.PagingList, onPressPagingCard, delegate(GameObject o)
|
|
{
|
|
onDoubleClick(o, CardSelectListUI_State_CardDrag.Mode.OutToIn);
|
|
}, _pagingAreaCardPositioning, isMerge: false, isImmediate);
|
|
}
|
|
|
|
private void RefreshBase(CardBundle cardList, UIEventListener.BoolDelegate callback, UIEventListener.VoidDelegate doubleClickCallback, CardSelectListUI_Positioning positioning, bool isMerge, bool isImmediate)
|
|
{
|
|
int countKind = cardList.CountKind;
|
|
GameObject[] array = new GameObject[countKind];
|
|
for (int i = 0; i < countKind; i++)
|
|
{
|
|
array[i] = cardList.FindWithIndex(i).CardObj;
|
|
if (!(array[i] != null))
|
|
{
|
|
continue;
|
|
}
|
|
BoxCollider component = array[i].GetComponent<BoxCollider>();
|
|
if (!component)
|
|
{
|
|
component = array[i].AddComponent<BoxCollider>();
|
|
UIWidget component2 = array[i].GetComponent<UIWidget>();
|
|
if ((bool)component2)
|
|
{
|
|
component.size = component2.localSize;
|
|
}
|
|
}
|
|
UIEventListener uIEventListener = UIEventListener.Get(array[i]);
|
|
uIEventListener.onPress = (UIEventListener.BoolDelegate)Delegate.Combine(uIEventListener.onPress, callback);
|
|
UIEventListener uIEventListener2 = UIEventListener.Get(array[i]);
|
|
uIEventListener2.onPressRight = (UIEventListener.BoolDelegate)Delegate.Combine(uIEventListener2.onPressRight, callback);
|
|
UIEventListener uIEventListener3 = UIEventListener.Get(array[i]);
|
|
uIEventListener3.onDoubleClick = (UIEventListener.VoidDelegate)Delegate.Combine(uIEventListener3.onDoubleClick, doubleClickCallback);
|
|
}
|
|
if (isMerge)
|
|
{
|
|
positioning.Add(array);
|
|
}
|
|
else
|
|
{
|
|
positioning.Change(array);
|
|
}
|
|
if (isImmediate)
|
|
{
|
|
positioning.Immediate();
|
|
}
|
|
}
|
|
|
|
public void NextPage(Action onMoved)
|
|
{
|
|
if (_pagingCoroutine != null)
|
|
{
|
|
StopCoroutine(_pagingCoroutine);
|
|
}
|
|
_pagingCoroutine = StartCoroutine(PagingBase(-1f, onMoved));
|
|
}
|
|
|
|
public void PrevPage(Action onMoved)
|
|
{
|
|
if (_pagingCoroutine != null)
|
|
{
|
|
StopCoroutine(_pagingCoroutine);
|
|
}
|
|
_pagingCoroutine = StartCoroutine(PagingBase(1f, onMoved));
|
|
}
|
|
|
|
private IEnumerator PagingBase(float direction, Action onMoved)
|
|
{
|
|
Vector3 offset = _pagingAreaCardPositioning.Offset;
|
|
offset.x = 1400f * direction;
|
|
_pagingAreaCardPositioning.Offset = offset;
|
|
_pagingAreaCardPositioning.Speed = 0.4f;
|
|
while (_pagingAreaCardPositioning.IsMoving)
|
|
{
|
|
yield return null;
|
|
}
|
|
offset.x = 1400f * (0f - direction);
|
|
_pagingAreaCardPositioning.Offset = offset;
|
|
_pagingAreaCardPositioning.Speed = 1f;
|
|
onMoved.Call();
|
|
yield return null;
|
|
offset.x = 0f;
|
|
_pagingAreaCardPositioning.Offset = offset;
|
|
_pagingAreaCardPositioning.Speed = 0.4f;
|
|
}
|
|
|
|
public void Fit()
|
|
{
|
|
SetupScrollComponent();
|
|
float num = Mathf.Max(0f, _scrollViewPanel.clipOffset.x);
|
|
int a = Mathf.Max(0, m_scene.SelectionAreaList.CountKind - 10);
|
|
float breakWidth = _selectionAreaCardPositioning.BreakWidth;
|
|
int b = Mathf.RoundToInt(num / breakWidth);
|
|
Fit(Mathf.Min(a, b));
|
|
m_scrollView.customMovement = ((m_scene.SelectionAreaList.CountKind > 10) ? Vector2.right : Vector2.zero);
|
|
}
|
|
|
|
public void CenterOn(int idx)
|
|
{
|
|
SetupScrollComponent();
|
|
idx -= 5;
|
|
int a = Mathf.Max(0, m_scene.SelectionAreaList.CountKind - 10);
|
|
int a2 = 0;
|
|
Fit(Mathf.Max(a2, Mathf.Min(a, idx)));
|
|
}
|
|
|
|
private void Fit(int idx)
|
|
{
|
|
float breakWidth = _selectionAreaCardPositioning.BreakWidth;
|
|
_springPanel.target.x = (float)(-idx) * breakWidth;
|
|
_springPanel.enabled = true;
|
|
}
|
|
|
|
public void ResetScroll()
|
|
{
|
|
SetupScrollComponent();
|
|
m_scrollView.ResetPosition();
|
|
_springPanel.target.x = 0f;
|
|
_springPanel.enabled = true;
|
|
}
|
|
|
|
private void SetupScrollComponent()
|
|
{
|
|
if (_scrollViewPanel == null)
|
|
{
|
|
_scrollViewPanel = m_scrollView.GetComponent<UIPanel>();
|
|
}
|
|
if (_springPanel == null)
|
|
{
|
|
_springPanel = m_scrollView.GetComponent<SpringPanel>();
|
|
}
|
|
m_scrollViewCenterOnChild.gameObject.SetActive(value: true);
|
|
}
|
|
|
|
public void RemoveSelectionArea()
|
|
{
|
|
_selectionAreaCardPositioning.Clear();
|
|
}
|
|
|
|
private void StartDragState(bool immediate)
|
|
{
|
|
CardObject cardObject = m_stateDragMode.TryGetCard(_pressObj);
|
|
if (cardObject != null)
|
|
{
|
|
m_scene.HideDetail();
|
|
m_scene.ChangeState(m_stateDragMode, skipCloseAnim: true);
|
|
m_stateDragMode.ImmediateMove = immediate;
|
|
m_stateDragMode.CreateDragCard(cardObject);
|
|
m_scrollView.enabled = false;
|
|
m_scrollViewCenterOnChild.gameObject.SetActive(value: true);
|
|
IsClick = false;
|
|
}
|
|
_pressObj = null;
|
|
_pressTime = 0f;
|
|
}
|
|
|
|
public override void onMove()
|
|
{
|
|
base.onMove();
|
|
if (!_pressObj)
|
|
{
|
|
return;
|
|
}
|
|
if (_requestImmediateMove)
|
|
{
|
|
StartDragState(immediate: true);
|
|
_requestImmediateMove = false;
|
|
}
|
|
else if (Input.GetMouseButton(0))
|
|
{
|
|
IsClick = true;
|
|
_pressTime += Time.deltaTime;
|
|
Vector2 vector = _pressPoint - (Vector2)Input.mousePosition;
|
|
vector.Normalize();
|
|
float num = Mathf.Abs(vector.y);
|
|
float num2 = Vector2.Distance(_pressPoint, Input.mousePosition);
|
|
if (num2 >= 35f && num < 0.8f)
|
|
{
|
|
_pressObj = null;
|
|
_pressTime = 0f;
|
|
}
|
|
else if (_pressTime >= 0.5f || (num >= 0.8f && num2 >= 35f))
|
|
{
|
|
StartDragState(immediate: false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_pressObj = null;
|
|
_pressTime = 0f;
|
|
}
|
|
}
|
|
|
|
private void onPressSelectionAreaCard(GameObject obj, bool state)
|
|
{
|
|
if (!m_scene.IsLoading)
|
|
{
|
|
bool mouseButton = Input.GetMouseButton(0);
|
|
bool flag = !mouseButton && Input.GetMouseButtonDown(1);
|
|
if (mouseButton || flag)
|
|
{
|
|
initPress(obj, CardSelectListUI_State_CardDrag.Mode.InToOut);
|
|
_requestImmediateMove = flag;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void onPressPagingCard(GameObject obj, bool state)
|
|
{
|
|
if (!m_scene.IsLoading)
|
|
{
|
|
bool mouseButton = Input.GetMouseButton(0);
|
|
bool flag = !mouseButton && Input.GetMouseButtonDown(1);
|
|
if (mouseButton || flag)
|
|
{
|
|
initPress(obj, CardSelectListUI_State_CardDrag.Mode.OutToIn);
|
|
_requestImmediateMove = flag;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initPress(GameObject obj, CardSelectListUI_State_CardDrag.Mode mode)
|
|
{
|
|
m_stateDragMode.EditMode = mode;
|
|
_pressObj = obj;
|
|
_pressPoint = Input.mousePosition;
|
|
}
|
|
|
|
private void onDoubleClick(GameObject obj, CardSelectListUI_State_CardDrag.Mode mode)
|
|
{
|
|
if (!m_scene.IsLoading)
|
|
{
|
|
_requestImmediateMove = true;
|
|
initPress(obj, mode);
|
|
}
|
|
}
|
|
}
|