diff --git a/InscryptionAPI/Card/CardExtensionsCosts.cs b/InscryptionAPI/Card/CardExtensionsCosts.cs index 3e452018..36ea9330 100644 --- a/InscryptionAPI/Card/CardExtensionsCosts.cs +++ b/InscryptionAPI/Card/CardExtensionsCosts.cs @@ -31,7 +31,7 @@ public static PlayableCard GetPlayableCard(this CardInfo cardInfo) /// public static int BloodCost(this PlayableCard card) { - //Debug.Log($"{card != null} {card?.Info != null} [{card?.GetType()}] {(card as DiskCardGame.Card)?.Info != null}"); + //InscryptionAPIPlugin.Logger.LogInfo($"{card != null} {card?.Info != null} [{card?.GetType()}] {(card as DiskCardGame.Card)?.Info != null}"); if (card != null && card.Info != null) { int originalBloodCost = CostProperties.CostProperties.OriginalBloodCost(card.Info); @@ -42,6 +42,7 @@ public static int BloodCost(this PlayableCard card) foreach (CardModificationInfo mod in card.TemporaryMods) originalBloodCost += mod.bloodCostAdjustment; + //InscryptionAPIPlugin.Logger.LogInfo(originalBloodCost); return originalBloodCost; } @@ -105,11 +106,21 @@ public static List GemsCost(this PlayableCard card) return gemsCost; } + /// + /// Checks if the given PlayableCard is gemified in either its CardInfo or TemporaryMods. + /// + /// True if the PlayableCard is gemified. public static bool IsGemified(this PlayableCard card) => card.Info.Gemified || card.TemporaryMods.Exists((CardModificationInfo x) => x.gemify); + /// + /// Determines if the given PlayableCard's owner possesses a blue gem. Works for the opponent and player. + /// public static bool OwnerHasBlueGem(this PlayableCard card) { return card.OpponentCard ? OpponentGemsManager.Instance.HasGem(GemType.Blue) : ResourcesManager.Instance.HasGem(GemType.Blue); } + /// + /// Whether or not the given PlayableCard is affected by a blue gem and should have its cost reduced. + /// public static bool IsUsingBlueGem(this PlayableCard card) => card.IsGemified() && card.OwnerHasBlueGem(); /// diff --git a/InscryptionAPI/Card/CardManager.cs b/InscryptionAPI/Card/CardManager.cs index b4de2a81..cad99a63 100644 --- a/InscryptionAPI/Card/CardManager.cs +++ b/InscryptionAPI/Card/CardManager.cs @@ -13,6 +13,9 @@ namespace InscryptionAPI.Card; +/// +/// Class for managing adding, retrieving, tracking custom cards added via the API. +/// [HarmonyPatch] public static class CardManager { @@ -21,14 +24,50 @@ private class CardExt // Needs to be defined first so the implicit static constr public readonly Dictionary TypeMap = new(); public readonly Dictionary StringMap = new(); } + /// + /// Class for storing the data for custom portrait Sprites added via the API. + /// + /// + /// Only tracks custom API portraits like sacrificable steel trap, etc. + /// public class CardAltPortraits { + /// + /// Portrait sprite used when calling GetAlternatePortrait in Act 2. + /// public Sprite PixelAlternatePortrait = null; + /// + /// Portrait sprite used when the Steel Trap sigil is triggered. + /// + /// + /// Example: Leaping Trap + /// public Sprite SteelTrapPortrait = null; + /// + /// Portrait sprite used when the Steel Trap sigil is triggered in Act 2. + /// public Sprite PixelSteelTrapPortrait = null; + /// + /// Portrait sprite used when a card's shield breaks. + /// + /// + /// Example: Mud Turtle + /// public Sprite BrokenShieldPortrait = null; + /// + /// Portrait sprite used when a card's shield breaks in Act 2. + /// public Sprite PixelBrokenShieldPortrait = null; + /// + /// Portrait sprite used when a card on the board can be chosen as a sacrifice. + /// + /// + /// Example: Squirrel + /// public Sprite SacrificablePortrait = null; + /// + /// Portrait sprite used when a card on the board can be chosen as a sacrifice in Act 2. + /// public Sprite PixelSacrificablePortrait = null; } private static readonly ConditionalWeakTable CardExtensionProperties = new(); @@ -55,14 +94,16 @@ private static IEnumerable GetBaseGameCards() foreach (CardInfo card in Resources.LoadAll("Data/Cards")) { card.SetBaseGameCard(true); - if (card.name == "Squirrel" || card.name == "AquaSquirrel" || card.name == "Rabbit") + if (card.name == "Squirrel" || card.name == "AquaSquirrel" || card.name == "Rabbit") { card.SetAffectedByTidalLock(); - else if (card.name == "SkeletonParrot") + } + else if (card.name == "SkeletonParrot") { card.AddTribes(Tribe.Bird); - else if (card.name == "MudTurtle") + } + else if (card.name == "MudTurtle") { card.SetBrokenShieldPortrait(card.alternatePortrait); - - yield return card; + } + yield return card; } } @@ -200,7 +241,6 @@ static CardManager() /// /// A copy of all cards in the card pool. /// - /// . public static List AllCardsCopy { get; private set; } = BaseGameCards.ToList(); /// @@ -292,47 +332,130 @@ public static CardInfo New(string modPrefix, string name, string displayName, in } } + /// + /// Retrieves the dictionary of card extended properties associated with the given CardInfo. + /// + /// The CardInfo whose extended properties we wish to retrieve. + /// The extension property dictionary for the given CardInfo. Creates and returns an empty dictionary if none exists. public static Dictionary GetCardExtensionTable(this CardInfo card) => CardExtensionProperties.GetOrCreateValue(card).StringMap; internal static CardAltPortraits GetAltPortraits(this CardInfo card) => CardAlternatePortraits.GetOrCreateValue(card); + /// + /// Retrieves the Sprite for the CardInfo's alternate portrait in Act 2 if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the pixel alternate portrait or null if it doesn't exist. + /// + /// Pixel alternate portraits are added through the API; they are not a vanilla feature. + /// public static Sprite PixelAlternatePortrait(this CardInfo card) { return card.GetAltPortraits().PixelAlternatePortrait; } + /// + /// Retrieves the Sprite for the CardInfo's portrait used when Steel Trap activates if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the Steel Trap portrait or null if it doesn't exist. + /// + /// Steel Trap portraits are added through the API; they are not a vanilla feature. + /// public static Sprite SteelTrapPortrait(this CardInfo card) { return card.GetAltPortraits().SteelTrapPortrait; } + /// + /// Retrieves the Sprite for the CardInfo's portrait emission used when Steel Trap activates if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the Steel Trap portrait emission or null if it doesn't exist. + /// + /// Steel Trap portraits are added through the API; they are not a vanilla feature. + /// public static Sprite EmissiveSteelTrapPortrait(this CardInfo card) { return card.GetAltPortraits().SteelTrapPortrait.GetEmissionSprite(); } + /// + /// Retrieves the Sprite for the CardInfo's Act 2 portrait used when Steel Trap activates if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the pixel Steel Trap portrait or null if it doesn't exist. + /// + /// Pixel Steel Trap portraits are added through the API; they are not a vanilla feature. + /// public static Sprite PixelSteelTrapPortrait(this CardInfo card) { return card.GetAltPortraits().PixelSteelTrapPortrait; } - + /// + /// Retrieves the Sprite for the CardInfo's portrait used when a card's shield breaks if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the broken shield portrait or null if it doesn't exist. + /// + /// Broken shield portraits are added through the API; they are not a vanilla feature. + /// public static Sprite BrokenShieldPortrait(this CardInfo card) { return card.GetAltPortraits().BrokenShieldPortrait; } + /// + /// Retrieves the Sprite for the CardInfo's portrait emission used when a card's shield breaks if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the broken shield portrait emission or null if it doesn't exist. + /// + /// Broken shield portraits are added through the API; they are not a vanilla feature. + /// public static Sprite EmissiveBrokenShieldPortrait(this CardInfo card) { return card.GetAltPortraits().BrokenShieldPortrait.GetEmissionSprite(); } + /// + /// Retrieves the Sprite for the CardInfo's Act 2 portrait used when a card's shield breaks if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the pixel broken shield portrait or null if it doesn't exist. + /// + /// Pixel broken shield portraits are added through the API; they are not a vanilla feature. + /// public static Sprite PixelBrokenShieldPortrait(this CardInfo card) { return card.GetAltPortraits().PixelBrokenShieldPortrait; } - + /// + /// Retrieves the Sprite for the CardInfo's portrait used when a card on the board can be sacrificed if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the sacrificable portrait or null if it doesn't exist. + /// + /// Sacrificable portraits are added through the API; they are not a vanilla feature. + /// public static Sprite SacrificablePortrait(this CardInfo card) { return card.GetAltPortraits().SacrificablePortrait; } + /// + /// Retrieves the Sprite for the CardInfo's portrait emission used when a card on the board can be sacrificed if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the sacrificable portrait emission or null if it doesn't exist. + /// + /// Sacrificable portraits are added through the API; they are not a vanilla feature. + /// public static Sprite EmissiveSacrificablePortrait(this CardInfo card) { return card.GetAltPortraits().SacrificablePortrait.GetEmissionSprite(); } + /// + /// Retrieves the Sprite for the CardInfo's Act 2 portrait used when a card on the board can be sacrificed if it exists. + /// + /// The CardInfo whose portrait we're retrieving. + /// The Sprite for the pixel sacrificable portrait or null if it doesn't exist. + /// + /// Pixel sacrificable portraits are added through the API; they are not a vanilla feature. + /// public static Sprite PixelSacrificablePortrait(this CardInfo card) { return card.GetAltPortraits().PixelSacrificablePortrait; diff --git a/InscryptionAPI/Card/CardModificationInfoManager.cs b/InscryptionAPI/Card/CardModificationInfoManager.cs index 765adb38..abcf0641 100644 --- a/InscryptionAPI/Card/CardModificationInfoManager.cs +++ b/InscryptionAPI/Card/CardModificationInfoManager.cs @@ -6,6 +6,10 @@ namespace InscryptionAPI.Card; +/// +/// Class for managing special CardModificationInfos. +/// These include persistent extended properties and custom costs. +/// [HarmonyPatch] public static class CardModificationInfoManager { @@ -69,6 +73,9 @@ internal static Dictionary GetCardModExtensionTable(this CardMod return exp.StringMap; } + /// + /// Re-executes events and rebuilds the special CardModificationInfo pool. + /// public static void SyncCardMods() { // Re-add persistent properties diff --git a/InscryptionAPI/Card/CostProperties.cs b/InscryptionAPI/Card/CostProperties.cs index d6f2d662..877d85e4 100644 --- a/InscryptionAPI/Card/CostProperties.cs +++ b/InscryptionAPI/Card/CostProperties.cs @@ -131,18 +131,58 @@ public static List ImprovedGemsCost(CardInfo instance) [HarmonyReversePatch, HarmonyPatch(typeof(CardInfo), nameof(CardInfo.EnergyCost), MethodType.Getter), MethodImpl(MethodImplOptions.NoInlining)] public static int OriginalEnergyCost(CardInfo __instance) { return 0; } + /// + /// Determines whether or not Gemified should reduce the blood cost on a multicost card. + /// + /// The gemified PlayableCard we're checking. + /// The PlayableCard's blood cost. Defaults to using the CardInfo's blood cost if null. + /// True if Gemified should reduce the blood cost by 1. + /// + /// Used for multicost cards to determine which cost Gemified should affect. + /// Priority from first choice to last: Energy, Bones, Mox, Blood + /// public static bool ReduceGemifiedBlood(PlayableCard card, int? bloodCost = null) { return (bloodCost ?? OriginalBloodCost(card.Info)) > 0 && !ReduceGemifiedEnergy(card) && !ReduceGemifiedBones(card) && !ReduceGemifiedMox(card); } + /// + /// Determines whether or not Gemified should reduce the gems cost on a multicost card. + /// + /// The gemified PlayableCard we're checking. + /// The PlayableCard's gems cost. Defaults to using the CardInfo's gems cost if null. + /// True if Gemified should reduce the gems cost by 1. + /// + /// Used for multicost cards to determine which cost Gemified should affect. + /// Priority from first choice to last: Energy, Bones, Mox, Blood + /// public static bool ReduceGemifiedMox(PlayableCard card, List gemsCost = null) { return (gemsCost?.Count ?? ImprovedGemsCost(card.Info).Count) > 0 && !ReduceGemifiedBones(card) && !ReduceGemifiedEnergy(card); } + /// + /// Determines whether or not Gemified should reduce the bone cost on a multicost card. + /// + /// The gemified PlayableCard we're checking. + /// The PlayableCard's bone cost. Defaults to using the CardInfo's bone cost if null. + /// True if Gemified should reduce the bone cost by 1. + /// + /// Used for multicost cards to determine which cost Gemified should affect. + /// Priority from first choice to last: Energy, Bones, Mox, Blood + /// public static bool ReduceGemifiedBones(PlayableCard card, int? bonesCost = null) { return (bonesCost ?? OriginalBonesCost(card.Info)) > 0 && !ReduceGemifiedEnergy(card); } + /// + /// Determines whether or not Gemified should reduce the energy cost on a multicost card. + /// + /// The gemified PlayableCard we're checking. + /// The PlayableCard's energy cost. Defaults to using the CardInfo's energy cost if null. + /// True if Gemified should reduce the energy cost by 1. + /// + /// Used for multicost cards to determine which cost Gemified should affect. + /// Priority from first choice to last: Energy, Bones, Mox, Blood + /// public static bool ReduceGemifiedEnergy(PlayableCard card, int? energyCost = null) { return (energyCost ?? OriginalEnergyCost(card.Info)) > 0; @@ -153,7 +193,7 @@ public static bool ReduceGemifiedEnergy(PlayableCard card, int? energyCost = nul internal static class ChangeCardCostGetter { [HarmonyPatch(typeof(CardInfo), nameof(CardInfo.BloodCost), MethodType.Getter), HarmonyPrefix] - public static bool BloodCost(CardInfo __instance, ref int __result) + private static bool BloodCost(CardInfo __instance, ref int __result) { PlayableCard card = __instance.GetPlayableCard(); //Debug.Log($"{card != null}"); @@ -162,7 +202,7 @@ public static bool BloodCost(CardInfo __instance, ref int __result) } [HarmonyPatch(typeof(CardInfo), nameof(CardInfo.BonesCost), MethodType.Getter), HarmonyPrefix] - public static bool BoneCost(CardInfo __instance, ref int __result) + private static bool BoneCost(CardInfo __instance, ref int __result) { PlayableCard card = __instance.GetPlayableCard(); if (card == null) @@ -173,7 +213,7 @@ public static bool BoneCost(CardInfo __instance, ref int __result) } [HarmonyPatch(typeof(CardInfo), nameof(CardInfo.GemsCost), MethodType.Getter), HarmonyPrefix] - public static bool GemsCost(CardInfo __instance, ref List __result) + private static bool GemsCost(CardInfo __instance, ref List __result) { PlayableCard card = __instance.GetPlayableCard(); __result = card?.GemsCost() ?? CostProperties.ImprovedGemsCost(__instance); @@ -181,7 +221,7 @@ public static bool GemsCost(CardInfo __instance, ref List __result) } [HarmonyPatch(typeof(CardInfo), nameof(CardInfo.EnergyCost), MethodType.Getter), HarmonyPrefix] - public static bool EnergyCost(CardInfo __instance, ref int __result) + private static bool EnergyCost(CardInfo __instance, ref int __result) { PlayableCard card = __instance.GetPlayableCard(); __result = card?.EnergyCost ?? CostProperties.OriginalEnergyCost(__instance); @@ -189,7 +229,7 @@ public static bool EnergyCost(CardInfo __instance, ref int __result) } [HarmonyPatch(typeof(PlayableCard), nameof(PlayableCard.EnergyCost), MethodType.Getter), HarmonyPrefix] - public static bool DisableVanillaEnergyCost(PlayableCard __instance, ref int __result) + private static bool DisableVanillaEnergyCost(PlayableCard __instance, ref int __result) { // patch this to follow the same pattern as the other cost methods int energyCost = CostProperties.OriginalEnergyCost(__instance.Info); diff --git a/InscryptionAPI/Costs/CardCostManager.cs b/InscryptionAPI/Costs/CardCostManager.cs index 743ca780..901ccf4b 100644 --- a/InscryptionAPI/Costs/CardCostManager.cs +++ b/InscryptionAPI/Costs/CardCostManager.cs @@ -2,8 +2,10 @@ using GBC; using HarmonyLib; using InscryptionAPI.Card; +using InscryptionAPI.Card.CostProperties; using InscryptionAPI.Dialogue; using InscryptionAPI.Guid; +using InscryptionAPI.Helpers; using Sirenix.Utilities; using System.Collections; using System.Collections.ObjectModel; @@ -13,6 +15,9 @@ namespace InscryptionAPI.CardCosts; +/// +/// Class for managing custom card costs. +/// [HarmonyPatch] public static class CardCostManager { @@ -91,6 +96,11 @@ public class FullCardCost /// public Func GetRewardBackTexture = null; + /// + /// Texture used if this card can show up in a card cost choice node. + /// + /// + /// public Texture RewardBackTexture(int amount) { if (GetRewardBackTexture == null) @@ -183,6 +193,10 @@ public FullCardCost(string modGUID, string costName, Type costBehaviour, TypeManager.Add(costName, costBehaviour); } + /// + /// Creates a deep clone of this FullCardCost. + /// + /// A new clone of this FullCardCost. public FullCardCost Clone() { FullCardCost retval = new( @@ -202,18 +216,29 @@ static CardCostManager() SyncCustomCostList(); }; } - + /// + /// A list of all card costs registered with the API. + /// public static List AllCustomCosts { get; private set; } = new(); internal readonly static ObservableCollection NewCosts = new(); + /// + /// This event runs every time the card card list is resynced. By adding listeners to this event, you can modify card costs that have been added to the list after your mod was loaded. + /// public static event Func, List> ModifyCustomCostList; // you don't have the [colour] gem for that. // no [colour] gem on the table? then you can't play it. // you need at least one [colour] gem on the board in order to play that [card] // play the required [colour] gem... then you can play that. + /// + /// The Hint used to tell the player when they cannot play a card that costs multiple gems of the same colour. + /// public static HintsHandler.Hint notEnoughSameColourGemsHint = new("Hint_NotEnoughSameColourGemsHint", 2); + /// + /// The DialogueEvent that is played when a player cannot play a card that costs multiple gems of the same colour. + /// public static DialogueEvent notEnoghSameColourGemsEvent = DialogueManager.GenerateEvent( InscryptionAPIPlugin.ModGUID, "Hint_NotEnoughSameColourGemsHint", new() { "You don't have enough [v:1] gems for that." }, @@ -223,16 +248,37 @@ static CardCostManager() new() { "You need more [v:1] gems on the board in order to play that [v:0]."}, new() { "Play the required [v:1] gems... then you can play that." } }); + /// + /// Re-executes events and rebuilds the card cost pool. + /// public static void SyncCustomCostList() { AllCustomCosts = NewCosts.Select(a => a.Clone()).ToList(); AllCustomCosts = ModifyCustomCostList?.Invoke(AllCustomCosts) ?? AllCustomCosts; } + /// + /// Retrieves the FullCardCost object associated with the given CustomCardCost class Type. + /// + /// Collection of FullCardCost objects to search through. + /// Type of the CustomCardCost to find the FUllCardCost object of. + /// The FullCardCost associated with the given Type, or null if it cannot be found. + /// + /// Intended to be used with the AllCustomCosts getter as a way of searching through all card costs added by the API. + /// public static FullCardCost CostByBehaviour(this IEnumerable customCosts, Type costBehaviour) { return customCosts.FirstOrDefault(x => x.CostBehaviour == costBehaviour); } + /// + /// Registers a new custom card cost with the API. + /// + /// The GUID of the mod adding this card cost. + /// The internal name of this card cost. + /// The type of the CustomCardCost class for this card cost. + /// The function used to determine what texture to display on cards with this cost. + /// The function used to determine what pixel texture to display on cards with this cost. + /// The FullCardCost object containing the new custom card cost's data. public static FullCardCost Register(string modGUID, string costName, Type costBehaviour, Func getCostTexture, Func getPixelCostTexture) { @@ -518,7 +564,19 @@ private static bool MultipleSameColourMoxSupport(PlayableCard card) private static IEnumerable FixDialogueSoftlock(IEnumerable instructions) { List codes = new(instructions); - //codes.LogCodeInscryptions(); + //codes.LogCodeInstructions(); + + // this part's for fixing blood cost nonsense I encountered when dealing with duplicate CardInfos + // it just changes the call from card.Info.BloodCost to card.BloodCost() + // and avoiding the janky dynamic cost find playablecard nonsenseseses + int bloodCostIdx = codes.FindIndex(x => x.opcode == OpCodes.Callvirt && x.operand.ToString() == "Int32 get_BloodCost()"); + if (bloodCostIdx > 0) { + bloodCostIdx--; + codes.RemoveRange(bloodCostIdx, 4); + MethodInfo playableCardBloodCost = AccessTools.Method(typeof(CardExtensions), nameof(CardExtensions.BloodCost), + new Type[] { typeof(PlayableCard) }); + codes.Insert(bloodCostIdx, new CodeInstruction(OpCodes.Callvirt, playableCardBloodCost)); + } int methodIndex = codes.FindIndex(x => x.opcode == OpCodes.Callvirt && x.operand.ToString() == "System.Collections.IEnumerator PlayCardOnSlot(DiskCardGame.PlayableCard, DiskCardGame.CardSlot)"); if (methodIndex > 0) @@ -820,4 +878,4 @@ public virtual IEnumerator OnPlayed(int cardCost, PlayableCard playableCard) { yield break; } -} \ No newline at end of file +}