22using Godot ;
33using HarmonyLib ;
44using MegaCrit . Sts2 . Core . Assets ;
5+ using MegaCrit . Sts2 . Core . Entities . Cards ;
6+ using MegaCrit . Sts2 . Core . Entities . UI ;
57using MegaCrit . Sts2 . Core . Events ;
68using MegaCrit . Sts2 . Core . Helpers ;
79using MegaCrit . Sts2 . Core . Models ;
10+ using MegaCrit . Sts2 . Core . Nodes . Cards ;
811using MegaCrit . Sts2 . Core . Runs ;
912using MegaCrit . Sts2 . Core . Timeline ;
1013using STS2RitsuLib . Patching . Models ;
1114using STS2RitsuLib . Scaffolding . Characters ;
15+ using STS2RitsuLib . Timeline . Scaffolding ;
1216using STS2RitsuLib . Utils ;
1317
1418namespace STS2RitsuLib . Scaffolding . Content . Patches
@@ -282,8 +286,8 @@ internal static bool TryUseCompressedTextureAsTexture2DOverride<TOverrides>(
282286 }
283287
284288 /// <summary>
285- /// Optional card art paths consumed by content asset Harmony patches on <see cref="CardModel" /> .
286- /// 由 <see cref="CardModel" /> 上的 content asset Harmony 补丁使用的可选卡牌美术路径 。
289+ /// Optional card art paths and materials consumed by content asset Harmony patches.
290+ /// 由 content asset Harmony 补丁使用的可选卡牌美术路径和材质 。
287291 /// </summary>
288292 public interface IModCardAssetOverrides
289293 {
@@ -305,6 +309,12 @@ public interface IModCardAssetOverrides
305309 /// </summary>
306310 string ? CustomBetaPortraitPath { get ; }
307311
312+ /// <summary>
313+ /// Override for card portrait <see cref="Material" /> resource path.
314+ /// 卡图 <see cref="Material" /> 资源路径覆盖。
315+ /// </summary>
316+ string ? CustomPortraitMaterialPath => AssetProfile . PortraitMaterialPath ;
317+
308318 /// <summary>
309319 /// Override for card frame texture path.
310320 /// 卡牌边框纹理路径覆盖。
@@ -348,6 +358,23 @@ public interface IModCardAssetOverrides
348358 string ? CustomBannerMaterialPath { get ; }
349359 }
350360
361+ /// <summary>
362+ /// Optional direct portrait <see cref="Material" /> override for cards.
363+ /// This is applied to the portrait TextureRect after <see cref="NCard" /> reloads its vanilla visuals.
364+ /// 用于卡牌的可选直接卡图 <see cref="Material" /> 覆盖。
365+ /// 会在 <see cref="NCard" /> 重载原版视觉后应用到卡图 TextureRect。
366+ /// </summary>
367+ public interface IModCardPortraitMaterialOverride
368+ {
369+ /// <summary>
370+ /// Direct portrait material override.
371+ /// Return <c>null</c> to continue with other override layers.
372+ /// 直接的卡图材质覆盖。
373+ /// 返回 <c>null</c> 以继续使用其它覆盖层。
374+ /// </summary>
375+ Material ? CustomPortraitMaterial => null ;
376+ }
377+
351378 /// <summary>
352379 /// Optional direct frame <see cref="Material" /> override for cards.
353380 /// This bypasses resource-path loading and is checked before
@@ -733,8 +760,9 @@ public static ModPatchTarget[] GetTargets()
733760 /// <summary>
734761 /// Suppresses the vanilla placeholder label for mod epochs with custom artwork.
735762 /// </summary>
736- // ReSharper disable once InconsistentNaming
763+ // ReSharper disable InconsistentNaming
737764 public static bool Prefix ( EpochModel __instance , ref bool __result )
765+ // ReSharper restore InconsistentNaming
738766 {
739767 if ( __instance is IModEpochAssetOverrides overrides &&
740768 ! string . IsNullOrWhiteSpace ( overrides . CustomBigPortraitPath ) &&
@@ -757,12 +785,10 @@ public static bool Prefix(EpochModel __instance, ref bool __result)
757785 private static bool IsCharacterUnlockEpochTemplate ( Type type )
758786 {
759787 for ( var current = type ; current != null ; current = current . BaseType )
760- {
761788 if ( current . IsGenericType &&
762789 current . GetGenericTypeDefinition ( ) ==
763- typeof ( STS2RitsuLib . Timeline . Scaffolding . CharacterUnlockEpochTemplate < > ) )
790+ typeof ( CharacterUnlockEpochTemplate < > ) )
764791 return true ;
765- }
766792
767793 return false ;
768794 }
@@ -1079,6 +1105,76 @@ public static bool Prefix(CardPoolModel __instance, ref Material __result)
10791105 }
10801106 }
10811107
1108+ /// <summary>
1109+ /// Applies custom portrait <see cref="Material" /> overrides after <see cref="NCard" /> reloads vanilla visuals.
1110+ /// 在 <see cref="NCard" /> 重载原版视觉后应用自定义卡图 <see cref="Material" /> 覆盖。
1111+ /// </summary>
1112+ public class CardPortraitMaterialPatch : IPatchMethod
1113+ {
1114+ /// <inheritdoc cref="IPatchMethod.PatchId" />
1115+ public static string PatchId => "content_asset_override_card_portrait_material" ;
1116+
1117+ /// <inheritdoc cref="IPatchMethod.Description" />
1118+ public static string Description => "Allow mod cards to override the NCard portrait material" ;
1119+
1120+ /// <inheritdoc cref="IPatchMethod.IsCritical" />
1121+ public static bool IsCritical => false ;
1122+
1123+ /// <inheritdoc cref="IPatchMethod.GetTargets" />
1124+ public static ModPatchTarget [ ] GetTargets ( )
1125+ {
1126+ return [ new ( typeof ( NCard ) , "Reload" ) ] ;
1127+ }
1128+
1129+ // ReSharper disable InconsistentNaming
1130+ /// <summary>
1131+ /// Reapplies the custom material because vanilla <c>Reload</c> clears portrait materials for visible cards.
1132+ /// 重新应用自定义材质,因为原版 <c>Reload</c> 会清空可见卡牌的卡图材质。
1133+ /// </summary>
1134+ public static void Postfix ( NCard __instance )
1135+ // ReSharper restore InconsistentNaming
1136+ {
1137+ var model = __instance . Model ;
1138+ if ( model == null || __instance . Visibility != ModelVisibility . Visible )
1139+ return ;
1140+
1141+ if ( ! TryGetPortraitMaterial ( model , out var material ) )
1142+ return ;
1143+
1144+ var portrait = GetPortraitNode ( __instance , model ) ;
1145+ if ( portrait == null )
1146+ return ;
1147+
1148+ portrait . Material = material ;
1149+ }
1150+
1151+ private static TextureRect ? GetPortraitNode ( NCard card , CardModel model )
1152+ {
1153+ var path = model . Rarity == CardRarity . Ancient ? "%AncientPortrait" : "%Portrait" ;
1154+ return card . GetNodeOrNull < TextureRect > ( path ) ;
1155+ }
1156+
1157+ private static bool TryGetPortraitMaterial ( CardModel card , out Material material )
1158+ {
1159+ material = null ! ;
1160+ if ( ! ContentAssetOverridePatchHelper . TryUseDirectMaterialOverride < IModCardPortraitMaterialOverride > (
1161+ card , ref material , static o => o . CustomPortraitMaterial ) )
1162+ return true ;
1163+
1164+ if ( ExternalCardMaterialOverrideRegistry . TryGetPortraitMaterial ( card , out material ) )
1165+ return true ;
1166+
1167+ if ( ! ModCharacterOwnedVisualOverrideHelper . TryCardPortraitMaterial ( card , ref material ) )
1168+ return true ;
1169+
1170+ return ! ContentAssetOverridePatchHelper . TryUseMaterialOverride < IModCardAssetOverrides > (
1171+ card ,
1172+ ref material ,
1173+ static o => o . CustomPortraitMaterialPath ,
1174+ nameof ( IModCardAssetOverrides . CustomPortraitMaterialPath ) ) ;
1175+ }
1176+ }
1177+
10821178 /// <summary>
10831179 /// Patches <see cref="CardModel.AllPortraitPaths" /> so custom portrait/beta paths participate in preload lists.
10841180 /// 修补<see cref="CardModel.AllPortraitPaths" />,使自定义 portrait/beta 路径 participate in 预加载 列表。
0 commit comments