From ffe9c1062d29327c6aa5dfdfac9c7f28f8d03216 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 20:35:13 -0800 Subject: [PATCH 01/11] Fixed GemsDraw only considering the player's slots when determining how many cards to draw --- CHANGELOG.md | 4 ++++ .../Card/Vanilla Ability Patches/GemsDrawFix.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c72b7a8..129463cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.23.4 +- Fixed GemsDraw only considering the player's slots when determining how many cards to draw +- + # 2.23.3 - Fixed custom deck exhaust sequence not performing the requisite checks - Added 'CanAppearRandomly' bool and associated extension method for custom regions diff --git a/InscryptionCommunityPatch/Card/Vanilla Ability Patches/GemsDrawFix.cs b/InscryptionCommunityPatch/Card/Vanilla Ability Patches/GemsDrawFix.cs index 50ae655d..d9ae9a50 100644 --- a/InscryptionCommunityPatch/Card/Vanilla Ability Patches/GemsDrawFix.cs +++ b/InscryptionCommunityPatch/Card/Vanilla Ability Patches/GemsDrawFix.cs @@ -23,7 +23,7 @@ public static IEnumerator BetterGemsDraw(GemsDraw __instance) Singleton.Instance.SwitchToView(SaveManager.SaveFile.IsMagnificus ? View.WizardBattleSlots : View.Default); yield return new WaitForSeconds(0.1f); - int numGems = Singleton.Instance.PlayerSlotsCopy.Count(x => x.Card != null && x.Card.HasTrait(Trait.Gem)); + int numGems = Singleton.Instance.GetSlots(!__instance.Card.OpponentCard).Count(x => x.Card != null && x.Card.HasTrait(Trait.Gem)); if (numGems == 0) { From e71f10471ed16589ecc4bd2c80c2661d9fe35886 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 20:37:51 -0800 Subject: [PATCH 02/11] Fixed Gemify affecting Blood cost when it shouldn't --- CHANGELOG.md | 2 +- InscryptionAPI/Card/CostProperties.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129463cc..43f57dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 2.23.4 - Fixed GemsDraw only considering the player's slots when determining how many cards to draw -- +- Fixed Gemify affecting Blood cost when it shouldn't # 2.23.3 - Fixed custom deck exhaust sequence not performing the requisite checks diff --git a/InscryptionAPI/Card/CostProperties.cs b/InscryptionAPI/Card/CostProperties.cs index c89a4cef..f4b3f522 100644 --- a/InscryptionAPI/Card/CostProperties.cs +++ b/InscryptionAPI/Card/CostProperties.cs @@ -128,7 +128,7 @@ public static List ImprovedGemsCost(CardInfo instance) public static bool ReduceGemifiedBlood(PlayableCard card, int? bloodCost = null) { - return (bloodCost ?? OriginalBloodCost(card.Info)) > 0 && !ReduceGemifiedMox(card) && !ReduceGemifiedBones(card) && !ReduceGemifiedMox(card); + return (bloodCost ?? OriginalBloodCost(card.Info)) > 0 && !ReduceGemifiedEnergy(card) && !ReduceGemifiedBones(card) && !ReduceGemifiedMox(card); } public static bool ReduceGemifiedMox(PlayableCard card, List gemsCost = null) { From 4876422d06eb0ea3d4d780640a6e5dd7d9ae45cd Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:00:54 -0800 Subject: [PATCH 03/11] Add Act 1 gemify visual indicators, bump ver to 2.23.4 --- InscryptionAPI/InscryptionAPI.csproj | 2 +- InscryptionAPI/InscryptionAPIPlugin.cs | 2 +- .../Assets/act1_gemify_attack.png | Bin 0 -> 2782 bytes .../Assets/act1_gemify_base.png | Bin 0 -> 6829 bytes .../Assets/act1_gemify_cost.png | Bin 0 -> 2713 bytes .../Assets/act1_gemify_health.png | Bin 0 -> 2755 bytes .../Card/Part1GemifyIndicator.cs | 146 ++++++++++++++++++ 7 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 InscryptionCommunityPatch/Assets/act1_gemify_attack.png create mode 100644 InscryptionCommunityPatch/Assets/act1_gemify_base.png create mode 100644 InscryptionCommunityPatch/Assets/act1_gemify_cost.png create mode 100644 InscryptionCommunityPatch/Assets/act1_gemify_health.png create mode 100644 InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs diff --git a/InscryptionAPI/InscryptionAPI.csproj b/InscryptionAPI/InscryptionAPI.csproj index bea651c4..5a9b03f3 100644 --- a/InscryptionAPI/InscryptionAPI.csproj +++ b/InscryptionAPI/InscryptionAPI.csproj @@ -10,7 +10,7 @@ full false true - 2.23.3 + 2.23.4 diff --git a/InscryptionAPI/InscryptionAPIPlugin.cs b/InscryptionAPI/InscryptionAPIPlugin.cs index 4002ce6c..7ae842b2 100644 --- a/InscryptionAPI/InscryptionAPIPlugin.cs +++ b/InscryptionAPI/InscryptionAPIPlugin.cs @@ -31,7 +31,7 @@ public class InscryptionAPIPlugin : BaseUnityPlugin { public const string ModGUID = "cyantist.inscryption.api"; public const string ModName = "InscryptionAPI"; - public const string ModVer = "2.23.3"; + public const string ModVer = "2.23.4"; public static string Directory = ""; diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_attack.png b/InscryptionCommunityPatch/Assets/act1_gemify_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..23d8a5384a7c9e01d48b66db25f6d38d38f9a4a9 GIT binary patch literal 2782 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 literal 0 HcmV?d00001 diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_cost.png b/InscryptionCommunityPatch/Assets/act1_gemify_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..b604c86abb411c3438276d5c4096237e9a648d60 GIT binary patch literal 2713 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 MOs!0w-?*3bZ`6C@oB#j- literal 0 HcmV?d00001 diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_health.png b/InscryptionCommunityPatch/Assets/act1_gemify_health.png new file mode 100644 index 0000000000000000000000000000000000000000..3e4ba9123bf055e17fee23942ac8721208a299cb GIT binary patch literal 2755 zcmd5;_g52G77i_l5Q?mrgeEYGAbmiZ6eS5Nl6BBg1PMi&1OkB+V2CscMIaz5B6UDl zXLUe|bOHqfLe;Y&|gOE%SD~L9s>X< zcJ4#MM&|N&`-M2TC+>1&FplUS9R#@Ke+z>%LBg*bwK6d`vDj&sY82-*26>(jK7Za2 za6s(H0H6{w012_WAcp$idjB;iB&7blmlSJr2}yvmn7zcfl`j3~eVAD9TMj`2z?S+i z|Ibi>IFu&~kQQf}S$uCMQSn#T{fROae|5Sq*83(Y?ko9criZPKSp8jPZf3S0%~bmA zavRvLD&9Q$JnYPsSkzL9Y<5r(gnWd6Re6=7d&2Ol(d6OELvJSoC=RtS^IEh~rq#pA zLvGo|4S(Um6vOJaLPbr_QY@1oTF=O1RvQdat_lsc+*E$Qex+tN zZI(s5Omm<~<&oPZ?4TWxQVMd;UST)V*&0#q91X|KEtgI2Dkw}=%J6{gF(qIova z_d!owk2FIkZo8!GKzt?nQCx@XH`^H|Vx*F+`F3{;xdodl5_+o1nlMTkh=CrP+S z&$q!=4-+HLBC>lDmelR_{#g!Ih&rq|@8471+X{_0fP$js18t_WjD91$R^HL9%OX|^ zH5_Cipwps*t&loJ8|g@=yR*mc(e!~;6dFANLld0EB?eAv^|Vc_ZFRI4LrBmYsSG1*1qC*Opuh>^{MC?3#?dvd zJ5V!;&>M#M-dzZRyys=SR9DzZ*Ueet?SZr~pjUmKsv4y3GF>&TPzF8pW>&6W*Tz$7<`M@Rx))V3MySPB)(&-CRu;P$1D%MHlgLhWp(trLX+Yut-YrlEX~7*oRpzPqy**g9 zOhtkR-NB>*v**ys@n1Tk3(;4{;u-CIaW8+z2NY(L1*)UgqdR#mOseQ*Q1P>xd>zWb z)nTRi=hohrY+tEiXYYkGf!6Og?T1}H(WiNwCY_zNQ!yp6jp&d>H-xW!J>Hf%oAe1s zV;D_xQ!o_%HoQV-9i#uzH{btr6wk+AzJsIZ-QZ2`#B=MHEtJR;*!FAOqvgrmw005c zT0_Q)50yLdywlla+USJS-9?dhdUv5+Qrxy>Y~N!3xsf`lkjULzJs$72>W z^JGfPxPhMEkqPv>WtfxWlH3jU3A9qKTOi-cVlKL8%ZcWmE;o~_Cd)iVuk5=@5uiF$ zM@p?tG?&I(cEjFc9v^JMr_`>8y}%bD$$0NqjRC&78k_J+?1LQW#C*u1WUWE041|>9 z7v6_VWw;SDEjbCvKaX8tK4mQB#f?#gr~5vQa-Y2dD>R=wqF~w*m}_-u@zQ#>_vAcr8;wQyeMYubm`#N zm4|F(znREKfvZ@ybfUdR3izYTV|x$}gBqnfl94V1w!#{rXE6_Q+jvu0WMCeyK3eqtVQ-?^8wgB^7f+=oW|4ITc~*B=f?Y~qzX_zT z97`$jX*v84CJP|NYxeTA-DJkD_{*xF+HgTgcF-jwg&|T`C#&msTg-73Px;AB^ZT4` zYNYRHo>fk>jF)K>g#RPXf=u{2G!yx;V8U&{>K$Dlc2h4W zYhCq8de#F0J7>1~2ndZ0{_fQ!{-@>-9|m$XzE$;~T?y)UEfH#}MZ1Ngqpmwv1!($( z=TvsDM@_J}1@swAV3Yw1ub*Pjb!C|_7o2XYSMC}kGXn!}jNQ6IZ1>DZhfc2 zRbI|vm)Fy?F7orYK7d8RjM95FR+7J#6#Lhc%XYF=U9qXX#y`f_ z#_hdh&-O3#90?BB^tQous=X+x6UWKGjKWzDS@~&js|{{TQWUR~y(GG4E~FG68Cro} zGhLUv_9A|*)A}3|uI)U_RbLay=8MR08vL@NJQmjDMVJEGk<@~&`N448Kc0Y(lTo*c ze6KmWxmptPpnmceYIvsJF$Ck!3as^0|Cs)+!E}{HD9WaSEgzxcC3?M4?wp|-XUE(p ztXAUZ3(T*BDT_G?x95kz_NQM=f-)DGQ^_Tsh*m*b-R(;kl x.gemify) || (playableCard != null && playableCard.IsGemified())) { + bool activateAttack = false; + bool activateHealth = false; + bool activateCost = false; + if (playableCard != null) { + if (playableCard.OpponentCard) { + activateAttack = OpponentGemsManager.Instance.HasGem(GemType.Orange); + activateHealth = OpponentGemsManager.Instance.HasGem(GemType.Green); + activateCost = OpponentGemsManager.Instance.HasGem(GemType.Blue); + } + else { + activateAttack = ResourcesManager.Instance.HasGem(GemType.Orange); + activateHealth = ResourcesManager.Instance.HasGem(GemType.Green); + activateCost = ResourcesManager.Instance.HasGem(GemType.Blue); + } + } + + // always render base texture + GemifyRenderers[0].enabled = true; + GemifyRenderers[1].enabled = activateAttack; + GemifyRenderers[2].enabled = activateHealth; + GemifyRenderers[3].enabled = activateCost; + } + else { + // turn off renderers by default + GemifyRenderers[0].enabled = false; + GemifyRenderers[1].enabled = false; + GemifyRenderers[2].enabled = false; + GemifyRenderers[3].enabled = false; + } + } + + public static void AddAct1GemifyVisuals(CardRenderCamera cardRenderCamera) { + Debug.Log("GemifyTest"); + CardDisplayer3D dis = cardRenderCamera.cardDisplayer as CardDisplayer3D; + + GameObject obj = new("GemifyTest"); + obj.transform.SetParent(cardRenderCamera.cardDisplayer.transform); + obj.transform.localPosition = Vector3.zero; + obj.transform.localScale = Vector3.one; + + // in order to get the gemify visualisers to appear correctly, we need to change the rendering order + // of basically every element in the card displayer + + // new order: + // portrait: 0 (default) + // gemify renderers: [1, 4] + // most everything else: 5 + // card cost: 6 + // decal renderers: [7, 11] + dis.healthText.gameObject.GetComponent().sortingOrder = 5; + dis.attackText.gameObject.GetComponent().sortingOrder = 5; + dis.nameText.gameObject.GetComponent().sortingOrder = 5; + dis.nameGraphicRenderer.sortingOrder = 5; + dis.emissivePortraitRenderer.sortingOrder = 5; + dis.costRenderer.sortingOrder = 6; + foreach (Renderer r in dis.tribeIconRenderers) { + r.sortingOrder = 5; + } + + for (int i = 0; i < dis.decalRenderers.Count; i++) { + dis.decalRenderers[i].sortingOrder = 7 + i; + dis.decalRenderers[i].GetComponent().sortingOrder = 7 + i; + } + + List acts = dis.AbilityIcons.GetComponentsInChildren(true).ToList(); + foreach (Transform tr in dis.transform) { + acts.Concat(tr.GetComponentsInChildren(true)); + } + foreach (AbilityIconInteractable a in acts) { + Renderer rend = a.GetComponent(); + if (rend.sortingOrder == 0) { + rend.sortingOrder = 5; + + SetSortingLayer layer = a.GetComponent(); + if (layer != null) { + layer.sortingOrder = 5; + } + } + } + + System.Reflection.Assembly asm = typeof(CommunityArtPatches).Assembly; + + // add new renderers for gemify textures, using the decal renderers as the base + GameObject obj1 = GameObject.Instantiate(dis.decalRenderers[0].gameObject, obj.transform); + GemifyRenderers[0] = obj1.GetComponent(); + GemifyRenderers[0].sortingOrder = obj1.GetComponent().sortingOrder = 1; + GemifyRenderers[0].enabled = true; + GemifyRenderers[0].material.mainTexture = TextureHelper.GetImageAsTexture($"act1_gemify_base.png", asm); + + GameObject obj2 = GameObject.Instantiate(dis.decalRenderers[0].gameObject, obj.transform); + GemifyRenderers[1] = obj2.GetComponent(); + GemifyRenderers[1].sortingOrder = obj2.GetComponent().sortingOrder = 2; + GemifyRenderers[1].enabled = true; + GemifyRenderers[1].material.mainTexture = TextureHelper.GetImageAsTexture($"act1_gemify_attack.png", asm); + + GameObject obj3 = GameObject.Instantiate(dis.decalRenderers[0].gameObject, obj.transform); + GemifyRenderers[2] = obj3.GetComponent(); + GemifyRenderers[2].sortingOrder = obj3.GetComponent().sortingOrder = 3; + GemifyRenderers[2].enabled = true; + GemifyRenderers[2].material.mainTexture = TextureHelper.GetImageAsTexture($"act1_gemify_health.png", asm); + + GameObject obj4 = GameObject.Instantiate(dis.decalRenderers[0].gameObject, obj.transform); + GemifyRenderers[3] = obj4.GetComponent(); + GemifyRenderers[3].sortingOrder = obj4.GetComponent().sortingOrder = 4; + GemifyRenderers[3].enabled = true; + GemifyRenderers[3].material.mainTexture = TextureHelper.GetImageAsTexture($"act1_gemify_cost.png", asm); + } + + [HarmonyPrefix, HarmonyPatch(typeof(CardRenderCamera), nameof(CardRenderCamera.UpdateTextureWhenReady))] + private static bool AddGemifyRenderersBeforeUpdateTexture(CardRenderCamera __instance) { + if (SaveManager.SaveFile.IsPart1 && GemifyRenderers[0] == null) { + AddAct1GemifyVisuals(__instance); + } + return true; + } +} From 228d385d048fd8525af6f2e3b27c42c8d16d1ec7 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:20:33 -0800 Subject: [PATCH 04/11] Added Gems Cost support for ExtendedActivatedAbilityBehaviour class --- CHANGELOG.md | 1 + InscryptionAPI/Card/DamageShieldBehaviour.cs | 54 +++++++++++++----- .../Card/ExtendedActivatedAbilityBehaviour.cs | 55 ++++++++++++++----- 3 files changed, 81 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f57dbf..04bc9f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 2.23.4 - Fixed GemsDraw only considering the player's slots when determining how many cards to draw - Fixed Gemify affecting Blood cost when it shouldn't +- Added Gems Cost support for ExtendedActivatedAbilityBehaviour class # 2.23.3 - Fixed custom deck exhaust sequence not performing the requisite checks diff --git a/InscryptionAPI/Card/DamageShieldBehaviour.cs b/InscryptionAPI/Card/DamageShieldBehaviour.cs index e6f44468..b2d5b76f 100644 --- a/InscryptionAPI/Card/DamageShieldBehaviour.cs +++ b/InscryptionAPI/Card/DamageShieldBehaviour.cs @@ -87,20 +87,25 @@ public abstract class ActivatedDamageShieldBehaviour : DamageShieldBehaviour public int bloodCostMod; public int bonesCostMod; public int energyCostMod; + public List gemsCostMod; public int healthCostMod; public virtual int StartingBloodCost { get; } public virtual int StartingBonesCost { get; } public virtual int StartingEnergyCost { get; } + public virtual List StartingGemsCost { get; } public virtual int StartingHealthCost { get; } public virtual int OnActivateBloodCostMod { get; set; } public virtual int OnActivateBonesCostMod { get; set; } public virtual int OnActivateEnergyCostMod { get; set; } + public virtual List OnActivateGemsCostMod { get; set; } + public virtual bool OnActivateGemsCostModRemovesGems { get; set; } public virtual int OnActivateHealthCostMod { get; set; } public int BloodCost => Mathf.Max(0, StartingBloodCost + bloodCostMod); public int BonesCost => Mathf.Max(0, StartingBonesCost + bonesCostMod); public int EnergyCost => Mathf.Max(0, StartingEnergyCost + energyCostMod); + public List GemsCost => StartingGemsCost.Concat(gemsCostMod).ToList(); public int HealthCost => Mathf.Max(0, StartingHealthCost + healthCostMod); public Dictionary currentSacrificedCardInfos = new(); @@ -187,12 +192,29 @@ public sealed override IEnumerator OnActivatedAbility() yield break; } } - if (OnActivateEnergyCostMod != 0) - energyCostMod += OnActivateEnergyCostMod; - if (OnActivateBonesCostMod != 0) - bonesCostMod += OnActivateBonesCostMod; - if (OnActivateHealthCostMod != 0) - healthCostMod += OnActivateHealthCostMod; + + int energyMod = OnActivateEnergyCostMod; + int bonesMod = OnActivateBonesCostMod; + List gemsMod = OnActivateGemsCostMod; + int healthMod = OnActivateHealthCostMod; + + if (energyMod != 0) + energyCostMod += energyMod; + + if (bonesMod != 0) + bonesCostMod += bonesMod; + + if (gemsMod != null && gemsMod.Count > 0) { + if (OnActivateGemsCostModRemovesGems) { + gemsMod.ForEach(x => gemsCostMod.Remove(x)); + } + else { + gemsCostMod.AddRange(gemsMod); + } + } + + if (healthMod != 0) + healthCostMod += healthMod; yield return PostActivate(); currentSacrificedCardInfos.Clear(); @@ -296,18 +318,22 @@ private IEnumerator ChooseSacrifices() manager.currentSacrifices.Clear(); } - private bool CanAfford() - { - if (BloodCost <= 0 || SacrificeValue() >= BloodCost) - { - if (base.Card.Health >= HealthCost) - { - if (Singleton.Instance.PlayerEnergy >= EnergyCost) - return Singleton.Instance.PlayerBones >= BonesCost; + private bool CanAfford() { + // if no blood cost or we can fulfill our blood cost + if (BloodCost < 1 || SacrificeValue() >= BloodCost) { + // if we have enough health, Energy, and Bones + if (base.Card.Health >= HealthCost && ResourcesManager.Instance.PlayerEnergy >= EnergyCost && ResourcesManager.Instance.PlayerBones >= BonesCost) { + bool enoughOrange = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); + bool enoughGreen = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Green) >= GemsCost.Count(x => x == GemType.Green); + bool enoughBlue = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); + + // if we have enough gems + return enoughOrange && enoughGreen && enoughBlue; } } return false; } + private bool LearnMechanic() => SaveManager.SaveFile.IsPart2 && !ProgressionData.LearnedMechanic(MechanicsConcept.GBCActivatedAbilities); private int SacrificeValue() { diff --git a/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs b/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs index cba377ee..89e24692 100644 --- a/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs +++ b/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs @@ -11,20 +11,25 @@ public abstract class ExtendedActivatedAbilityBehaviour : AbilityBehaviour public int bloodCostMod; public int bonesCostMod; public int energyCostMod; + public List gemsCostMod; public int healthCostMod; public virtual int StartingBloodCost { get; } public virtual int StartingBonesCost { get; } public virtual int StartingEnergyCost { get; } + public virtual List StartingGemsCost { get; } public virtual int StartingHealthCost { get; } public virtual int OnActivateBloodCostMod { get; set; } public virtual int OnActivateBonesCostMod { get; set; } public virtual int OnActivateEnergyCostMod { get; set; } + public virtual List OnActivateGemsCostMod { get; set; } + public virtual bool OnActivateGemsCostModRemovesGems { get; set; } public virtual int OnActivateHealthCostMod { get; set; } public int BloodCost => Mathf.Max(0, StartingBloodCost + bloodCostMod); public int BonesCost => Mathf.Max(0, StartingBonesCost + bonesCostMod); public int EnergyCost => Mathf.Max(0, StartingEnergyCost + energyCostMod); + public List GemsCost => StartingGemsCost.Concat(gemsCostMod).ToList(); public int HealthCost => Mathf.Max(0, StartingHealthCost + healthCostMod); public Dictionary currentSacrificedCardInfos = new(); @@ -93,8 +98,9 @@ public sealed override IEnumerator OnActivatedAbility() } } } - if (BonesCost > 0) + if (BonesCost > 0) { yield return Singleton.Instance.SpendBones(BonesCost); + } yield return new WaitForSeconds(0.1f); yield return base.PreSuccessfulTriggerSequence(); @@ -111,12 +117,28 @@ public sealed override IEnumerator OnActivatedAbility() yield break; } } - if (OnActivateEnergyCostMod != 0) - energyCostMod += OnActivateEnergyCostMod; - if (OnActivateBonesCostMod != 0) - bonesCostMod += OnActivateBonesCostMod; - if (OnActivateHealthCostMod != 0) - healthCostMod += OnActivateHealthCostMod; + int energyMod = OnActivateEnergyCostMod; + int bonesMod = OnActivateBonesCostMod; + List gemsMod = OnActivateGemsCostMod; + int healthMod = OnActivateHealthCostMod; + + if (energyMod != 0) + energyCostMod += energyMod; + + if (bonesMod != 0) + bonesCostMod += bonesMod; + + if (gemsMod != null && gemsMod.Count > 0) { + if (OnActivateGemsCostModRemovesGems) { + gemsMod.ForEach(x => gemsCostMod.Remove(x)); + } + else { + gemsCostMod.AddRange(gemsMod); + } + } + + if (healthMod != 0) + healthCostMod += healthMod; yield return PostActivate(); currentSacrificedCardInfos.Clear(); @@ -220,14 +242,17 @@ private IEnumerator ChooseSacrifices() manager.currentSacrifices.Clear(); } - private bool CanAfford() - { - if (BloodCost <= 0 || SacrificeValue() >= BloodCost) - { - if (base.Card.Health >= HealthCost) - { - if (Singleton.Instance.PlayerEnergy >= EnergyCost) - return Singleton.Instance.PlayerBones >= BonesCost; + private bool CanAfford() { + // if no blood cost or we can fulfill our blood cost + if (BloodCost < 1 || SacrificeValue() >= BloodCost) { + // if we have enough health, Energy, and Bones + if (base.Card.Health >= HealthCost && ResourcesManager.Instance.PlayerEnergy >= EnergyCost && ResourcesManager.Instance.PlayerBones >= BonesCost) { + bool enoughOrange = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); + bool enoughGreen = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Green) >= GemsCost.Count(x => x == GemType.Green); + bool enoughBlue = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); + + // if we have enough gems + return enoughOrange && enoughGreen && enoughBlue; } } return false; From 674e11f6a58adab0369cb2f16067320c102bca03 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:38:58 -0800 Subject: [PATCH 05/11] Fix CanAfford gem check only checking the opponent's gems, add documentation for ResourcesManagerHelpers.GemCount --- InscryptionAPI/Card/DamageShieldBehaviour.cs | 18 +++++++++++++---- .../Card/ExtendedActivatedAbilityBehaviour.cs | 20 ++++++++++++++----- .../Helpers/ResourcesManagerHelpers.cs | 7 +++++++ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/InscryptionAPI/Card/DamageShieldBehaviour.cs b/InscryptionAPI/Card/DamageShieldBehaviour.cs index b2d5b76f..458d7c5c 100644 --- a/InscryptionAPI/Card/DamageShieldBehaviour.cs +++ b/InscryptionAPI/Card/DamageShieldBehaviour.cs @@ -1,5 +1,6 @@ using DiskCardGame; using GBC; +using InscryptionAPI.Helpers; using InscryptionAPI.Helpers.Extensions; using System.Collections; using UnityEngine; @@ -105,7 +106,16 @@ public abstract class ActivatedDamageShieldBehaviour : DamageShieldBehaviour public int BloodCost => Mathf.Max(0, StartingBloodCost + bloodCostMod); public int BonesCost => Mathf.Max(0, StartingBonesCost + bonesCostMod); public int EnergyCost => Mathf.Max(0, StartingEnergyCost + energyCostMod); - public List GemsCost => StartingGemsCost.Concat(gemsCostMod).ToList(); + public List GemsCost { + get { + List retval = new(); + if (StartingGemsCost != null && StartingGemsCost.Count > 0) { + retval.AddRange(StartingGemsCost); + } + retval.AddRange(gemsCostMod); + return retval; + } + } public int HealthCost => Mathf.Max(0, StartingHealthCost + healthCostMod); public Dictionary currentSacrificedCardInfos = new(); @@ -323,9 +333,9 @@ private bool CanAfford() { if (BloodCost < 1 || SacrificeValue() >= BloodCost) { // if we have enough health, Energy, and Bones if (base.Card.Health >= HealthCost && ResourcesManager.Instance.PlayerEnergy >= EnergyCost && ResourcesManager.Instance.PlayerBones >= BonesCost) { - bool enoughOrange = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); - bool enoughGreen = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Green) >= GemsCost.Count(x => x == GemType.Green); - bool enoughBlue = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); + bool enoughOrange = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); + bool enoughGreen = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Green) >= GemsCost.Count(x => x == GemType.Green); + bool enoughBlue = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); // if we have enough gems return enoughOrange && enoughGreen && enoughBlue; diff --git a/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs b/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs index 89e24692..bee1226c 100644 --- a/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs +++ b/InscryptionAPI/Card/ExtendedActivatedAbilityBehaviour.cs @@ -1,6 +1,7 @@ using DiskCardGame; using GBC; using HarmonyLib; +using InscryptionAPI.Helpers; using InscryptionAPI.Helpers.Extensions; using System.Collections; using UnityEngine; @@ -11,7 +12,7 @@ public abstract class ExtendedActivatedAbilityBehaviour : AbilityBehaviour public int bloodCostMod; public int bonesCostMod; public int energyCostMod; - public List gemsCostMod; + public List gemsCostMod = new(); public int healthCostMod; public virtual int StartingBloodCost { get; } @@ -29,7 +30,16 @@ public abstract class ExtendedActivatedAbilityBehaviour : AbilityBehaviour public int BloodCost => Mathf.Max(0, StartingBloodCost + bloodCostMod); public int BonesCost => Mathf.Max(0, StartingBonesCost + bonesCostMod); public int EnergyCost => Mathf.Max(0, StartingEnergyCost + energyCostMod); - public List GemsCost => StartingGemsCost.Concat(gemsCostMod).ToList(); + public List GemsCost { + get { + List retval = new(); + if (StartingGemsCost != null && StartingGemsCost.Count > 0) { + retval.AddRange(StartingGemsCost); + } + retval.AddRange(gemsCostMod); + return retval; + } + } public int HealthCost => Mathf.Max(0, StartingHealthCost + healthCostMod); public Dictionary currentSacrificedCardInfos = new(); @@ -247,9 +257,9 @@ private bool CanAfford() { if (BloodCost < 1 || SacrificeValue() >= BloodCost) { // if we have enough health, Energy, and Bones if (base.Card.Health >= HealthCost && ResourcesManager.Instance.PlayerEnergy >= EnergyCost && ResourcesManager.Instance.PlayerBones >= BonesCost) { - bool enoughOrange = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); - bool enoughGreen = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Green) >= GemsCost.Count(x => x == GemType.Green); - bool enoughBlue = OpponentGemsManager.Instance.opponentGems.Count(x => x == GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); + bool enoughOrange = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Orange) >= GemsCost.Count(x => x == GemType.Orange); + bool enoughGreen = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Green) >= GemsCost.Count(x => x == GemType.Green); + bool enoughBlue = ResourcesManagerHelpers.GemCount(!base.Card.OpponentCard, GemType.Blue) >= GemsCost.Count(x => x == GemType.Blue); // if we have enough gems return enoughOrange && enoughGreen && enoughBlue; diff --git a/InscryptionAPI/Helpers/ResourcesManagerHelpers.cs b/InscryptionAPI/Helpers/ResourcesManagerHelpers.cs index 95170918..c872e039 100644 --- a/InscryptionAPI/Helpers/ResourcesManagerHelpers.cs +++ b/InscryptionAPI/Helpers/ResourcesManagerHelpers.cs @@ -68,6 +68,13 @@ public static int GemsOfType(this ResourcesManager instance, GemType gem) { return instance.gems.Count(x => x == gem); } + + /// + /// Counts how many gems of the given type are owned by the specified player. + /// + /// True to check the player's gems or false to check the opponent's gems. + /// GemType to get the count of. + /// The number of gems of the given type that are owned by the player/opponent. public static int GemCount(bool playerGems, GemType gemToCheck) { if (playerGems) From 0394d8e322ccc1b11c206008518c2529f9b1bce1 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 22:11:23 -0800 Subject: [PATCH 06/11] Added extension AbilityManager.FullAbility.FlipYIfOpponent --- CHANGELOG.md | 1 + InscryptionAPI/Card/AbilityExtensions.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bc9f65..51a8ae97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Fixed GemsDraw only considering the player's slots when determining how many cards to draw - Fixed Gemify affecting Blood cost when it shouldn't - Added Gems Cost support for ExtendedActivatedAbilityBehaviour class +- Added extension AbilityManager.FullAbility.FlipYIfOpponent # 2.23.3 - Fixed custom deck exhaust sequence not performing the requisite checks diff --git a/InscryptionAPI/Card/AbilityExtensions.cs b/InscryptionAPI/Card/AbilityExtensions.cs index 911261a6..934b31d2 100644 --- a/InscryptionAPI/Card/AbilityExtensions.cs +++ b/InscryptionAPI/Card/AbilityExtensions.cs @@ -424,6 +424,16 @@ public static AbilityInfo SetFlipYIfOpponent(this AbilityInfo abilityInfo, bool return abilityInfo; } /// + /// Sets whether or not the ability's icon should be flipped upside-down when it's on an opponent card. + /// + /// The instance of AbilityInfo. + /// If the icon should be flipped. + /// The same AbilityInfo so a chain can continue. + public static FullAbility SetFlipYIfOpponent(this FullAbility fullAbility, bool flipY = true) { + fullAbility.Info.SetFlipYIfOpponent(flipY); + return fullAbility; + } + /// /// Sets whether or not the ability's icon's colour should be overridden, and what the override colour should be. /// The colour override only applies to the default ability icons; totem and merge icons are unaffected. /// From 85535044eac493504dab4bda4615e65059cf9a76 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sat, 13 Dec 2025 23:53:20 -0800 Subject: [PATCH 07/11] - Fixed Act 2 Tutor sequence softlocking when there are no cards to display - Fixed Act 2 Tutor sequence not displaying cards when you have less than 7 cards remaining in your deck --- CHANGELOG.md | 2 ++ .../PixelTutor/PixelPlayableCardArray.cs | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a8ae97..472f9791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # 2.23.4 - Fixed GemsDraw only considering the player's slots when determining how many cards to draw +- Fixed Act 2 Tutor sequence softlocking when there are no cards to display +- Fixed Act 2 Tutor sequence not displaying cards when you have less than 7 cards remaining in your deck - Fixed Gemify affecting Blood cost when it shouldn't - Added Gems Cost support for ExtendedActivatedAbilityBehaviour class - Added extension AbilityManager.FullAbility.FlipYIfOpponent diff --git a/InscryptionCommunityPatch/PixelTutor/PixelPlayableCardArray.cs b/InscryptionCommunityPatch/PixelTutor/PixelPlayableCardArray.cs index 698876c5..e50db0bb 100644 --- a/InscryptionCommunityPatch/PixelTutor/PixelPlayableCardArray.cs +++ b/InscryptionCommunityPatch/PixelTutor/PixelPlayableCardArray.cs @@ -235,6 +235,11 @@ protected IEnumerator CleanUpCards() protected IEnumerator SpawnAndPlaceCards(List cards, int numRows, int pageIndex, bool isDeckReview = false, bool forPositiveEffect = true) { + if (numRows < 1) { + PatchPlugin.Logger.LogDebug($"NumRows for PixelPlayableCardArray is 0, displaying no cards"); + yield break; + } + SetOverlayEnabled(true); SetBoardEnabled(false); displayedCards.ForEach(delegate (PixelPlayableCard x) @@ -244,11 +249,6 @@ protected IEnumerator SpawnAndPlaceCards(List cards, int numRows, int }); displayedCards.Clear(); - if (numRows < 1) - { - PatchPlugin.Logger.LogDebug($"NumRows for PixelPlayableCardArray is 0, displaying no cards"); - yield break; - } // can only show 42 cards per page int maxPerPage = maxRows * maxCardsPerRow; int startingIndex = maxPerPage * pageIndex; @@ -296,6 +296,10 @@ private void InitializeGamepadGrid() private PixelPlayableCard CreateAndPlaceCard(CardInfo info, Vector3 cardPos) { PixelPlayableCard component = Instantiate(gameObjectReference, base.transform); + component.transform.position = Vector3.zeroVector; + component.SetInfo(info); + component.SetFaceDown(false, true); + component.SetEnabled(enabled: false); PixelCardAnimationController controller = component.Anim as PixelCardAnimationController; controller.cardRenderer.sortingGroupID = gameObjectReference.Anim.cardRenderer.sortingGroupID; @@ -303,11 +307,6 @@ private PixelPlayableCard CreateAndPlaceCard(CardInfo info, Vector3 cardPos) controller.cardRenderer.sortingOrder = -9000; controller.cardbackRenderer.sortingOrder = -8000; - component.SetFaceDown(false, true); - component.SetInfo(info); - component.SetEnabled(enabled: false); - component.transform.position = Vector3.zeroVector; - gamepadGrid.Rows[0].interactables.Add(component); displayedCards.Add(component); TweenInCard(component.transform, cardPos); @@ -338,7 +337,15 @@ private void SetOverlayEnabled(bool enabled) } } - private int GetNumRows(int numCards) => Mathf.Min(maxRows, numCards / maxCardsPerRow); + private int GetNumRows(int numCards) { + if (numCards == 0) + return 0; + + if (numCards < maxCardsPerRow) + return 1; + + return Mathf.Min(maxRows, numCards / maxCardsPerRow); + } private void SetCardsEnabled(bool enabled) => displayedCards.ForEach(x => x.SetEnabled(enabled)); private void SetBoardEnabled(bool enabled) { From 3c3d6eda7fb5fc78b0c3cf4e69e3f95cd0fa08df Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Sun, 14 Dec 2025 00:07:01 -0800 Subject: [PATCH 08/11] Rename field variable to _field since github doesn't like that anymore?? --- InscryptionAPI/Compatibility/TypeMapper.cs | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/InscryptionAPI/Compatibility/TypeMapper.cs b/InscryptionAPI/Compatibility/TypeMapper.cs index d7faa67c..e4d0625c 100644 --- a/InscryptionAPI/Compatibility/TypeMapper.cs +++ b/InscryptionAPI/Compatibility/TypeMapper.cs @@ -21,16 +21,16 @@ private static Dictionary FieldAccessors { _accessors = new(); - foreach (var field in AccessTools.GetDeclaredFields(typeof(S)).Where(x => !x.GetCustomAttributes(typeof(IgnoreMappingAttribute), false).Any())) + foreach (var _field in AccessTools.GetDeclaredFields(typeof(S)).Where(x => !x.GetCustomAttributes(typeof(IgnoreMappingAttribute), false).Any())) { - var accessor = new DynamicMethodDefinition("get_" + field.Name, typeof(object), new Type[] { typeof(S) }); + var accessor = new DynamicMethodDefinition("get_" + _field.Name, typeof(object), new Type[] { typeof(S) }); var il = accessor.GetILProcessor(); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, accessor.Module.ImportReference(field)); - if (field.FieldType.IsValueType) - il.Emit(OpCodes.Box, field.FieldType); + il.Emit(OpCodes.Ldfld, accessor.Module.ImportReference(_field)); + if (_field.FieldType.IsValueType) + il.Emit(OpCodes.Box, _field.FieldType); il.Emit(OpCodes.Ret); - _accessors.Add(field.Name, accessor.Generate()); + _accessors.Add(_field.Name, accessor.Generate()); } } return _accessors; @@ -46,16 +46,16 @@ private static Dictionary FieldSetters { _setters = new(); - foreach (var field in AccessTools.GetDeclaredFields(typeof(D))) + foreach (var _field in AccessTools.GetDeclaredFields(typeof(D))) { - var setter = new DynamicMethodDefinition("set_" + field.Name, typeof(void), new Type[] { typeof(D), typeof(object) }); + var setter = new DynamicMethodDefinition("set_" + _field.Name, typeof(void), new Type[] { typeof(D), typeof(object) }); var il = setter.GetILProcessor(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Unbox_Any, setter.Module.ImportReference(field.FieldType)); - il.Emit(OpCodes.Stfld, setter.Module.ImportReference(field)); + il.Emit(OpCodes.Unbox_Any, setter.Module.ImportReference(_field.FieldType)); + il.Emit(OpCodes.Stfld, setter.Module.ImportReference(_field)); il.Emit(OpCodes.Ret); - _setters.Add(field.Name, setter.Generate()); + _setters.Add(_field.Name, setter.Generate()); } } return _setters; @@ -64,12 +64,12 @@ private static Dictionary FieldSetters public static D Convert(S source, D destination) { - foreach (var field in FieldAccessors) + foreach (var _field in FieldAccessors) { - object val = field.Value.Invoke(null, new object[] { source }); - if (val is not null && FieldSetters.ContainsKey(field.Key)) + object val = _field.Value.Invoke(null, new object[] { source }); + if (val is not null && FieldSetters.ContainsKey(_field.Key)) { - FieldSetters[field.Key].Invoke(null, new object[] { destination, val }); + FieldSetters[_field.Key].Invoke(null, new object[] { destination, val }); } } From b3af046448f3e0f9a1179bf83008eac1a375593e Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:59:44 -0800 Subject: [PATCH 09/11] Added GetCustomTribeInfo method for retrieving custom TribeInfos, reformatted TribeManager --- InscryptionAPI/Card/TribeManager.cs | 291 ++++++++++++++-------------- 1 file changed, 145 insertions(+), 146 deletions(-) diff --git a/InscryptionAPI/Card/TribeManager.cs b/InscryptionAPI/Card/TribeManager.cs index 0b96aca4..a654326e 100644 --- a/InscryptionAPI/Card/TribeManager.cs +++ b/InscryptionAPI/Card/TribeManager.cs @@ -16,6 +16,24 @@ namespace InscryptionAPI.Card; [HarmonyPatch] public static class TribeManager { + /// + /// The internal object used to store all relevant info about a Tribe. + /// guid - The mod GUID that added the Tribe. + /// name - The internal name of the Tribe. + /// tribe - The enum value corresponding to this Tribe. + /// icon - The sprite displayed on cards with this Tribe. + /// tribeChoice - Whether or not this Tribe can appear at card tribe choice nodes. + /// cardBack - The texture displayed at card tribe choice nodes. If null, the API will create one using the icon Sprite. + /// + public class TribeInfo { + public string guid; + public string name; + public Tribe tribe; + public Sprite icon; + public bool tribeChoice; + public Texture2D cardback; + } + private static readonly List tribes = new(); private static readonly List tribeTypes = new(); public static readonly ReadOnlyCollection NewTribes = new(tribes); @@ -23,97 +41,6 @@ public static class TribeManager private static readonly Texture2D TribeIconMissing = TextureHelper.GetImageAsTexture("tribeicon_none.png", Assembly.GetExecutingAssembly()); - [HarmonyPatch(typeof(CardDisplayer3D), nameof(CardDisplayer3D.UpdateTribeIcon))] - [HarmonyPostfix] - private static void UpdateTribeIcon(CardDisplayer3D __instance, CardInfo info) - { - if (info == null || __instance.tribeIconRenderers.Count == 0) - return; - - foreach (TribeInfo tribeInfo in tribes) - { - if (tribeInfo?.icon == null || info.IsNotOfTribe(tribeInfo.tribe)) - continue; - - bool foundSpriteRenderer = false; - foreach (SpriteRenderer spriteRenderer in __instance.tribeIconRenderers) - { - if (spriteRenderer.sprite == null) - { - spriteRenderer.sprite = tribeInfo.icon; - foundSpriteRenderer = true; - break; - } - } - if (!foundSpriteRenderer) - { - SpriteRenderer last = __instance.tribeIconRenderers.Last(); - SpriteRenderer spriteRenderer = UnityObject.Instantiate(last); - spriteRenderer.transform.parent = last.transform.parent; - spriteRenderer.transform.localPosition = last.transform.localPosition + (__instance.tribeIconRenderers[1].transform.localPosition - __instance.tribeIconRenderers[0].transform.localPosition); - } - } - } - - [HarmonyPatch(typeof(CardSingleChoicesSequencer), nameof(CardSingleChoicesSequencer.GetCardbackTexture))] - [HarmonyPostfix] - private static void GetCardbackTexture(ref Texture __result, CardChoice choice) - { - if (choice != null && choice.tribe != Tribe.None && __result == null) - { - __result = tribes.Find(x => x?.tribe == choice.tribe)?.cardback; - } - } - - [HarmonyPatch(typeof(Part1CardChoiceGenerator), nameof(Part1CardChoiceGenerator.GenerateTribeChoices))] - [HarmonyPrefix] - private static bool GenerateTribeChoices(ref List __result, int randomSeed) - { - // create list of chooseable vanilla tribes then add all chooseable custom tribes - List list = new() - { - Tribe.Bird, - Tribe.Canine, - Tribe.Hooved, - Tribe.Insect, - Tribe.Reptile - }; - list.AddRange(TribeManager.tribes.FindAll((x) => x != null && x.tribeChoice).ConvertAll((x) => x.tribe)); - // create a list of this region's dominant tribes - List tribes = new(RunState.CurrentMapRegion.dominantTribes); - // get a list of cards obtainable at choice nodes - List obtainableCards = CardManager.AllCardsCopy.FindAll(c => c.HasCardMetaCategory(CardMetaCategory.ChoiceNode)); - // remove all non-chooseable tribes and all tribes with no cards - tribes.RemoveAll(t => (TribeManager.tribes.Exists(ct => ct.tribe == t && !ct.tribeChoice)) || !obtainableCards.Exists(c => c.IsOfTribe(t))); - list.RemoveAll(t => tribes.Contains(t) || !obtainableCards.Exists(c => c.IsOfTribe(t))); - // if list is empty, add Insect as a fallback - if (list.Count == 0) - list.Add(Tribe.Insect); - - while (tribes.Count < 3) - { - Tribe item = list[SeededRandom.Range(0, list.Count, randomSeed++)]; - tribes.Add(item); - if (list.Count > 1) // prevents softlock - list.Remove(item); - } - while (tribes.Count > 3) // if there are more than 3 tribes, reduce it to 3 - tribes.RemoveAt(SeededRandom.Range(0, tribes.Count, randomSeed++)); - - // randomise the order the Tribes will be displayed - List list2 = new(); - foreach (Tribe tribe in tribes.Randomize()) - { - list2.Add(new CardChoice - { - tribe = tribe - }); - } - - __result = list2; - return false; - } - /// /// Adds a new tribe to the game. /// @@ -140,6 +67,62 @@ public static Tribe Add(string guid, string name, Texture2D tribeIcon = null, bo tribeTypes.Add(tribe); return tribe; } + + /// + /// Adds a new tribe to the game + /// + /// The guid of the mod adding the tribe. + /// The name of the tribe. + /// Path to the tribal icon that will appear as a watermark on all cards belonging to this tribe. + /// Indicates if the card should appear in tribal choice nodes. + /// Path to the card back texture to display if the card should appear in tribal choice nodes. + /// The unique identifier for the new tribe. + public static Tribe Add(string guid, string name, string pathToTribeIcon = null, bool appearInTribeChoices = false, string pathToChoiceCardBackTexture = null) { + // Reason for 'is not null' is because if we pass 'null' to GetImageAsTexture, It will throw an exception. + return Add(guid, name, pathToTribeIcon is not null ? TextureHelper.GetImageAsTexture(pathToTribeIcon) : null, appearInTribeChoices, pathToChoiceCardBackTexture is not null ? TextureHelper.GetImageAsTexture(pathToChoiceCardBackTexture) : null); + } + + public static bool IsCustomTribe(Tribe tribe) => tribeTypes.Contains(tribe); + + /// + /// Retrieves the TribeInfo object associated with the given Tribe enum value. + /// + /// Tribe to retrieve the TribeInfo of. + /// Only custom Tribes have an associated TribeInfo; returns null for vanilla Tribes. + public static TribeInfo GetCustomTribeInfo(Tribe tribe) => tribes.Find(x => x.tribe == tribe); + + /// + /// Returns the icon texture associated with the given Tribe. + /// + /// Tribe to get the icon of. + /// If true, return the missing icon texure when the checked Tribe has no icon texture. + public static Texture2D GetTribeIcon(Tribe tribe, bool useMissingIconIfNull = true) { + Texture2D texture2D = null; + if (IsCustomTribe(tribe)) { + foreach (TribeInfo tribeInfo in NewTribes) { + if (tribeInfo.tribe != tribe) + continue; + + if (tribeInfo.icon != null && tribeInfo.icon.texture != null) + texture2D = tribeInfo.icon.texture; + + break; + } + } + else { + // Vanilla tribe icon + string str = "Art/Cards/TribeIcons/tribeicon_" + tribe.ToString().ToLowerInvariant(); + Sprite sprite = ResourceBank.Get(str); + if (sprite != null) + texture2D = sprite.texture; + } + + if (texture2D == null && useMissingIconIfNull) + texture2D = TribeIconMissing; + + return texture2D; + } + private static Texture2D MakePlaceholderCardback(Texture2D tribeIcon) { Texture2D emptyCardback = TextureHelper.GetImageAsTexture("empty_rewardCardBack.png", InscryptionAPIPlugin.APIAssembly); @@ -174,70 +157,86 @@ private static Texture2D MakePlaceholderCardback(Texture2D tribeIcon) emptyCardback.Apply(); return emptyCardback; } - /// - /// Adds a new tribe to the game - /// - /// The guid of the mod adding the tribe. - /// The name of the tribe. - /// Path to the tribal icon that will appear as a watermark on all cards belonging to this tribe. - /// Indicates if the card should appear in tribal choice nodes. - /// Path to the card back texture to display if the card should appear in tribal choice nodes. - /// The unique identifier for the new tribe. - public static Tribe Add(string guid, string name, string pathToTribeIcon = null, bool appearInTribeChoices = false, string pathToChoiceCardBackTexture = null) - { - // Reason for 'is not null' is because if we pass 'null' to GetImageAsTexture, It will thorw an exception. - return Add(guid, name, pathToTribeIcon is not null ? TextureHelper.GetImageAsTexture(pathToTribeIcon) : null, appearInTribeChoices, pathToChoiceCardBackTexture is not null ? TextureHelper.GetImageAsTexture(pathToChoiceCardBackTexture) : null); - } - public static bool IsCustomTribe(Tribe tribe) => tribeTypes.Contains(tribe); + [HarmonyPatch(typeof(CardDisplayer3D), nameof(CardDisplayer3D.UpdateTribeIcon))] + [HarmonyPostfix] + private static void UpdateTribeIcon(CardDisplayer3D __instance, CardInfo info) { + if (info == null || __instance.tribeIconRenderers.Count == 0) + return; - public static Texture2D GetTribeIcon(Tribe tribe, bool useMissingIconIfNull = true) - { - Texture2D texture2D = null; - if (IsCustomTribe(tribe)) - { - foreach (TribeInfo tribeInfo in NewTribes) - { - if (tribeInfo.tribe != tribe) - continue; + foreach (TribeInfo tribeInfo in tribes) { + if (tribeInfo?.icon == null || info.IsNotOfTribe(tribeInfo.tribe)) + continue; - if (tribeInfo.icon != null && tribeInfo.icon.texture != null) - texture2D = tribeInfo.icon.texture; + bool foundSpriteRenderer = false; + foreach (SpriteRenderer spriteRenderer in __instance.tribeIconRenderers) { + if (spriteRenderer.sprite == null) { + spriteRenderer.sprite = tribeInfo.icon; + foundSpriteRenderer = true; + break; + } + } - break; + // create new sprite renderer to hold custom tribe icon if all current renderers are full + if (!foundSpriteRenderer) { + SpriteRenderer last = __instance.tribeIconRenderers.Last(); + SpriteRenderer spriteRenderer = UnityObject.Instantiate(last); + spriteRenderer.transform.parent = last.transform.parent; + spriteRenderer.transform.localPosition = last.transform.localPosition + (__instance.tribeIconRenderers[1].transform.localPosition - __instance.tribeIconRenderers[0].transform.localPosition); } } - else - { - // Vanilla tribe icon - string str = "Art/Cards/TribeIcons/tribeicon_" + tribe.ToString().ToLowerInvariant(); - Sprite sprite = ResourceBank.Get(str); - if (sprite != null) - texture2D = sprite.texture; + } + + [HarmonyPatch(typeof(CardSingleChoicesSequencer), nameof(CardSingleChoicesSequencer.GetCardbackTexture))] + [HarmonyPostfix] + private static void GetCardbackTexture(ref Texture __result, CardChoice choice) { + if (choice != null && choice.tribe != Tribe.None && __result == null) { + __result = tribes.Find(x => x?.tribe == choice.tribe)?.cardback; } + } - if (texture2D == null && useMissingIconIfNull) - texture2D = TribeIconMissing; + [HarmonyPatch(typeof(Part1CardChoiceGenerator), nameof(Part1CardChoiceGenerator.GenerateTribeChoices))] + [HarmonyPrefix] + private static bool GenerateTribeChoices(ref List __result, int randomSeed) { + // create list of chooseable vanilla tribes then add all chooseable custom tribes + List list = new() + { + Tribe.Bird, + Tribe.Canine, + Tribe.Hooved, + Tribe.Insect, + Tribe.Reptile + }; + list.AddRange(TribeManager.tribes.FindAll((x) => x != null && x.tribeChoice).ConvertAll((x) => x.tribe)); + // create a list of this region's dominant tribes + List tribes = new(RunState.CurrentMapRegion.dominantTribes); + // get a list of cards obtainable at choice nodes + List obtainableCards = CardManager.AllCardsCopy.FindAll(c => c.HasCardMetaCategory(CardMetaCategory.ChoiceNode)); + // remove all non-chooseable tribes and all tribes with no cards + tribes.RemoveAll(t => (TribeManager.tribes.Exists(ct => ct.tribe == t && !ct.tribeChoice)) || !obtainableCards.Exists(c => c.IsOfTribe(t))); + list.RemoveAll(t => tribes.Contains(t) || !obtainableCards.Exists(c => c.IsOfTribe(t))); + // if list is empty, add Insect as a fallback + if (list.Count == 0) + list.Add(Tribe.Insect); - return texture2D; - } + while (tribes.Count < 3) { + Tribe item = list[SeededRandom.Range(0, list.Count, randomSeed++)]; + tribes.Add(item); + if (list.Count > 1) // prevents softlock + list.Remove(item); + } + while (tribes.Count > 3) // if there are more than 3 tribes, reduce it to 3 + tribes.RemoveAt(SeededRandom.Range(0, tribes.Count, randomSeed++)); - /// - /// The internal object used to store all relevant info about a Tribe. - /// guid - The mod GUID that added the Tribe. - /// name - The internal name of the Tribe. - /// tribe - The enum value corresponding to this Tribe. - /// icon - The sprite displayed on cards with this Tribe. - /// tribeChoice - Whether or not this Tribe can appear at card tribe choice nodes. - /// cardBack - The texture displayed at card tribe choice nodes. If null, the API will create one using the icon Sprite. - /// - public class TribeInfo - { - public string guid; - public string name; - public Tribe tribe; - public Sprite icon; - public bool tribeChoice; - public Texture2D cardback; + // randomise the order the Tribes will be displayed + List list2 = new(); + foreach (Tribe tribe in tribes.Randomize()) { + list2.Add(new CardChoice { + tribe = tribe + }); + } + + __result = list2; + return false; } } \ No newline at end of file From a9fdaed6b94f4289367725aecb5436e9c1d989a5 Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:28:39 -0800 Subject: [PATCH 10/11] Remove debug message --- InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs b/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs index c80b68b9..6ac57709 100644 --- a/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs +++ b/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs @@ -60,7 +60,6 @@ private static void HandleGemifyAct1(CardRenderInfo renderInfo, PlayableCard pla } public static void AddAct1GemifyVisuals(CardRenderCamera cardRenderCamera) { - Debug.Log("GemifyTest"); CardDisplayer3D dis = cardRenderCamera.cardDisplayer as CardDisplayer3D; GameObject obj = new("GemifyTest"); From 915d9aed61f9fa4494020d7141d8cf4aede0d3df Mon Sep 17 00:00:00 2001 From: WhistleWind <69230920+HumabHatterZed@users.noreply.github.com> Date: Wed, 24 Dec 2025 19:32:33 -0800 Subject: [PATCH 11/11] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472f9791..ae87a2c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Fixed Gemify affecting Blood cost when it shouldn't - Added Gems Cost support for ExtendedActivatedAbilityBehaviour class - Added extension AbilityManager.FullAbility.FlipYIfOpponent +- Add config option to prevent Act 1 card emissions from rendering above the play costs # 2.23.3 - Fixed custom deck exhaust sequence not performing the requisite checks