diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c72b7a8..ae87a2c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+# 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
+- 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
- Added 'CanAppearRandomly' bool and associated extension method for custom regions
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.
///
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)
{
diff --git a/InscryptionAPI/Card/DamageShieldBehaviour.cs b/InscryptionAPI/Card/DamageShieldBehaviour.cs
index e6f44468..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;
@@ -87,20 +88,34 @@ 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 {
+ 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();
@@ -187,12 +202,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 +328,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 = 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;
}
}
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..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,20 +12,34 @@ public abstract class ExtendedActivatedAbilityBehaviour : AbilityBehaviour
public int bloodCostMod;
public int bonesCostMod;
public int energyCostMod;
+ public List gemsCostMod = new();
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 {
+ 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();
@@ -93,8 +108,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 +127,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 +252,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 = 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;
}
}
return false;
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
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 });
}
}
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)
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 00000000..23d8a538
Binary files /dev/null and b/InscryptionCommunityPatch/Assets/act1_gemify_attack.png differ
diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_base.png b/InscryptionCommunityPatch/Assets/act1_gemify_base.png
new file mode 100644
index 00000000..28372295
Binary files /dev/null and b/InscryptionCommunityPatch/Assets/act1_gemify_base.png differ
diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_cost.png b/InscryptionCommunityPatch/Assets/act1_gemify_cost.png
new file mode 100644
index 00000000..b604c86a
Binary files /dev/null and b/InscryptionCommunityPatch/Assets/act1_gemify_cost.png differ
diff --git a/InscryptionCommunityPatch/Assets/act1_gemify_health.png b/InscryptionCommunityPatch/Assets/act1_gemify_health.png
new file mode 100644
index 00000000..3e4ba912
Binary files /dev/null and b/InscryptionCommunityPatch/Assets/act1_gemify_health.png differ
diff --git a/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs b/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs
new file mode 100644
index 00000000..6ac57709
--- /dev/null
+++ b/InscryptionCommunityPatch/Card/Part1GemifyIndicator.cs
@@ -0,0 +1,145 @@
+using DiskCardGame;
+using HarmonyLib;
+using InscryptionAPI.Card;
+using InscryptionAPI.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+
+namespace InscryptionCommunityPatch.Card;
+[HarmonyPatch]
+public class Part1GemifyIndicator {
+ // 0 = base gemify
+ // 1 = power
+ // 2 = health
+ // 3 = cost
+ public static Renderer[] GemifyRenderers = new Renderer[4];
+
+ [HarmonyPostfix, HarmonyPatch(typeof(CardDisplayer3D), nameof(CardDisplayer3D.DisplayInfo))]
+ private static void HandleGemifyAct1(CardRenderInfo renderInfo, PlayableCard playableCard) {
+ if (!SaveManager.SaveFile.IsPart1) {
+ return;
+ }
+
+ if (GemifyRenderers[0] == null) {
+ AddAct1GemifyVisuals(CardRenderCamera.Instance);
+ }
+
+ // if the card we're rendering if gemified
+ if (renderInfo.baseInfo.Gemified || renderInfo.temporaryMods.Exists(x => 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) {
+ 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;
+ }
+}
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)
{
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)
{