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

387 lines
17 KiB
C#

using System.Collections.Generic;
using Cute;
using UnityEngine;
using Wizard;
using Wizard.Battle;
using Wizard.Battle.Card;
using Wizard.Battle.Resource;
public class CardCreatorBase
{
private class CardTypeBuildInfo
{
public bool isActive;
public Vector3 localPosition;
public Vector3 localScale;
public Quaternion localRotation;
public Transform parent;
}
private readonly GameObject _cardRootObject;
protected static Dictionary<CardBasePrm.ClanType, Material> _classIconCache = new Dictionary<CardBasePrm.ClanType, Material>();
private static BattleCardBase _dummyCardInstance;
public static Material GetSharedClassIconMaterial(CardBasePrm.ClanType clanType)
{
Material material;
if (_classIconCache.ContainsKey(clanType))
{
material = _classIconCache[clanType];
if (material == null || material.mainTexture == null)
{
if (material != null)
{
Object.Destroy(material);
}
_classIconCache.Remove(clanType);
}
material = null;
}
if (_classIconCache.ContainsKey(clanType))
{
material = _classIconCache[clanType];
}
else
{
material = Object.Instantiate(Toolbox.ResourcesManager.LoadObject(Toolbox.ResourcesManager.GetAssetTypePath("CardFrameClassIcon", ResourcesManager.AssetLoadPathType.CardFrameMaterialPlus, isfetch: true)) as Material);
material.mainTexture = ClassCharaPrm.GetClassIconTexture((int)clanType);
_classIconCache[clanType] = material;
}
return material;
}
public CardCreatorBase(GameObject cardRootObject)
{
_cardRootObject = cardRootObject;
}
public GameObject LoadRootObject()
{
return _cardRootObject;
}
public static BattleCardBase CreateCard(int cardId, bool isPlayer, int index, SBattleLoad sBattleLoad, BattleManagerBase battleMgr, IBattleResourceMgr resourceMgr, IInnerOptionsBuilder innerOptionsBuilder, bool isChoiceBrave = false)
{
bool flag = !isPlayer;
CardParameter cardParameterFromId = CardMaster.GetInstanceForBattle().GetCardParameterFromId(cardId);
CardBasePrm.CharaType charType = cardParameterFromId.CharType;
CardCreatorBase cardCreatorBase = null;
switch (charType)
{
case CardBasePrm.CharaType.NORMAL:
cardCreatorBase = new UnitCardCreator(sBattleLoad.UnitCardTemplate.gameObject);
break;
case CardBasePrm.CharaType.FIELD:
case CardBasePrm.CharaType.CHANT_FIELD:
cardCreatorBase = new FieldCardCreator(sBattleLoad.FieldCardTemplate.gameObject);
break;
case CardBasePrm.CharaType.SPELL:
cardCreatorBase = new SpellCardCreator(sBattleLoad.SpellCardTemplate.gameObject);
break;
default:
return null;
}
GameObject gobj = cardCreatorBase.LoadRootObject();
GameObject gameObject = battleMgr.GameMgr.GetPrefabMgr().CloneObjectToParent(gobj, battleMgr.Battle3DContainer);
CardTemplate component = gameObject.GetComponent<CardTemplate>();
if (flag && !battleMgr.GameMgr.IsWatchBattle && !sBattleLoad.isDbgEnableEnemyHandView && !battleMgr.GameMgr.IsAdmin)
{
gameObject.GetComponent<CardTemplate>().CardNormalLodGroup.enabled = false;
}
GameObject gameObject2 = component.CardNormalTemp.gameObject;
if (charType == CardBasePrm.CharaType.NORMAL)
{
gameObject2.SetActive(value: false);
}
component.NormalCardBaseMeshTemp.sharedMaterial = resourceMgr.GetSleeveMaterial(isPlayer);
if (charType == CardBasePrm.CharaType.NORMAL)
{
component.EvolCardBaseMeshTemp.sharedMaterial = resourceMgr.GetSleeveMaterial(isPlayer);
}
Transform transform = gameObject.transform.Find("CardObj");
Transform transform2 = gameObject.transform.Find("Collider");
string tag = (transform.tag = (gameObject2.tag = (flag ? "Enemy" : "Player")));
transform2.tag = tag;
GameObject gameObject3 = null;
if (component.LifeLabelTemp != null)
{
gameObject3 = component.LifeLabelTemp.transform.parent.gameObject;
}
UILabel lifeLabelTemp = component.LifeLabelTemp;
if (lifeLabelTemp != null)
{
lifeLabelTemp.text = cardParameterFromId.Life.ToString();
}
if (gameObject3 != null)
{
gameObject3.SetActive(value: false);
}
if (isChoiceBrave)
{
component.SetEffectColor(Global.CARD_HBP_LABEL_COST_COLOR);
}
if (cardParameterFromId.IsVariableCost)
{
component.NormalSignLabelTemp.text = "-";
component.NormalSignedCostLabelTemp.text = "X";
component.ShowSignedCostLabel();
component.NormalChoiceBraveNameLabelTemp.text = cardParameterFromId.CardName;
component.ShowChoiceBraveNameLabel();
}
else if (isChoiceBrave)
{
if (cardParameterFromId.Cost != 0)
{
component.NormalSignLabelTemp.text = ((cardParameterFromId.Cost > 0) ? "-" : "+");
component.NormalSignedCostLabelTemp.text = Mathf.Abs(cardParameterFromId.Cost).ToString();
component.ShowSignedCostLabel();
}
else
{
component.NormalZeroCostLabelTemp.text = "0";
component.ShowZeroCostLabel();
}
component.NormalChoiceBraveNameLabelTemp.text = cardParameterFromId.CardName;
component.ShowChoiceBraveNameLabel();
}
else
{
component.NormalCostLabelTemp.text = cardParameterFromId.Cost.ToString();
component.NormalNameLabelTemp.text = cardParameterFromId.CardName;
}
component.SetNumberLabelStyle(cardParameterFromId.IsFoil);
component.SetNameLabelStyle(cardParameterFromId.IsFoil, isChoiceBrave);
component.SetRepositionNameLabel(cardParameterFromId.CardName, isChoiceBrave);
if (charType == CardBasePrm.CharaType.NORMAL)
{
component.NormalLifeLabelTemp.text = lifeLabelTemp.text;
GameObject gameObject4 = component.AtkLabelTemp.transform.parent.gameObject;
UILabel atkLabelTemp = component.AtkLabelTemp;
atkLabelTemp.text = cardParameterFromId.Atk.ToString();
gameObject4.SetActive(value: false);
component.NormalAtkLabelTemp.text = atkLabelTemp.text;
}
UILabel component2 = gameObject2.transform.Find("Cost(Clone)").Find("CostLabel").GetComponent<UILabel>();
if (charType == CardBasePrm.CharaType.NORMAL)
{
UILabel component3 = gameObject2.transform.Find("Life(Clone)").Find("LifeLabel").GetComponent<UILabel>();
UILabel component4 = gameObject2.transform.Find("Atk(Clone)").Find("AtkLabel").GetComponent<UILabel>();
GameObject gameObject5 = component2.gameObject;
GameObject gameObject6 = component3.gameObject;
int num = (component4.gameObject.layer = 12);
int layer = (gameObject6.layer = num);
gameObject5.layer = layer;
UIManager.GetInstance().getUIBase_CardManager().SetNumberLabelStyle(component.NormalAtkLabelTemp, cardParameterFromId.IsFoil);
UIManager.GetInstance().getUIBase_CardManager().SetNumberLabelStyle(component.NormalLifeLabelTemp, cardParameterFromId.IsFoil);
}
return CreateCard(cardId, gameObject, index, isPlayer, battleMgr, component, transform, innerOptionsBuilder, isChoiceBrave);
}
public static BattleCardBase CreateSpecialSkillCard(int cardId, bool isPlayer, int index, SBattleLoad sBattleLoad, BattleManagerBase battleMgr, IBattleResourceMgr resourceMgr, IInnerOptionsBuilder innerOptionsBuilder, BossRushSpecialSkill skill)
{
bool flag = !isPlayer;
CardParameter cardParameterFromId = CardMaster.GetInstanceForBattle().GetCardParameterFromId(cardId);
_ = cardParameterFromId.CharType;
GameObject gobj = new SpecialSkillCardCreator(sBattleLoad.SpellCardTemplate.gameObject).LoadRootObject();
GameObject gameObject = battleMgr.GameMgr.GetPrefabMgr().CloneObjectToParent(gobj, battleMgr.Battle3DContainer);
CardTemplate component = gameObject.GetComponent<CardTemplate>();
if (flag && !battleMgr.GameMgr.IsWatchBattle && !sBattleLoad.isDbgEnableEnemyHandView && !battleMgr.GameMgr.IsAdmin)
{
gameObject.GetComponent<CardTemplate>().CardNormalLodGroup.enabled = false;
}
GameObject gameObject2 = component.CardNormalTemp.gameObject;
component.NormalCardBaseMeshTemp.sharedMaterial = resourceMgr.GetSleeveMaterial(isPlayer);
Transform transform = gameObject.transform.Find("CardObj");
Transform transform2 = gameObject.transform.Find("Collider");
string tag = (transform.tag = (gameObject2.tag = (flag ? "Enemy" : "Player")));
transform2.tag = tag;
GameObject gameObject3 = null;
if (component.LifeLabelTemp != null)
{
gameObject3 = component.LifeLabelTemp.transform.parent.gameObject;
}
UILabel lifeLabelTemp = component.LifeLabelTemp;
if (lifeLabelTemp != null)
{
lifeLabelTemp.text = cardParameterFromId.Life.ToString();
}
if (gameObject3 != null)
{
gameObject3.SetActive(value: false);
}
component.NormalCostLabelTemp.text = cardParameterFromId.Cost.ToString();
component.NormalNameLabelTemp.text = skill.Name;
UIManager.GetInstance().getUIBase_CardManager().SetNumberLabelStyle(component.NormalCostLabelTemp, cardParameterFromId.IsFoil);
UIManager.GetInstance().getUIBase_CardManager().SetNameLabelStyle(component.NormalNameLabelTemp, cardParameterFromId.IsFoil);
Global.SetRepositionNameLabel(component.NormalNameLabelTemp, skill.Name, is2D: false);
string tag2;
if (isPlayer)
{
tag2 = "Player";
gameObject.name = "P0";
}
else
{
tag2 = "Enemy";
gameObject.name = "E0";
}
CardTypeBuildInfo cardTypeBuildInfo = CreateCardTypeBuildInfo(battleMgr, cardParameterFromId.CharType, isPlayer);
gameObject.SetActive(cardTypeBuildInfo.isActive);
gameObject.transform.localPosition = cardTypeBuildInfo.localPosition;
gameObject.transform.localScale = cardTypeBuildInfo.localScale;
gameObject.transform.localRotation = cardTypeBuildInfo.localRotation;
gameObject.transform.parent = cardTypeBuildInfo.parent;
gameObject.tag = tag2;
transform.tag = tag2;
transform.transform.Find("NormalField").tag = tag2;
transform.transform.Find("EvolField").tag = tag2;
SkillCreator.CardSkillsBuildInfo cardSkillsBuildInfo = SkillCreator.CreateBuildInfo(cardParameterFromId);
BattleCardBase.BuildInfo buildInfo = new BattleCardBase.BuildInfo(gameObject, cardId, battleMgr.GetBattlePlayer(isPlayer), battleMgr.GetBattlePlayer(!isPlayer), battleMgr.GetBattlePlayer(isPlayer), cardSkillsBuildInfo.normalSkillBuildInfos, cardSkillsBuildInfo.evolveSkillBuildInfos, isPlayer, 0, innerOptionsBuilder.CreateCardOptions(), battleMgr, battleMgr.BattleResourceMgr);
SpecialSkillBattleCard specialSkillBattleCard = new SpecialSkillBattleCard(skill, buildInfo);
specialSkillBattleCard.Setup(createNullView: true);
return specialSkillBattleCard;
}
public static BattleCardBase CreateCard(int cardId, GameObject gameObject, int battleCardIndex, bool isPlayer, BattleManagerBase battleMgr, CardTemplate CardTemplateIns, Transform parentObject, IInnerOptionsBuilder innerOptionsBuilder, bool isChoiceBrave)
{
CardParameter cardParameterFromId = CardMaster.GetInstanceForBattle().GetCardParameterFromId(cardId);
string tag;
if (isPlayer)
{
tag = "Player";
gameObject.name = "P" + battleCardIndex;
}
else
{
tag = "Enemy";
gameObject.name = "E" + battleCardIndex;
}
CardTypeBuildInfo cardTypeBuildInfo = CreateCardTypeBuildInfo(battleMgr, cardParameterFromId.CharType, isPlayer);
gameObject.SetActive(cardTypeBuildInfo.isActive);
gameObject.transform.localPosition = cardTypeBuildInfo.localPosition;
gameObject.transform.localScale = cardTypeBuildInfo.localScale;
gameObject.transform.localRotation = cardTypeBuildInfo.localRotation;
gameObject.transform.parent = cardTypeBuildInfo.parent;
gameObject.tag = tag;
parentObject.tag = tag;
if (cardParameterFromId.CharType == CardBasePrm.CharaType.NORMAL)
{
CardTemplateIns.CardNormalTemp.tag = tag;
CardTemplateIns.CardNormalTemp.gameObject.SetActive(value: true);
}
parentObject.transform.Find("NormalField").tag = tag;
parentObject.transform.Find("EvolField").tag = tag;
SkillCreator.CardSkillsBuildInfo cardSkillsBuildInfo = SkillCreator.CreateBuildInfo(cardParameterFromId);
return CreateBase(new BattleCardBase.BuildInfo(gameObject, cardId, battleMgr.GetBattlePlayer(isPlayer), battleMgr.GetBattlePlayer(!isPlayer), battleMgr.GetBattlePlayer(isPlayer), cardSkillsBuildInfo.normalSkillBuildInfos, cardSkillsBuildInfo.evolveSkillBuildInfos, isPlayer, battleCardIndex, innerOptionsBuilder.CreateCardOptions(), battleMgr, battleMgr.BattleResourceMgr), createNullView: false, isChoiceBrave);
}
private static BattleCardBase CreateCardWithoutResources(int cardId, int battleCardIndex, bool isPlayer, BattleManagerBase battleMgr, IInnerOptionsBuilder innerOptionsBuilder)
{
SkillCreator.CardSkillsBuildInfo cardSkillsBuildInfo = SkillCreator.CreateBuildInfo(CardMaster.GetInstanceForBattle().GetCardParameterFromId(cardId));
return CreateBase(new BattleCardBase.BuildInfo(null, cardId, battleMgr.GetBattlePlayer(isPlayer), battleMgr.GetBattlePlayer(!isPlayer), battleMgr.GetBattlePlayer(isPlayer), cardSkillsBuildInfo.normalSkillBuildInfos, cardSkillsBuildInfo.evolveSkillBuildInfos, isPlayer, battleCardIndex, innerOptionsBuilder.CreateCardOptions(), battleMgr, battleMgr.BattleResourceMgr), createNullView: true);
}
public static BattleCardBase CreateVirtualCard(int cardId, int index, bool isPlayer, BattleManagerBase battleMgr, BattlePlayerBase selfBattlePlayer, BattlePlayerBase opponentBattlePlayer, IInnerOptionsBuilder innerOptionsBuilder)
{
CardParameter cardParameterFromId = CardMaster.GetInstanceForBattle().GetCardParameterFromId(cardId);
SkillCreator.CardSkillsBuildInfo cardSkillsBuildInfo = SkillCreator.CreateBuildInfo(cardParameterFromId);
BattleCardBase.BuildInfo buildInfo = new BattleCardBase.BuildInfo(null, cardId, selfBattlePlayer, opponentBattlePlayer, selfBattlePlayer, cardSkillsBuildInfo.normalSkillBuildInfos, cardSkillsBuildInfo.evolveSkillBuildInfos, isPlayer, index, innerOptionsBuilder.CreateCardOptions(), battleMgr, NullBattleResourceMgr.GetInstance());
BattleCardBase battleCardBase;
switch (cardParameterFromId.CharType)
{
case CardBasePrm.CharaType.NORMAL:
case CardBasePrm.CharaType.EVOLUTION:
battleCardBase = new VirtualUnitBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.FIELD:
battleCardBase = new VirtualFieldBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.CHANT_FIELD:
battleCardBase = new VirtualChantFieldBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.SPELL:
battleCardBase = new VirtualSpellBattleCard(buildInfo, isChoiceBrave: false);
break;
default:
battleCardBase = NullBattleCard.Create();
break;
}
battleCardBase.Setup();
return battleCardBase;
}
public static BattleCardBase CreateToken(BattleCardBase.BuildInfo buildInfo, bool createNullView = false)
{
return CreateBase(buildInfo, createNullView);
}
private static BattleCardBase CreateBase(BattleCardBase.BuildInfo buildInfo, bool createNullView = false, bool isChoiceBrave = false)
{
BattleCardBase battleCardBase;
switch (CardMaster.GetInstanceForBattle().GetCardParameterFromId(buildInfo.CardId).CharType)
{
case CardBasePrm.CharaType.NORMAL:
case CardBasePrm.CharaType.EVOLUTION:
battleCardBase = new UnitBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.FIELD:
battleCardBase = new FieldBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.CHANT_FIELD:
battleCardBase = new ChantFieldBattleCard(buildInfo);
break;
case CardBasePrm.CharaType.SPELL:
battleCardBase = new SpellBattleCard(buildInfo, isChoiceBrave);
battleCardBase.IsChoiceBraveSkillCard = isChoiceBrave;
break;
default:
battleCardBase = NullBattleCard.Create();
break;
}
battleCardBase.Setup(createNullView);
return battleCardBase;
}
public static BattleCardBase CreateDummyInstance()
{
return NullBattleCard.Create();
}
public static BattleCardBase GetDummyInstance()
{
if (_dummyCardInstance == null)
{
_dummyCardInstance = NullBattleCard.Create();
}
return _dummyCardInstance;
}
private static CardTypeBuildInfo CreateCardTypeBuildInfo(BattleManagerBase ins, CardBasePrm.CharaType type, bool isPlayer)
{
CardTypeBuildInfo cardTypeBuildInfo = new CardTypeBuildInfo();
if (type == CardBasePrm.CharaType.CLASS)
{
cardTypeBuildInfo.isActive = true;
cardTypeBuildInfo.localPosition = (isPlayer ? new Vector3(0f, -400f, 30f) : new Vector3(0f, 420f, 30f));
cardTypeBuildInfo.localScale = Global.CLASS_BATTLE_SCALE;
cardTypeBuildInfo.localRotation = Quaternion.identity;
cardTypeBuildInfo.parent = ins.Battle3DContainer.transform;
}
else
{
cardTypeBuildInfo.isActive = false;
cardTypeBuildInfo.localPosition = (isPlayer ? ins.CardHolder.transform.localPosition : ins.ECardHolder.transform.localPosition);
cardTypeBuildInfo.localScale = Global.CARD_BATTLE_SCALE;
cardTypeBuildInfo.localRotation = Quaternion.Euler(0f, -90f, 90f);
cardTypeBuildInfo.parent = ins.PCardPlace.transform;
}
return cardTypeBuildInfo;
}
}