Commit 2a369a96 authored by Senator John's avatar Senator John 💬

Merge branch 'patch' into 'master'

Bugs Fixed

See merge request !27
parents 58147aea fc945e10
...@@ -384,7 +384,7 @@ MonoBehaviour: ...@@ -384,7 +384,7 @@ MonoBehaviour:
m_Elasticity: 0.1 m_Elasticity: 0.1
m_Inertia: 1 m_Inertia: 1
m_DecelerationRate: 0.135 m_DecelerationRate: 0.135
m_ScrollSensitivity: 1000 m_ScrollSensitivity: 10
m_Viewport: {fileID: 277287946404074816} m_Viewport: {fileID: 277287946404074816}
m_HorizontalScrollbar: {fileID: 0} m_HorizontalScrollbar: {fileID: 0}
m_VerticalScrollbar: {fileID: 277287947542810653} m_VerticalScrollbar: {fileID: 277287947542810653}
......
...@@ -494,6 +494,17 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -494,6 +494,17 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
private RectMask2D _overFrameSideClipL; private RectMask2D _overFrameSideClipL;
private RectMask2D _overFrameSideClipR; private RectMask2D _overFrameSideClipR;
// Pendulum-only extra continuation clips for the left/right scale parchments.
private RectTransform _overFramePendulumCenterClipRt;
private RectMask2D _overFramePendulumCenterClip;
private RawImage _overFramePendulumCenterArt;
private RectTransform _overFramePendulumScaleClipL_Rt;
private RectTransform _overFramePendulumScaleClipR_Rt;
private RectMask2D _overFramePendulumScaleClipL;
private RectMask2D _overFramePendulumScaleClipR;
private RawImage _overFramePendulumScaleArtL;
private RawImage _overFramePendulumScaleArtR;
// How strong the base-art continuation is inside the parchment // How strong the base-art continuation is inside the parchment
// Tuned to match Konami proxy readability in the effect box (less muddy continuation). // Tuned to match Konami proxy readability in the effect box (less muddy continuation).
private const float OverFrameTextBgAlpha = 0.00f; private const float OverFrameTextBgAlpha = 0.00f;
...@@ -511,11 +522,19 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -511,11 +522,19 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
private const float OverFrameFadeTopAlpha = 0.05f; private const float OverFrameFadeTopAlpha = 0.05f;
private const float OverFrameFadeBottomAlpha = 0.11f; private const float OverFrameFadeBottomAlpha = 0.11f;
private const float OverFramePendulumFadeTopAlpha = 0.025f;
private const float OverFramePendulumFadeBottomAlpha = 0.055f;
// When the parchment background Image exists, use it as the wash overlay (looks like the proxy, not a flat rectangle) // When the parchment background Image exists, use it as the wash overlay (looks like the proxy, not a flat rectangle)
private const bool OverFramePreferSpriteWash = true; private const bool OverFramePreferSpriteWash = true;
private const bool OverFrameEnableWashOverlay = true; private const bool OverFrameEnableWashOverlay = true;
private const float OverFrameWashSpriteAlpha = 0.12f; private const float OverFrameWashSpriteAlpha = 0.12f;
private const float OverFramePendulumWashSpriteAlpha = 0.07f;
// Pendulum-only extra fade overlays for left/right scale parchment zones.
private VerticalGradientGraphic _overFrameEffectFadePendulumC;
private VerticalGradientGraphic _overFrameEffectFadePendulumL;
private VerticalGradientGraphic _overFrameEffectFadePendulumR;
private const float OverFrameFadePadTop = 0f; private const float OverFrameFadePadTop = 0f;
private const float OverFrameFadePadBottom = 0f; private const float OverFrameFadePadBottom = 0f;
...@@ -538,13 +557,22 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -538,13 +557,22 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
if (_overFrameTextClipRt) _overFrameTextClipRt.gameObject.SetActive(false); if (_overFrameTextClipRt) _overFrameTextClipRt.gameObject.SetActive(false);
if (_overFrameArtSideL) _overFrameArtSideL.gameObject.SetActive(false); if (_overFrameArtSideL) _overFrameArtSideL.gameObject.SetActive(false);
if (_overFrameArtSideR) _overFrameArtSideR.gameObject.SetActive(false); if (_overFrameArtSideR) _overFrameArtSideR.gameObject.SetActive(false);
if (_overFramePendulumCenterArt) _overFramePendulumCenterArt.gameObject.SetActive(false);
if (_overFramePendulumScaleArtL) _overFramePendulumScaleArtL.gameObject.SetActive(false);
if (_overFramePendulumScaleArtR) _overFramePendulumScaleArtR.gameObject.SetActive(false);
if (_overFrameSideClipL_Rt) _overFrameSideClipL_Rt.gameObject.SetActive(false); if (_overFrameSideClipL_Rt) _overFrameSideClipL_Rt.gameObject.SetActive(false);
if (_overFrameSideClipR_Rt) _overFrameSideClipR_Rt.gameObject.SetActive(false); if (_overFrameSideClipR_Rt) _overFrameSideClipR_Rt.gameObject.SetActive(false);
if (_overFramePendulumCenterClipRt) _overFramePendulumCenterClipRt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipL_Rt) _overFramePendulumScaleClipL_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipR_Rt) _overFramePendulumScaleClipR_Rt.gameObject.SetActive(false);
if (_overFrameEffectFadeMaskRt) _overFrameEffectFadeMaskRt.gameObject.SetActive(false); if (_overFrameEffectFadeMaskRt) _overFrameEffectFadeMaskRt.gameObject.SetActive(false);
if (_overFrameEffectFade) _overFrameEffectFade.gameObject.SetActive(false); if (_overFrameEffectFade) _overFrameEffectFade.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumC) _overFrameEffectFadePendulumC.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumL) _overFrameEffectFadePendulumL.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumR) _overFrameEffectFadePendulumR.gameObject.SetActive(false);
} }
...@@ -561,6 +589,14 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -561,6 +589,14 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
return cardArt; return cardArt;
} }
private bool IsPendulumArtImage(RawImage artImage)
{
return artImage != null &&
(artImage == cardArtPendulum ||
artImage == cardArtPendulumSquare ||
artImage == cardArtPendulumWidth);
}
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
// Helper: move overlay out of Mask / RectMask2D so it can extend past art window // Helper: move overlay out of Mask / RectMask2D so it can extend past art window
...@@ -667,20 +703,29 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -667,20 +703,29 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
private Text GetActiveOcgDescriptionText() private Text GetActiveOcgDescriptionText()
{ {
// Pendulum cards keep both description texts active; prefer the pendulum zone when it has content.
if (cardDescriptionPendulum != null &&
cardDescriptionPendulum.gameObject.activeInHierarchy &&
!string.IsNullOrEmpty(cardDescriptionPendulum.text))
return cardDescriptionPendulum;
if (cardDescription != null && cardDescription.gameObject.activeInHierarchy) return cardDescription; if (cardDescription != null && cardDescription.gameObject.activeInHierarchy) return cardDescription;
if (cardDescriptionPendulum != null && cardDescriptionPendulum.gameObject.activeInHierarchy) return cardDescriptionPendulum; if (cardDescriptionPendulum != null && cardDescriptionPendulum.gameObject.activeInHierarchy) return cardDescriptionPendulum;
return cardDescription; return cardDescription;
} }
private RectTransform GetEffectBoxRectTransform() private RectTransform GetEffectBoxRectTransform(Text preferredDesc = null, bool allowExplicitOverride = true, float minAreaOverTextFactor = 1.05f)
{ {
// 1) Explicit override (preferred): the REAL parchment Image rect. // 1) Explicit override (preferred): the REAL parchment Image rect.
// This prevents accidentally masking with TMP text rects / padding containers. // This prevents accidentally masking with TMP text rects / padding containers.
if (overFrameEffectBoxImage != null && overFrameEffectBoxImage.gameObject != null && overFrameEffectBoxImage.gameObject.activeInHierarchy) if (allowExplicitOverride &&
overFrameEffectBoxImage != null &&
overFrameEffectBoxImage.gameObject != null &&
overFrameEffectBoxImage.gameObject.activeInHierarchy)
return overFrameEffectBoxImage.rectTransform; return overFrameEffectBoxImage.rectTransform;
var desc = GetActiveOcgDescriptionText(); var desc = preferredDesc ?? GetActiveOcgDescriptionText();
if (desc == null) return null; if (desc == null) return null;
var descRt = desc.GetComponent<RectTransform>(); var descRt = desc.GetComponent<RectTransform>();
...@@ -758,7 +803,7 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -758,7 +803,7 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
float area = Mathf.Max(0.0001f, (oMaxX - oMinX) * (oMaxY - oMinY)); float area = Mathf.Max(0.0001f, (oMaxX - oMinX) * (oMaxY - oMinY));
// Must be meaningfully larger than text rect, but not huge // Must be meaningfully larger than text rect, but not huge
if (area < dArea * 1.05f) continue; if (area < dArea * minAreaOverTextFactor) continue;
if (area > maxArea) continue; if (area > maxArea) continue;
bool bordered = img.type == Image.Type.Sliced; bool bordered = img.type == Image.Type.Sliced;
...@@ -910,17 +955,545 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo ...@@ -910,17 +955,545 @@ public OverFrameEffectBoxRectMode overFrameEffectBoxRectMode = OverFrameEffectBo
return true; return true;
} }
// Converts an arbitrary RectTransform world-rect into frame-normalized coordinates [0..1].
// Unlike TryGetEffectBoxNormalizedRect, this has no sanity filter and is used for pendulum-specific regions.
private static bool TryGetRectNormalizedInFrame(RectTransform frameRt, RectTransform sourceRt, out Rect rectNrm)
{
rectNrm = default;
if (frameRt == null || sourceRt == null) return false;
var fc = new Vector3[4];
frameRt.GetWorldCorners(fc); // 0=BL,1=TL,2=TR,3=BR
Vector3 bl = fc[0];
Vector3 br = fc[3];
Vector3 tl = fc[1];
Vector3 w = br - bl;
Vector3 h = tl - bl;
float wLen2 = Vector3.Dot(w, w);
float hLen2 = Vector3.Dot(h, h);
if (wLen2 < 1e-6f || hLen2 < 1e-6f) return false;
var sc = new Vector3[4];
sourceRt.GetWorldCorners(sc);
float minX = 999f, minY = 999f;
float maxX = -999f, maxY = -999f;
for (int i = 0; i < 4; i++)
{
Vector3 rel = sc[i] - bl;
float nx = Vector3.Dot(rel, w) / wLen2;
float ny = Vector3.Dot(rel, h) / hLen2;
minX = Mathf.Min(minX, nx);
minY = Mathf.Min(minY, ny);
maxX = Mathf.Max(maxX, nx);
maxY = Mathf.Max(maxY, ny);
}
minX = Mathf.Clamp01(minX);
minY = Mathf.Clamp01(minY);
maxX = Mathf.Clamp01(maxX);
maxY = Mathf.Clamp01(maxY);
float width = Mathf.Max(0f, maxX - minX);
float height = Mathf.Max(0f, maxY - minY);
if (width < 0.001f || height < 0.001f) return false;
rectNrm = new Rect(minX, minY, width, height);
return true;
}
private static Rect ExpandNormalizedRect(Rect rectNrm, float padLeft, float padRight, float padTop, float padBottom)
{
float xMin = Mathf.Clamp01(rectNrm.xMin - padLeft);
float xMax = Mathf.Clamp01(rectNrm.xMin + rectNrm.width + padRight);
float yMin = Mathf.Clamp01(rectNrm.yMin - padBottom);
float yMax = Mathf.Clamp01(rectNrm.yMin + rectNrm.height + padTop);
float w = Mathf.Max(0f, xMax - xMin);
float h = Mathf.Max(0f, yMax - yMin);
return new Rect(xMin, yMin, w, h);
}
private bool TryGetImageBackedZoneNormalized(
RectTransform frameRt,
Text zoneText,
out RectTransform zoneBoxRt,
out Rect zoneNrm)
{
zoneBoxRt = null;
zoneNrm = default;
if (frameRt == null || zoneText == null || !zoneText.gameObject.activeInHierarchy)
return false;
float minAreaFactor = (zoneText == lScale || zoneText == rScale) ? 0.92f : 1.02f;
var candidateRt = GetEffectBoxRectTransform(zoneText, false, minAreaFactor);
if (candidateRt == null) return false;
var img = candidateRt.GetComponent<Image>();
if (img == null || img.sprite == null) return false;
if (!TryGetRectNormalizedInFrame(frameRt, candidateRt, out zoneNrm))
return false;
if (zoneNrm.width <= 0.001f || zoneNrm.height <= 0.001f)
return false;
zoneBoxRt = candidateRt;
return true;
}
private static bool IsLikelyMatchingPendulumZone(Rect fallbackZoneNrm, Rect imageZoneNrm)
{
if (fallbackZoneNrm.width <= 0.001f || fallbackZoneNrm.height <= 0.001f ||
imageZoneNrm.width <= 0.001f || imageZoneNrm.height <= 0.001f)
return false;
float widthRatio = imageZoneNrm.width / Mathf.Max(0.0001f, fallbackZoneNrm.width);
float heightRatio = imageZoneNrm.height / Mathf.Max(0.0001f, fallbackZoneNrm.height);
if (widthRatio < 0.55f || widthRatio > 1.12f ||
heightRatio < 0.55f || heightRatio > 1.20f)
return false;
float ixMin = Mathf.Max(fallbackZoneNrm.xMin, imageZoneNrm.xMin);
float iyMin = Mathf.Max(fallbackZoneNrm.yMin, imageZoneNrm.yMin);
float ixMax = Mathf.Min(fallbackZoneNrm.xMax, imageZoneNrm.xMax);
float iyMax = Mathf.Min(fallbackZoneNrm.yMax, imageZoneNrm.yMax);
float iArea = Mathf.Max(0f, ixMax - ixMin) * Mathf.Max(0f, iyMax - iyMin);
float baseArea = Mathf.Max(0.0001f, Mathf.Min(
fallbackZoneNrm.width * fallbackZoneNrm.height,
imageZoneNrm.width * imageZoneNrm.height));
float overlapRatio = iArea / baseArea;
float fallbackCx = 0.5f * (fallbackZoneNrm.xMin + fallbackZoneNrm.xMax);
float imageCx = 0.5f * (imageZoneNrm.xMin + imageZoneNrm.xMax);
float fallbackCy = 0.5f * (fallbackZoneNrm.yMin + fallbackZoneNrm.yMax);
float imageCy = 0.5f * (imageZoneNrm.yMin + imageZoneNrm.yMax);
return overlapRatio >= 0.40f &&
Mathf.Abs(fallbackCx - imageCx) <= 0.14f &&
Mathf.Abs(fallbackCy - imageCy) <= 0.07f;
}
private bool TryGetPendulumParchmentZonesNormalized(
RectTransform frameRt,
out Rect centerZoneNrm,
out Rect leftScaleZoneNrm,
out Rect rightScaleZoneNrm,
out Rect fullBandNrm,
out bool usedImageBoxRects,
out RectTransform centerZoneBoxRt,
out RectTransform leftScaleZoneBoxRt,
out RectTransform rightScaleZoneBoxRt,
float separatorGapNrm)
{
centerZoneNrm = default;
leftScaleZoneNrm = default;
rightScaleZoneNrm = default;
fullBandNrm = default;
usedImageBoxRects = false;
centerZoneBoxRt = null;
leftScaleZoneBoxRt = null;
rightScaleZoneBoxRt = null;
if (frameRt == null ||
cardDescriptionPendulum == null ||
lScale == null ||
rScale == null ||
!cardDescriptionPendulum.gameObject.activeInHierarchy ||
!lScale.gameObject.activeInHierarchy ||
!rScale.gameObject.activeInHierarchy ||
string.IsNullOrEmpty(cardDescriptionPendulum.text))
return false;
// 1) Prefer REAL parchment Image rects (same behavior as normal overframe seam clipping).
// We intentionally bypass explicit override here because pendulum has separate center/scale parchments.
RectTransform centerBoxRt = null;
RectTransform leftBoxRt = null;
RectTransform rightBoxRt = null;
bool hasCenterBoxRt = TryGetImageBackedZoneNormalized(frameRt, cardDescriptionPendulum, out centerBoxRt, out var centerBoxNrm);
bool hasLeftBoxRt = TryGetImageBackedZoneNormalized(frameRt, lScale, out leftBoxRt, out var leftBoxNrm);
bool hasRightBoxRt = TryGetImageBackedZoneNormalized(frameRt, rScale, out rightBoxRt, out var rightBoxNrm);
if (hasCenterBoxRt && hasLeftBoxRt && hasRightBoxRt)
{
float imgLeftX0 = leftBoxNrm.xMin;
float imgLeftX1 = leftBoxNrm.xMin + leftBoxNrm.width;
float imgCenterX0 = centerBoxNrm.xMin;
float imgCenterX1 = centerBoxNrm.xMin + centerBoxNrm.width;
float imgRightX0 = rightBoxNrm.xMin;
float imgRightX1 = rightBoxNrm.xMin + rightBoxNrm.width;
bool ordered =
imgLeftX0 < imgLeftX1 &&
imgCenterX0 < imgCenterX1 &&
imgRightX0 < imgRightX1 &&
imgLeftX0 < imgCenterX0 &&
imgCenterX1 < imgRightX1;
// Side scale boxes are expected to be narrower than the center pendulum text parchment.
bool shapeLooksLikePendulumParchment =
leftBoxNrm.width < centerBoxNrm.width &&
rightBoxNrm.width < centerBoxNrm.width;
if (ordered && shapeLooksLikePendulumParchment)
{
float imgYMin = Mathf.Min(centerBoxNrm.yMin, Mathf.Min(leftBoxNrm.yMin, rightBoxNrm.yMin));
float imgYMax = Mathf.Max(centerBoxNrm.yMin + centerBoxNrm.height, Mathf.Max(leftBoxNrm.yMin + leftBoxNrm.height, rightBoxNrm.yMin + rightBoxNrm.height));
if (imgYMax > imgYMin)
{
// With real parchment Image rects, use their exact bounds (no midpoint approximation).
// The border-safe inner cut is handled later by clip/fade inset logic.
leftScaleZoneNrm = leftBoxNrm;
centerZoneNrm = centerBoxNrm;
rightScaleZoneNrm = rightBoxNrm;
centerZoneBoxRt = centerBoxRt;
leftScaleZoneBoxRt = leftBoxRt;
rightScaleZoneBoxRt = rightBoxRt;
float imgBandXMin = Mathf.Min(leftScaleZoneNrm.xMin, Mathf.Min(centerZoneNrm.xMin, rightScaleZoneNrm.xMin));
float imgBandXMax = Mathf.Max(leftScaleZoneNrm.xMax, Mathf.Max(centerZoneNrm.xMax, rightScaleZoneNrm.xMax));
fullBandNrm = new Rect(imgBandXMin, imgYMin, imgBandXMax - imgBandXMin, imgYMax - imgYMin);
usedImageBoxRects = true;
return fullBandNrm.width > 0.001f && fullBandNrm.height > 0.001f;
}
}
}
// 2) Fallback: infer parchments from text rects + tuned padding.
if (!TryGetRectNormalizedInFrame(frameRt, cardDescriptionPendulum.rectTransform, out var centerTextNrm) ||
!TryGetRectNormalizedInFrame(frameRt, lScale.rectTransform, out var leftScaleTextNrm) ||
!TryGetRectNormalizedInFrame(frameRt, rScale.rectTransform, out var rightScaleTextNrm))
return false;
Rect center = ExpandNormalizedRect(
centerTextNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadTopNrm,
OverFramePendulumCenterPadBottomNrm);
float yMin = center.yMin;
float yMax = center.yMin + center.height;
if (yMax <= yMin) return false;
float leftX0 = Mathf.Clamp01(leftScaleTextNrm.xMin - OverFramePendulumScalePadOuterNrm);
float rightX1 = Mathf.Clamp01(rightScaleTextNrm.xMin + rightScaleTextNrm.width + OverFramePendulumScalePadOuterNrm);
// In fallback mode, center text rect is narrower than center parchment.
// Derive seam boundaries from the scale box inner edges so fade/clip reaches inner separator walls.
float centerBoundaryLeft = Mathf.Clamp01(leftScaleTextNrm.xMin + leftScaleTextNrm.width + OverFramePendulumScalePadInnerNrm);
float centerBoundaryRight = Mathf.Clamp01(rightScaleTextNrm.xMin - OverFramePendulumScalePadInnerNrm);
if (centerBoundaryRight <= centerBoundaryLeft)
return false;
float separatorHalfGap = Mathf.Max(0f, separatorGapNrm * 0.5f);
float leftX1 = Mathf.Clamp01(centerBoundaryLeft - separatorHalfGap);
float centerX0 = Mathf.Clamp01(centerBoundaryLeft + separatorHalfGap);
float centerX1 = Mathf.Clamp01(centerBoundaryRight - separatorHalfGap);
float rightX0 = Mathf.Clamp01(centerBoundaryRight + separatorHalfGap);
if (leftX1 <= leftX0 || centerX1 <= centerX0 || rightX1 <= rightX0)
return false;
leftScaleZoneNrm = new Rect(leftX0, yMin, leftX1 - leftX0, yMax - yMin);
centerZoneNrm = new Rect(centerX0, yMin, centerX1 - centerX0, yMax - yMin);
rightScaleZoneNrm = new Rect(rightX0, yMin, rightX1 - rightX0, yMax - yMin);
// Use REAL parchment Image rects when they clearly match the intended zone role.
// This prevents accidental "full upper band" picks from collapsing all three zones.
float roleTolX = 10f / 704f;
bool centerBoxRoleValid =
hasCenterBoxRt &&
centerBoxNrm.xMin >= centerBoundaryLeft - roleTolX &&
centerBoxNrm.xMax <= centerBoundaryRight + roleTolX &&
centerBoxNrm.width > 0.04f;
bool leftBoxRoleValid =
hasLeftBoxRt &&
leftBoxNrm.xMax <= centerBoundaryLeft + roleTolX &&
leftBoxNrm.xMin <= centerBoundaryLeft - roleTolX &&
leftBoxNrm.width > 0.02f;
bool rightBoxRoleValid =
hasRightBoxRt &&
rightBoxNrm.xMin >= centerBoundaryRight - roleTolX &&
rightBoxNrm.xMax >= centerBoundaryRight + roleTolX &&
rightBoxNrm.width > 0.02f;
if (centerBoxRoleValid)
{
centerZoneNrm = centerBoxNrm;
centerZoneBoxRt = centerBoxRt;
}
if (leftBoxRoleValid)
{
leftScaleZoneNrm = leftBoxNrm;
leftScaleZoneBoxRt = leftBoxRt;
}
if (rightBoxRoleValid)
{
rightScaleZoneNrm = rightBoxNrm;
rightScaleZoneBoxRt = rightBoxRt;
}
// Lock fallback seam boundaries to adjacent real boxes (when present), otherwise to
// scale-inner fallback boundaries. This removes vertical dead strips at separators.
float seamLeft = centerZoneBoxRt != null
? centerZoneNrm.xMin
: (leftScaleZoneBoxRt != null ? leftScaleZoneNrm.xMax : centerBoundaryLeft);
float seamRight = centerZoneBoxRt != null
? centerZoneNrm.xMax
: (rightScaleZoneBoxRt != null ? rightScaleZoneNrm.xMin : centerBoundaryRight);
seamLeft = Mathf.Clamp01(seamLeft);
seamRight = Mathf.Clamp01(seamRight);
if (seamRight <= seamLeft)
return false;
if (leftScaleZoneBoxRt == null)
leftScaleZoneNrm = new Rect(
leftScaleZoneNrm.xMin,
leftScaleZoneNrm.yMin,
Mathf.Max(0f, seamLeft - leftScaleZoneNrm.xMin),
leftScaleZoneNrm.height);
if (centerZoneBoxRt == null)
centerZoneNrm = new Rect(
seamLeft,
centerZoneNrm.yMin,
Mathf.Max(0f, seamRight - seamLeft),
centerZoneNrm.height);
if (rightScaleZoneBoxRt == null)
rightScaleZoneNrm = new Rect(
seamRight,
rightScaleZoneNrm.yMin,
Mathf.Max(0f, rightScaleZoneNrm.xMax - seamRight),
rightScaleZoneNrm.height);
if (leftScaleZoneNrm.width <= 0.001f || centerZoneNrm.width <= 0.001f || rightScaleZoneNrm.width <= 0.001f)
return false;
usedImageBoxRects = centerZoneBoxRt != null || leftScaleZoneBoxRt != null || rightScaleZoneBoxRt != null;
float bandXMin = Mathf.Min(leftScaleZoneNrm.xMin, Mathf.Min(centerZoneNrm.xMin, rightScaleZoneNrm.xMin));
float bandXMax = Mathf.Max(leftScaleZoneNrm.xMax, Mathf.Max(centerZoneNrm.xMax, rightScaleZoneNrm.xMax));
float bandYMin = Mathf.Min(leftScaleZoneNrm.yMin, Mathf.Min(centerZoneNrm.yMin, rightScaleZoneNrm.yMin));
float bandYMax = Mathf.Max(leftScaleZoneNrm.yMax, Mathf.Max(centerZoneNrm.yMax, rightScaleZoneNrm.yMax));
fullBandNrm = new Rect(bandXMin, bandYMin, bandXMax - bandXMin, bandYMax - bandYMin);
return fullBandNrm.width > 0.001f && fullBandNrm.height > 0.001f;
}
private void UpdateMaskedContinuationClip(
ref RectTransform clipRt,
ref RectMask2D clipMask,
ref RawImage artCopy,
string clipName,
string artName,
Rect zoneNrm,
RectTransform frameRt,
RectTransform clipParentRt,
RectTransform artReferenceRt,
Texture2D tex,
Rect uvRect,
OverFrameSpec spec,
float alpha,
float insetLeft = 0f,
float insetRight = 0f,
float insetTop = 0f,
float insetBottom = 0f,
RectTransform parchmentBoxRt = null,
float borderCutFactor = OverFrameBorderCutFactor,
float borderSafetyInset = OverFrameBorderSafetyInset)
{
bool hasZoneNrm = zoneNrm.width > 0.001f && zoneNrm.height > 0.001f;
bool hasParchmentBox = parchmentBoxRt != null;
bool valid =
tex != null &&
frameRt != null &&
clipParentRt != null &&
artReferenceRt != null &&
(hasZoneNrm || hasParchmentBox);
if (!valid)
{
if (clipRt != null) clipRt.gameObject.SetActive(false);
if (artCopy != null) artCopy.gameObject.SetActive(false);
return;
}
if (clipRt == null)
{
var go = new GameObject(clipName, typeof(RectTransform), typeof(RectMask2D));
go.transform.SetParent(clipParentRt, false);
clipRt = go.GetComponent<RectTransform>();
clipMask = go.GetComponent<RectMask2D>();
}
else if (clipRt.parent != clipParentRt)
{
clipRt.SetParent(clipParentRt, false);
}
clipRt.gameObject.SetActive(true);
if (hasParchmentBox)
MatchRectByWorldCorners(clipRt, parchmentBoxRt, clipParentRt);
else
MatchRectToFrameNormalized(clipRt, frameRt, clipParentRt, zoneNrm);
float borderInsetLeft = 0f;
float borderInsetRight = 0f;
float borderInsetTop = 0f;
float borderInsetBottom = 0f;
if (hasParchmentBox &&
TryGetEffectBoxBorderMidWorld(parchmentBoxRt, out var midLeftW, out var midRightW, out var midTopW, out var midBottomW))
{
midLeftW *= borderCutFactor;
midRightW *= borderCutFactor;
midTopW *= borderCutFactor;
midBottomW *= borderCutFactor;
borderInsetLeft = WorldToLocalX(clipRt, midLeftW) + borderSafetyInset;
borderInsetRight = WorldToLocalX(clipRt, midRightW) + borderSafetyInset;
borderInsetTop = WorldToLocalY(clipRt, midTopW) + borderSafetyInset;
borderInsetBottom = WorldToLocalY(clipRt, midBottomW) + borderSafetyInset;
}
InsetRect(
clipRt,
Mathf.Max(0f, borderInsetLeft + insetLeft),
Mathf.Max(0f, borderInsetRight + insetRight),
Mathf.Max(0f, borderInsetTop + insetTop),
Mathf.Max(0f, borderInsetBottom + insetBottom));
if (artCopy == null && _overFrameArt != null)
{
var clone = Instantiate(_overFrameArt, clipRt);
clone.name = artName;
clone.raycastTarget = false;
artCopy = clone;
var arf = artCopy.GetComponent<AspectRatioFitter>();
if (arf) arf.enabled = false;
}
else if (artCopy != null && artCopy.transform.parent != clipRt)
{
artCopy.transform.SetParent(clipRt, false);
}
if (artCopy == null) return;
artCopy.gameObject.SetActive(true);
artCopy.texture = tex;
artCopy.uvRect = uvRect;
artCopy.color = new Color(1f, 1f, 1f, alpha);
artCopy.maskable = true;
MatchRectByWorldCorners(artCopy.rectTransform, artReferenceRt, clipRt);
artCopy.rectTransform.sizeDelta *= spec.scale;
artCopy.rectTransform.anchoredPosition += spec.offset;
}
private void UpdateGradientFadeZone(
ref VerticalGradientGraphic fadeGraphic,
string objectName,
Rect zoneNrm,
RectTransform frameRt,
RectTransform fadeParentRt,
Color washRgb,
float topAlpha,
float bottomAlpha,
float insetLeft,
float insetRight,
float insetTop,
float insetBottom,
RectTransform parchmentBoxRt = null,
float borderCutFactor = OverFrameBorderCutFactor,
float borderSafetyInset = OverFrameBorderSafetyInset)
{
bool hasZoneNrm = zoneNrm.width > 0.001f && zoneNrm.height > 0.001f;
bool hasParchmentBox = parchmentBoxRt != null;
bool valid =
fadeParentRt != null &&
(hasParchmentBox || (frameRt != null && hasZoneNrm));
if (!valid)
{
if (fadeGraphic != null) fadeGraphic.gameObject.SetActive(false);
return;
}
if (fadeGraphic == null)
{
var go = new GameObject(objectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(VerticalGradientGraphic));
go.transform.SetParent(fadeParentRt, false);
fadeGraphic = go.GetComponent<VerticalGradientGraphic>();
fadeGraphic.raycastTarget = false;
}
else if (fadeGraphic.transform.parent != fadeParentRt)
{
fadeGraphic.transform.SetParent(fadeParentRt, false);
}
fadeGraphic.gameObject.SetActive(true);
fadeGraphic.topColor = new Color(washRgb.r, washRgb.g, washRgb.b, topAlpha);
fadeGraphic.bottomColor = new Color(washRgb.r, washRgb.g, washRgb.b, bottomAlpha);
RectTransform rt = fadeGraphic.rectTransform;
if (hasParchmentBox)
MatchRectByWorldCorners(rt, parchmentBoxRt, fadeParentRt);
else
MatchRectToFrameNormalized(rt, frameRt, fadeParentRt, zoneNrm);
float borderInsetLeft = 0f;
float borderInsetRight = 0f;
float borderInsetTop = 0f;
float borderInsetBottom = 0f;
if (hasParchmentBox &&
TryGetEffectBoxBorderMidWorld(parchmentBoxRt, out var midLeftW, out var midRightW, out var midTopW, out var midBottomW))
{
midLeftW *= borderCutFactor;
midRightW *= borderCutFactor;
midTopW *= borderCutFactor;
midBottomW *= borderCutFactor;
borderInsetLeft = WorldToLocalX(rt, midLeftW) + borderSafetyInset;
borderInsetRight = WorldToLocalX(rt, midRightW) + borderSafetyInset;
borderInsetTop = WorldToLocalY(rt, midTopW) + borderSafetyInset;
borderInsetBottom = WorldToLocalY(rt, midBottomW) + borderSafetyInset;
}
InsetRect(
rt,
Mathf.Max(0f, borderInsetLeft + insetLeft),
Mathf.Max(0f, borderInsetRight + insetRight),
Mathf.Max(0f, borderInsetTop + insetTop),
Mathf.Max(0f, borderInsetBottom + insetBottom));
rt.sizeDelta += new Vector2(OverFrameFadePadSide * 2f, OverFrameFadePadTop + OverFrameFadePadBottom);
rt.anchoredPosition += new Vector2(0f, (OverFrameFadePadTop - OverFrameFadePadBottom) * 0.5f);
}
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
// Effect-box BORDER mid-thickness in WORLD units. // Effect-box BORDER mid-thickness in WORLD units.
// We use the parchment background sprite's 9-slice border to compute where // We use the parchment background sprite's 9-slice border to compute where
// the proxy cuts the art: right in the MIDDLE of the border thickness. // the proxy cuts the art: right in the MIDDLE of the border thickness.
// Returns mid-border distances (left/right/top/bottom) measured in world units. // Returns mid-border distances (left/right/top/bottom) measured in world units.
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
private bool TryGetEffectBoxBorderMidWorld(out float midLeftW, out float midRightW, out float midTopW, out float midBottomW) private bool TryGetEffectBoxBorderMidWorld(
RectTransform boxRt,
out float midLeftW,
out float midRightW,
out float midTopW,
out float midBottomW)
{ {
midLeftW = midRightW = midTopW = midBottomW = 0f; midLeftW = midRightW = midTopW = midBottomW = 0f;
var boxRt = GetEffectBoxRectTransform();
if (boxRt == null) return false; if (boxRt == null) return false;
var img = boxRt.GetComponent<Image>(); var img = boxRt.GetComponent<Image>();
...@@ -956,6 +1529,11 @@ private bool TryGetEffectBoxBorderMidWorld(out float midLeftW, out float midRigh ...@@ -956,6 +1529,11 @@ private bool TryGetEffectBoxBorderMidWorld(out float midLeftW, out float midRigh
return true; return true;
} }
private bool TryGetEffectBoxBorderMidWorld(out float midLeftW, out float midRightW, out float midTopW, out float midBottomW)
{
return TryGetEffectBoxBorderMidWorld(GetEffectBoxRectTransform(), out midLeftW, out midRightW, out midTopW, out midBottomW);
}
private static float WorldToLocalX(RectTransform rt, float worldDist) private static float WorldToLocalX(RectTransform rt, float worldDist)
{ {
if (rt == null) return 0f; if (rt == null) return 0f;
...@@ -1050,27 +1628,24 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1050,27 +1628,24 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Must be: OverFrameArt → FadeOverlay → Text/UI // Must be: OverFrameArt → FadeOverlay → Text/UI
// ──────────────────────────────────────────────── // ────────────────────────────────────────────────
private void ApplyOverFrameEffectBoxFade() private void ApplyOverFrameEffectBoxFade(bool isPendulum)
{ {
if (!OverFrameEnableWashOverlay) if (!OverFrameEnableWashOverlay)
{ {
// Only fade the artwork layers (continuation), never tint the parchment/text. // Only fade the artwork layers (continuation), never tint the parchment/text.
if (_overFrameEffectFadeMaskRt) _overFrameEffectFadeMaskRt.gameObject.SetActive(false); if (_overFrameEffectFadeMaskRt) _overFrameEffectFadeMaskRt.gameObject.SetActive(false);
if (_overFrameEffectFade) _overFrameEffectFade.gameObject.SetActive(false); if (_overFrameEffectFade) _overFrameEffectFade.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumC) _overFrameEffectFadePendulumC.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumL) _overFrameEffectFadePendulumL.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumR) _overFrameEffectFadePendulumR.gameObject.SetActive(false);
return; return;
} }
var desc = GetActiveOcgDescriptionText(); var desc = (isPendulum && cardDescription != null && cardDescription.gameObject.activeInHierarchy)
? cardDescription
: GetActiveOcgDescriptionText();
if (desc == null) return; if (desc == null) return;
bool hasExplicitEffectBox =
overFrameEffectBoxImage != null &&
overFrameEffectBoxImage.gameObject != null &&
overFrameEffectBoxImage.gameObject.activeInHierarchy;
var boxRt = hasExplicitEffectBox ? overFrameEffectBoxImage.rectTransform : GetEffectBoxRectTransform();
if (boxRt == null) return;
// IMPORTANT: parent fade in the SAME UI layer as the description text // IMPORTANT: parent fade in the SAME UI layer as the description text
// (this is the layer that is visible above the artwork). // (this is the layer that is visible above the artwork).
var fadeParentRt = desc.transform.parent as RectTransform; var fadeParentRt = desc.transform.parent as RectTransform;
...@@ -1081,6 +1656,136 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1081,6 +1656,136 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
? new Color(1f, 0f, 1f, 1f) ? new Color(1f, 0f, 1f, 1f)
: new Color(0.93f, 0.86f, 0.74f, 1f); : new Color(0.93f, 0.86f, 0.74f, 1f);
float topA = OverFrameFadeDebugMagenta
? 1f
: (isPendulum ? OverFramePendulumFadeTopAlpha : OverFrameFadeTopAlpha);
float botA = OverFrameFadeDebugMagenta
? 1f
: (isPendulum ? OverFramePendulumFadeBottomAlpha : OverFrameFadeBottomAlpha);
if (isPendulum &&
cardFrame != null &&
cardFrame.gameObject.activeInHierarchy &&
TryGetPendulumParchmentZonesNormalized(
cardFrame.rectTransform,
out var pendCenterNrm,
out var pendLeftNrm,
out var pendRightNrm,
out _,
out _,
out var pendCenterBoxRt,
out var pendLeftBoxRt,
out var pendRightBoxRt,
OverFramePendulumSeparatorGapFadeNrm))
{
if (_overFrameEffectFadeMaskRt != null)
_overFrameEffectFadeMaskRt.gameObject.SetActive(false);
float centerFadeInsetSide = OverFramePendulumCenterFadeInsetSide;
float centerFadeInsetTop = OverFramePendulumCenterFadeInsetTop;
float centerFadeInsetBottom = OverFramePendulumCenterFadeInsetBottom;
float scaleFadeInsetOuter = OverFramePendulumScaleFadeInsetOuter;
float scaleFadeInsetInner = OverFramePendulumScaleFadeInsetInner;
float scaleFadeInsetTop = OverFramePendulumScaleFadeInsetTop;
float scaleFadeInsetBottom = OverFramePendulumScaleFadeInsetBottom;
float fallbackFadeInsetSide = OverFramePendulumZoneFallbackFadeInsetSide;
float fallbackFadeInsetTop = OverFramePendulumZoneFallbackFadeInsetTop;
float fallbackFadeInsetBottom = OverFramePendulumZoneFallbackFadeInsetBottom;
if (pendCenterBoxRt == null)
{
centerFadeInsetSide += fallbackFadeInsetSide;
centerFadeInsetTop += fallbackFadeInsetTop;
centerFadeInsetBottom += fallbackFadeInsetBottom;
}
if (pendLeftBoxRt == null)
{
scaleFadeInsetOuter += fallbackFadeInsetSide;
scaleFadeInsetInner += fallbackFadeInsetSide;
scaleFadeInsetTop += fallbackFadeInsetTop;
scaleFadeInsetBottom += fallbackFadeInsetBottom;
}
if (pendRightBoxRt == null)
{
scaleFadeInsetOuter += fallbackFadeInsetSide;
scaleFadeInsetInner += fallbackFadeInsetSide;
scaleFadeInsetTop += fallbackFadeInsetTop;
scaleFadeInsetBottom += fallbackFadeInsetBottom;
}
UpdateGradientFadeZone(
ref _overFrameEffectFadePendulumC,
"OverFrameEffectFadePendulumC",
pendCenterNrm,
cardFrame.rectTransform,
fadeParentRt,
washRgb,
topA,
botA,
centerFadeInsetSide,
centerFadeInsetSide,
centerFadeInsetTop,
centerFadeInsetBottom,
pendCenterBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
UpdateGradientFadeZone(
ref _overFrameEffectFadePendulumL,
"OverFrameEffectFadePendulumL",
pendLeftNrm,
cardFrame.rectTransform,
fadeParentRt,
washRgb,
topA,
botA,
scaleFadeInsetOuter,
scaleFadeInsetInner,
scaleFadeInsetTop,
scaleFadeInsetBottom,
pendLeftBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
UpdateGradientFadeZone(
ref _overFrameEffectFadePendulumR,
"OverFrameEffectFadePendulumR",
pendRightNrm,
cardFrame.rectTransform,
fadeParentRt,
washRgb,
topA,
botA,
scaleFadeInsetInner,
scaleFadeInsetOuter,
scaleFadeInsetTop,
scaleFadeInsetBottom,
pendRightBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
int fadeIdx = Mathf.Clamp(desc.transform.GetSiblingIndex(), 0, fadeParentRt.childCount - 1);
if (_overFrameEffectFadePendulumC) _overFrameEffectFadePendulumC.rectTransform.SetSiblingIndex(fadeIdx);
if (_overFrameEffectFadePendulumL) _overFrameEffectFadePendulumL.rectTransform.SetSiblingIndex(fadeIdx);
if (_overFrameEffectFadePendulumR) _overFrameEffectFadePendulumR.rectTransform.SetSiblingIndex(fadeIdx);
}
else
{
if (_overFrameEffectFadePendulumC) _overFrameEffectFadePendulumC.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumL) _overFrameEffectFadePendulumL.gameObject.SetActive(false);
if (_overFrameEffectFadePendulumR) _overFrameEffectFadePendulumR.gameObject.SetActive(false);
}
bool hasExplicitEffectBox =
overFrameEffectBoxImage != null &&
overFrameEffectBoxImage.gameObject != null &&
overFrameEffectBoxImage.gameObject.activeInHierarchy;
var boxRt = hasExplicitEffectBox ? overFrameEffectBoxImage.rectTransform : GetEffectBoxRectTransform(desc);
if (boxRt == null) return;
// If we can, use the parchment background sprite itself as the wash overlay. // If we can, use the parchment background sprite itself as the wash overlay.
// This looks like the proxy (corners/edges match) and avoids a flat gray rectangle. // This looks like the proxy (corners/edges match) and avoids a flat gray rectangle.
Image boxImg = hasExplicitEffectBox ? overFrameEffectBoxImage : boxRt.GetComponent<Image>(); Image boxImg = hasExplicitEffectBox ? overFrameEffectBoxImage : boxRt.GetComponent<Image>();
...@@ -1113,7 +1818,9 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1113,7 +1818,9 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
_overFrameEffectFadeMaskImg.type = boxImg.type; _overFrameEffectFadeMaskImg.type = boxImg.type;
_overFrameEffectFadeMaskImg.preserveAspect = boxImg.preserveAspect; _overFrameEffectFadeMaskImg.preserveAspect = boxImg.preserveAspect;
_overFrameEffectFadeMaskImg.fillCenter = boxImg.fillCenter; _overFrameEffectFadeMaskImg.fillCenter = boxImg.fillCenter;
_overFrameEffectFadeMaskImg.color = new Color(washRgb.r, washRgb.g, washRgb.b, OverFrameWashSpriteAlpha);
float washAlpha = isPendulum ? OverFramePendulumWashSpriteAlpha : OverFrameWashSpriteAlpha;
_overFrameEffectFadeMaskImg.color = new Color(washRgb.r, washRgb.g, washRgb.b, washAlpha);
// If an old gradient exists from earlier builds, disable it so ONLY the art is faded. // If an old gradient exists from earlier builds, disable it so ONLY the art is faded.
if (_overFrameEffectFade != null) if (_overFrameEffectFade != null)
...@@ -1147,9 +1854,6 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1147,9 +1854,6 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
rt.anchoredPosition = Vector2.zero; rt.anchoredPosition = Vector2.zero;
rt.sizeDelta = Vector2.zero; rt.sizeDelta = Vector2.zero;
float topA = OverFrameFadeDebugMagenta ? 1f : OverFrameFadeTopAlpha;
float botA = OverFrameFadeDebugMagenta ? 1f : OverFrameFadeBottomAlpha;
_overFrameEffectFade.topColor = new Color(washRgb.r, washRgb.g, washRgb.b, topA); _overFrameEffectFade.topColor = new Color(washRgb.r, washRgb.g, washRgb.b, topA);
_overFrameEffectFade.bottomColor = new Color(washRgb.r, washRgb.g, washRgb.b, botA); _overFrameEffectFade.bottomColor = new Color(washRgb.r, washRgb.g, washRgb.b, botA);
} }
...@@ -1160,8 +1864,21 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1160,8 +1864,21 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
if (cardFrame != null && cardFrame.gameObject.activeInHierarchy) if (cardFrame != null && cardFrame.gameObject.activeInHierarchy)
{ {
Rect boxNrm = OverFrameEffectBoxNrm; Rect boxNrm = OverFrameEffectBoxNrm;
if (hasExplicitEffectBox && TryGetEffectBoxNormalizedRect(cardFrame.rectTransform, out var dynBoxNrm)) if (isPendulum &&
desc == cardDescriptionPendulum &&
TryGetRectNormalizedInFrame(cardFrame.rectTransform, boxRt, out var pendBoxNrm))
{
boxNrm = ExpandNormalizedRect(
pendBoxNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadTopNrm,
OverFramePendulumCenterPadBottomNrm);
}
else if (hasExplicitEffectBox && TryGetEffectBoxNormalizedRect(cardFrame.rectTransform, out var dynBoxNrm))
{
boxNrm = dynBoxNrm; boxNrm = dynBoxNrm;
}
MatchRectToFrameNormalized(targetRt, cardFrame.rectTransform, fadeParentRt, boxNrm); MatchRectToFrameNormalized(targetRt, cardFrame.rectTransform, fadeParentRt, boxNrm);
} }
...@@ -1171,7 +1888,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1171,7 +1888,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
} }
// Inset so we don't tint the parchment border line // Inset so we don't tint the parchment border line
InsetRect(targetRt, OverFrameFadeInsetSide, OverFrameFadeInsetSide, OverFrameFadeInsetTop, OverFrameFadeInsetBottom); float fadeInsetSide = isPendulum ? OverFramePendulumFadeInsetSide : OverFrameFadeInsetSide;
float fadeInsetTop = isPendulum ? OverFramePendulumFadeInsetTop : OverFrameFadeInsetTop;
float fadeInsetBottom = isPendulum ? OverFramePendulumFadeInsetBottom : OverFrameFadeInsetBottom;
InsetRect(targetRt, fadeInsetSide, fadeInsetSide, fadeInsetTop, fadeInsetBottom);
// Expand to match proxy region feel // Expand to match proxy region feel
targetRt.sizeDelta += new Vector2(OverFrameFadePadSide * 2f, OverFrameFadePadTop + OverFrameFadePadBottom); targetRt.sizeDelta += new Vector2(OverFrameFadePadSide * 2f, OverFrameFadePadTop + OverFrameFadePadBottom);
...@@ -1200,10 +1920,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1200,10 +1920,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
); );
private static readonly Rect OverFrameEffectBoxNrm = new Rect( private static readonly Rect OverFrameEffectBoxNrm = new Rect(
0.05397727f, // xMin = 38 / 704 (expanded 14px each side) 0.05965909f, // xMin = 42 / 704
0.0625f, 0.05859375f, // yMin = 60 / 1024
0.89204545f, // width = 628 / 704 (600 + 28) 0.88068182f, // width = 620 / 704
0.18847656f + (OverFrameParchmentTopRaisePx / OverFrameRefH) 0.19238281f + (OverFrameParchmentTopRaisePx / OverFrameRefH) // height = 197 / 1024 (+ top raise)
); );
...@@ -1241,11 +1961,16 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1241,11 +1961,16 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Faint continuation strength inside the parchment box // Faint continuation strength inside the parchment box
// (you already have OverFrameTextArtAlpha; tweak here if desired) // (you already have OverFrameTextArtAlpha; tweak here if desired)
private const float OverFrameTextArtAlpha = 0.36f; private const float OverFrameTextArtAlpha = 0.36f;
private const float OverFramePendulumTextArtAlpha = 0.36f;
private const float OverFramePendulumUpperZoneArtAlpha = OverFramePendulumTextArtAlpha;
private const bool OverFramePendulumUpperUseBaseArtContinuation = false;
// Because the overlay is rendered ABOVE the frame sprite, we must keep the entire // Because the overlay is rendered ABOVE the frame sprite, we must keep the entire
// border thickness clear (otherwise the artwork tints/overlaps the border line). // border thickness clear (otherwise the artwork tints/overlaps the border line).
// 1.0 = cut in the middle of the border, 2.0 = cut at the inner edge (full border). // 1.0 = cut in the middle of the border, 2.0 = cut at the inner edge (full border).
private const float OverFrameBorderCutFactor = 2.0f; private const float OverFrameBorderCutFactor = 2.0f;
private const float OverFramePendulumUpperZoneBorderCutFactor = 2.00f;
private const float OverFramePendulumUpperZoneBorderSafetyInset = 0.25f;
// If the parchment background sprite has no 9-slice border data, // If the parchment background sprite has no 9-slice border data,
// we fall back to a safe local-unit border inset so the artwork never touches the border line. // we fall back to a safe local-unit border inset so the artwork never touches the border line.
...@@ -1254,9 +1979,26 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1254,9 +1979,26 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// When the chosen effect-box RectTransform already represents the INNER fill (border excluded), // When the chosen effect-box RectTransform already represents the INNER fill (border excluded),
// we only apply a tiny inset to avoid texture filtering/half-pixel bleed. // we only apply a tiny inset to avoid texture filtering/half-pixel bleed.
private const float OverFrameInnerRectNoBorderInset = 0.15f; private const float OverFrameInnerRectNoBorderInset = 0.15f;
private const float OverFramePendulumInnerRectNoBorderInset = 0.20f;
// Inner-rect variant for the upper clip nudge (usually 0; the rect is already on the inner edge). // Inner-rect variant for the upper clip nudge (usually 0; the rect is already on the inner edge).
private const float OverFrameUpperClipInsetBottomInner = 0.0f; private const float OverFrameUpperClipInsetBottomInner = 0.0f;
private const float OverFramePendulumUpperClipInsetBottomInner = 0.35f;
// Pendulum center text rect expansion in normalized frame space (704x1024 reference).
// This maps the center parchment box boundaries from the text rect without touching separator lines.
private const float OverFramePendulumCenterPadSideNrm = 8.5f / 704f;
private const float OverFramePendulumCenterPadTopNrm = 8.5f / 1024f;
private const float OverFramePendulumCenterPadBottomNrm = 8f / 1024f;
// Pendulum scale parchment widths inferred from the scale text rects.
private const float OverFramePendulumScalePadOuterNrm = 9f / 704f;
private const float OverFramePendulumScalePadInnerNrm = 2f / 704f;
// Keep separate separator dead-zones for wash-fade vs hard-clip:
// - Fade gap: small, so wash reaches close to inner parchment walls.
// - Clip gap: larger, so art clips hard on delimiter seams.
private const float OverFramePendulumSeparatorGapFadeNrm = 0.00f / 704f;
private const float OverFramePendulumSeparatorGapClipNrm = 0.35f / 704f;
private const float OverFramePendulumUpperFallbackSeamRaiseNrm = 4.20f / 1024f;
// Auto-detection thresholds in normalized frame space (0..1). // Auto-detection thresholds in normalized frame space (0..1).
// These only matter in Auto mode and are generous to handle minor prefab/layout differences. // These only matter in Auto mode and are generous to handle minor prefab/layout differences.
...@@ -1269,26 +2011,59 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1269,26 +2011,59 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// IMPORTANT: // IMPORTANT:
// - The border thickness itself comes from the parchment sprite's 9-slice border (TryGetEffectBoxBorderMidWorld + OverFrameBorderCutFactor). // - The border thickness itself comes from the parchment sprite's 9-slice border (TryGetEffectBoxBorderMidWorld + OverFrameBorderCutFactor).
// - These values are EXTRA "safety" insets added on top, to avoid any texture filtering / half-pixel bleed on the border lines. // - These values are EXTRA "safety" insets added on top, to avoid any texture filtering / half-pixel bleed on the border lines.
private const float OverFrameUpperClipInsetBottom = 1.0f; // pushes the upper hard-stop slightly ABOVE the border line private const float OverFrameUpperClipInsetBottom = 1.20f; // pushes the upper hard-stop slightly ABOVE the border line
private const float OverFrameBorderSafetyInset = 0.75f; // extra shrink to keep border lines perfectly clean private const float OverFrameBorderSafetyInset = 0.75f; // extra shrink to keep border lines perfectly clean
// Extra insets for the continuation clip inside the parchment (usually keep at 0 and tune safety above). // Extra insets for the continuation clip inside the parchment (usually keep at 0 and tune safety above).
private const float OverFrameTextClipInsetLeft = 0.0f; private const float OverFrameTextClipInsetLeft = 0.0f;
private const float OverFrameTextClipInsetRight = 0.0f; private const float OverFrameTextClipInsetRight = 0.0f;
private const float OverFrameTextClipInsetTop = 0.0f; private const float OverFrameTextClipInsetTop = 0.0f;
private const float OverFrameTextClipInsetBottom = 0.0f; private const float OverFrameTextClipInsetBottom = -1.35f;
private const float OverFramePendulumTextClipInsetBottom = -2.10f;
// Side continuation clip insets (preserve outer card border + parchment frame line) // Side continuation clip insets (preserve outer card border + parchment frame line)
private const float OverFrameSideClipInsetOuter = 0.0f; private const float OverFrameSideClipInsetOuter = 0.0f;
private const float OverFrameSideClipInsetInner = 0.0f; private const float OverFrameSideClipInsetInner = 0.0f;
private const float OverFrameSideClipInsetTop = 0.0f; private const float OverFrameSideClipInsetTop = 0.0f;
private const float OverFrameSideClipInsetBottom = 0.0f; private const float OverFrameSideClipInsetBottom = 0.0f;
private const float OverFrameSideContinuationSafetyInset = 0.00f;
private const float OverFramePendulumSideClipInsetOuter = 0.00f;
private const float OverFramePendulumSideContinuationSafetyInset = 0.00f;
// Tiny overlap between split clips to hide 1px seam gaps at left/right. // Tiny overlap between split clips to hide 1px seam gaps at left/right.
private const float OverFrameSplitSeamOverlap = 0.90f; private const float OverFrameSplitSeamOverlap = 0.90f;
private const float OverFramePendulumSplitSeamOverlap = 0.90f;
// Optional: also inset the parchment wash overlay so it doesn't tint the border // Optional: also inset the parchment wash overlay so it doesn't tint the border
private const float OverFrameFadeInsetSide = 0.80f; private const float OverFrameFadeInsetSide = 0.00f;
private const float OverFrameFadeInsetTop = 0.00f; private const float OverFrameFadeInsetTop = 0.00f;
private const float OverFrameFadeInsetBottom = 0.90f; private const float OverFrameFadeInsetBottom = -0.20f;
private const float OverFramePendulumFadeInsetSide = 0.70f;
private const float OverFramePendulumFadeInsetTop = 0.10f;
private const float OverFramePendulumFadeInsetBottom = 0.75f;
// Upper pendulum fade boxes (center + scales) are tuned separately from lower effect-box fade.
private const float OverFramePendulumCenterFadeInsetSide = 0.10f;
private const float OverFramePendulumCenterFadeInsetTop = 0.25f;
private const float OverFramePendulumCenterFadeInsetBottom = 0.00f;
private const float OverFramePendulumScaleFadeInsetOuter = 0.60f;
private const float OverFramePendulumScaleFadeInsetInner = 0.10f;
private const float OverFramePendulumScaleFadeInsetTop = 0.25f;
private const float OverFramePendulumScaleFadeInsetBottom = 0.00f;
// Upper pendulum parchments (center/left-scale/right-scale) use separate clips.
// Keep center and scale seam trims separate so narrow scale boxes don't get over-masked.
private const float OverFramePendulumCenterClipInsetSide = 0.20f;
private const float OverFramePendulumCenterClipInsetTop = 0.45f;
private const float OverFramePendulumCenterClipInsetBottom = 0.00f;
private const float OverFramePendulumScaleClipInsetOuter = 0.60f;
private const float OverFramePendulumScaleClipInsetInner = 0.20f;
private const float OverFramePendulumScaleClipInsetTop = 0.45f;
private const float OverFramePendulumScaleClipInsetBottom = 0.00f;
// Applied when a pendulum upper zone has no dedicated parchment Image rect.
// Split fade vs clip so fade can reach inner walls while clip stays hard/stable.
private const float OverFramePendulumZoneFallbackFadeInsetSide = 0.00f;
private const float OverFramePendulumZoneFallbackFadeInsetTop = 0.00f;
private const float OverFramePendulumZoneFallbackFadeInsetBottom = 0.00f;
private const float OverFramePendulumZoneFallbackClipInsetSide = 0.00f;
private const float OverFramePendulumZoneFallbackClipInsetTop = 0.00f;
private const float OverFramePendulumZoneFallbackClipInsetBottom = 0.00f;
private static void InsetRect(RectTransform rt, float left, float right, float top, float bottom) private static void InsetRect(RectTransform rt, float left, float right, float top, float bottom)
{ {
...@@ -1323,10 +2098,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1323,10 +2098,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
SetRectByWorldBLTR(dst, worldBL, worldTR, dstParent); SetRectByWorldBLTR(dst, worldBL, worldTR, dstParent);
} }
private bool ApplyOverFrameProxySplit(Texture2D tex, RawImage baseArt, OverFrameSpec spec, RectTransform frameRt, Transform anchorParent, RectTransform anchorParentRt) private bool ApplyOverFrameProxySplit(Texture2D tex, RawImage baseArt, OverFrameSpec spec, RectTransform frameRt, Transform anchorParent, RectTransform anchorParentRt, bool isPendulum)
{ {
if (tex == null || frameRt == null || anchorParent == null || anchorParentRt == null) if (tex == null || frameRt == null || anchorParent == null || anchorParentRt == null)
return false; return false;
// Full-card overframe art should track the card frame bounds (same behavior as normal overframe path).
RectTransform splitArtReferenceRt = frameRt;
float splitSeamOverlap = isPendulum ? OverFramePendulumSplitSeamOverlap : OverFrameSplitSeamOverlap;
// Prefer stable reference bounds unless an explicit parchment Image is assigned. // Prefer stable reference bounds unless an explicit parchment Image is assigned.
// This avoids auto-detect picking text/padding rects that shrink the mask width. // This avoids auto-detect picking text/padding rects that shrink the mask width.
bool hasExplicitEffectBox = bool hasExplicitEffectBox =
...@@ -1334,23 +2114,130 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1334,23 +2114,130 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
overFrameEffectBoxImage.gameObject != null && overFrameEffectBoxImage.gameObject != null &&
overFrameEffectBoxImage.gameObject.activeInHierarchy; overFrameEffectBoxImage.gameObject.activeInHierarchy;
// Dynamically derive the parchment/effect-box bounds only from explicit rects. // Dynamically derive the parchment/effect-box bounds.
Rect effectBoxNrm = OverFrameEffectBoxNrm; Rect effectBoxNrm = OverFrameEffectBoxNrm;
Rect effectBoxInnerNrm = OverFrameEffectBoxInnerNrm; Rect effectBoxInnerNrm = OverFrameEffectBoxInnerNrm;
Rect dynEffectBoxNrm = default;
bool haveDynEffectBox = false;
if (hasExplicitEffectBox)
haveDynEffectBox = TryGetEffectBoxNormalizedRect(frameRt, out dynEffectBoxNrm);
if (haveDynEffectBox)
effectBoxNrm = dynEffectBoxNrm;
// IMPORTANT: clip using explicit parchment Image when available.
// Decide whether that rect is OUTER (includes border) or already INNER (border excluded).
var realBoxRt = hasExplicitEffectBox ? overFrameEffectBoxImage.rectTransform : null; var realBoxRt = hasExplicitEffectBox ? overFrameEffectBoxImage.rectTransform : null;
bool effectBoxIsInnerFill = IsEffectBoxRectInnerFill(realBoxRt, effectBoxNrm); bool effectBoxIsInnerFill;
if (realBoxRt == null) effectBoxIsInnerFill = true; Rect pendCenterNrm = default;
float clipSafetyInset = effectBoxIsInnerFill ? OverFrameInnerRectNoBorderInset : OverFrameBorderSafetyInset; Rect pendLeftScaleNrm = default;
Rect pendRightScaleNrm = default;
Rect pendFullBandNrm = default;
bool pendUsedImageBoxRects = false;
RectTransform pendCenterBoxRt = null;
RectTransform pendLeftBoxRt = null;
RectTransform pendRightBoxRt = null;
bool hasPendulumParchmentZones = isPendulum &&
TryGetPendulumParchmentZonesNormalized(
frameRt,
out pendCenterNrm,
out pendLeftScaleNrm,
out pendRightScaleNrm,
out pendFullBandNrm,
out pendUsedImageBoxRects,
out pendCenterBoxRt,
out pendLeftBoxRt,
out pendRightBoxRt,
OverFramePendulumSeparatorGapClipNrm);
if (hasPendulumParchmentZones)
{
effectBoxNrm = pendCenterNrm;
effectBoxInnerNrm = effectBoxNrm;
effectBoxIsInnerFill = true;
realBoxRt = null;
}
else if (isPendulum &&
cardDescriptionPendulum != null &&
cardDescriptionPendulum.gameObject.activeInHierarchy &&
!string.IsNullOrEmpty(cardDescriptionPendulum.text) &&
TryGetRectNormalizedInFrame(frameRt, cardDescriptionPendulum.rectTransform, out var pendCenterFallbackNrm))
{
effectBoxNrm = ExpandNormalizedRect(
pendCenterFallbackNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadSideNrm,
OverFramePendulumCenterPadTopNrm,
OverFramePendulumCenterPadBottomNrm);
effectBoxInnerNrm = effectBoxNrm;
effectBoxIsInnerFill = true;
realBoxRt = null;
}
else
{
Rect dynEffectBoxNrm = default;
bool haveDynEffectBox = false;
if (hasExplicitEffectBox)
haveDynEffectBox = TryGetEffectBoxNormalizedRect(frameRt, out dynEffectBoxNrm);
if (haveDynEffectBox)
effectBoxNrm = dynEffectBoxNrm;
// IMPORTANT: clip using explicit parchment Image when available.
// Decide whether that rect is OUTER (includes border) or already INNER (border excluded).
effectBoxIsInnerFill = IsEffectBoxRectInnerFill(realBoxRt, effectBoxNrm);
if (realBoxRt == null) effectBoxIsInnerFill = true;
}
float yCut = Mathf.Clamp01(effectBoxNrm.yMin + effectBoxNrm.height); float clipSafetyInset = effectBoxIsInnerFill
? (isPendulum ? OverFramePendulumInnerRectNoBorderInset : OverFrameInnerRectNoBorderInset)
: OverFrameBorderSafetyInset;
float yCut;
if (hasPendulumParchmentZones)
{
float pendBandTop = pendFullBandNrm.yMin + pendFullBandNrm.height;
float fallbackSeamRaise = !pendUsedImageBoxRects ? OverFramePendulumUpperFallbackSeamRaiseNrm : 0f;
yCut = Mathf.Clamp01(pendBandTop + fallbackSeamRaise);
// Keep fallback pendulum clip zones touching the moved seam.
// Without this, raising yCut can leave a thin uncovered strip near upper separators.
if (fallbackSeamRaise > 0f)
{
float raisedTop = yCut;
if (raisedTop > (pendCenterNrm.yMin + pendCenterNrm.height))
pendCenterNrm = new Rect(
pendCenterNrm.xMin,
pendCenterNrm.yMin,
pendCenterNrm.width,
Mathf.Max(0f, raisedTop - pendCenterNrm.yMin));
if (raisedTop > (pendLeftScaleNrm.yMin + pendLeftScaleNrm.height))
pendLeftScaleNrm = new Rect(
pendLeftScaleNrm.xMin,
pendLeftScaleNrm.yMin,
pendLeftScaleNrm.width,
Mathf.Max(0f, raisedTop - pendLeftScaleNrm.yMin));
if (raisedTop > (pendRightScaleNrm.yMin + pendRightScaleNrm.height))
pendRightScaleNrm = new Rect(
pendRightScaleNrm.xMin,
pendRightScaleNrm.yMin,
pendRightScaleNrm.width,
Mathf.Max(0f, raisedTop - pendRightScaleNrm.yMin));
if (raisedTop > (effectBoxNrm.yMin + effectBoxNrm.height))
{
effectBoxNrm = new Rect(
effectBoxNrm.xMin,
effectBoxNrm.yMin,
effectBoxNrm.width,
Mathf.Max(0f, raisedTop - effectBoxNrm.yMin));
effectBoxInnerNrm = effectBoxNrm;
}
if (raisedTop > (pendFullBandNrm.yMin + pendFullBandNrm.height))
pendFullBandNrm = new Rect(
pendFullBandNrm.xMin,
pendFullBandNrm.yMin,
pendFullBandNrm.width,
Mathf.Max(0f, raisedTop - pendFullBandNrm.yMin));
}
}
else
{
yCut = Mathf.Clamp01(effectBoxNrm.yMin + effectBoxNrm.height);
}
Rect upperAreaNrm = new Rect(0f, yCut, 1f, Mathf.Max(0f, 1f - yCut)); Rect upperAreaNrm = new Rect(0f, yCut, 1f, Mathf.Max(0f, 1f - yCut));
...@@ -1387,9 +2274,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1387,9 +2274,10 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
MatchRectToFrameNormalized(_overFrameMainClipRt, frameRt, anchorParentRt, upperAreaNrm); MatchRectToFrameNormalized(_overFrameMainClipRt, frameRt, anchorParentRt, upperAreaNrm);
// Inset so we don't tint the parchment border line // Inset so we don't tint the parchment border line
float upperClipInsetBottomInner = isPendulum ? OverFramePendulumUpperClipInsetBottomInner : OverFrameUpperClipInsetBottomInner;
float mainCutLocal = float mainCutLocal =
(effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalY(_overFrameMainClipRt, midTopW) : OverFrameFallbackBorderInset)) (effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalY(_overFrameMainClipRt, midTopW) : OverFrameFallbackBorderInset))
+ (effectBoxIsInnerFill ? OverFrameUpperClipInsetBottomInner : OverFrameUpperClipInsetBottom) + (effectBoxIsInnerFill ? upperClipInsetBottomInner : OverFrameUpperClipInsetBottom)
+ clipSafetyInset; // keep the top parchment border line perfectly clean // keep the top parchment border line perfectly clean + clipSafetyInset; // keep the top parchment border line perfectly clean // keep the top parchment border line perfectly clean
InsetRect(_overFrameMainClipRt, 0f, 0f, 0f, mainCutLocal); InsetRect(_overFrameMainClipRt, 0f, 0f, 0f, mainCutLocal);
...@@ -1403,7 +2291,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1403,7 +2291,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Align main art to full frame (then it gets clipped) // Align main art to full frame (then it gets clipped)
if (_overFrameArt != null) if (_overFrameArt != null)
{ {
MatchRectByWorldCorners(_overFrameArt.rectTransform, frameRt, _overFrameMainClipRt); MatchRectByWorldCorners(_overFrameArt.rectTransform, splitArtReferenceRt, _overFrameMainClipRt);
_overFrameArt.rectTransform.sizeDelta *= spec.scale; _overFrameArt.rectTransform.sizeDelta *= spec.scale;
_overFrameArt.rectTransform.anchoredPosition += spec.offset; _overFrameArt.rectTransform.anchoredPosition += spec.offset;
} }
...@@ -1415,13 +2303,29 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1415,13 +2303,29 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
float boxX0 = effectBoxNrm.xMin; float boxX0 = effectBoxNrm.xMin;
float boxX1 = effectBoxNrm.xMin + effectBoxNrm.width; float boxX1 = effectBoxNrm.xMin + effectBoxNrm.width;
float boxY1 = effectBoxNrm.yMin + effectBoxNrm.height; float boxY1 = effectBoxNrm.yMin + effectBoxNrm.height;
// Side continuation should run from card bottom up to the top of the effect box, // Continue side gutters from card bottom to the current split seam.
// so beams/light on the side are not cut halfway near the footer area. // For pendulum cards this avoids empty left/right zones below the lower parchment box.
float sideY0 = 0f; float sideY0 = 0f;
float sideH = Mathf.Max(0f, boxY1 - sideY0); float sideY1 = boxY1;
float sideH = Mathf.Max(0f, sideY1 - sideY0);
Rect leftSideNrm;
Rect rightSideNrm;
if (hasPendulumParchmentZones)
{
float leftOuterW = Mathf.Max(0f, pendLeftScaleNrm.xMin);
float rightOuterX = pendRightScaleNrm.xMin + pendRightScaleNrm.width;
leftSideNrm = new Rect(0f, sideY0, leftOuterW, sideH);
rightSideNrm = new Rect(rightOuterX, sideY0, Mathf.Max(0f, 1f - rightOuterX), sideH);
}
else
{
leftSideNrm = new Rect(0f, sideY0, Mathf.Max(0f, boxX0), sideH);
rightSideNrm = new Rect(boxX1, sideY0, Mathf.Max(0f, 1f - boxX1), sideH);
}
Rect leftSideNrm = new Rect(0f, sideY0, Mathf.Max(0f, boxX0), sideH); Texture sideTex = tex;
Rect rightSideNrm = new Rect(boxX1, sideY0, Mathf.Max(0f, 1f - boxX1), sideH); Rect sideUv = _overFrameArt != null ? _overFrameArt.uvRect : new Rect(0f, 0f, 1f, 1f);
// Left clip // Left clip
if (leftSideNrm.width > 0.001f && leftSideNrm.height > 0.001f) if (leftSideNrm.width > 0.001f && leftSideNrm.height > 0.001f)
...@@ -1443,13 +2347,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1443,13 +2347,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Inset so the over-art never touches the parchment border lines (left side margin) // Inset so the over-art never touches the parchment border lines (left side margin)
// NOTE: This clip is OUTSIDE the parchment box, so we MUST NOT apply the parchment border thickness here. // NOTE: This clip is OUTSIDE the parchment box, so we MUST NOT apply the parchment border thickness here.
// Applying the fallback border inset (13px) creates visible "empty margins" near the corners. // Applying the fallback border inset (13px) creates visible "empty margins" near the corners.
float sideL_Inner = clipSafetyInset + OverFrameSideClipInsetInner; float sideSafety = isPendulum ? OverFramePendulumSideContinuationSafetyInset : OverFrameSideContinuationSafetyInset;
float sideL_Top = clipSafetyInset + OverFrameSideClipInsetTop; float sideOuterInset = isPendulum ? OverFramePendulumSideClipInsetOuter : OverFrameSideClipInsetOuter;
float sideL_Bottom = clipSafetyInset + OverFrameSideClipInsetBottom; float sideL_Inner = sideSafety + OverFrameSideClipInsetInner;
float sideL_Top = sideSafety + OverFrameSideClipInsetTop;
float sideL_Bottom = sideSafety + OverFrameSideClipInsetBottom;
InsetRect(_overFrameSideClipL_Rt, InsetRect(_overFrameSideClipL_Rt,
OverFrameSideClipInsetOuter, sideOuterInset,
Mathf.Max(0f, sideL_Inner - OverFrameSplitSeamOverlap), Mathf.Max(0f, sideL_Inner - splitSeamOverlap),
Mathf.Max(0f, sideL_Top), Mathf.Max(0f, sideL_Top),
Mathf.Max(0f, sideL_Bottom)); Mathf.Max(0f, sideL_Bottom));
if (_overFrameArtSideL == null) if (_overFrameArtSideL == null)
...@@ -1468,12 +2374,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1468,12 +2374,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
} }
_overFrameArtSideL.gameObject.SetActive(true); _overFrameArtSideL.gameObject.SetActive(true);
_overFrameArtSideL.texture = tex; _overFrameArtSideL.texture = sideTex;
_overFrameArtSideL.uvRect = _overFrameArt != null ? _overFrameArt.uvRect : _overFrameArtSideL.uvRect; _overFrameArtSideL.uvRect = sideUv;
_overFrameArtSideL.color = Color.white; _overFrameArtSideL.color = Color.white;
_overFrameArtSideL.maskable = true; _overFrameArtSideL.maskable = true;
MatchRectByWorldCorners(_overFrameArtSideL.rectTransform, frameRt, _overFrameSideClipL_Rt); MatchRectByWorldCorners(_overFrameArtSideL.rectTransform, splitArtReferenceRt, _overFrameSideClipL_Rt);
_overFrameArtSideL.rectTransform.sizeDelta *= spec.scale; _overFrameArtSideL.rectTransform.sizeDelta *= spec.scale;
_overFrameArtSideL.rectTransform.anchoredPosition += spec.offset; _overFrameArtSideL.rectTransform.anchoredPosition += spec.offset;
} }
...@@ -1503,13 +2409,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1503,13 +2409,15 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Inset so the over-art never touches the parchment border lines (right side margin) // Inset so the over-art never touches the parchment border lines (right side margin)
// NOTE: This clip is OUTSIDE the parchment box, so we MUST NOT apply the parchment border thickness here. // NOTE: This clip is OUTSIDE the parchment box, so we MUST NOT apply the parchment border thickness here.
// Applying the fallback border inset (13px) creates visible "empty margins" near the corners. // Applying the fallback border inset (13px) creates visible "empty margins" near the corners.
float sideR_Inner = clipSafetyInset + OverFrameSideClipInsetInner; float sideSafety = isPendulum ? OverFramePendulumSideContinuationSafetyInset : OverFrameSideContinuationSafetyInset;
float sideR_Top = clipSafetyInset + OverFrameSideClipInsetTop; float sideOuterInset = isPendulum ? OverFramePendulumSideClipInsetOuter : OverFrameSideClipInsetOuter;
float sideR_Bottom = clipSafetyInset + OverFrameSideClipInsetBottom; float sideR_Inner = sideSafety + OverFrameSideClipInsetInner;
float sideR_Top = sideSafety + OverFrameSideClipInsetTop;
float sideR_Bottom = sideSafety + OverFrameSideClipInsetBottom;
InsetRect(_overFrameSideClipR_Rt, InsetRect(_overFrameSideClipR_Rt,
Mathf.Max(0f, sideR_Inner - OverFrameSplitSeamOverlap), Mathf.Max(0f, sideR_Inner - splitSeamOverlap),
OverFrameSideClipInsetOuter, sideOuterInset,
Mathf.Max(0f, sideR_Top), Mathf.Max(0f, sideR_Top),
Mathf.Max(0f, sideR_Bottom)); Mathf.Max(0f, sideR_Bottom));
if (_overFrameArtSideR == null) if (_overFrameArtSideR == null)
...@@ -1528,12 +2436,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1528,12 +2436,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
} }
_overFrameArtSideR.gameObject.SetActive(true); _overFrameArtSideR.gameObject.SetActive(true);
_overFrameArtSideR.texture = tex; _overFrameArtSideR.texture = sideTex;
_overFrameArtSideR.uvRect = _overFrameArt != null ? _overFrameArt.uvRect : _overFrameArtSideR.uvRect; _overFrameArtSideR.uvRect = sideUv;
_overFrameArtSideR.color = Color.white; _overFrameArtSideR.color = Color.white;
_overFrameArtSideR.maskable = true; _overFrameArtSideR.maskable = true;
MatchRectByWorldCorners(_overFrameArtSideR.rectTransform, frameRt, _overFrameSideClipR_Rt); MatchRectByWorldCorners(_overFrameArtSideR.rectTransform, splitArtReferenceRt, _overFrameSideClipR_Rt);
_overFrameArtSideR.rectTransform.sizeDelta *= spec.scale; _overFrameArtSideR.rectTransform.sizeDelta *= spec.scale;
_overFrameArtSideR.rectTransform.anchoredPosition += spec.offset; _overFrameArtSideR.rectTransform.anchoredPosition += spec.offset;
} }
...@@ -1544,11 +2452,94 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1544,11 +2452,94 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
} }
// ── B) TEXT CLIP (continuation inside parchment) ───────────────── // ── B) TEXT CLIP (continuation inside parchment) ─────────────────
var desc = GetActiveOcgDescriptionText(); var desc = (isPendulum && cardDescription != null && cardDescription.gameObject.activeInHierarchy)
if (desc == null) return true; ? cardDescription
: GetActiveOcgDescriptionText();
if (desc == null)
{
if (_overFramePendulumCenterClipRt) _overFramePendulumCenterClipRt.gameObject.SetActive(false);
if (_overFramePendulumCenterArt) _overFramePendulumCenterArt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipL_Rt) _overFramePendulumScaleClipL_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipR_Rt) _overFramePendulumScaleClipR_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleArtL) _overFramePendulumScaleArtL.gameObject.SetActive(false);
if (_overFramePendulumScaleArtR) _overFramePendulumScaleArtR.gameObject.SetActive(false);
return true;
}
var fadeParentRt = desc.transform.parent as RectTransform; var fadeParentRt = desc.transform.parent as RectTransform;
if (fadeParentRt == null) return true; if (fadeParentRt == null)
{
if (_overFramePendulumCenterClipRt) _overFramePendulumCenterClipRt.gameObject.SetActive(false);
if (_overFramePendulumCenterArt) _overFramePendulumCenterArt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipL_Rt) _overFramePendulumScaleClipL_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipR_Rt) _overFramePendulumScaleClipR_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleArtL) _overFramePendulumScaleArtL.gameObject.SetActive(false);
if (_overFramePendulumScaleArtR) _overFramePendulumScaleArtR.gameObject.SetActive(false);
return true;
}
RectTransform textClipBoxRt = realBoxRt;
Rect textClipBoxNrm = effectBoxInnerNrm;
bool textClipIsInnerFill = effectBoxIsInnerFill;
float textClipSafetyInset = clipSafetyInset;
float textMidLeftW = midLeftW;
float textMidRightW = midRightW;
float textMidTopW = midTopW;
float textMidBottomW = midBottomW;
bool textHasBorder = hasBorder;
if (isPendulum)
{
textClipBoxRt = hasExplicitEffectBox ? overFrameEffectBoxImage.rectTransform : GetEffectBoxRectTransform(desc);
bool trustDetectedLowerBox = false;
if (textClipBoxRt != null)
{
if (hasExplicitEffectBox)
{
trustDetectedLowerBox = true;
}
else
{
var detectedImg = textClipBoxRt.GetComponent<Image>();
trustDetectedLowerBox = detectedImg != null && detectedImg.sprite != null;
}
}
if (trustDetectedLowerBox && textClipBoxRt != null && TryGetRectNormalizedInFrame(frameRt, textClipBoxRt, out var pendLowerBoxNrm))
{
// Reject text-rect-like detections: pendulum lower box should be close to normal effect-box width.
bool likelyTooNarrow = pendLowerBoxNrm.width < (OverFrameEffectBoxNrm.width - (8f / 704f));
textClipBoxNrm = pendLowerBoxNrm;
textClipIsInnerFill = likelyTooNarrow ? true : IsEffectBoxRectInnerFill(textClipBoxRt, textClipBoxNrm);
if (likelyTooNarrow && !hasExplicitEffectBox)
{
textClipBoxRt = null;
textClipBoxNrm = OverFrameEffectBoxNrm;
}
}
else
{
textClipBoxRt = null;
textClipBoxNrm = OverFrameEffectBoxNrm;
textClipIsInnerFill = true;
}
textClipSafetyInset = textClipIsInnerFill
? OverFramePendulumInnerRectNoBorderInset
: OverFrameBorderSafetyInset;
textHasBorder = TryGetEffectBoxBorderMidWorld(textClipBoxRt, out textMidLeftW, out textMidRightW, out textMidTopW, out textMidBottomW);
if (textHasBorder)
{
textMidLeftW *= OverFrameBorderCutFactor;
textMidRightW *= OverFrameBorderCutFactor;
textMidTopW *= OverFrameBorderCutFactor;
textMidBottomW *= OverFrameBorderCutFactor;
}
}
if (_overFrameTextClipRt == null) if (_overFrameTextClipRt == null)
{ {
...@@ -1566,27 +2557,28 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1566,27 +2557,28 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Clip area = parchment box (prefer the REAL parchment Image rect when available) // Clip area = parchment box (prefer the REAL parchment Image rect when available)
// realBoxRt already computed above (explicit parchment Image preferred). // realBoxRt already computed above (explicit parchment Image preferred).
if (realBoxRt != null) if (textClipBoxRt != null)
MatchRectByWorldCorners(_overFrameTextClipRt, realBoxRt, fadeParentRt); MatchRectByWorldCorners(_overFrameTextClipRt, textClipBoxRt, fadeParentRt);
else else
MatchRectToFrameNormalized(_overFrameTextClipRt, frameRt, fadeParentRt, effectBoxInnerNrm); MatchRectToFrameNormalized(_overFrameTextClipRt, frameRt, fadeParentRt, textClipBoxNrm);
// Inset so continuation doesn't tint the orange border line. // Inset so continuation doesn't tint the orange border line.
// We cut at the INNER edge of the parchment border (9-slice border * OverFrameBorderCutFactor), // We cut at the INNER edge of the parchment border (9-slice border * OverFrameBorderCutFactor),
// then add a small safety inset to avoid half-pixel filtering bleed. // then add a small safety inset to avoid half-pixel filtering bleed.
float textInsetL = (effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalX(_overFrameTextClipRt, midLeftW) : OverFrameFallbackBorderInset)) float textInsetL = (textClipIsInnerFill ? 0f : (textHasBorder ? WorldToLocalX(_overFrameTextClipRt, textMidLeftW) : OverFrameFallbackBorderInset))
+ clipSafetyInset + OverFrameTextClipInsetLeft; + textClipSafetyInset + OverFrameTextClipInsetLeft;
float textInsetR = (effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalX(_overFrameTextClipRt, midRightW) : OverFrameFallbackBorderInset)) float textInsetR = (textClipIsInnerFill ? 0f : (textHasBorder ? WorldToLocalX(_overFrameTextClipRt, textMidRightW) : OverFrameFallbackBorderInset))
+ clipSafetyInset + OverFrameTextClipInsetRight; + textClipSafetyInset + OverFrameTextClipInsetRight;
float textInsetT = (effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalY(_overFrameTextClipRt, midTopW) : OverFrameFallbackBorderInset)) float textInsetT = (textClipIsInnerFill ? 0f : (textHasBorder ? WorldToLocalY(_overFrameTextClipRt, textMidTopW) : OverFrameFallbackBorderInset))
+ clipSafetyInset + OverFrameTextClipInsetTop; + textClipSafetyInset + OverFrameTextClipInsetTop;
float textInsetB = (effectBoxIsInnerFill ? 0f : (hasBorder ? WorldToLocalY(_overFrameTextClipRt, midBottomW) : OverFrameFallbackBorderInset)) float textClipInsetBottom = isPendulum ? OverFramePendulumTextClipInsetBottom : OverFrameTextClipInsetBottom;
+ clipSafetyInset + OverFrameTextClipInsetBottom; float textInsetB = (textClipIsInnerFill ? 0f : (textHasBorder ? WorldToLocalY(_overFrameTextClipRt, textMidBottomW) : OverFrameFallbackBorderInset))
+ textClipSafetyInset + textClipInsetBottom;
InsetRect( InsetRect(
_overFrameTextClipRt, _overFrameTextClipRt,
Mathf.Max(0f, textInsetL - OverFrameSplitSeamOverlap), Mathf.Max(0f, textInsetL - splitSeamOverlap),
Mathf.Max(0f, textInsetR - OverFrameSplitSeamOverlap), Mathf.Max(0f, textInsetR - splitSeamOverlap),
Mathf.Max(0f, textInsetT), Mathf.Max(0f, textInsetT),
Mathf.Max(0f, textInsetB)); Mathf.Max(0f, textInsetB));
// --- BG continuation under the parchment (prevents "empty" transparent gaps) --- // --- BG continuation under the parchment (prevents "empty" transparent gaps) ---
...@@ -1626,7 +2618,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1626,7 +2618,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
_overFrameArtTextBG.maskable = true; _overFrameArtTextBG.maskable = true;
// Align BG continuation to full frame (then clipped by parchment mask) // Align BG continuation to full frame (then clipped by parchment mask)
MatchRectByWorldCorners(_overFrameArtTextBG.rectTransform, frameRt, _overFrameTextClipRt); MatchRectByWorldCorners(_overFrameArtTextBG.rectTransform, splitArtReferenceRt, _overFrameTextClipRt);
_overFrameArtTextBG.rectTransform.sizeDelta *= spec.scale; _overFrameArtTextBG.rectTransform.sizeDelta *= spec.scale;
_overFrameArtTextBG.rectTransform.anchoredPosition += spec.offset; _overFrameArtTextBG.rectTransform.anchoredPosition += spec.offset;
...@@ -1651,11 +2643,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1651,11 +2643,12 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
_overFrameArtText.gameObject.SetActive(true); _overFrameArtText.gameObject.SetActive(true);
_overFrameArtText.texture = tex; _overFrameArtText.texture = tex;
_overFrameArtText.uvRect = _overFrameArt != null ? _overFrameArt.uvRect : _overFrameArtText.uvRect; _overFrameArtText.uvRect = _overFrameArt != null ? _overFrameArt.uvRect : _overFrameArtText.uvRect;
_overFrameArtText.color = new Color(1f, 1f, 1f, OverFrameTextArtAlpha); float textArtAlpha = isPendulum ? OverFramePendulumTextArtAlpha : OverFrameTextArtAlpha;
_overFrameArtText.color = new Color(1f, 1f, 1f, textArtAlpha);
_overFrameArtText.maskable = true; _overFrameArtText.maskable = true;
// Align continuation to full frame (then clipped by parchment mask) // Align continuation to full frame (then clipped by parchment mask)
MatchRectByWorldCorners(_overFrameArtText.rectTransform, frameRt, _overFrameTextClipRt); MatchRectByWorldCorners(_overFrameArtText.rectTransform, splitArtReferenceRt, _overFrameTextClipRt);
_overFrameArtText.rectTransform.sizeDelta *= spec.scale; _overFrameArtText.rectTransform.sizeDelta *= spec.scale;
_overFrameArtText.rectTransform.anchoredPosition += spec.offset; _overFrameArtText.rectTransform.anchoredPosition += spec.offset;
} }
...@@ -1663,11 +2656,138 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1663,11 +2656,138 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
if (_overFrameArtTextBG != null) _overFrameArtTextBG.transform.SetSiblingIndex(0); if (_overFrameArtTextBG != null) _overFrameArtTextBG.transform.SetSiblingIndex(0);
if (_overFrameArtText != null) _overFrameArtText.transform.SetSiblingIndex(1); if (_overFrameArtText != null) _overFrameArtText.transform.SetSiblingIndex(1);
if (hasPendulumParchmentZones)
{
Texture2D pendTex = tex;
Rect pendUv = _overFrameArt != null ? _overFrameArt.uvRect : new Rect(0f, 0f, 1f, 1f);
if (OverFramePendulumUpperUseBaseArtContinuation &&
baseArt != null &&
baseArt.texture is Texture2D baseTex)
{
// Scale parchments should continue the underlying artwork, not transparent cutout regions.
pendTex = baseTex;
pendUv = baseArt.uvRect;
}
float pendAlpha = OverFramePendulumUpperZoneArtAlpha;
float centerInsetSide = OverFramePendulumCenterClipInsetSide;
float centerInsetTop = OverFramePendulumCenterClipInsetTop;
float centerInsetBottom = OverFramePendulumCenterClipInsetBottom;
float scaleInsetOuter = OverFramePendulumScaleClipInsetOuter;
float scaleInsetInner = OverFramePendulumScaleClipInsetInner;
float scaleInsetTop = OverFramePendulumScaleClipInsetTop;
float scaleInsetBottom = OverFramePendulumScaleClipInsetBottom;
float fallbackClipInsetSide = OverFramePendulumZoneFallbackClipInsetSide;
float fallbackClipInsetTop = OverFramePendulumZoneFallbackClipInsetTop;
float fallbackClipInsetBottom = OverFramePendulumZoneFallbackClipInsetBottom;
if (pendCenterBoxRt == null)
{
centerInsetSide += fallbackClipInsetSide;
centerInsetTop += fallbackClipInsetTop;
centerInsetBottom += fallbackClipInsetBottom;
}
if (pendLeftBoxRt == null)
{
scaleInsetOuter += fallbackClipInsetSide;
scaleInsetInner += fallbackClipInsetSide;
scaleInsetTop += fallbackClipInsetTop;
scaleInsetBottom += fallbackClipInsetBottom;
}
if (pendRightBoxRt == null)
{
scaleInsetOuter += fallbackClipInsetSide;
scaleInsetInner += fallbackClipInsetSide;
scaleInsetTop += fallbackClipInsetTop;
scaleInsetBottom += fallbackClipInsetBottom;
}
UpdateMaskedContinuationClip(
ref _overFramePendulumCenterClipRt,
ref _overFramePendulumCenterClip,
ref _overFramePendulumCenterArt,
"OverFramePendulumCenterClip",
"OverFramePendulumCenterArt",
pendCenterNrm,
frameRt,
fadeParentRt,
splitArtReferenceRt,
pendTex,
pendUv,
spec,
pendAlpha,
centerInsetSide,
centerInsetSide,
centerInsetTop,
centerInsetBottom,
pendCenterBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
UpdateMaskedContinuationClip(
ref _overFramePendulumScaleClipL_Rt,
ref _overFramePendulumScaleClipL,
ref _overFramePendulumScaleArtL,
"OverFramePendulumScaleClipL",
"OverFramePendulumScaleArtL",
pendLeftScaleNrm,
frameRt,
fadeParentRt,
splitArtReferenceRt,
pendTex,
pendUv,
spec,
pendAlpha,
scaleInsetOuter,
scaleInsetInner,
scaleInsetTop,
scaleInsetBottom,
pendLeftBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
UpdateMaskedContinuationClip(
ref _overFramePendulumScaleClipR_Rt,
ref _overFramePendulumScaleClipR,
ref _overFramePendulumScaleArtR,
"OverFramePendulumScaleClipR",
"OverFramePendulumScaleArtR",
pendRightScaleNrm,
frameRt,
fadeParentRt,
splitArtReferenceRt,
pendTex,
pendUv,
spec,
pendAlpha,
scaleInsetInner,
scaleInsetOuter,
scaleInsetTop,
scaleInsetBottom,
pendRightBoxRt,
OverFramePendulumUpperZoneBorderCutFactor,
OverFramePendulumUpperZoneBorderSafetyInset);
}
else
{
if (_overFramePendulumCenterClipRt) _overFramePendulumCenterClipRt.gameObject.SetActive(false);
if (_overFramePendulumCenterArt) _overFramePendulumCenterArt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipL_Rt) _overFramePendulumScaleClipL_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleClipR_Rt) _overFramePendulumScaleClipR_Rt.gameObject.SetActive(false);
if (_overFramePendulumScaleArtL) _overFramePendulumScaleArtL.gameObject.SetActive(false);
if (_overFramePendulumScaleArtR) _overFramePendulumScaleArtR.gameObject.SetActive(false);
}
// IMPORTANT: the continuation must be BELOW the description text (so text stays crisp), // IMPORTANT: the continuation must be BELOW the description text (so text stays crisp),
// but ABOVE the parchment background (so you can actually see the art "under" the parchment like the official proxy). // but ABOVE the parchment background (so you can actually see the art "under" the parchment like the official proxy).
// We insert at desc index so Unity shifts desc (and anything above it) one slot up. // We insert at desc index so Unity shifts desc (and anything above it) one slot up.
int descIdx = desc.transform.GetSiblingIndex(); int descIdx = desc.transform.GetSiblingIndex();
_overFrameTextClipRt.SetSiblingIndex(Mathf.Clamp(descIdx, 0, fadeParentRt.childCount - 1)); _overFrameTextClipRt.SetSiblingIndex(Mathf.Clamp(descIdx, 0, fadeParentRt.childCount - 1));
if (_overFramePendulumCenterClipRt) _overFramePendulumCenterClipRt.SetSiblingIndex(Mathf.Clamp(descIdx, 0, fadeParentRt.childCount - 1));
if (_overFramePendulumScaleClipL_Rt) _overFramePendulumScaleClipL_Rt.SetSiblingIndex(Mathf.Clamp(descIdx, 0, fadeParentRt.childCount - 1));
if (_overFramePendulumScaleClipR_Rt) _overFramePendulumScaleClipR_Rt.SetSiblingIndex(Mathf.Clamp(descIdx, 0, fadeParentRt.childCount - 1));
return true; return true;
} }
...@@ -1691,6 +2811,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1691,6 +2811,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
CleanupOverFrame(); CleanupOverFrame();
return false; return false;
} }
bool isPendulum = IsPendulumArtImage(baseArt);
// 2) Load overlay PNG from OverFrame/Overframe folder by card ID. // 2) Load overlay PNG from OverFrame/Overframe folder by card ID.
var tex = LoadOverFrameTexture(code); var tex = LoadOverFrameTexture(code);
...@@ -1748,35 +2869,11 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1748,35 +2869,11 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// Decide UV cropping: // Decide UV cropping:
// - Full-card overlays must NOT inherit baseArt.uvRect (it would zoom/crop the overlay). // - Full-card overlays must NOT inherit baseArt.uvRect (it would zoom/crop the overlay).
// - If the overlay texture aspect differs from the frame, crop UV to "cover" the frame (no stretching). // - Keep full UV for full-card overlays to match the legacy overframe mod behavior.
Rect overUv = baseArt.uvRect; Rect overUv = baseArt.uvRect;
if (looksFullCard && cardFrame != null) if (looksFullCard && cardFrame != null)
{ {
var fr = cardFrame.rectTransform.rect; overUv = new Rect(0f, 0f, 1f, 1f);
float targetAspect = fr.width / Mathf.Max(1f, fr.height);
float texAspect = (float)tex.width / Mathf.Max(1f, tex.height);
if (Mathf.Abs(texAspect - targetAspect) > 0.0005f)
{
if (texAspect > targetAspect)
{
// Texture is wider: crop left/right.
float w = targetAspect / texAspect;
float x = (1f - w) * 0.5f;
overUv = new Rect(x, 0f, w, 1f);
}
else
{
// Texture is taller/narrower: crop top/bottom.
float h = texAspect / targetAspect;
float y = (1f - h) * 0.5f;
overUv = new Rect(0f, y, 1f, h);
}
}
else
{
overUv = new Rect(0f, 0f, 1f, 1f);
}
} }
_overFrameArt.uvRect = overUv; _overFrameArt.uvRect = overUv;
...@@ -1817,7 +2914,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1817,7 +2914,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
if (looksFullCard && cardFrame != null) if (looksFullCard && cardFrame != null)
{ {
splitApplied = ApplyOverFrameProxySplit(tex, baseArt, spec, cardFrame.rectTransform, anchorParent, parentRt); splitApplied = ApplyOverFrameProxySplit(tex, baseArt, spec, cardFrame.rectTransform, anchorParent, parentRt, isPendulum);
} }
if (!splitApplied) if (!splitApplied)
...@@ -1871,7 +2968,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -1871,7 +2968,7 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
// 10) FINAL: Apply effect-box fade + enforce ordering // 10) FINAL: Apply effect-box fade + enforce ordering
// OverFrameArt → FadeOverlay → Text/UI // OverFrameArt → FadeOverlay → Text/UI
ApplyOverFrameEffectBoxFade(); ApplyOverFrameEffectBoxFade(isPendulum);
// Keep important UI on top (don’t hide ATK/Level/etc) // Keep important UI on top (don’t hide ATK/Level/etc)
if (cardName) cardName.transform.SetAsLastSibling(); if (cardName) cardName.transform.SetAsLastSibling();
...@@ -2123,8 +3220,8 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm) ...@@ -2123,8 +3220,8 @@ private bool IsEffectBoxRectInnerFill(RectTransform boxRt, Rect measuredNrm)
line.SetActive(true); line.SetActive(true);
textATK.SetActive(true); textATK.SetActive(true);
textDEF.SetActive(true); textDEF.SetActive(true);
numATK.text = data.Attack == -2 ? "?" : data.Attack.ToString(); numATK.text = data.GetAttackString();
numDEF.text = data.Defense == -2 ? "?" : data.Defense.ToString(); numDEF.text = data.GetDefenseString();
linkCount.gameObject.SetActive(false); linkCount.gameObject.SetActive(false);
spellType.text = string.Empty; spellType.text = string.Empty;
cardDescription.GetComponent<RectTransform>().sizeDelta = new Vector2(590f, 160f); cardDescription.GetComponent<RectTransform>().sizeDelta = new Vector2(590f, 160f);
......
...@@ -240,24 +240,30 @@ namespace MDPro3.Duel ...@@ -240,24 +240,30 @@ namespace MDPro3.Duel
#region Mate #region Mate
var mateConfig = Config.Get(condition.ToString() + "Mate0", Program.items.mates[0].id.ToString()); var mateConfig = Config.Get(condition.ToString() + "Mate0", Program.items.mates[0].id.ToString());
if (mateConfig != Items.CODE_NONE.ToString() || deck != null) var overrideDeckAppearance = Config.GetBool("OverrideDeckAppearance", false);
var mateConfigIsNone = mateConfig == Items.CODE_NONE.ToString();
if (!mateConfigIsNone || (deck != null && !overrideDeckAppearance))
{ {
int mateCode = int.Parse(mateConfig); int mateCode = int.Parse(mateConfig);
if (deck != null && !Config.GetBool("OverrideDeckAppearance", false)) if (deck != null && !overrideDeckAppearance && !mateConfigIsNone)
mateCode = deck.Mate; mateCode = deck.Mate;
var mate = await ABLoader.LoadMateAsync(mateCode);
if (mate != null) if (mateCode != Items.CODE_NONE)
{ {
mate0 = mate; var mate = await ABLoader.LoadMateAsync(mateCode);
mate0.parent = pos_Avatar_near; if (mate != null)
mate0.gameObject.SetActive(false); {
mate0 = mate;
mate0.parent = pos_Avatar_near;
mate0.gameObject.SetActive(false);
}
} }
} }
mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString()); mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString());
if (mateConfig != Items.CODE_NONE.ToString()) if (mateConfig != Items.CODE_NONE.ToString())
{ {
var mate = await ABLoader.LoadMateAsync(int.Parse(Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString()))); var mate = await ABLoader.LoadMateAsync(int.Parse(mateConfig));
if (mate != null) if (mate != null)
{ {
mate1 = mate; mate1 = mate;
...@@ -3295,4 +3301,4 @@ namespace MDPro3.Duel ...@@ -3295,4 +3301,4 @@ namespace MDPro3.Duel
#endregion #endregion
} }
} }
\ No newline at end of file
...@@ -202,10 +202,12 @@ namespace MDPro3 ...@@ -202,10 +202,12 @@ namespace MDPro3
manager.GetElement("Defense").SetActive(true); manager.GetElement("Defense").SetActive(true);
manager.GetElement("TextDefense").SetActive(true); manager.GetElement("TextDefense").SetActive(true);
manager.GetElement<Text>("TextDefense").text = data.Defense == -2 ? "?" : data.Defense.ToString(); var defense = Card.NormalizeBattleValue(data.Defense, true);
if (data.Defense > (origin.Defense < 0 ? 0 : origin.Defense)) var originDefense = Card.NormalizeBattleValue(origin.Defense, false);
manager.GetElement<Text>("TextDefense").text = Card.FormatBattleValue(defense);
if (Card.NormalizeBattleValue(defense, false) > originDefense)
manager.GetElement<Text>("TextDefense").color = upColor; manager.GetElement<Text>("TextDefense").color = upColor;
else if (data.Defense < origin.Defense) else if (Card.NormalizeBattleValue(defense, false) < originDefense)
manager.GetElement<Text>("TextDefense").color = downColor; manager.GetElement<Text>("TextDefense").color = downColor;
else else
manager.GetElement<Text>("TextDefense").color = equalColor; manager.GetElement<Text>("TextDefense").color = equalColor;
...@@ -238,10 +240,12 @@ namespace MDPro3 ...@@ -238,10 +240,12 @@ namespace MDPro3
} }
} }
manager.GetElement<Text>("TextAttack").text = data.Attack == -2 ? "?" : data.Attack.ToString(); var attack = Card.NormalizeBattleValue(data.Attack, true);
if (data.Attack > (origin.Attack < 0 ? 0 : origin.Attack)) var originAttack = Card.NormalizeBattleValue(origin.Attack, false);
manager.GetElement<Text>("TextAttack").text = Card.FormatBattleValue(attack);
if (Card.NormalizeBattleValue(attack, false) > originAttack)
manager.GetElement<Text>("TextAttack").color = upColor; manager.GetElement<Text>("TextAttack").color = upColor;
else if (data.Attack < origin.Attack) else if (Card.NormalizeBattleValue(attack, false) < originAttack)
manager.GetElement<Text>("TextAttack").color = downColor; manager.GetElement<Text>("TextAttack").color = downColor;
else else
manager.GetElement<Text>("TextAttack").color = equalColor; manager.GetElement<Text>("TextAttack").color = equalColor;
...@@ -527,4 +531,4 @@ namespace MDPro3 ...@@ -527,4 +531,4 @@ namespace MDPro3
} }
} }
} }
\ No newline at end of file
...@@ -423,10 +423,10 @@ namespace MDPro3 ...@@ -423,10 +423,10 @@ namespace MDPro3
else if (data.Id > 0) else if (data.Id > 0)
data.CloneTo(lastValidData); data.CloneTo(lastValidData);
if (d.Attack < 0) d.Attack = Card.NormalizeBattleValue(d.Attack, false);
d.Attack = 0; d.Defense = Card.NormalizeBattleValue(d.Defense, false);
if (d.Defense < 0) d.rAttack = Card.NormalizeBattleValue(d.rAttack, false);
d.Defense = 0; d.rDefense = Card.NormalizeBattleValue(d.rDefense, false);
if (d.Id != data.Id) if (d.Id != data.Id)
{ {
...@@ -441,6 +441,8 @@ namespace MDPro3 ...@@ -441,6 +441,8 @@ namespace MDPro3
} }
} }
data = d; data = d;
if (model != null && p.InLocation(CardLocation.Hand) && !inAnimation)
RefreshHandTurnByCode();
RefreshLabel(); RefreshLabel();
UpdateExDeckTop(); UpdateExDeckTop();
} }
...@@ -1547,6 +1549,7 @@ namespace MDPro3 ...@@ -1547,6 +1549,7 @@ namespace MDPro3
Program.instance.ocgcore.SetExDeckTop(this); Program.instance.ocgcore.SetExDeckTop(this);
ShowFaceDownCardOrNot(NeedShowFaceDownCard()); ShowFaceDownCardOrNot(NeedShowFaceDownCard());
RefreshHandTurnByCode();
if (p.InLocation(CardLocation.Deck, CardLocation.Extra)) if (p.InLocation(CardLocation.Deck, CardLocation.Extra))
Program.instance.ocgcore.DuelBGManager.ResizeDecks(); Program.instance.ocgcore.DuelBGManager.ResizeDecks();
...@@ -1554,6 +1557,13 @@ namespace MDPro3 ...@@ -1554,6 +1557,13 @@ namespace MDPro3
Program.instance.ocgcore.DuelBGManager.RefreshGravesState(); Program.instance.ocgcore.DuelBGManager.RefreshGravesState();
} }
private void RefreshHandTurnByCode()
{
if (model == null || !p.InLocation(CardLocation.Hand))
return;
manager.GetElement<Transform>("Turn").localEulerAngles = new Vector3(0, 0, data.Id == 0 ? 180 : 0);
}
public Sequence StartCardSequence(Vector3 fromPosition, Vector3 fromRotation, float interval = 0f) public Sequence StartCardSequence(Vector3 fromPosition, Vector3 fromRotation, float interval = 0f)
{ {
if (model == null) if (model == null)
......
...@@ -10,6 +10,9 @@ namespace MDPro3.Duel.YGOSharp ...@@ -10,6 +10,9 @@ namespace MDPro3.Duel.YGOSharp
{ {
public class Card public class Card
{ {
public const int MAX_BATTLE_POWER_DISPLAY = 99_999_999;
public const int UNKNOWN_BATTLE_VALUE = -2;
public int Id; public int Id;
public int Ot; public int Ot;
public int Alias; public int Alias;
...@@ -229,15 +232,31 @@ namespace MDPro3.Duel.YGOSharp ...@@ -229,15 +232,31 @@ namespace MDPro3.Duel.YGOSharp
public string GetAttackString() public string GetAttackString()
{ {
return Attack == -2 ? "?" : Attack.ToString(); return FormatBattleValue(Attack);
} }
public string GetDefenseString() public string GetDefenseString()
{ {
return Defense == -2 ? "?" : Defense.ToString(); return FormatBattleValue(Defense);
}
public static int NormalizeBattleValue(int value, bool keepUnknown = true)
{
if (value == UNKNOWN_BATTLE_VALUE)
return keepUnknown ? UNKNOWN_BATTLE_VALUE : 0;
if (value < 0)
return MAX_BATTLE_POWER_DISPLAY;
if (value > MAX_BATTLE_POWER_DISPLAY)
return MAX_BATTLE_POWER_DISPLAY;
return value;
}
public static string FormatBattleValue(int value)
{
var normalized = NormalizeBattleValue(value, true);
return normalized == UNKNOWN_BATTLE_VALUE ? "?" : normalized.ToString();
} }
// Put this near other fields/helpers inside Card class:
private static readonly string PendulumSeparatorLine = new string('─', 14); private static readonly string PendulumSeparatorLine = new string('─', 14);
public string GetDescription(bool withSetName = false) public string GetDescription(bool withSetName = false)
...@@ -614,4 +633,4 @@ namespace MDPro3.Duel.YGOSharp ...@@ -614,4 +633,4 @@ namespace MDPro3.Duel.YGOSharp
#endregion #endregion
} }
} }
\ No newline at end of file
...@@ -290,7 +290,10 @@ namespace MDPro3.Duel.YGOSharp ...@@ -290,7 +290,10 @@ namespace MDPro3.Duel.YGOSharp
var ydk = GetYDK(); var ydk = GetYDK();
try try
{ {
deckName = Path.GetFileNameWithoutExtension(deckName); deckName = NormalizeDeckFileName(deckName);
if (!IsValidDeckFileName(deckName))
return false;
var path = Program.PATH_DECK + (type == string.Empty ? string.Empty : $"{type}/") + deckName + Program.EXPANSION_YDK; var path = Program.PATH_DECK + (type == string.Empty ? string.Empty : $"{type}/") + deckName + Program.EXPANSION_YDK;
var dir = Path.GetDirectoryName(path); var dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir)) if (!Directory.Exists(dir))
...@@ -308,6 +311,25 @@ namespace MDPro3.Duel.YGOSharp ...@@ -308,6 +311,25 @@ namespace MDPro3.Duel.YGOSharp
return true; return true;
} }
public static string NormalizeDeckFileName(string deckName)
{
deckName = deckName?.Trim() ?? string.Empty;
if (deckName.EndsWith(Program.EXPANSION_YDK, StringComparison.OrdinalIgnoreCase))
deckName = deckName[..^Program.EXPANSION_YDK.Length];
return deckName;
}
public static bool IsValidDeckFileName(string deckName)
{
if (string.IsNullOrWhiteSpace(deckName))
return false;
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
if (deckName.EndsWith(" ", StringComparison.Ordinal) || deckName.EndsWith(".", StringComparison.Ordinal))
return false;
#endif
return deckName.IndexOfAny(Path.GetInvalidFileNameChars()) < 0;
}
public string GetYDK() public string GetYDK()
{ {
var value = deckHint + "\r\n#main"; var value = deckHint + "\r\n#main";
......
...@@ -28,20 +28,22 @@ namespace MDPro3.Net ...@@ -28,20 +28,22 @@ namespace MDPro3.Net
private static async UniTask InitializeGenesysLflist() private static async UniTask InitializeGenesysLflist()
{ {
// Load cached/local genesys data immediately so Deck Editor works offline.
ParseGenesysLflist();
var eTag = await GetETagAsync(URL_GENESYS_LFLIST); var eTag = await GetETagAsync(URL_GENESYS_LFLIST);
if (!string.IsNullOrEmpty(eTag)) if (string.IsNullOrEmpty(eTag))
return;
var configTag = Config.Get(GetLocalETagKey(URL_GENESYS_LFLIST), Config.EMPTY_STRING);
if(!string.Equals(eTag, configTag, StringComparison.Ordinal))
{ {
var configTag = Config.Get(GetLocalETagKey(URL_GENESYS_LFLIST), Config.EMPTY_STRING); Program.Debug("Update Genesys Lflist.");
if(!string.Equals(eTag, configTag, StringComparison.Ordinal)) await DownloadGenesysLflist(eTag);
{ ParseGenesysLflist();
Program.Debug("Update Genesys Lflist.");
await DownloadGenesysLflist(eTag);
}
else
Program.Debug("Genesys Lflist do not need update.");
} }
else
ParseGenesysLflist(); Program.Debug("Genesys Lflist do not need update.");
} }
private static bool GenesysRequiresDownload() private static bool GenesysRequiresDownload()
...@@ -74,6 +76,10 @@ namespace MDPro3.Net ...@@ -74,6 +76,10 @@ namespace MDPro3.Net
private static void ParseGenesysLflist() private static void ParseGenesysLflist()
{ {
genesysBannedCards.Clear();
genesysPoints.Clear();
officialGenesysLimit = 100;
if (!File.Exists(PATH_GENESYS_LFLIST)) if (!File.Exists(PATH_GENESYS_LFLIST))
return; return;
...@@ -88,6 +94,13 @@ namespace MDPro3.Net ...@@ -88,6 +94,13 @@ namespace MDPro3.Net
if(string.IsNullOrEmpty(line) || line.StartsWith("#")) continue; if(string.IsNullOrEmpty(line) || line.StartsWith("#")) continue;
if (line.StartsWith("$")) if (line.StartsWith("$"))
{ {
// e.g. "$genesys 100"
if (line.StartsWith("$genesys", StringComparison.OrdinalIgnoreCase))
{
var limitParts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (limitParts.Length >= 2 && int.TryParse(limitParts[1], out var limit))
officialGenesysLimit = limit;
}
currentType = line; currentType = line;
continue; continue;
} }
...@@ -242,6 +255,7 @@ namespace MDPro3.Net ...@@ -242,6 +255,7 @@ namespace MDPro3.Net
public static async UniTask<string> GetETagAsync(string url) public static async UniTask<string> GetETagAsync(string url)
{ {
using var headRequest = UnityWebRequest.Head(url); using var headRequest = UnityWebRequest.Head(url);
headRequest.timeout = 8;
await headRequest.SendWebRequest(); await headRequest.SendWebRequest();
if(headRequest.result != UnityWebRequest.Result.Success) if(headRequest.result != UnityWebRequest.Result.Success)
...@@ -269,4 +283,4 @@ namespace MDPro3.Net ...@@ -269,4 +283,4 @@ namespace MDPro3.Net
public string banType; public string banType;
public int point; public int point;
} }
} }
\ No newline at end of file
...@@ -193,14 +193,14 @@ namespace MDPro3.Servant ...@@ -193,14 +193,14 @@ namespace MDPro3.Servant
duelFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 1); duelFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 1);
duelFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 2); duelFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 2);
duelFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 3); duelFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("DuelFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 3);
watchFace0 = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace0", Program.items.faces[0].id.ToString()), Items.ItemType.Face); watchFace0 = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace0", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 0);
watchFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face); watchFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 1);
watchFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face); watchFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 2);
watchFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face); watchFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("WatchFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 3);
replayFace0 = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace0", Program.items.faces[0].id.ToString()), Items.ItemType.Face); replayFace0 = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace0", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 0);
replayFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face); replayFace1 = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace1", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 1);
replayFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face); replayFace0Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace0Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 2);
replayFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face); replayFace1Tag = await Program.items.LoadConcreteItemIconAsync(Config.Get("ReplayFace1Tag", Program.items.faces[0].id.ToString()), Items.ItemType.Face, 3);
defaultFace0 = await Program.items.LoadConcreteItemIconAsync("1010039", Items.ItemType.Face); defaultFace0 = await Program.items.LoadConcreteItemIconAsync("1010039", Items.ItemType.Face);
defaultFace1 = await Program.items.LoadConcreteItemIconAsync("1010001", Items.ItemType.Face); defaultFace1 = await Program.items.LoadConcreteItemIconAsync("1010001", Items.ItemType.Face);
......
...@@ -1948,13 +1948,13 @@ namespace MDPro3.Servant ...@@ -1948,13 +1948,13 @@ namespace MDPro3.Servant
DuelBGManager.PlayGraveEffect(p, isIn); DuelBGManager.PlayGraveEffect(p, isIn);
} }
public int GetAllAtk(bool mySide) public long GetAllAtk(bool mySide)
{ {
int allAttack = 0; long allAttack = 0;
var monsters = GCS_GetLocationCards(mySide ? 0 : 1, (int)CardLocation.MonsterZone); var monsters = GCS_GetLocationCards(mySide ? 0 : 1, (int)CardLocation.MonsterZone);
foreach (var card in monsters) foreach (var card in monsters)
if ((card.p.position & (uint)CardPosition.FaceUpAttack) > 0) if ((card.p.position & (uint)CardPosition.FaceUpAttack) > 0)
allAttack += card.GetData().Attack; allAttack += Card.NormalizeBattleValue(card.GetData().Attack, false);
return allAttack; return allAttack;
} }
......
...@@ -165,7 +165,7 @@ namespace MDPro3 ...@@ -165,7 +165,7 @@ namespace MDPro3
MouseMovedEvent(); MouseMovedEvent();
} }
if(MoveInput != Vector2.zero) if(MoveInput != Vector2.zero && !InputFieldActivating())
{ {
if (Cursor.lockState == CursorLockMode.None) if (Cursor.lockState == CursorLockMode.None)
{ {
...@@ -456,10 +456,11 @@ namespace MDPro3 ...@@ -456,10 +456,11 @@ namespace MDPro3
if (current == null) return false; if (current == null) return false;
if (!current.TryGetComponent<Selectable>(out var selectable)) if (!current.TryGetComponent<Selectable>(out var selectable))
return false; return false;
if(selectable is TMP_InputField inputField) if (selectable is TMP_InputField tmpInputField)
return inputField.isFocused; return tmpInputField.isFocused;
else if (selectable is InputField legacyInputField)
return false; return legacyInputField.isFocused;
return false;
} }
#endregion #endregion
......
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI; using UnityEngine.UI;
namespace MDPro3.UI namespace MDPro3.UI
...@@ -17,13 +18,17 @@ namespace MDPro3.UI ...@@ -17,13 +18,17 @@ namespace MDPro3.UI
{ {
base.InitializeSelections(); base.InitializeSelections();
Program.instance.currentServant.returnAction = null; Program.instance.currentServant.returnAction = null;
var selectionButtons = new List<Button>();
for (int i = 1; i < selections.Count; i++) for (int i = 1; i < selections.Count; i++)
{ {
GameObject newSelection = Instantiate(item); GameObject newSelection = Instantiate(item);
newSelection.transform.SetParent(scrollRect.content, false); newSelection.transform.SetParent(scrollRect.content, false);
newSelection.transform.GetChild(0).GetChild(0).GetComponent<Text>().text = selections[i]; newSelection.transform.GetChild(0).GetChild(0).GetComponent<Text>().text = selections[i];
newSelection.transform.GetChild(0).name = responses[i - 1].ToString(); var buttonTransform = newSelection.transform.GetChild(0);
newSelection.transform.GetChild(0).GetComponent<Button>().onClick.AddListener(() => buttonTransform.name = responses[i - 1].ToString();
var button = buttonTransform.GetComponent<Button>();
button.onClick.AddListener(() =>
{ {
string selected = UnityEngine.EventSystems.EventSystem.current. string selected = UnityEngine.EventSystems.EventSystem.current.
currentSelectedGameObject.name; currentSelectedGameObject.name;
...@@ -35,14 +40,39 @@ namespace MDPro3.UI ...@@ -35,14 +40,39 @@ namespace MDPro3.UI
} }
Hide(); Hide();
}); });
selectionButtons.Add(button);
newSelection.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -20 - 90 * (i - 1)); newSelection.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -20 - 90 * (i - 1));
} }
for (int i = 0; i < selectionButtons.Count; i++)
{
var button = selectionButtons[i];
var navigation = button.navigation;
navigation.mode = Navigation.Mode.Explicit;
navigation.selectOnUp = selectionButtons[Mathf.Max(0, i - 1)];
navigation.selectOnDown = selectionButtons[Mathf.Min(selectionButtons.Count - 1, i + 1)];
navigation.selectOnLeft = button;
navigation.selectOnRight = button;
button.navigation = navigation;
}
if (scrollRect.verticalScrollbar != null)
{
var scrollbarNavigation = scrollRect.verticalScrollbar.navigation;
scrollbarNavigation.mode = Navigation.Mode.None;
scrollRect.verticalScrollbar.navigation = scrollbarNavigation;
}
scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, 25 + (selections.Count - 1) * 90); scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, 25 + (selections.Count - 1) * 90);
baseRect.sizeDelta = new Vector2(baseRect.sizeDelta.x, baseRect.sizeDelta = new Vector2(baseRect.sizeDelta.x,
scrollRect.content.sizeDelta.y + 50 > 800 ? scrollRect.content.sizeDelta.y + 50 > 800 ?
800 : 800 :
scrollRect.content.sizeDelta.y + 50 scrollRect.content.sizeDelta.y + 50
); );
scrollRect.verticalNormalizedPosition = 1f;
if (selectionButtons.Count > 0)
EventSystem.current.SetSelectedGameObject(selectionButtons[0].gameObject);
} }
} }
......
...@@ -2,12 +2,19 @@ using System.Collections.Generic; ...@@ -2,12 +2,19 @@ using System.Collections.Generic;
using DG.Tweening; using DG.Tweening;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
using TMPro;
using YgomSystem.UI; using YgomSystem.UI;
namespace MDPro3.UI namespace MDPro3.UI
{ {
public class SelectionButton_MainMenu : SelectionButton public class SelectionButton_MainMenu : SelectionButton
{ {
private const float ArrowRestX = -5f;
private const float ArrowStartX = -262f;
private const float ArrowTextGap = 30f;
private static float s_BaseButtonWidth = -1f;
private static float s_BaseTextWidth = -1f;
protected override void Awake() protected override void Awake()
{ {
ElementsReset(); ElementsReset();
...@@ -19,6 +26,8 @@ namespace MDPro3.UI ...@@ -19,6 +26,8 @@ namespace MDPro3.UI
private void ElementsReset() private void ElementsReset()
{ {
EnsureMainMenuButtonWidth();
// Out // Out
Manager.GetElement<CanvasGroup>("Out").alpha = 1f; Manager.GetElement<CanvasGroup>("Out").alpha = 1f;
Manager.GetElement<RectTransform>("Line").localScale = Vector3.one; Manager.GetElement<RectTransform>("Line").localScale = Vector3.one;
...@@ -27,7 +36,7 @@ namespace MDPro3.UI ...@@ -27,7 +36,7 @@ namespace MDPro3.UI
Manager.GetElement<CanvasGroup>("Hover").alpha = 0f; Manager.GetElement<CanvasGroup>("Hover").alpha = 0f;
Manager.GetElement<RectTransform>("PlateTween").localScale = new Vector3(0.5f, 1f, 1f); Manager.GetElement<RectTransform>("PlateTween").localScale = new Vector3(0.5f, 1f, 1f);
Manager.GetElement<RectTransform>("HoverTextMask").offsetMax = new Vector2(-340f, 0f); Manager.GetElement<RectTransform>("HoverTextMask").offsetMax = new Vector2(-340f, 0f);
Manager.GetElement<RectTransform>("Arrow").localPosition = new Vector2(-5f, 0f); Manager.GetElement<RectTransform>("Arrow").localPosition = new Vector2(ArrowRestX, 0f);
// Cursor // Cursor
Manager.GetElement<RectTransform>("Corner").offsetMin = new Vector2(0f, 0f); Manager.GetElement<RectTransform>("Corner").offsetMin = new Vector2(0f, 0f);
...@@ -42,6 +51,7 @@ namespace MDPro3.UI ...@@ -42,6 +51,7 @@ namespace MDPro3.UI
if (hoverd) if (hoverd)
return; return;
base.HoverOn(); base.HoverOn();
EnsureMainMenuButtonWidth();
Manager.GetElement<CanvasGroup>("Out").alpha = 0f; Manager.GetElement<CanvasGroup>("Out").alpha = 0f;
...@@ -54,11 +64,73 @@ namespace MDPro3.UI ...@@ -54,11 +64,73 @@ namespace MDPro3.UI
Manager.GetElement<RectTransform>("HoverTextMask").offsetMax = new Vector2(-340f, 0f); Manager.GetElement<RectTransform>("HoverTextMask").offsetMax = new Vector2(-340f, 0f);
Manager.GetElement<RectTransform>("HoverTextMask").DOSizeDelta(Vector2.zero, 0.2f); Manager.GetElement<RectTransform>("HoverTextMask").DOSizeDelta(Vector2.zero, 0.2f);
Manager.GetElement<RectTransform>("Arrow").anchoredPosition = new Vector2(-262f, 0f); Manager.GetElement<RectTransform>("Arrow").anchoredPosition = new Vector2(ArrowStartX, 0f);
var tween2 = Manager.GetElement<RectTransform>("Arrow").DOAnchorPosX(-5f, 0.33f).SetEase(Ease.OutQuart); var tween2 = Manager.GetElement<RectTransform>("Arrow").DOAnchorPosX(ArrowRestX, 0.33f).SetEase(Ease.OutQuart);
hoverOnTweens.Add(tween2); hoverOnTweens.Add(tween2);
} }
private void EnsureMainMenuButtonWidth()
{
var selfRect = transform as RectTransform;
if (selfRect == null || selfRect.parent == null)
return;
var selfTextRect = Manager.GetElement<RectTransform>("Text");
if (s_BaseButtonWidth < 0f)
s_BaseButtonWidth = selfRect.sizeDelta.x;
if (s_BaseTextWidth < 0f && selfTextRect != null)
s_BaseTextWidth = selfTextRect.rect.width;
if (s_BaseButtonWidth <= 0f || s_BaseTextWidth <= 0f)
return;
var buttons = selfRect.parent.GetComponentsInChildren<SelectionButton_MainMenu>(true);
var maxTargetWidth = s_BaseButtonWidth;
foreach (var button in buttons)
{
if (button == null)
continue;
var buttonRect = button.transform as RectTransform;
if (buttonRect == null)
continue;
var text = button.Manager.GetElement<TextMeshProUGUI>("Text");
var textOver = button.Manager.GetElement<TextMeshProUGUI>("TextOver");
var textRect = button.Manager.GetElement<RectTransform>("Text");
if (text == null || textRect == null)
continue;
text.ForceMeshUpdate();
textOver?.ForceMeshUpdate();
var maxLabelWidth = text.preferredWidth;
if (textOver != null && textOver.preferredWidth > maxLabelWidth)
maxLabelWidth = textOver.preferredWidth;
var neededTextWidth = maxLabelWidth + ArrowTextGap;
var extraWidth = Mathf.Max(0f, neededTextWidth - s_BaseTextWidth);
var targetWidth = s_BaseButtonWidth + extraWidth;
if (targetWidth > maxTargetWidth)
maxTargetWidth = targetWidth;
}
var resolvedWidth = Mathf.Ceil(maxTargetWidth);
foreach (var button in buttons)
{
if (button == null)
continue;
var buttonRect = button.transform as RectTransform;
if (buttonRect == null)
continue;
if (Mathf.Abs(buttonRect.sizeDelta.x - resolvedWidth) < 0.01f)
continue;
buttonRect.sizeDelta = new Vector2(resolvedWidth, buttonRect.sizeDelta.y);
}
}
protected override void HoverOff(bool force = false) protected override void HoverOff(bool force = false)
{ {
base.HoverOff(); base.HoverOff();
......
...@@ -99,17 +99,31 @@ namespace MDPro3.UI ...@@ -99,17 +99,31 @@ namespace MDPro3.UI
if (index == 0) if (index == 0)
return; return;
await UniTask.WaitWhile(() => Program.instance.deckSelector.inTransition); try
{
await UniTask.WaitWhile(
() => Program.instance.deckSelector.inTransition,
cancellationToken: destroyCancellationToken);
for (int i = 0; i < transform.GetSiblingIndex(); i++) int siblingIndex = transform.GetSiblingIndex();
await UniTask.Yield(cancellationToken: destroyCancellationToken); for (int i = 0; i < siblingIndex; i++)
await UniTask.Yield(cancellationToken: destroyCancellationToken);
if (gameObject == null) if (this == null || gameObject == null)
return; return;
var sprite = await Program.items.LoadDeckCaseIconAsync(deckCase, "_L_SD");
if (this == null || gameObject == null || sprite == null)
return;
var sprite = await Program.items.LoadDeckCaseIconAsync(deckCase, "_L_SD");
if (sprite != null)
Manager.GetElement<Image>("DeckImage").sprite = sprite; Manager.GetElement<Image>("DeckImage").sprite = sprite;
}
catch (System.OperationCanceledException)
{
}
catch (MissingReferenceException)
{
}
} }
protected override async UniTask RefreshAsync() protected override async UniTask RefreshAsync()
......
...@@ -87,13 +87,31 @@ namespace MDPro3.UI ...@@ -87,13 +87,31 @@ namespace MDPro3.UI
protected override async UniTask RefreshDeckCaseAsync() protected override async UniTask RefreshDeckCaseAsync()
{ {
await UniTask.WaitWhile(() => Program.instance.deckSelector.inTransition); try
for (int i = 0; i < transform.GetSiblingIndex(); i++) {
await UniTask.Yield(); await UniTask.WaitWhile(
() => Program.instance.deckSelector.inTransition,
cancellationToken: destroyCancellationToken);
int siblingIndex = transform.GetSiblingIndex();
for (int i = 0; i < siblingIndex; i++)
await UniTask.Yield(cancellationToken: destroyCancellationToken);
if (this == null || gameObject == null)
return;
var sprite = await Program.items.LoadDeckCaseIconAsync(deckCase, "_L_SD");
if (this == null || gameObject == null || sprite == null)
return;
var sprite = await Program.items.LoadDeckCaseIconAsync(deckCase, "_L_SD");
if (sprite != null)
Manager.GetElement<Image>("DeckImage").sprite = sprite; Manager.GetElement<Image>("DeckImage").sprite = sprite;
}
catch (System.OperationCanceledException)
{
}
catch (MissingReferenceException)
{
}
} }
protected override void OnClick() protected override void OnClick()
......
...@@ -783,7 +783,8 @@ namespace MDPro3.UI ...@@ -783,7 +783,8 @@ namespace MDPro3.UI
if (condition == Condition.Editable && !deckLoaded) return false; if (condition == Condition.Editable && !deckLoaded) return false;
Deck = FromObjectDeckToCodedDeck(); Deck = FromObjectDeckToCodedDeck();
DeckFileSave(); if (!DeckFileSave())
return false;
SetCondition(Condition.Editable); SetCondition(Condition.Editable);
return true; return true;
} }
...@@ -1293,29 +1294,80 @@ namespace MDPro3.UI ...@@ -1293,29 +1294,80 @@ namespace MDPro3.UI
return DeckLocation.All; return DeckLocation.All;
} }
protected void DeckFileSave() protected bool DeckFileSave()
{ {
try try
{ {
var deckName = GetDeckName(); var deckName = MDPro3.Duel.YGOSharp.Deck.NormalizeDeckFileName(GetDeckName());
// TODO: 检查违法字符。 if (!MDPro3.Duel.YGOSharp.Deck.IsValidDeckFileName(deckName))
throw new InvalidOperationException($"Invalid deck name: \"{GetDeckName()}\"");
Deck.type = deckType; Deck.type = deckType;
Deck.Save(deckName, DateTime.UtcNow);
if (deckName != deckFileName) var oldDeckPath = GetDeckFilePath(deckFileName);
File.Delete(Program.PATH_DECK + this.deckNameWithType + Program.EXPANSION_YDK); if (!Deck.Save(deckName, DateTime.UtcNow))
throw new IOException($"Failed to save deck \"{deckName}\".");
var newDeckPath = GetDeckFilePath(deckName);
if (IsSameDeckPath(oldDeckPath, newDeckPath))
ApplyCaseOnlyRename(oldDeckPath, newDeckPath);
else if (File.Exists(oldDeckPath))
File.Delete(oldDeckPath);
deckFileName = deckName; deckFileName = deckName;
deckNameWithType = deckType == string.Empty ? string.Empty : $"/{deckType}" + deckName; deckNameWithType = deckType == string.Empty ? deckName : $"{deckType}/{deckName}";
InputDeckName.text = deckFileName;
TextDeckName.text = deckFileName;
MessageManager.Toast(InterString.Get("本地卡组「[?]」已保存。", deckName)); MessageManager.Toast(InterString.Get("本地卡组「[?]」已保存。", deckName));
Config.SetConfigDeck(deckName, true); Config.SetConfigDeck(deckName, true);
SetDirty(false); SetDirty(false);
return true;
} }
catch (Exception e) catch (Exception e)
{ {
MessageManager.Toast(InterString.Get("保存失败!")); MessageManager.Toast(InterString.Get("保存失败!"));
MessageManager.Cast(e.Message); MessageManager.Cast(e.Message);
return false;
} }
} }
private string GetDeckFilePath(string deckName)
{
return Program.PATH_DECK + (deckType == string.Empty ? string.Empty : $"{deckType}/")
+ deckName + Program.EXPANSION_YDK;
}
private static bool IsSameDeckPath(string leftPath, string rightPath)
{
var fullLeftPath = Path.GetFullPath(leftPath);
var fullRightPath = Path.GetFullPath(rightPath);
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
return string.Equals(fullLeftPath, fullRightPath, StringComparison.OrdinalIgnoreCase);
#else
return string.Equals(fullLeftPath, fullRightPath, StringComparison.Ordinal);
#endif
}
private static void ApplyCaseOnlyRename(string oldPath, string newPath)
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
var fullOldPath = Path.GetFullPath(oldPath);
var fullNewPath = Path.GetFullPath(newPath);
if (string.Equals(fullOldPath, fullNewPath, StringComparison.Ordinal))
return;
if (!string.Equals(fullOldPath, fullNewPath, StringComparison.OrdinalIgnoreCase))
return;
if (!File.Exists(fullOldPath))
return;
var directory = Path.GetDirectoryName(fullOldPath);
var tempPath = Path.Combine(directory ?? Program.PATH_DECK, $"__mdpro3_casefix_{Guid.NewGuid():N}.tmp");
File.Move(fullOldPath, tempPath);
File.Move(tempPath, fullNewPath);
#endif
}
#region Pickup #region Pickup
private void PrePick() private void PrePick()
......
...@@ -311,6 +311,11 @@ namespace MDPro3.UI ...@@ -311,6 +311,11 @@ namespace MDPro3.UI
public static PlayerPosition GetPlayerPosition(int player) public static PlayerPosition GetPlayerPosition(int player)
{ {
player = GetRoomPlayerIndex(player); player = GetRoomPlayerIndex(player);
bool spectatorInRoomLobby =
RoomServant.SelfType == 7
&& Program.instance.room != null
&& Program.instance.room.showing
&& !Program.instance.ocgcore.showing;
PlayerPosition position; PlayerPosition position;
if (player < 4) if (player < 4)
{ {
...@@ -326,9 +331,9 @@ namespace MDPro3.UI ...@@ -326,9 +331,9 @@ namespace MDPro3.UI
else else
{ {
if (player == 0) if (player == 0)
position = PlayerPosition.WatchMe; position = spectatorInRoomLobby ? PlayerPosition.Me : PlayerPosition.WatchMe;
else else
position = PlayerPosition.WatchOp; position = spectatorInRoomLobby ? PlayerPosition.Op : PlayerPosition.WatchOp;
} }
} }
else else
...@@ -350,13 +355,13 @@ namespace MDPro3.UI ...@@ -350,13 +355,13 @@ namespace MDPro3.UI
else else
{ {
if (player == 0) if (player == 0)
position = PlayerPosition.WatchMe; position = spectatorInRoomLobby ? PlayerPosition.Me : PlayerPosition.WatchMe;
else if (player == 1) else if (player == 1)
position = PlayerPosition.WatchMyTag; position = spectatorInRoomLobby ? PlayerPosition.MyTag : PlayerPosition.WatchMyTag;
else if (player == 2) else if (player == 2)
position = PlayerPosition.WatchOp; position = spectatorInRoomLobby ? PlayerPosition.Op : PlayerPosition.WatchOp;
else else
position = PlayerPosition.WatchOpTag; position = spectatorInRoomLobby ? PlayerPosition.OpTag : PlayerPosition.WatchOpTag;
} }
} }
} }
......
...@@ -21,6 +21,7 @@ namespace MDPro3.Utility ...@@ -21,6 +21,7 @@ namespace MDPro3.Utility
private static readonly ConcurrentDictionary<int, CacheEntry> cachedCards = new(); private static readonly ConcurrentDictionary<int, CacheEntry> cachedCards = new();
private static readonly ConcurrentDictionary<int, Texture2D> cachedCardNames = new(); private static readonly ConcurrentDictionary<int, Texture2D> cachedCardNames = new();
private static readonly ConcurrentDictionary<int, Texture> cachedVideoCards = new(); private static readonly ConcurrentDictionary<int, Texture> cachedVideoCards = new();
private static readonly ConcurrentDictionary<int, byte> knownOverFrameArts = new();
private static readonly ConcurrentDictionary<int, SemaphoreSlim> artLoadingLocks = new(); private static readonly ConcurrentDictionary<int, SemaphoreSlim> artLoadingLocks = new();
private static readonly ConcurrentDictionary<int, SemaphoreSlim> cardLoadingLocks = new(); private static readonly ConcurrentDictionary<int, SemaphoreSlim> cardLoadingLocks = new();
...@@ -86,7 +87,7 @@ namespace MDPro3.Utility ...@@ -86,7 +87,7 @@ namespace MDPro3.Utility
Interlocked.Increment(ref entry.ReferenceCount); Interlocked.Increment(ref entry.ReferenceCount);
entry.IsPersistent |= persistent; entry.IsPersistent |= persistent;
} }
else else if (!HasOverFrameArtFile(code))
Debug.LogError($"Art texture is null for code {code}"); Debug.LogError($"Art texture is null for code {code}");
return entry.Texture; return entry.Texture;
...@@ -111,7 +112,7 @@ namespace MDPro3.Utility ...@@ -111,7 +112,7 @@ namespace MDPro3.Utility
cachedArts.TryRemove(code, out _); cachedArts.TryRemove(code, out _);
throw ex; throw ex;
} }
if (newEntry.Texture == null) if (newEntry.Texture == null && !HasOverFrameArtFile(code))
Debug.LogError($"{code} art is null"); Debug.LogError($"{code} art is null");
newEntry.LoadingTask = null; newEntry.LoadingTask = null;
...@@ -255,6 +256,7 @@ namespace MDPro3.Utility ...@@ -255,6 +256,7 @@ namespace MDPro3.Utility
cachedCardNames.Clear(); cachedCardNames.Clear();
ClearArtVideos(); ClearArtVideos();
knownOverFrameArts.Clear();
} }
#endregion #endregion
...@@ -290,7 +292,7 @@ namespace MDPro3.Utility ...@@ -290,7 +292,7 @@ namespace MDPro3.Utility
} }
if (art == null) if (art == null)
{ {
lastCardFoundArt = false; lastCardFoundArt = HasOverFrameArtFile(code);
return null; return null;
} }
...@@ -354,7 +356,8 @@ namespace MDPro3.Utility ...@@ -354,7 +356,8 @@ namespace MDPro3.Utility
if (art == null) if (art == null)
{ {
Debug.LogError($"Get null from ArtLoad for Card {data.Id}:"); if (!HasOverFrameArtFile(data.Id))
Debug.LogError($"Get null from ArtLoad for Card {data.Id}:");
art = TextureManager.container.unknownArt.texture; art = TextureManager.container.unknownArt.texture;
} }
if (!Program.instance.cardRenderer.RenderCard(code, art)) if (!Program.instance.cardRenderer.RenderCard(code, art))
...@@ -397,7 +400,8 @@ namespace MDPro3.Utility ...@@ -397,7 +400,8 @@ namespace MDPro3.Utility
if (art == null) if (art == null)
{ {
Debug.LogError($"Get null from ArtLoad for Card {data.Id}:"); if (!HasOverFrameArtFile(data.Id))
Debug.LogError($"Get null from ArtLoad for Card {data.Id}:");
art = TextureManager.container.unknownArt.texture; art = TextureManager.container.unknownArt.texture;
} }
...@@ -559,6 +563,30 @@ namespace MDPro3.Utility ...@@ -559,6 +563,30 @@ namespace MDPro3.Utility
#region Art File List Cache #region Art File List Cache
private static bool HasOverFrameArtFile(int code)
{
if (knownOverFrameArts.ContainsKey(code))
return true;
var fileName = code + Program.EXPANSION_PNG;
#if !UNITY_EDITOR && (UNITY_ANDROID || UNITY_IOS)
var overFramePath = Path.Combine(Application.persistentDataPath, "Picture", "OverFrame", fileName);
var overframePath = Path.Combine(Application.persistentDataPath, "Picture", "Overframe", fileName);
#elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX
var overFramePath = Path.Combine(Environment.CurrentDirectory, "Picture", "OverFrame", fileName);
var overframePath = Path.Combine(Environment.CurrentDirectory, "Picture", "Overframe", fileName);
#else
var overFramePath = Path.Combine(Environment.CurrentDirectory, "Picture", "OverFrame", fileName);
var overframePath = Path.Combine(Environment.CurrentDirectory, "Picture", "Overframe", fileName);
#endif
var exists = File.Exists(overFramePath) || File.Exists(overframePath);
if (exists)
knownOverFrameArts.TryAdd(code, 0);
return exists;
}
private static readonly List<int> artFileList = new(); private static readonly List<int> artFileList = new();
private static readonly Dictionary<int, string> artAltFileList = new(); private static readonly Dictionary<int, string> artAltFileList = new();
private static bool artFileListInitialized; private static bool artFileListInitialized;
...@@ -688,4 +716,4 @@ namespace MDPro3.Utility ...@@ -688,4 +716,4 @@ namespace MDPro3.Utility
#endregion #endregion
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment