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>
647 lines
11 KiB
C#
647 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
[ExecuteInEditMode]
|
|
[AddComponentMenu("NGUI/UI/NGUI Font")]
|
|
public class UIFont : MonoBehaviour
|
|
{
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private Material mMat;
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private Rect mUVRect = new Rect(0f, 0f, 1f, 1f);
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private BMFont mFont = new BMFont();
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private UIAtlas mAtlas;
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private UIFont mReplacement;
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private List<BMSymbol> mSymbols = new List<BMSymbol>();
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private Font mDynamicFont;
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private int mDynamicFontSize = 16;
|
|
|
|
[HideInInspector]
|
|
[SerializeField]
|
|
private FontStyle mDynamicFontStyle;
|
|
|
|
[NonSerialized]
|
|
private UISpriteData mSprite;
|
|
|
|
private int mPMA = -1;
|
|
|
|
private int mPacked = -1;
|
|
|
|
public BMFont bmFont
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mFont;
|
|
}
|
|
return mReplacement.bmFont;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.bmFont = value;
|
|
}
|
|
else
|
|
{
|
|
mFont = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int texWidth
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
if (mFont == null)
|
|
{
|
|
return 1;
|
|
}
|
|
return mFont.texWidth;
|
|
}
|
|
return mReplacement.texWidth;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.texWidth = value;
|
|
}
|
|
else if (mFont != null)
|
|
{
|
|
mFont.texWidth = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int texHeight
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
if (mFont == null)
|
|
{
|
|
return 1;
|
|
}
|
|
return mFont.texHeight;
|
|
}
|
|
return mReplacement.texHeight;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.texHeight = value;
|
|
}
|
|
else if (mFont != null)
|
|
{
|
|
mFont.texHeight = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool hasSymbols
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
if (mSymbols != null)
|
|
{
|
|
return mSymbols.Count != 0;
|
|
}
|
|
return false;
|
|
}
|
|
return mReplacement.hasSymbols;
|
|
}
|
|
}
|
|
|
|
public List<BMSymbol> symbols
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mSymbols;
|
|
}
|
|
return mReplacement.symbols;
|
|
}
|
|
}
|
|
|
|
public UIAtlas atlas
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mAtlas;
|
|
}
|
|
return mReplacement.atlas;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.atlas = value;
|
|
}
|
|
else
|
|
{
|
|
if (!(mAtlas != value))
|
|
{
|
|
return;
|
|
}
|
|
mPMA = -1;
|
|
mAtlas = value;
|
|
if (mAtlas != null)
|
|
{
|
|
mMat = mAtlas.spriteMaterial;
|
|
if (sprite != null)
|
|
{
|
|
mUVRect = uvRect;
|
|
}
|
|
}
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Material material
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.material;
|
|
}
|
|
if (mAtlas != null)
|
|
{
|
|
return mAtlas.spriteMaterial;
|
|
}
|
|
if (mMat != null)
|
|
{
|
|
if (mDynamicFont != null && mMat != mDynamicFont.material)
|
|
{
|
|
mMat.mainTexture = mDynamicFont.material.mainTexture;
|
|
}
|
|
return mMat;
|
|
}
|
|
if (mDynamicFont != null)
|
|
{
|
|
return mDynamicFont.material;
|
|
}
|
|
return null;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.material = value;
|
|
}
|
|
else if (mMat != value)
|
|
{
|
|
mPMA = -1;
|
|
mMat = value;
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool premultipliedAlphaShader
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.premultipliedAlphaShader;
|
|
}
|
|
if (mAtlas != null)
|
|
{
|
|
return mAtlas.premultipliedAlpha;
|
|
}
|
|
if (mPMA == -1)
|
|
{
|
|
Material material = this.material;
|
|
mPMA = ((material != null && material.shader != null && material.shader.name.Contains("Premultiplied")) ? 1 : 0);
|
|
}
|
|
return mPMA == 1;
|
|
}
|
|
}
|
|
|
|
public bool packedFontShader
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.packedFontShader;
|
|
}
|
|
if (mAtlas != null)
|
|
{
|
|
return false;
|
|
}
|
|
if (mPacked == -1)
|
|
{
|
|
Material material = this.material;
|
|
mPacked = ((material != null && material.shader != null && material.shader.name.Contains("Packed")) ? 1 : 0);
|
|
}
|
|
return mPacked == 1;
|
|
}
|
|
}
|
|
|
|
public Texture2D texture
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.texture;
|
|
}
|
|
Material material = this.material;
|
|
if (!(material != null))
|
|
{
|
|
return null;
|
|
}
|
|
return material.mainTexture as Texture2D;
|
|
}
|
|
}
|
|
|
|
public Rect uvRect
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.uvRect;
|
|
}
|
|
if (!(mAtlas != null) || sprite == null)
|
|
{
|
|
return new Rect(0f, 0f, 1f, 1f);
|
|
}
|
|
return mUVRect;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.uvRect = value;
|
|
}
|
|
else if (sprite == null && mUVRect != value)
|
|
{
|
|
mUVRect = value;
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public string spriteName
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mFont.spriteName;
|
|
}
|
|
return mReplacement.spriteName;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.spriteName = value;
|
|
}
|
|
else if (mFont.spriteName != value)
|
|
{
|
|
mFont.spriteName = value;
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int defaultSize
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.defaultSize;
|
|
}
|
|
if (isDynamic || mFont == null)
|
|
{
|
|
return mDynamicFontSize;
|
|
}
|
|
return mFont.charSize;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.defaultSize = value;
|
|
}
|
|
else
|
|
{
|
|
mDynamicFontSize = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public UISpriteData sprite
|
|
{
|
|
get
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
return mReplacement.sprite;
|
|
}
|
|
if (mSprite == null && mAtlas != null && !string.IsNullOrEmpty(mFont.spriteName))
|
|
{
|
|
mSprite = mAtlas.GetSprite(mFont.spriteName);
|
|
if (mSprite == null)
|
|
{
|
|
mSprite = mAtlas.GetSprite(base.name);
|
|
}
|
|
if (mSprite == null)
|
|
{
|
|
mFont.spriteName = null;
|
|
}
|
|
else
|
|
{
|
|
UpdateUVRect();
|
|
}
|
|
int i = 0;
|
|
for (int count = mSymbols.Count; i < count; i++)
|
|
{
|
|
symbols[i].MarkAsChanged();
|
|
}
|
|
}
|
|
return mSprite;
|
|
}
|
|
}
|
|
|
|
public UIFont replacement
|
|
{
|
|
get
|
|
{
|
|
return mReplacement;
|
|
}
|
|
set
|
|
{
|
|
UIFont uIFont = value;
|
|
if (uIFont == this)
|
|
{
|
|
uIFont = null;
|
|
}
|
|
if (mReplacement != uIFont)
|
|
{
|
|
if (uIFont != null && uIFont.replacement == this)
|
|
{
|
|
uIFont.replacement = null;
|
|
}
|
|
if (mReplacement != null)
|
|
{
|
|
MarkAsChanged();
|
|
}
|
|
mReplacement = uIFont;
|
|
if (uIFont != null)
|
|
{
|
|
mPMA = -1;
|
|
mMat = null;
|
|
mFont = null;
|
|
mDynamicFont = null;
|
|
}
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool isDynamic
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mDynamicFont != null;
|
|
}
|
|
return mReplacement.isDynamic;
|
|
}
|
|
}
|
|
|
|
public Font dynamicFont
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mDynamicFont;
|
|
}
|
|
return mReplacement.dynamicFont;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.dynamicFont = value;
|
|
}
|
|
else if (mDynamicFont != value)
|
|
{
|
|
if (mDynamicFont != null)
|
|
{
|
|
material = null;
|
|
}
|
|
mDynamicFont = value;
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public FontStyle dynamicFontStyle
|
|
{
|
|
get
|
|
{
|
|
if (!(mReplacement != null))
|
|
{
|
|
return mDynamicFontStyle;
|
|
}
|
|
return mReplacement.dynamicFontStyle;
|
|
}
|
|
set
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.dynamicFontStyle = value;
|
|
}
|
|
else if (mDynamicFontStyle != value)
|
|
{
|
|
mDynamicFontStyle = value;
|
|
MarkAsChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private Texture dynamicTexture
|
|
{
|
|
get
|
|
{
|
|
if ((bool)mReplacement)
|
|
{
|
|
return mReplacement.dynamicTexture;
|
|
}
|
|
if (isDynamic)
|
|
{
|
|
return mDynamicFont.material.mainTexture;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void Trim()
|
|
{
|
|
if (mAtlas.texture != null && mSprite != null)
|
|
{
|
|
Rect rect = NGUIMath.ConvertToPixels(mUVRect, texture.width, texture.height, round: true);
|
|
Rect rect2 = new Rect(mSprite.x, mSprite.y, mSprite.width, mSprite.height);
|
|
int xMin = Mathf.RoundToInt(rect2.xMin - rect.xMin);
|
|
int yMin = Mathf.RoundToInt(rect2.yMin - rect.yMin);
|
|
int xMax = Mathf.RoundToInt(rect2.xMax - rect.xMin);
|
|
int yMax = Mathf.RoundToInt(rect2.yMax - rect.yMin);
|
|
mFont.Trim(xMin, yMin, xMax, yMax);
|
|
}
|
|
}
|
|
|
|
private bool References(UIFont font)
|
|
{
|
|
if (font == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (font == this)
|
|
{
|
|
return true;
|
|
}
|
|
if (!(mReplacement != null))
|
|
{
|
|
return false;
|
|
}
|
|
return mReplacement.References(font);
|
|
}
|
|
|
|
public static bool CheckIfRelated(UIFont a, UIFont b)
|
|
{
|
|
if (a == null || b == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (a.isDynamic && b.isDynamic && a.dynamicFont.fontNames[0] == b.dynamicFont.fontNames[0])
|
|
{
|
|
return true;
|
|
}
|
|
if (!(a == b) && !a.References(b))
|
|
{
|
|
return b.References(a);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void MarkAsChanged()
|
|
{
|
|
if (mReplacement != null)
|
|
{
|
|
mReplacement.MarkAsChanged();
|
|
}
|
|
mSprite = null;
|
|
UILabel[] array = NGUITools.FindActive<UILabel>();
|
|
int i = 0;
|
|
for (int num = array.Length; i < num; i++)
|
|
{
|
|
UILabel uILabel = array[i];
|
|
if (uILabel.enabled && NGUITools.GetActive(uILabel.gameObject) && CheckIfRelated(this, uILabel.bitmapFont))
|
|
{
|
|
UIFont bitmapFont = uILabel.bitmapFont;
|
|
uILabel.bitmapFont = null;
|
|
uILabel.bitmapFont = bitmapFont;
|
|
}
|
|
}
|
|
int j = 0;
|
|
for (int count = symbols.Count; j < count; j++)
|
|
{
|
|
symbols[j].MarkAsChanged();
|
|
}
|
|
}
|
|
|
|
public void UpdateUVRect()
|
|
{
|
|
if (mAtlas == null)
|
|
{
|
|
return;
|
|
}
|
|
Texture texture = mAtlas.texture;
|
|
if (texture != null)
|
|
{
|
|
mUVRect = new Rect(mSprite.x - mSprite.paddingLeft, mSprite.y - mSprite.paddingTop, mSprite.width + mSprite.paddingLeft + mSprite.paddingRight, mSprite.height + mSprite.paddingTop + mSprite.paddingBottom);
|
|
mUVRect = NGUIMath.ConvertToTexCoords(mUVRect, texture.width, texture.height);
|
|
if (mSprite.hasPadding)
|
|
{
|
|
Trim();
|
|
}
|
|
}
|
|
}
|
|
|
|
public BMSymbol MatchSymbol(string text, int offset, int textLength)
|
|
{
|
|
int count = mSymbols.Count;
|
|
if (count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
textLength -= offset;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
BMSymbol bMSymbol = mSymbols[i];
|
|
int length = bMSymbol.length;
|
|
if (length == 0 || textLength < length)
|
|
{
|
|
continue;
|
|
}
|
|
bool flag = true;
|
|
for (int j = 0; j < length; j++)
|
|
{
|
|
if (text[offset + j] != bMSymbol.sequence[j])
|
|
{
|
|
flag = false;
|
|
break;
|
|
}
|
|
}
|
|
if (flag && bMSymbol.Validate(atlas))
|
|
{
|
|
return bMSymbol;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|