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

263 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Wizard.Battle.View.Vfx;
public class NetworkSkill_cost_change : Skill_cost_change
{
private bool isCall;
public bool IsForHandResident { get; private set; }
public NetworkSkill_cost_change(NetworkBattleManagerBase battleManager, SkillParameter skillPrm, string option)
: base(skillPrm, option)
{
NetworkSkill_cost_change networkSkill_cost_change = this;
base.OnSkillEnd += delegate
{
networkSkill_cost_change.isCall = false;
return NullVfx.GetInstance();
};
base.OnSkillStart += delegate(SkillBase skill, List<BattleCardBase> targetCards, SkillConditionCheckerOption checkerOption)
{
if (!networkSkill_cost_change.isCall && networkSkill_cost_change.IsSend(networkSkill_cost_change) && !(skill.ApplyBattlePlayerFilter is BothBattlePlayerFilter) && !skill.IsUserSelectType && battleManager._networkBattleSetupCardEventBase.IsSettingUnapprovedCard(networkSkill_cost_change) && ((networkSkill_cost_change.SkillPrm.ownerCard.IsPlayer && networkSkill_cost_change.ApplyBattlePlayerFilter is OpponentBattlePlayerFilter) || (!networkSkill_cost_change.SkillPrm.ownerCard.IsPlayer && networkSkill_cost_change.ApplyBattlePlayerFilter is SelfBattlePlayerFilter)) && networkSkill_cost_change.IsPrivate())
{
networkSkill_cost_change.SettingCostChangeData();
ICardCostModifier modifire = networkSkill_cost_change.CreateCostModifier();
networkSkill_cost_change.PrivateTargetRegister(battleManager, modifire, targetCards);
networkSkill_cost_change.isCall = true;
}
};
OnAccumulationCostChange = (Action<BattleCardBase, List<BattleCardBase>, ICardCostModifier>)Delegate.Combine(OnAccumulationCostChange, (Action<BattleCardBase, List<BattleCardBase>, ICardCostModifier>)delegate(BattleCardBase ownerCard, List<BattleCardBase> targetCards, ICardCostModifier modifier)
{
List<BattleCardBase> list = targetCards.Where((BattleCardBase c) => c.IsInHand || c.IsInDeck).ToList();
bool flag = list.Count == 1 && list.Any((BattleCardBase c) => c == ownerCard) && networkSkill_cost_change.OnWhenFusion == 0 && networkSkill_cost_change.OnWhenReturnStart == 0 && !networkSkill_cost_change.IsOpen();
if ((!networkSkill_cost_change.isCall || networkSkill_cost_change.HasEachTargetOption()) && networkSkill_cost_change.IsSend(networkSkill_cost_change) && list != null && list.Count > 0 && !flag)
{
List<BattleCardBase> list2 = list.Where((BattleCardBase c) => c.IsPlayer).ToList();
List<BattleCardBase> list3 = list.Where((BattleCardBase c) => !c.IsPlayer).ToList();
bool flag2 = networkSkill_cost_change.IsPrivate();
if (list2.Count > 0)
{
networkSkill_cost_change.PublicTargetRegister(battleManager, modifier, list2);
}
if (list3.Count > 0)
{
if (flag2)
{
networkSkill_cost_change.PrivateTargetRegister(battleManager, modifier, list3);
}
else
{
networkSkill_cost_change.PublicTargetRegister(battleManager, modifier, list3);
}
}
networkSkill_cost_change.isCall = true;
}
});
}
private bool IsPrivate()
{
if (NetworkBattleGenericTool.IsNeedUnapprovedListSkill(this) || RegisterFilter.IsFilterCard(this))
{
return !IsOpen();
}
return false;
}
public override void SkillCreateEnd()
{
base.SkillCreateEnd();
if (base.SkillPrm.ownerCard.IsPlayer)
{
if (NetworkBattleGenericTool.IsNeedUnapprovedListSkill(this))
{
NetworkExecutionInfoCreator networkExecutionInfoCreator = base._executionInfoCreator as NetworkExecutionInfoCreator;
if (base.IsWhenDestroySkill || OnWhenReturnStart != 0)
{
networkExecutionInfoCreator.SetPlaySkill();
base.OnSkillEnd += PublicAnytimeRandomTargetRegister;
isCall = true;
}
else
{
base.OnSkillEnd += NetworkBattleGenericTool.Event_SetupPlayerUnapprovedAddEvent;
}
}
}
else if (NetworkBattleGenericTool.IsNeedUnapprovedListSkill(this))
{
NetworkExecutionInfoCreator networkExecutionInfoCreator2 = base._executionInfoCreator as NetworkExecutionInfoCreator;
if (base.IsWhenDestroySkill || OnWhenReturnStart != 0)
{
networkExecutionInfoCreator2.SetPlaySkill();
base.OnSkillEnd += PrivateAnytimeRandomTargetRegister;
isCall = true;
}
networkExecutionInfoCreator2.SetUnapproved();
}
}
public void CheckForHandResident(List<SkillBase> skillList)
{
IsForHandResident = false;
if (base.OptionValue.HasInfoByName(SkillFilterCreator.ContentKeyword.set) || base.OptionValue.HasInfoByName(SkillFilterCreator.ContentKeyword.random_set_range))
{
if (base.ApplyingTargetFilter is SkillTargetHandFilter && base.ApplySelectFilter is SkillSelectAllFilter && OnSelfTurnStartStart == 1)
{
IsForHandResident = true;
}
else if (base.ApplyingTargetFilter is SkillTargetSkillDrewCardFilter && skillList.Any((SkillBase s) => s.SkillTimingText == SkillTimingText && s.ApplyingTargetFilter is SkillTargetDestroyedThisTurnCardListFilter))
{
IsForHandResident = true;
}
else if (skillList.Any((SkillBase s) => s.SkillTimingText == SkillTimingText && s is Skill_draw) && !base.PreprocessList.Any((SkillPreprocessBase p) => p is SkillPreprocessTurnEndStop))
{
IsForHandResident = true;
}
}
}
private VfxBase PublicAnytimeRandomTargetRegister(SkillBase skillBase, List<BattleCardBase> cards, SkillConditionCheckerOption checkerOption, SkillProcessor skillProcessor)
{
if (cards != null && cards.Count() >= 1)
{
PublicTargetRegister(SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr as NetworkBattleManagerBase, CreateCostModifier(), cards);
return NetworkBattleGenericTool.Event_SetupPlayerUnapprovedAddEvent(skillBase, cards, checkerOption, skillProcessor);
}
if (cards == null || cards.Count() == 0)
{
(SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr as NetworkBattleManagerBase).StableRandomDouble();
}
return NullVfx.GetInstance();
}
private VfxBase PrivateAnytimeRandomTargetRegister(SkillBase skillBase, IEnumerable<BattleCardBase> cards, SkillConditionCheckerOption checkerOption, SkillProcessor skillProcessor)
{
RegisterLotCardBase registerLotCardBase = null;
NetworkBattleManagerBase networkBattleManagerBase = SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr as NetworkBattleManagerBase;
if (skillBase.SkillPrm.ownerCard.SelfBattlePlayer.IsSelfTurn && cards != null && cards.Count() >= 1)
{
registerLotCardBase = NetworkBattleGenericTool.MakeRegisterLotAndRandomAdvance(skillBase, cards, checkerOption);
}
else
{
NetworkBattleDefine.NetworkCardPlaceState networkCardPlaceState = NetworkBattleDefine.NetworkCardPlaceState.None;
if (skillBase.ApplyingTargetFilter is SkillTargetHandFilter || skillBase.ApplyingTargetFilter is SkillTargetHandOtherSelfFilter)
{
networkCardPlaceState = NetworkBattleDefine.NetworkCardPlaceState.Hand;
}
else if (skillBase.ApplyingTargetFilter is SkillTargetDeckFilter)
{
networkCardPlaceState = NetworkBattleDefine.NetworkCardPlaceState.Deck;
}
double num = 0.0;
registerLotCardBase = new RegisterLotCardBase(rand: (cards == null || cards.Count() < 1) ? networkBattleManagerBase.StableRandomDouble() : networkBattleManagerBase.randomResult, registerActionManager: networkBattleManagerBase.RegisterActionManager, mgr: networkBattleManagerBase, isplayer: false, from: networkCardPlaceState, targetIndex: -1, skill: this);
registerLotCardBase.SettingTargetStatusToSearchSkill(networkBattleManagerBase, skillBase);
networkBattleManagerBase.RegisterActionManager.Add(registerLotCardBase);
}
SettingCostChangeData();
RegisterCostChangeCard data = new RegisterCostChangeCard(networkBattleManagerBase, networkBattleManagerBase.RegisterActionManager, CreateCostModifier(), null, this, checkerOption, registerLotCardBase, isNotCheckCard: true);
networkBattleManagerBase.RegisterActionManager.Add(data);
return NullVfx.GetInstance();
}
private void PrivateTargetRegister(NetworkBattleManagerBase battleManager, ICardCostModifier modifire, List<BattleCardBase> targets)
{
bool flag = base.ApplyBattlePlayerFilter is SelfBattlePlayerFilter;
if (!base.SkillPrm.ownerCard.IsPlayer)
{
flag = !flag;
}
RegisterTargetBase registerTargetBase = null;
SkillConditionCheckerOption skillConditionCheckerOption = new SkillConditionCheckerOption();
if (NetworkBattleGenericTool.IsNeedUnapprovedListSkill(this))
{
if (targets.Count == 0)
{
return;
}
battleManager._networkBattleSetupCardEventBase.SetSkillTargetsConditionCheckUList(this, targets, skillConditionCheckerOption);
registerTargetBase = NetworkBattleGenericTool.MakeRegisterLotAndRandomAdvance(this, targets, skillConditionCheckerOption);
}
else if (RegisterFilter.IsFilterCard(this))
{
if ((base.ApplyingTargetFilter is SkillTargetSkillUpdateDeckCardFilter && targets.Count == 0) || (base.ApplyingTargetFilter is SkillTargetLoadTargetFilter && OnBeforeAttackStart != 0 && targets.Count((BattleCardBase c) => !c.IsInCemetery) == 0))
{
return;
}
battleManager._networkBattleSetupCardEventBase.SetSkillTargetsCondition(this, skillConditionCheckerOption);
registerTargetBase = new RegisterFilter(battleManager.RegisterActionManager, battleManager, flag, this, targets, isStop: false, skillConditionCheckerOption);
battleManager.RegisterActionManager.Add(registerTargetBase);
}
RegisterCostChangeCard register = new RegisterCostChangeCard(battleManager, battleManager.RegisterActionManager, modifire, targets, this, skillConditionCheckerOption, registerTargetBase);
battleManager._networkBattleSetupCardEventBase.AddRegisterActionManager(register);
}
private void PublicTargetRegister(NetworkBattleManagerBase battleManager, ICardCostModifier modifire, List<BattleCardBase> targets)
{
SkillConditionCheckerOption option = new SkillConditionCheckerOption();
battleManager._networkBattleSetupCardEventBase.SetSkillTargetsCondition(this, option);
RegisterCostChangeCard register = new RegisterCostChangeCard(battleManager, battleManager.RegisterActionManager, modifire, targets, this, option);
battleManager._networkBattleSetupCardEventBase.AddRegisterActionManager(register);
}
private bool IsSend(SkillBase skill)
{
if (((skill.SkillPrm.ownerCard.FinalMetamorphoseCard != null) ? skill.SkillPrm.ownerCard.FinalMetamorphoseCard : skill.SkillPrm.ownerCard).IsInHand && skill.OnWhenReturnStart == 0 && skill.OnWhenFusion == 0 && !IsOpen())
{
return false;
}
return true;
}
public override void SetEventAfterReplace(BattleCardBase card, BuffInfo buff)
{
card.OnResetCardParameter += delegate
{
card.RemoveBuffInfo(buff);
};
}
protected override bool IsUnconditionalCostChange()
{
BattleCardBase ownerCard = base.SkillPrm.ownerCard;
GameMgr ins = SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr.GameMgr;
if (!ownerCard.IsPlayer && !ins.IsAdminWatch && !SkillPrm.ownerCard.SelfBattlePlayer.BattleMgr.GameMgr.IsReplayBattle && RegisterFilter.IsFilterPreprocessCondition(this))
{
return !IsRightSkillPreprocessConditionCheck();
}
return false;
}
private bool IsRightSkillPreprocessConditionCheck()
{
if (!(base.PreprocessList.FirstOrDefault((SkillPreprocessBase p) => p is SkillPreprocessConditionCheck) is SkillPreprocessConditionCheck skillPreprocessConditionCheck))
{
return true;
}
return skillPreprocessConditionCheck.GetFilter().VariableCompareFilter.All((SkillVariableComareFilter s) => s.Filtering(base.OptionValue));
}
public override VfxWithLoading Start(CallParameter parameter)
{
int count = base.CostModifierTypeList.Count;
VfxWithLoading result = base.Start(parameter);
List<ICardCostModifier> range = base.CostModifierTypeList.GetRange(count, base.CostModifierTypeList.Count - count);
bool isOpenCard = base.SkillPrm.ownerCard.IsPlayer && base.PreprocessList.FirstOrDefault((SkillPreprocessBase p) => p is SkillPreprocessOpenCard) != null && base.ApplyingTargetFilter is SkillTargetHandSelfFilter;
base.SkillPrm.selfBattlePlayer.CallOnCostChange(base.SkillPrm.ownerCard, _targets, (from c in range
where c is CostAddModifier
select c.Cost).ToList(), (from c in range
where c is CostSetModifier
select c.Cost).ToList(), _isCostUpList, IsHalfRoundUpCostSkill(SkillFilterCreator.ContentKeyword.add), OnWhenSpellChargeStart != 0, isOpenCard);
return result;
}
public override VfxWithLoading Stop(SkillProcessor skillProcessor)
{
base.SkillPrm.ownerCard.SelfBattlePlayer.CallOnRemoveCostChange(buffInfoContainer, OnWhenSpellChargeStart != 0, _addValue == int.MinValue || _addValue <= 0);
return base.Stop(skillProcessor);
}
}