Skip to content

Commit ba391e1

Browse files
authored
Merge pull request #316 from HumabHatterZed/main
Added IShieldPreventedDamage trigger and ICustomExhaustSequence
2 parents a1a7b67 + 739625c commit ba391e1

File tree

9 files changed

+170
-64
lines changed

9 files changed

+170
-64
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
<details>
22
<summary>View Changelog</summary>
33

4+
# 2.22.1
5+
- Added IShieldPreventedDamage and IShieldPreventedDamageInHand ability triggers and interfaces
6+
- Added TriggerBreakShield, wraps BreakShield in an IEnumerator for additional customisation by modders
7+
- Added ICustomExhaustSequence trigger interface for modifying the draw pile exhaustion effect - use with Opponents
8+
- Fixed board slots being uninteractable if a slot effect with a rulebook interactable was reset
9+
- Removed debug logging related to rulebook redirect coordinates
10+
411
# 2.22.0
512
- Added FullBoon objects for each vanilla Boon
613
- Added 'AllFullBoons' list to BoonManager

InscryptionAPI/Card/ShieldManager.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using DiskCardGame.CompositeRules;
33
using HarmonyLib;
44
using InscryptionAPI.Helpers.Extensions;
5+
using InscryptionAPI.Triggers;
56
using System.Collections;
67
using System.Collections.ObjectModel;
78
using System.Reflection;
@@ -24,6 +25,29 @@ public static class ShieldManager
2425
public static List<AbilityInfo> AllShieldInfos { get; internal set; } = AllShieldAbilities.Select(x => x.Info).ToList();
2526

2627

28+
public static IEnumerator TriggerBreakShield(PlayableCard target, int damage, PlayableCard attacker)
29+
{
30+
BreakShield(target, damage, attacker);
31+
32+
List<IShieldPreventedDamage> shieldTriggers = CustomTriggerFinder.FindTriggersOnBoard<IShieldPreventedDamage>(false).ToList();
33+
shieldTriggers.Sort((IShieldPreventedDamage a, IShieldPreventedDamage b) => b.ShieldPreventedDamagePriority(target, damage, attacker) - a.ShieldPreventedDamagePriority(target, damage, attacker));
34+
foreach (IShieldPreventedDamage damageTrigger in shieldTriggers)
35+
{
36+
if ((damageTrigger as TriggerReceiver) != null && damageTrigger.RespondsToShieldPreventedDamage(target, damage, attacker))
37+
{
38+
yield return damageTrigger.OnShieldPreventedDamage(target, damage, attacker);
39+
}
40+
}
41+
List<IShieldPreventedDamageInHand> shieldInHandTriggers = CustomTriggerFinder.FindTriggersInHand<IShieldPreventedDamageInHand>().ToList();
42+
shieldInHandTriggers.Sort((IShieldPreventedDamageInHand a, IShieldPreventedDamageInHand b) => b.ShieldPreventedDamageInHandPriority(target, damage, attacker) - a.ShieldPreventedDamageInHandPriority(target, damage, attacker));
43+
foreach (IShieldPreventedDamageInHand damageTrigger in shieldInHandTriggers)
44+
{
45+
if ((damageTrigger as TriggerReceiver) != null && damageTrigger.RespondsToShieldPreventedDamageInHand(target, damage, attacker))
46+
{
47+
yield return damageTrigger.OnShieldPreventedDamageInHand(target, damage, attacker);
48+
}
49+
}
50+
}
2751
/// <summary>
2852
/// The method used for when a shielded card is damaged. Includes extra parameters for modders looking to modify this further.
2953
/// This method is only called when damage > 0 and the target has a shield.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using DiskCardGame;
2+
using System.Collections;
3+
4+
namespace InscryptionAPI.Encounters;
5+
6+
/// <summary>
7+
/// An interface that implements custom logic when the player has exhausted both of their draw piles.
8+
/// </summary>
9+
/// <remarks>
10+
/// Only for Opponents and SpecialSequences.
11+
/// </remarks>
12+
public interface ICustomExhaustSequence
13+
{
14+
public bool RespondsToCustomExhaustSequence(CardDrawPiles drawPiles);
15+
/// <summary>
16+
/// Executes the sequence that plays when the player exhausts their draw piles.
17+
/// </summary>
18+
/// <param name="drawPiles">The CardDrawPiles instance for this scene.</param>
19+
/// <returns>An enumeration of Unity events.</returns>
20+
public IEnumerator DoCustomExhaustSequence(CardDrawPiles drawPiles);
21+
}

InscryptionAPI/Encounters/OpponentManager.cs

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using InscryptionAPI.Guid;
44
using InscryptionAPI.Masks;
55
using InscryptionAPI.Saves;
6+
using System.Collections;
67
using System.Collections.ObjectModel;
78
using System.Reflection;
89
using System.Reflection.Emit;
@@ -89,10 +90,49 @@ public static FullOpponent Add(string guid, string opponentName, string sequence
8990
NewOpponents.Add(opp);
9091
return opp;
9192
}
93+
public static List<Opponent.Type> RunStateOpponents
94+
{
95+
get
96+
{
97+
List<Opponent.Type> previousBosses = new List<Opponent.Type>();
98+
99+
string value = ModdedSaveManager.RunState.GetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses"); // 2,0,1
100+
if (value == null)
101+
{
102+
// Do nothing
103+
}
104+
else if (!value.Contains(','))
105+
{
106+
// Single boss encounter
107+
previousBosses.Add((Opponent.Type)int.Parse(value));
108+
}
109+
else
110+
{
111+
// Multiple boss encounters
112+
IEnumerable<Opponent.Type> ids = value.Split(',').Select(static (a) => (Opponent.Type)int.Parse(a));
113+
previousBosses.AddRange(ids);
114+
}
115+
116+
return previousBosses;
117+
}
118+
set
119+
{
120+
string result = ""; // 2,0,1
121+
for (int i = 0; i < value.Count; i++)
122+
{
123+
if (i > 0)
124+
{
125+
result += ",";
126+
}
127+
result += (int)value[i];
128+
129+
}
130+
ModdedSaveManager.RunState.SetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses", result);
131+
}
132+
}
92133

93134
#region Patches
94-
[HarmonyPatch(typeof(Opponent), nameof(Opponent.SpawnOpponent))]
95-
[HarmonyPrefix]
135+
[HarmonyPrefix, HarmonyPatch(typeof(Opponent), nameof(Opponent.SpawnOpponent))]
96136
private static bool ReplaceSpawnOpponent(EncounterData encounterData, ref Opponent __result)
97137
{
98138
if (encounterData.opponentType == Opponent.Type.Default || !ProgressionData.LearnedMechanic(MechanicsConcept.OpponentQueue))
@@ -124,27 +164,19 @@ private static bool ReplaceSpawnOpponent(EncounterData encounterData, ref Oppone
124164
[MethodImpl(MethodImplOptions.NoInlining)]
125165
public static string OriginalGetSequencerIdForBoss(Opponent.Type bossType) { throw new NotImplementedException(); }
126166

127-
[HarmonyPatch(typeof(BossBattleSequencer), nameof(BossBattleSequencer.GetSequencerIdForBoss))]
128-
[HarmonyPrefix]
167+
[HarmonyPrefix, HarmonyPatch(typeof(BossBattleSequencer), nameof(BossBattleSequencer.GetSequencerIdForBoss))]
129168
private static bool ReplaceGetSequencerId(Opponent.Type bossType, ref string __result)
130169
{
131170
__result = AllOpponents.First(o => o.Id == bossType).SpecialSequencerId;
132171
return false;
133172
}
134173

135-
[HarmonyPatch(typeof(BossBattleNodeData), nameof(BossBattleNodeData.PrefabPath), MethodType.Getter)]
136-
[HarmonyPrefix]
174+
[HarmonyPrefix, HarmonyPatch(typeof(BossBattleNodeData), nameof(BossBattleNodeData.PrefabPath), MethodType.Getter)]
137175
private static bool ReplacePrefabPath(ref string __result, Opponent.Type ___bossType)
138176
{
139-
GameObject obj = ResourceBank.Get<GameObject>("Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType);
140-
if (obj != null)
141-
{
142-
__result = "Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType;
143-
}
144-
else
145-
{
146-
__result = "Prefabs/Map/MapNodesPart1/MapNode_ProspectorBoss";
147-
}
177+
string fullPath = "Prefabs/Map/MapNodesPart1/MapNode_" + ___bossType;
178+
GameObject obj = ResourceBank.Get<GameObject>(fullPath);
179+
__result = obj != null ? fullPath : "Prefabs/Map/MapNodesPart1/MapNode_ProspectorBoss";
148180
return false;
149181
}
150182

@@ -220,47 +252,20 @@ public static void ProcessBossType(NodeData nodeData)
220252
}
221253
}
222254

223-
public static List<Opponent.Type> RunStateOpponents
255+
[HarmonyPostfix, HarmonyPatch(typeof(CardDrawPiles), nameof(CardDrawPiles.ExhaustedSequence))]
256+
private static IEnumerator CustomBossExhaustionSequence(IEnumerator enumerator, CardDrawPiles __instance)
224257
{
225-
get
258+
if (TurnManager.Instance.Opponent is ICustomExhaustSequence exhaustSeq && exhaustSeq != null)
226259
{
227-
List<Opponent.Type> previousBosses = new List<Opponent.Type>();
228-
229-
string value = ModdedSaveManager.RunState.GetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses"); // 2,0,1
230-
if (value == null)
231-
{
232-
// Do nothing
233-
}
234-
else if (!value.Contains(','))
235-
{
236-
// Single boss encounter
237-
previousBosses.Add((Opponent.Type)int.Parse(value));
238-
}
239-
else
240-
{
241-
// Multiple boss encounters
242-
IEnumerable<Opponent.Type> ids = value.Split(',').Select(static (a) => (Opponent.Type)int.Parse(a));
243-
previousBosses.AddRange(ids);
244-
}
245-
246-
return previousBosses;
260+
Singleton<ViewManager>.Instance.SwitchToView(View.CardPiles, immediate: false, lockAfter: true);
261+
yield return new WaitForSeconds(1f);
262+
yield return exhaustSeq.DoCustomExhaustSequence(__instance);
247263
}
248-
set
264+
else
249265
{
250-
string result = ""; // 2,0,1
251-
for (int i = 0; i < value.Count; i++)
252-
{
253-
if (i > 0)
254-
{
255-
result += ",";
256-
}
257-
result += (int)value[i];
258-
259-
}
260-
ModdedSaveManager.RunState.SetValue(InscryptionAPIPlugin.ModGUID, "PreviousBosses", result);
266+
yield return enumerator;
261267
}
262268
}
263-
264269
#endregion
265270

266271
#region Optimization Patches

InscryptionAPI/InscryptionAPI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<DebugType>full</DebugType>
1111
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
1212
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
13-
<Version>2.22.0</Version>
13+
<Version>2.22.1</Version>
1414
</PropertyGroup>
1515

1616
<PropertyGroup>

InscryptionAPI/Rulebook/RuleBookRedirectManager.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void ClearActiveInteractables()
5252
}
5353
public void UpdateActiveInteractables(TextMeshPro description, GameObject currentPageObj, Dictionary<string, RuleBookManager.RedirectInfo> redirects)
5454
{
55-
InscryptionAPIPlugin.Logger.LogDebug($"[UpdateActiveInteractables]");
55+
//InscryptionAPIPlugin.Logger.LogDebug($"[UpdateActiveInteractables]");
5656
Bounds pageBounds;
5757
Bounds borderBounds = currentPageObj.transform.Find("Border").GetComponent<SpriteRenderer>().bounds; // in world space
5858
Vector3 pageBottomLeft;
@@ -82,9 +82,9 @@ public void UpdateActiveInteractables(TextMeshPro description, GameObject curren
8282
descriptionLengths ??= new float[2] { borderBounds.size.x, (currentPageLengths[1] / currentPageLengths[0]) * borderBounds.size.x };
8383
}
8484
zLength = pageBottomLeft.z - currentPageTopLeft.z;
85-
InscryptionAPIPlugin.Logger.LogDebug($"[DescTopLeft] {descriptionTopLeft.x} {descriptionTopLeft.y} {descriptionTopLeft.z}");
86-
InscryptionAPIPlugin.Logger.LogDebug($"[PageTopLeft] {currentPageTopLeft.x} {currentPageTopLeft.y} {currentPageTopLeft.z}");
87-
InscryptionAPIPlugin.Logger.LogDebug($"[PageBottomLeft] {pageBottomLeft.x} {pageBottomLeft.y} {pageBottomLeft.z} | {zLength}");
85+
//InscryptionAPIPlugin.Logger.LogDebug($"[DescTopLeft] {descriptionTopLeft.x} {descriptionTopLeft.y} {descriptionTopLeft.z}");
86+
//InscryptionAPIPlugin.Logger.LogDebug($"[PageTopLeft] {currentPageTopLeft.x} {currentPageTopLeft.y} {currentPageTopLeft.z}");
87+
//InscryptionAPIPlugin.Logger.LogDebug($"[PageBottomLeft] {pageBottomLeft.x} {pageBottomLeft.y} {pageBottomLeft.z} | {zLength}");
8888

8989
ClearActiveInteractables();
9090
CreateInteractables(redirects, description, zLength);
@@ -113,8 +113,8 @@ public void CreateInteractableObject(Vector3 worldPosition, Vector3 colliderSize
113113

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

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

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

271271
}
272272
correctedPos = new(correctedX, correctedY, currentPageTopLeft.z + (zCorrection + zCorrection * correctedYProportion));
273-
Debug.Log($"[Corrected] ({correctedPos.x} {correctedPos.y} {correctedPos.z}) | {correctedXProportion} {correctedYProportion}");
273+
//Debug.Log($"[Corrected] ({correctedPos.x} {correctedPos.y} {correctedPos.z}) | {correctedXProportion} {correctedYProportion}");
274274
colliderPositions.Add(correctedPos);
275275
colliderSizes.Add(new((topRight.x - bottomLeft.x) + sizeY / 2f, sizeY * 3f / 2f, 0.001f)); // add padding to compensate for inaccurate positioning
276276
}

InscryptionAPI/Slots/SlotModificationExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static IEnumerator SetSlotModification(this CardSlot slot, ModificationTy
9898
SlotModificationInteractable interactable = slot.GetComponent<SlotModificationInteractable>();
9999
if (defn == null || modType == ModificationType.NoModification || (defn.SharedRulebook == ModificationType.NoModification && string.IsNullOrEmpty(defn.RulebookName)))
100100
{
101-
interactable?.SetEnabled(false);
101+
UnityObject.Destroy(interactable);
102102
}
103103
else
104104
{

InscryptionAPI/Triggers/Interfaces.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,24 @@ public interface IOnCardDealtDamageDirectly
802802
public IEnumerator OnCardDealtDamageDirectly(PlayableCard attacker, CardSlot opposingSlot, int damage);
803803
}
804804

805+
/// <summary>
806+
/// Trigger that is called after a shielded card was attacked and lost a shield.
807+
/// </summary>
808+
public interface IShieldPreventedDamage
809+
{
810+
public bool RespondsToShieldPreventedDamage(PlayableCard target, int damage, PlayableCard attacker);
811+
public IEnumerator OnShieldPreventedDamage(PlayableCard target, int damage, PlayableCard attacker);
812+
public int ShieldPreventedDamagePriority(PlayableCard target, int damage, PlayableCard attacker);
813+
}
814+
/// <summary>
815+
/// Variant of IShieldPreventDamage that triggers for cards in the hand.
816+
/// </summary>
817+
public interface IShieldPreventedDamageInHand
818+
{
819+
public bool RespondsToShieldPreventedDamageInHand(PlayableCard target, int damage, PlayableCard attacker);
820+
public IEnumerator OnShieldPreventedDamageInHand(PlayableCard target, int damage, PlayableCard attacker);
821+
public int ShieldPreventedDamageInHandPriority(PlayableCard target, int damage, PlayableCard attacker);
822+
}
805823
/*public interface IOnPreTakeDamageFromHammer
806824
{
807825
public bool RespondsToPreTakeDamageFromHammer(HammerItem hammer, CardSlot targetSlot, GameObject firstPersonItem);

InscryptionAPI/Triggers/TakeDamagePatches.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
9494
int shieldStart = -1, shieldEnd = -1;
9595
for (int i = 0; i < codes.Count; i++)
9696
{
97+
9798
// grab the required operands, in order of appearance in the code
9899
if (shieldStart == -1 && codes[i].operand?.ToString() == "Boolean HasShield()")
99100
{
@@ -130,11 +131,22 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
130131
attacker = codes[i].operand;
131132
if (shieldEnd > 0)
132133
{
134+
CodeInstruction switch_ = codes.Find(x => x.opcode == OpCodes.Switch);
135+
//switch_.WithLabels(breakShieldLabel);
136+
object state = codes.Find(x => x.opcode == OpCodes.Stfld && x.operand.ToString() == "System.Int32 <>1__state").operand;
137+
object current = codes.Find(x => x.opcode == OpCodes.Stfld && x.operand.ToString() == "System.Object <>2__current").operand;
133138
// if (HasShield && damage > 0)
134-
// BreakShield();
139+
// yield return TriggerBreakShield();
135140
// break;
136141

137-
MethodBase breakShield = AccessTools.Method(typeof(ShieldManager), nameof(ShieldManager.BreakShield),
142+
// TriggerBreakShield
143+
// this.current = TriggerBreakShield
144+
// this.state = 7
145+
// return true
146+
// this.state = -1
147+
// yield break (new label)
148+
149+
MethodBase breakShield = AccessTools.Method(typeof(ShieldManager), nameof(ShieldManager.TriggerBreakShield),
138150
new Type[] { typeof(PlayableCard), typeof(int), typeof(PlayableCard) });
139151

140152
codes.RemoveRange(shieldStart, shieldEnd - shieldStart);
@@ -145,14 +157,33 @@ private static IEnumerable<CodeInstruction> TakeDamageTranspiler(IEnumerable<Cod
145157
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_0));
146158
codes.Insert(shieldStart++, new(OpCodes.Cgt));
147159
codes.Insert(shieldStart++, new(OpCodes.Brfalse, hasShieldLabel));
148-
// BreakShield();
149-
//break;
160+
161+
// TriggerBreakShield();
162+
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
150163
codes.Insert(shieldStart++, new(OpCodes.Ldloc_1));
151164
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
152165
codes.Insert(shieldStart++, new(OpCodes.Ldfld, damage));
153166
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
154167
codes.Insert(shieldStart++, new(OpCodes.Ldfld, attacker));
155-
codes.Insert(shieldStart++, new(OpCodes.Call, breakShield));
168+
codes.Insert(shieldStart++, new(OpCodes.Callvirt, breakShield));
169+
170+
// this.current = TriggerBreakShield
171+
codes.Insert(shieldStart++, new(OpCodes.Stfld, current));
172+
// this.state = 5
173+
codes.Insert(shieldStart++, new(OpCodes.Ldarg_0));
174+
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_4));
175+
codes.Insert(shieldStart++, new(OpCodes.Stfld, state));
176+
// return true
177+
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_1));
178+
codes.Insert(shieldStart++, new(OpCodes.Ret));
179+
// this.state = -1
180+
//generator.MarkLabel(breakShieldLabel);
181+
/*CodeInstruction it = new(OpCodes.Ldarg_0);
182+
it.labels.Add(breakShieldLabel);
183+
codes.Insert(shieldStart++, it);
184+
codes.Insert(shieldStart++, new(OpCodes.Ldc_I4_M1));
185+
codes.Insert(shieldStart++, new(OpCodes.Stfld, state));*/
186+
// yield break
156187
}
157188
break;
158189
}

0 commit comments

Comments
 (0)