Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added IShieldPreventedDamage trigger and ICustomExhaustSequence #316

Merged
merged 7 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<details>
<summary>View Changelog</summary>

# 2.22.1
- Added IShieldPreventedDamage and IShieldPreventedDamageInHand ability triggers and interfaces
- Added TriggerBreakShield, wraps BreakShield in an IEnumerator for additional customisation by modders
- Added ICustomExhaustSequence trigger interface for modifying the draw pile exhaustion effect - use with Opponents
- Fixed board slots being uninteractable if a slot effect with a rulebook interactable was reset
- Removed debug logging related to rulebook redirect coordinates

# 2.22.0
- Added FullBoon objects for each vanilla Boon
- Added 'AllFullBoons' list to BoonManager
Expand Down
24 changes: 24 additions & 0 deletions InscryptionAPI/Card/ShieldManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DiskCardGame.CompositeRules;
using HarmonyLib;
using InscryptionAPI.Helpers.Extensions;
using InscryptionAPI.Triggers;
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
Expand All @@ -24,6 +25,29 @@ public static class ShieldManager
public static List<AbilityInfo> AllShieldInfos { get; internal set; } = AllShieldAbilities.Select(x => x.Info).ToList();


public static IEnumerator TriggerBreakShield(PlayableCard target, int damage, PlayableCard attacker)
{
BreakShield(target, damage, attacker);

List<IShieldPreventedDamage> shieldTriggers = CustomTriggerFinder.FindTriggersOnBoard<IShieldPreventedDamage>(false).ToList();
shieldTriggers.Sort((IShieldPreventedDamage a, IShieldPreventedDamage b) => b.ShieldPreventedDamagePriority(target, damage, attacker) - a.ShieldPreventedDamagePriority(target, damage, attacker));
foreach (IShieldPreventedDamage damageTrigger in shieldTriggers)
{
if ((damageTrigger as TriggerReceiver) != null && damageTrigger.RespondsToShieldPreventedDamage(target, damage, attacker))
{
yield return damageTrigger.OnShieldPreventedDamage(target, damage, attacker);
}
}
List<IShieldPreventedDamageInHand> shieldInHandTriggers = CustomTriggerFinder.FindTriggersInHand<IShieldPreventedDamageInHand>().ToList();
shieldInHandTriggers.Sort((IShieldPreventedDamageInHand a, IShieldPreventedDamageInHand b) => b.ShieldPreventedDamageInHandPriority(target, damage, attacker) - a.ShieldPreventedDamageInHandPriority(target, damage, attacker));
foreach (IShieldPreventedDamageInHand damageTrigger in shieldInHandTriggers)
{
if ((damageTrigger as TriggerReceiver) != null && damageTrigger.RespondsToShieldPreventedDamageInHand(target, damage, attacker))
{
yield return damageTrigger.OnShieldPreventedDamageInHand(target, damage, attacker);
}
}
}
/// <summary>
/// The method used for when a shielded card is damaged. Includes extra parameters for modders looking to modify this further.
/// This method is only called when damage > 0 and the target has a shield.
Expand Down
21 changes: 21 additions & 0 deletions InscryptionAPI/Encounters/ICustomExhaustSequence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using DiskCardGame;
using System.Collections;

namespace InscryptionAPI.Encounters;

/// <summary>
/// An interface that implements custom logic when the player has exhausted both of their draw piles.
/// </summary>
/// <remarks>
/// Only for Opponents and SpecialSequences.
/// </remarks>
public interface ICustomExhaustSequence
{
public bool RespondsToCustomExhaustSequence(CardDrawPiles drawPiles);
/// <summary>
/// Executes the sequence that plays when the player exhausts their draw piles.
/// </summary>
/// <param name="drawPiles">The CardDrawPiles instance for this scene.</param>
/// <returns>An enumeration of Unity events.</returns>
public IEnumerator DoCustomExhaustSequence(CardDrawPiles drawPiles);
}
105 changes: 55 additions & 50 deletions InscryptionAPI/Encounters/OpponentManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using InscryptionAPI.Guid;
using InscryptionAPI.Masks;
using InscryptionAPI.Saves;
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Reflection.Emit;
Expand Down Expand Up @@ -89,10 +90,49 @@
NewOpponents.Add(opp);
return opp;
}
public static List<Opponent.Type> RunStateOpponents
{
get
{
List<Opponent.Type> previousBosses = new List<Opponent.Type>();

string value = ModdedSaveManager.RunState.GetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses"); // 2,0,1
if (value == null)
{
// Do nothing
}
else if (!value.Contains(','))
{
// Single boss encounter
previousBosses.Add((Opponent.Type)int.Parse(value));
}
else
{
// Multiple boss encounters
IEnumerable<Opponent.Type> ids = value.Split(',').Select(static (a) => (Opponent.Type)int.Parse(a));
previousBosses.AddRange(ids);
}

return previousBosses;
}
set
{
string result = ""; // 2,0,1
for (int i = 0; i < value.Count; i++)
{
if (i > 0)
{
result += ",";
}
result += (int)value[i];

}
ModdedSaveManager.RunState.SetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses", result);
}
}

#region Patches
[HarmonyPatch(typeof(Opponent), nameof(Opponent.SpawnOpponent))]
[HarmonyPrefix]
[HarmonyPrefix, HarmonyPatch(typeof(Opponent), nameof(Opponent.SpawnOpponent))]
private static bool ReplaceSpawnOpponent(EncounterData encounterData, ref Opponent __result)
{
if (encounterData.opponentType == Opponent.Type.Default || !ProgressionData.LearnedMechanic(MechanicsConcept.OpponentQueue))
Expand Down Expand Up @@ -124,27 +164,19 @@
[MethodImpl(MethodImplOptions.NoInlining)]
public static string OriginalGetSequencerIdForBoss(Opponent.Type bossType) { throw new NotImplementedException(); }

[HarmonyPatch(typeof(BossBattleSequencer), nameof(BossBattleSequencer.GetSequencerIdForBoss))]
[HarmonyPrefix]
[HarmonyPrefix, HarmonyPatch(typeof(BossBattleSequencer), nameof(BossBattleSequencer.GetSequencerIdForBoss))]
private static bool ReplaceGetSequencerId(Opponent.Type bossType, ref string __result)
{
__result = AllOpponents.First(o => o.Id == bossType).SpecialSequencerId;
return false;
}

[HarmonyPatch(typeof(BossBattleNodeData), nameof(BossBattleNodeData.PrefabPath), MethodType.Getter)]
[HarmonyPrefix]
[HarmonyPrefix, HarmonyPatch(typeof(BossBattleNodeData), nameof(BossBattleNodeData.PrefabPath), MethodType.Getter)]
private static bool ReplacePrefabPath(ref string __result, Opponent.Type ___bossType)
{
GameObject obj = ResourceBank.Get<GameObject>("Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType);
if (obj != null)
{
__result = "Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType;
}
else
{
__result = "Prefabs/Map/MapNodesPart1/MapNode_ProspectorBoss";
}
string fullPath = "Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType;
GameObject obj = ResourceBank.Get<GameObject>(fullPath);
__result = obj != null ? fullPath : "Prefabs/Map/MapNodesPart1/MapNode_ProspectorBoss";
return false;
}

Expand Down Expand Up @@ -191,7 +223,7 @@
CodeInstruction codeInstruction = codes[i];
if (codeInstruction.opcode == OpCodes.Stfld)
{
if (codeInstruction.operand == bossTypeField)

Check warning on line 226 in InscryptionAPI/Encounters/OpponentManager.cs

View workflow job for this annotation

GitHub Actions / build

Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'FieldInfo'
{
codes.Insert(++i, new CodeInstruction(OpCodes.Ldloc_0));
codes.Insert(++i, new CodeInstruction(OpCodes.Call, ProcessMethodInfo));
Expand Down Expand Up @@ -220,47 +252,20 @@
}
}

public static List<Opponent.Type> RunStateOpponents
[HarmonyPostfix, HarmonyPatch(typeof(CardDrawPiles), nameof(CardDrawPiles.ExhaustedSequence))]
private static IEnumerator CustomBossExhaustionSequence(IEnumerator enumerator, CardDrawPiles __instance)
{
get
if (TurnManager.Instance.Opponent is ICustomExhaustSequence exhaustSeq && exhaustSeq != null)
{
List<Opponent.Type> previousBosses = new List<Opponent.Type>();

string value = ModdedSaveManager.RunState.GetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses"); // 2,0,1
if (value == null)
{
// Do nothing
}
else if (!value.Contains(','))
{
// Single boss encounter
previousBosses.Add((Opponent.Type)int.Parse(value));
}
else
{
// Multiple boss encounters
IEnumerable<Opponent.Type> ids = value.Split(',').Select(static (a) => (Opponent.Type)int.Parse(a));
previousBosses.AddRange(ids);
}

return previousBosses;
Singleton<ViewManager>.Instance.SwitchToView(View.CardPiles, immediate: false, lockAfter: true);
yield return new WaitForSeconds(1f);
yield return exhaustSeq.DoCustomExhaustSequence(__instance);
}
set
else
{
string result = ""; // 2,0,1
for (int i = 0; i < value.Count; i++)
{
if (i > 0)
{
result += ",";
}
result += (int)value[i];

}
ModdedSaveManager.RunState.SetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses", result);
yield return enumerator;
}
}

#endregion

#region Optimization Patches
Expand Down Expand Up @@ -324,7 +329,7 @@

for (int i = 0; i < codes.Count; i++)
{
if (codes[i].opcode == OpCodes.Ldstr && codes[i].operand == "knives_table_exit")

Check warning on line 332 in InscryptionAPI/Encounters/OpponentManager.cs

View workflow job for this annotation

GitHub Actions / build

Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'string'
{

// ldstr "knives_table_exit"
Expand Down
2 changes: 1 addition & 1 deletion InscryptionAPI/InscryptionAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<DebugType>full</DebugType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Version>2.22.0</Version>
<Version>2.22.1</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
14 changes: 7 additions & 7 deletions InscryptionAPI/Rulebook/RuleBookRedirectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void ClearActiveInteractables()
}
public void UpdateActiveInteractables(TextMeshPro description, GameObject currentPageObj, Dictionary<string, RuleBookManager.RedirectInfo> redirects)
{
InscryptionAPIPlugin.Logger.LogDebug($"[UpdateActiveInteractables]");
//InscryptionAPIPlugin.Logger.LogDebug($"[UpdateActiveInteractables]");
Bounds pageBounds;
Bounds borderBounds = currentPageObj.transform.Find("Border").GetComponent<SpriteRenderer>().bounds; // in world space
Vector3 pageBottomLeft;
Expand Down Expand Up @@ -82,9 +82,9 @@ public void UpdateActiveInteractables(TextMeshPro description, GameObject curren
descriptionLengths ??= new float[2] { borderBounds.size.x, (currentPageLengths[1] / currentPageLengths[0]) * borderBounds.size.x };
}
zLength = pageBottomLeft.z - currentPageTopLeft.z;
InscryptionAPIPlugin.Logger.LogDebug($"[DescTopLeft] {descriptionTopLeft.x} {descriptionTopLeft.y} {descriptionTopLeft.z}");
InscryptionAPIPlugin.Logger.LogDebug($"[PageTopLeft] {currentPageTopLeft.x} {currentPageTopLeft.y} {currentPageTopLeft.z}");
InscryptionAPIPlugin.Logger.LogDebug($"[PageBottomLeft] {pageBottomLeft.x} {pageBottomLeft.y} {pageBottomLeft.z} | {zLength}");
//InscryptionAPIPlugin.Logger.LogDebug($"[DescTopLeft] {descriptionTopLeft.x} {descriptionTopLeft.y} {descriptionTopLeft.z}");
//InscryptionAPIPlugin.Logger.LogDebug($"[PageTopLeft] {currentPageTopLeft.x} {currentPageTopLeft.y} {currentPageTopLeft.z}");
//InscryptionAPIPlugin.Logger.LogDebug($"[PageBottomLeft] {pageBottomLeft.x} {pageBottomLeft.y} {pageBottomLeft.z} | {zLength}");

ClearActiveInteractables();
CreateInteractables(redirects, description, zLength);
Expand Down Expand Up @@ -113,8 +113,8 @@ public void CreateInteractableObject(Vector3 worldPosition, Vector3 colliderSize

Vector3 newWorldPosition = PixelCamera.ScreenToWorldPoint(RuleBookCamera.WorldToScreenPoint(worldPosition));

InscryptionAPIPlugin.Logger.LogDebug($"[CreateInteractable] ({colliderSize.x} {colliderSize.y})");
InscryptionAPIPlugin.Logger.LogDebug($"[CreateInteractable] ({worldPosition.x} {worldPosition.y} {worldPosition.z}) ({newWorldPosition.x} {newWorldPosition.y} {newWorldPosition.z})");
//InscryptionAPIPlugin.Logger.LogDebug($"[CreateInteractable] ({colliderSize.x} {colliderSize.y})");
//InscryptionAPIPlugin.Logger.LogDebug($"[CreateInteractable] ({worldPosition.x} {worldPosition.y} {worldPosition.z}) ({newWorldPosition.x} {newWorldPosition.y} {newWorldPosition.z})");

obj.name = $"RuleBookPageInteractable ({keyText})";
obj.transform.SetParent(PixelCamera.transform);
Expand Down Expand Up @@ -270,7 +270,7 @@ private void CreateColliderSizeAndPosition(Transform textMesh, float sizeY, floa

}
correctedPos = new(correctedX, correctedY, currentPageTopLeft.z + (zCorrection + zCorrection * correctedYProportion));
Debug.Log($"[Corrected] ({correctedPos.x} {correctedPos.y} {correctedPos.z}) | {correctedXProportion} {correctedYProportion}");
//Debug.Log($"[Corrected] ({correctedPos.x} {correctedPos.y} {correctedPos.z}) | {correctedXProportion} {correctedYProportion}");
colliderPositions.Add(correctedPos);
colliderSizes.Add(new((topRight.x - bottomLeft.x) + sizeY / 2f, sizeY * 3f / 2f, 0.001f)); // add padding to compensate for inaccurate positioning
}
Expand Down
2 changes: 1 addition & 1 deletion InscryptionAPI/Slots/SlotModificationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static IEnumerator SetSlotModification(this CardSlot slot, ModificationTy
SlotModificationInteractable interactable = slot.GetComponent<SlotModificationInteractable>();
if (defn == null || modType == ModificationType.NoModification || (defn.SharedRulebook == ModificationType.NoModification && string.IsNullOrEmpty(defn.RulebookName)))
{
interactable?.SetEnabled(false);
UnityObject.Destroy(interactable);
}
else
{
Expand Down
18 changes: 18 additions & 0 deletions InscryptionAPI/Triggers/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,24 @@ public interface IOnCardDealtDamageDirectly
public IEnumerator OnCardDealtDamageDirectly(PlayableCard attacker, CardSlot opposingSlot, int damage);
}

/// <summary>
/// Trigger that is called after a shielded card was attacked and lost a shield.
/// </summary>
public interface IShieldPreventedDamage
{
public bool RespondsToShieldPreventedDamage(PlayableCard target, int damage, PlayableCard attacker);
public IEnumerator OnShieldPreventedDamage(PlayableCard target, int damage, PlayableCard attacker);
public int ShieldPreventedDamagePriority(PlayableCard target, int damage, PlayableCard attacker);
}
/// <summary>
/// Variant of IShieldPreventDamage that triggers for cards in the hand.
/// </summary>
public interface IShieldPreventedDamageInHand
{
public bool RespondsToShieldPreventedDamageInHand(PlayableCard target, int damage, PlayableCard attacker);
public IEnumerator OnShieldPreventedDamageInHand(PlayableCard target, int damage, PlayableCard attacker);
public int ShieldPreventedDamageInHandPriority(PlayableCard target, int damage, PlayableCard attacker);
}
/*public interface IOnPreTakeDamageFromHammer
{
public bool RespondsToPreTakeDamageFromHammer(HammerItem hammer, CardSlot targetSlot, GameObject firstPersonItem);
Expand Down
41 changes: 36 additions & 5 deletions InscryptionAPI/Triggers/TakeDamagePatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
int shieldStart = -1, shieldEnd = -1;
for (int i = 0; i < codes.Count; i++)
{

// grab the required operands, in order of appearance in the code
if (shieldStart == -1 && codes[i].operand?.ToString() == "Boolean HasShield()")
{
Expand Down Expand Up @@ -130,11 +131,22 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
attacker = codes[i].operand;
if (shieldEnd > 0)
{
CodeInstruction switch_ = codes.Find(x => x.opcode == OpCodes.Switch);
//switch_.WithLabels(breakShieldLabel);
object state = codes.Find(x => x.opcode == OpCodes.Stfld && x.operand.ToString() == "System.Int32 <>1__state").operand;
object current = codes.Find(x => x.opcode == OpCodes.Stfld && x.operand.ToString() == "System.Object <>2__current").operand;
// if (HasShield && damage > 0)
// BreakShield();
// yield return TriggerBreakShield();
// break;

MethodBase breakShield = AccessTools.Method(typeof(ShieldManager), nameof(ShieldManager.BreakShield),
// TriggerBreakShield
// this.current = TriggerBreakShield
// this.state = 7
// return true
// this.state = -1
// yield break (new label)

MethodBase breakShield = AccessTools.Method(typeof(ShieldManager), nameof(ShieldManager.TriggerBreakShield),
new Type[] { typeof(PlayableCard), typeof(int), typeof(PlayableCard) });

codes.RemoveRange(shieldStart, shieldEnd - shieldStart);
Expand All @@ -145,14 +157,33 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_0));
codes.Insert(shieldStart++, new(OpCodes.Cgt));
codes.Insert(shieldStart++, new(OpCodes.Brfalse, hasShieldLabel));
// BreakShield();
//break;

// TriggerBreakShield();
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
codes.Insert(shieldStart++, new(OpCodes.Ldloc_1));
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
codes.Insert(shieldStart++, new(OpCodes.Ldfld, damage));
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
codes.Insert(shieldStart++, new(OpCodes.Ldfld, attacker));
codes.Insert(shieldStart++, new(OpCodes.Call, breakShield));
codes.Insert(shieldStart++, new(OpCodes.Callvirt, breakShield));

// this.current = TriggerBreakShield
codes.Insert(shieldStart++, new(OpCodes.Stfld, current));
// this.state = 5
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_4));
codes.Insert(shieldStart++, new(OpCodes.Stfld, state));
// return true
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_1));
codes.Insert(shieldStart++, new(OpCodes.Ret));
// this.state = -1
//generator.MarkLabel(breakShieldLabel);
/*CodeInstruction it = new(OpCodes.Ldarg_0);
it.labels.Add(breakShieldLabel);
codes.Insert(shieldStart++, it);
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_M1));
codes.Insert(shieldStart++, new(OpCodes.Stfld, state));*/
// yield break
}
break;
}
Expand Down
Loading