diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc43c89..bae5fd1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.23.6 +- Fixed card modifications added to a card's base ice cube card via CardInfo.SetIceCube being ignored +- Fixed PlayableCard.CanPlay not consistently accounting for temporary mod cost adjustments +- Fixed dynamic play costs not consistently updating +- Added additional methods for adding specific types of resources to the ResourceBank + # 2.23.5 - Fixed cards appearing as blank outside Act 1 - Added extension methods for FullAbility that mirror AbilityInfo extension methods diff --git a/InscryptionAPI/Card/AbilityManager.cs b/InscryptionAPI/Card/AbilityManager.cs index c39e0a92..b8f85825 100644 --- a/InscryptionAPI/Card/AbilityManager.cs +++ b/InscryptionAPI/Card/AbilityManager.cs @@ -4,6 +4,7 @@ using InscryptionAPI.Helpers; using InscryptionAPI.Helpers.Extensions; using InscryptionAPI.RuleBook; +using Sirenix.Utilities; using System.Collections; using System.Collections.ObjectModel; using System.Reflection; @@ -683,6 +684,38 @@ private static void LogAbilityInfo(Ability ability, AbilityInfo abilityInfo, Car InscryptionAPIPlugin.Logger.LogError("Cannot find ability " + ability + " for " + info.displayedName); } + [HarmonyPatch(typeof(IceCube), nameof(IceCube.OnDie), MethodType.Enumerator)] + [HarmonyTranspiler] + private static IEnumerable AddInherentModsToIceCube(IEnumerable instructions) { + List codes = new(instructions); + + for (int i = 0; i < codes.Count; i++) { + if (codes[i].opcode == OpCodes.Ldloc_2) { + // this probably belongs in the community patches but this transpiler was already here, so eh + // overrides the transformer icon so it can display numbers + MethodInfo customMethod = AccessTools.Method(typeof(AbilityManager), nameof(AbilityManager.GetIceCubeInfoWithMods), + new Type[] { typeof(IceCube), typeof(string) }); + + // ldloc_1 <- IceCube + // ldloc_2 <- name + // call (customMethod) + codes[i + 1] = new(OpCodes.Call, customMethod); + codes.Insert(i, new(OpCodes.Ldloc_1)); + break; + } + } + + return codes; + } + + private static CardInfo GetIceCubeInfoWithMods(IceCube instance, string cardName) { + CardInfo info = CardLoader.GetCardByName(cardName); + if (instance.Card.Info.iceCubeParams != null && instance.Card.Info.iceCubeParams.creatureWithin != null && instance.Card.Info.iceCubeParams.creatureWithin.mods != null && instance.Card.Info.iceCubeParams.creatureWithin.mods.Count > 0) { + info.Mods.AddRange(instance.Card.Info.iceCubeParams.creatureWithin.mods); + } + return info; + } + #region Evolve Changes [HarmonyPatch(typeof(Evolve), nameof(Evolve.OnUpkeep), MethodType.Enumerator)] [HarmonyTranspiler] @@ -734,13 +767,6 @@ private static bool OverrideTransformIcon(ref Texture __result, AbilityIconInter } return true; } - //[HarmonyPrefix, HarmonyPatch(typeof(AbilitiesUtil), nameof(AbilitiesUtil.LoadAbilityIcon))] - //private static bool OverrideEvolveAndTransformerIcon(ref Texture __result, string abilityName) { - // if (abilityName.StartsWith("Evolve") || abilityName.StartsWith("Transformer")) { - // return false; - // } - // return true; - //} private static void OverrideEvolveDerivedIcon(Evolve evolve, int turnsLeftToEvolve) { if (evolve.Ability == Ability.Evolve) diff --git a/InscryptionAPI/Card/CostProperties.cs b/InscryptionAPI/Card/CostProperties.cs index f4b3f522..d6f2d662 100644 --- a/InscryptionAPI/Card/CostProperties.cs +++ b/InscryptionAPI/Card/CostProperties.cs @@ -69,6 +69,11 @@ public bool GemsChanged(List a, List b) public static ConditionalWeakTable>> CardInfoToCard = new(); + + // a table that maps a CardInfo object to a list of weak references to every PlayableCard that uses that same CardInfo + public static ConditionalWeakTable>> CardInfoToPlayableCardReferences = new(); + + /// /// ChangeCardCostGetter patches BloodCost so we can change the cost on the fly /// This reverse patch gives us access to the original method without any changes. @@ -151,6 +156,7 @@ internal static class ChangeCardCostGetter public static bool BloodCost(CardInfo __instance, ref int __result) { PlayableCard card = __instance.GetPlayableCard(); + //Debug.Log($"{card != null}"); __result = Mathf.Max(0, card?.BloodCost() ?? CostProperties.OriginalBloodCost(__instance)); return false; } @@ -196,94 +202,72 @@ public static bool DisableVanillaEnergyCost(PlayableCard __instance, ref int __r __result = Mathf.Max(0, energyCost); return false; } + + //[HarmonyPostfix, HarmonyPatch(typeof(PlayableCard), nameof(PlayableCard.SetInfo))] + //private static void GetCardInfoReferences(PlayableCard __instance) { + + // // if this is a new PlayableCard being associated with the given CardInfo + // // returns true if the CardInfo is in the table + // if (CostProperties.CardInfoToPlayableCardReferences.TryGetValue(__instance.Info, out List> connectedCards)) { + // PlayableCard card = null; + // for (int i = connectedCards.Count - 1; i >= 0; i--) { + // // NOTE: We store a list of cards so if we don't clear this list then it will fill up forever + // // remove PlayableCard references that no longer point to anything + // if (!connectedCards[i].TryGetTarget(out PlayableCard cardReference) || cardReference == null) { + // connectedCards.RemoveAt(i); + // } + // else if (cardReference == __instance) { + // card = cardReference; + // } + // } + + // if (card == null) { + // connectedCards.Add(new WeakReference(__instance)); + // if (connectedCards.Count > 1) { + // InscryptionAPIPlugin.Logger.LogWarning($"More than 1 card are using the same card info. This can cause unexpected problems with dynamic costs! {__instance.Info.displayedName}"); + // } + // } + // } + // else { + // // add CardInfo to table with reference to this PlayableCard + // CostProperties.CardInfoToPlayableCardReferences.Add(__instance.Info, new() { new(__instance) }); + // } + //} } -/*[HarmonyPatch(typeof(DiskCardGame.Card), nameof(DiskCardGame.Card.Info), MethodType.Setter)] -internal static class Card_SetInfo +[HarmonyPatch(typeof(PlayableCard), nameof(PlayableCard.SetInfo))] +internal static class AddRefreshBehaviourToCard { - public static void Postfix(DiskCardGame.Card __instance) + private static void Postfix(PlayableCard __instance) { - //return; - if (__instance is not PlayableCard playableCard) - return; - - CardInfo info = playableCard.Info; - - if (CostProperties.CardInfoToCard.TryGetValue(info, out List> cardList)) - { + // add the refresh component if it doesn't exist, then set the Card to the calling instance + CostProperties.RefreshCostMonoBehaviour refreshCostDisplay = __instance.GetComponent() ?? __instance.gameObject.AddComponent(); + refreshCostDisplay.playableCard = __instance; + + // if this is a new PlayableCard being associated with the given CardInfo + // returns true if the CardInfo is in the table + if (CostProperties.CardInfoToCard.TryGetValue(__instance.Info, out List> cardList)) { PlayableCard card = null; - for (int i = cardList.Count - 1; i >= 0; i--) - { + for (int i = cardList.Count - 1; i >= 0; i--) { + // NOTE: We store a list of cards so if we don't clear this list then it will fill up forever + // remove PlayableCard references that no longer point to anything if (!cardList[i].TryGetTarget(out PlayableCard innerCard) || innerCard == null) - { - // NOTE: We store a list of cards so if we don't clear this list then it will fill up forever cardList.RemoveAt(i); - } - else if(innerCard == playableCard) - { + + else if (innerCard == __instance) card = innerCard; - } } - - if (card == null) - { - cardList.Add(new WeakReference(playableCard)); - if (cardList.Count > 1) - { - InscryptionAPIPlugin.Logger.LogWarning($"More than 1 card are using the same card info. This can cause unexpected problems with dynamic costs! {info.displayedName}"); - } - } - } - else - { - Debug.Log($"New-un"); - CostProperties.CardInfoToCard.Add(info, new List>() - { - new WeakReference(playableCard) - }); - } - } -}*/ - -[HarmonyPatch(typeof(PlayableCard), nameof(PlayableCard.SetInfo))] -internal static class AddRefreshBehaviourToCard -{ - private static void Postfix(PlayableCard __instance) - { - // add the refresh component if it doesn't exist, then set the Card to the calling instance - if (__instance.GetComponent() == null) - { - __instance.gameObject.AddComponent().playableCard = __instance; - if (CostProperties.CardInfoToCard.TryGetValue(__instance.Info, out List> cardList)) - { - PlayableCard card = null; - for (int i = cardList.Count - 1; i >= 0; i--) - { - // NOTE: We store a list of cards so if we don't clear this list then it will fill up forever - if (!cardList[i].TryGetTarget(out PlayableCard innerCard) || innerCard == null) - cardList.RemoveAt(i); - - else if (innerCard == __instance) - card = innerCard; - } - if (card == null) - { - cardList.Add(new WeakReference(__instance)); - if (cardList.Count > 1) - { - InscryptionAPIPlugin.Logger.LogWarning($"More than 1 card are using the same card info. This can cause unexpected problems with dynamic costs! {__instance.Info.displayedName}"); - } + if (card == null) { + cardList.Add(new WeakReference(__instance)); + if (cardList.Count > 1) { + InscryptionAPIPlugin.Logger.LogWarning($"More than 1 card are using the same CardInfo. This can cause unexpected problems with dynamic costs! {__instance.Info.displayedName}"); } } - else - { - CostProperties.CardInfoToCard.Add(__instance.Info, new List>() - { - new WeakReference(__instance) - }); - } - + } + else { + // add CardInfo to table with reference to this PlayableCard + CostProperties.CardInfoToCard.Add(__instance.Info, new() { new (__instance) }); } } } diff --git a/InscryptionAPI/Costs/CardCostManager.cs b/InscryptionAPI/Costs/CardCostManager.cs index 60a1800d..7af707a3 100644 --- a/InscryptionAPI/Costs/CardCostManager.cs +++ b/InscryptionAPI/Costs/CardCostManager.cs @@ -324,18 +324,17 @@ public static FullCardCost SetFoundAtChoiceNodes(this FullCardCost fullCardCost, #region Patches [HarmonyPostfix, HarmonyPatch(typeof(PlayableCard), nameof(PlayableCard.CanPlay))] - private static void CanPlayCustomCosts(ref bool __result, ref PlayableCard __instance) - { - if (!__result) - return; - - foreach (CustomCardCost cost in __instance.GetCustomCardCosts()) - { - FullCardCost fullCost = AllCustomCosts.CostByBehaviour(cost.GetType()); - if (!cost.CostSatisfied(__instance.GetCustomCost(fullCost), __instance)) - { - __result = false; - return; + private static void CanPlayCustomCosts(ref bool __result, ref PlayableCard __instance) { + if (__instance.BloodCost() <= Singleton.Instance.AvailableSacrificeValue && __instance.BonesCost() <= Singleton.Instance.PlayerBones && __instance.EnergyCost <= Singleton.Instance.PlayerEnergy && __instance.GemsCostRequirementMet()) { + if (Singleton.Instance.SacrificesCreateRoomForCard(__instance, Singleton.Instance.PlayerSlotsCopy)) { + foreach (CustomCardCost cost in __instance.GetCustomCardCosts()) { + FullCardCost fullCost = AllCustomCosts.CostByBehaviour(cost.GetType()); + if (!cost.CostSatisfied(__instance.GetCustomCost(fullCost), __instance)) { + __result = false; + return; + } + } + __result = true; } } } diff --git a/InscryptionAPI/InscryptionAPI.csproj b/InscryptionAPI/InscryptionAPI.csproj index a11afb37..66183eab 100644 --- a/InscryptionAPI/InscryptionAPI.csproj +++ b/InscryptionAPI/InscryptionAPI.csproj @@ -10,7 +10,7 @@ full false true - 2.23.5 + 2.23.6 diff --git a/InscryptionAPI/InscryptionAPIPlugin.cs b/InscryptionAPI/InscryptionAPIPlugin.cs index e50389f4..676a1aaf 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.5"; + public const string ModVer = "2.23.6"; public static string Directory = ""; diff --git a/InscryptionAPI/ResourceBank/ResourceBankManager.cs b/InscryptionAPI/ResourceBank/ResourceBankManager.cs index e4b4a34f..5d6a526d 100644 --- a/InscryptionAPI/ResourceBank/ResourceBankManager.cs +++ b/InscryptionAPI/ResourceBank/ResourceBankManager.cs @@ -1,3 +1,4 @@ +using DiskCardGame; using HarmonyLib; using UnityEngine; @@ -14,15 +15,6 @@ public class ResourceData private static readonly List CustomResources = new(); - public static ResourceData AddDecal(string pluginGUID, string resourceName, Texture decalTexture, bool overrideExistingAsset = false) - { - return Add(pluginGUID, new ResourceBank.Resource() - { - path = $"Art/Cards/Decals/{resourceName}", - asset = decalTexture - }, overrideExistingAsset); - } - public static ResourceData Add(string pluginGUID, string path, UnityObject unityObject, bool overrideExistingAsset = false) { return Add(pluginGUID, new ResourceBank.Resource() @@ -56,6 +48,89 @@ public static ResourceData Add(string pluginGUID, ResourceBank.Resource resource return resourceData; } + /// + /// Adds a custom GameObject resource located at the path Inscryption uses to retrieve Prefabs when instantiating weights for the scales. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The scales weight GameObject. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddScaleWeightPrefab(string pluginGUID, string resourceName, GameObject prefab, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Prefabs/Environment/ScaleWeights/" + resourceName, prefab, overrideExistingAsset); + } + + /// + /// Adds a custom GameObject resource located at the path Inscryption uses to retrieve Prefabs when instantiating card battle idle events, eg, the spider. + /// + /// + /// Note: Object must have a CardBattleIdleEvent component attached. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The GameObject for the CardBattleIdleEvent. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddCardBattleIdleEvent(string pluginGUID, string resourceName, GameObject eventPrefab, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Prefabs/Environment/CardBattleIdleEvents/" + resourceName, eventPrefab, overrideExistingAsset); + } + + /// + /// Adds a custom GameObject resource located at the path Inscryption uses to retrieve Prefabs when instantiating first person animations. + /// + /// + /// Note: The game checks the Prefab's children for the Animator and FirstPersonAnimatorObject. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The table effect GameObject. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddFirstPersonAnimation(string pluginGUID, string resourceName, GameObject animPrefab, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Prefabs/FirstPersonAnimations/" + resourceName, animPrefab, overrideExistingAsset); + } + + /// + /// Adds a custom GameObject resource located at the path Inscryption uses to retrieve Prefabs when instantiating table effects, ie, during boss fights. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The table effect GameObject. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddTableEffect(string pluginGUID, string resourceName, GameObject tableEffectPrefab, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Prefabs/Environment/TableEffects/" + resourceName, tableEffectPrefab, overrideExistingAsset); + } + + /// + /// Adds a custom GameObject resource located at the path Inscryption uses to retrieve Prefabs when generating map scenery. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The map scenery GameObject. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddMapScenery(string pluginGUID, string resourceName, GameObject sceneryPrefab, bool overrideExistingAsset = false) { + return Add(pluginGUID, SceneryData.PREFABS_ROOT + resourceName, sceneryPrefab, overrideExistingAsset); + } + + /// + /// Adds a custom Texture resource located at the path Inscryption uses to retrieve ability icons. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The ability icon's texture. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddAbilityIcon(string pluginGUID, string resourceName, Texture iconTexture, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Art/Cards/AbilityIcons/" + resourceName, iconTexture, overrideExistingAsset); + } + + /// + /// Adds a custom Texture resource located at the path Inscryption uses to retrieve card decal textures. + /// + /// The GUID of the plugin adding the resource. + /// The name used to identify the resource. Used when retrieving it from the ResourceBank + /// The decal texture. + /// If we should override any existing asset that shares this resource's path. + public static ResourceData AddDecal(string pluginGUID, string resourceName, Texture decalTexture, bool overrideExistingAsset = false) { + return Add(pluginGUID, "Art/Cards/Decals/" + resourceName, decalTexture, overrideExistingAsset); + } + [HarmonyPatch(typeof(ResourceBank), "Awake", new Type[] { })] internal class ResourceBank_Awake {