Skip to content

Commit

Permalink
Merge pull request #306 from divisionbyz0rro/slot-modification-manager
Browse files Browse the repository at this point in the history
Slot Modification Manager
  • Loading branch information
IngoHHacks authored Jul 19, 2024
2 parents e1f8743 + 6714a48 commit 1b0fd23
Show file tree
Hide file tree
Showing 10 changed files with 952 additions and 2 deletions.
21 changes: 21 additions & 0 deletions InscryptionAPI/Encounters/CachedGBCNPCDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using GBC;

namespace InscryptionAPI.Encounters;

public class CachedGCBNPCDescriptor
{
public string ID { get; set; }
public CardTemple BossTemple { get; set; }
public bool IsBoss { get; set; }
public PixelBoardSpriteSetter.BoardTheme BattleBackgroundTheme { get; set; }
public DialogueSpeaker DialogueSpeaker { get; set; }

public CachedGCBNPCDescriptor(CardBattleNPC npc)
{
ID = npc.ID;
BossTemple = npc.BossTemple;
IsBoss = npc.IsBoss;
BattleBackgroundTheme = npc.BattleBackgroundTheme;
DialogueSpeaker = npc.DialogueSpeaker;
}
}
31 changes: 31 additions & 0 deletions InscryptionAPI/Encounters/EncounterExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using System.Collections;
using System.Runtime.CompilerServices;
using DiskCardGame;
using GBC;
using HarmonyLib;
using InscryptionAPI.Card;
using static DiskCardGame.EncounterBlueprintData;

namespace InscryptionAPI.Encounters;

[HarmonyPatch]
public static class EncounterExtensions
{
#region Opponent Extensions
Expand Down Expand Up @@ -275,4 +279,31 @@ public static T SyncTurnDifficulties<T>(this T blueprint, int minDifficulty, int
#endregion

#endregion

#region GBC NPC Information

private static ConditionalWeakTable<GBCEncounterManager, CachedGCBNPCDescriptor> LAST_KNOWN_NPC = new();

[HarmonyPatch(typeof(GBCEncounterManager), nameof(GBCEncounterManager.EncounterSequence)), HarmonyPostfix]
private static IEnumerator CaptureLastKnownTriggeringNPC(IEnumerator sequence, CardBattleNPC triggeringNPC)
{
LAST_KNOWN_NPC.Remove(GBCEncounterManager.Instance);
LAST_KNOWN_NPC.Add(GBCEncounterManager.Instance, new CachedGCBNPCDescriptor(triggeringNPC));

yield return sequence;

LAST_KNOWN_NPC.Remove(GBCEncounterManager.Instance);
}

/// <summary>
/// Gets information about the NPC that triggered the current battle
/// </summary>
public static CachedGCBNPCDescriptor GetTriggeringNPC(this GBCEncounterManager mgr)
{
if (LAST_KNOWN_NPC.TryGetValue(mgr, out CachedGCBNPCDescriptor value))
return value;
return null;
}

#endregion
}
67 changes: 67 additions & 0 deletions InscryptionAPI/Saves/SaveFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using GBC;
using InscryptionAPI.Encounters;
using UnityEngine.SceneManagement;

namespace InscryptionAPI.Saves;

public static class SaveFileExtensions
{
/// <summary>
/// Gets the player's current location as a CardTemple
/// </summary>
/// <returns>The temple of the player's current location OR null if the player is in an ambiguous location</returns>
public static CardTemple? GetSceneAsCardTemple(this SaveFile save)
{
// Easy stuff
if (save.IsGrimora)
return CardTemple.Undead;
if (save.IsMagnificus)
return CardTemple.Wizard;
if (save.IsPart1)
return CardTemple.Nature;
if (save.IsPart3)
return CardTemple.Tech;

// Now the hard part; if this is Act 2
if (save.IsPart2)
{
// If there is an active battle, we should be able to get it from the NPC
var npc = GBCEncounterManager.Instance.GetTriggeringNPC();
if (npc != null)
{
// Translate the theme to a card temlpe
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.Nature)
return CardTemple.Nature;
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.Tech)
return CardTemple.Tech;
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.P03)
return CardTemple.Tech;
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.Undead)
return CardTemple.Undead;
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.Wizard)
return CardTemple.Wizard;

// A bit of an arbitrary choice here for "finale"
// P03 takes over so...
if (npc.BattleBackgroundTheme == PixelBoardSpriteSetter.BoardTheme.Finale)
return CardTemple.Tech;
}

// Okay, let's try to figure it out from the scene name
string sceneName = SceneManager.GetActiveScene().name.ToLowerInvariant();
if (sceneName.Contains("nature"))
return CardTemple.Nature;
if (sceneName.Contains("tech"))
return CardTemple.Tech;
if (sceneName.Contains("wizard"))
return CardTemple.Wizard;
if (sceneName.Contains("undead"))
return CardTemple.Undead;
}

// And now we're at the point where there's no way to figure it out.
// You're either in a neutral area of the Act 2 map
// Or you're not in a game scene at all.
return null;
}
}
31 changes: 31 additions & 0 deletions InscryptionAPI/Slots/SlotModificationBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections;
using DiskCardGame;

namespace InscryptionAPI.Slots;

/// <summary>
/// Base class for all slot modification behaviors
/// </summary>
public abstract class SlotModificationBehaviour : TriggerReceiver
{
/// <summary>
/// The slot that the behaviour is applied to.
/// </summary>
public CardSlot Slot => gameObject.GetComponent<CardSlot>();

/// <summary>
/// Use to setup any additional custom slot visualizations when created.
/// </summary>
public virtual IEnumerator Setup()
{
yield break;
}

/// <summary>
/// Use to clean up any additional custom slot visualizations before being removed
/// </summary>
public virtual IEnumerator Cleanup(SlotModificationManager.ModificationType replacement)
{
yield break;
}
}
182 changes: 182 additions & 0 deletions InscryptionAPI/Slots/SlotModificationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using System.Collections;
using DiskCardGame;
using GBC;
using HarmonyLib;
using InscryptionAPI.Encounters;
using InscryptionAPI.Helpers.Extensions;
using InscryptionAPI.Saves;
using UnityEngine;
using UnityEngine.UIElements;

namespace InscryptionAPI.Slots;

[HarmonyPatch]
/// <summary>
/// Contains extension methods to simplify slot modification management
/// </summary>
public static class SlotModificationExtensions
{
/// <summary>
/// Assigns a new slot modification to a slot.
/// </summary>
/// <param name="slot">The slot to assign the modification to</param>
/// <param name="modType">The modification type to assign</param>
public static IEnumerator SetSlotModification(this CardSlot slot, SlotModificationManager.ModificationType modType)
{
if (slot == null)
yield break;

SlotModificationManager.Info defn = SlotModificationManager.AllSlotModifications.FirstOrDefault(m => m.ModificationType == modType);

// Set the ability behaviour
var oldSlotModification = slot.GetComponent<SlotModificationBehaviour>();
if (oldSlotModification != null)
{
yield return oldSlotModification.Cleanup(modType);
CustomCoroutine.WaitOnConditionThenExecute(() => GlobalTriggerHandler.Instance.StackSize == 0, () => GameObject.Destroy(oldSlotModification));
SlotModificationManager.Instance.SlotReceivers.Remove(slot);
}

if (defn != null && defn.SlotBehaviour != null)
{
SlotModificationBehaviour newBehaviour = slot.gameObject.AddComponent(defn.SlotBehaviour) as SlotModificationBehaviour;

SlotModificationManager.Instance.SlotReceivers[slot] = new(modType, newBehaviour);
yield return newBehaviour.Setup();
}

// Set the texture and/or sprite
CardTemple temple = SaveManager.SaveFile.GetSceneAsCardTemple() ?? CardTemple.Nature;

if (defn == null)
{
slot.ResetSlotTexture();
}
else if (slot is PixelCardSlot pcs)
{
pcs.SetSlotSprite(defn);
}
else
{
if (defn.Texture == null || !defn.Texture.ContainsKey(temple))
slot.ResetSlotTexture();
else
slot.SetTexture(defn.Texture[temple]);
}
}

/// <summary>
/// Gets the current modification of a slot
/// </summary>
public static SlotModificationManager.ModificationType GetSlotModification(this CardSlot slot)
{
return slot == null
? SlotModificationManager.ModificationType.NoModification
: SlotModificationManager.Instance.SlotReceivers.ContainsKey(slot)
? SlotModificationManager.Instance.SlotReceivers[slot].Item1
: SlotModificationManager.ModificationType.NoModification;
}

private static void SetSlotSprite(this PixelCardSlot slot, SlotModificationManager.Info defn)
{
if (defn == null)
{
InscryptionAPIPlugin.Logger.LogDebug($"Resetting slot {slot.Index} to default because mod info was null");
slot.ResetSlotSprite();
return;
}

if (defn.PixelBoardSlotSprites == null)
{
InscryptionAPIPlugin.Logger.LogDebug($"Resetting slot {slot.Index} to default because mod info did not contain pixel slot info");
slot.ResetSlotSprite();
return;
}

var triggeringNPC = GBCEncounterManager.Instance?.GetTriggeringNPC();
if (triggeringNPC == null)
{
InscryptionAPIPlugin.Logger.LogDebug($"Doing nothing to slot {slot.Index} because the triggering NPC was null");
return;
}

if (!defn.PixelBoardSlotSprites.ContainsKey(triggeringNPC.BattleBackgroundTheme))
{
InscryptionAPIPlugin.Logger.LogDebug($"Resetting slot {slot.Index} to default because pixel slot info did not contain a definition for {triggeringNPC.BattleBackgroundTheme}");
slot.ResetSlotSprite();
return;
}

var spriteSet = defn.PixelBoardSlotSprites[triggeringNPC.BattleBackgroundTheme];
if (spriteSet == null)
{
InscryptionAPIPlugin.Logger.LogDebug($"Resetting slot {slot.Index} to default because pixel slot info had a null definition for {triggeringNPC.BattleBackgroundTheme}");
slot.ResetSlotSprite();
return;
}

var specificSprites = spriteSet.specificSlotSprites.Find(s => s.playerSlot == slot.IsPlayerSlot && s.index == slot.Index);

if (specificSprites == null)
slot.SetSprites(spriteSet.slotDefault, spriteSet.slotHighlight, slot.IsPlayerSlot && spriteSet.flipPlayerSlotSpriteY, false);
else
slot.SetSprites(specificSprites.slotDefault, specificSprites.slotHighlight, slot.IsPlayerSlot && spriteSet.flipPlayerSlotSpriteY, false);
}

private static void ResetSlotSprite(this PixelCardSlot slot)
{
var triggeringNPC = GBCEncounterManager.Instance?.GetTriggeringNPC();
if (triggeringNPC == null)
return;

var spriteSet = PixelBoardSpriteSetter.Instance.themeSpriteSets.Find(s => s.id == triggeringNPC.BattleBackgroundTheme);
if (spriteSet == null)
return;

var specificSprites = spriteSet.specificSlotSprites.Find(s => s.playerSlot == slot.IsPlayerSlot && s.index == slot.Index);

if (specificSprites != null)
slot.SetSprites(specificSprites.slotDefault, specificSprites.slotHighlight, slot.IsPlayerSlot && spriteSet.flipPlayerSlotSpriteY, false);
else
slot.SetSprites(spriteSet.slotDefault, spriteSet.slotHighlight, slot.IsPlayerSlot && spriteSet.flipPlayerSlotSpriteY, false);
}

/// <summary>
/// Resets a slot's texture back to the default texture for that slot based on the current act.
/// </summary>
public static void ResetSlotTexture(this CardSlot slot)
{
if (slot is PixelCardSlot pcs)
{
pcs.ResetSlotSprite();
return;
}

CardTemple temple = SaveManager.SaveFile.GetSceneAsCardTemple() ?? CardTemple.Nature;

Dictionary<CardTemple, List<Texture>> lookup = slot.IsOpponentSlot() ? SlotModificationManager.OpponentOverrideSlots : SlotModificationManager.PlayerOverrideSlots;
var newTexture = SlotModificationManager.DefaultSlotTextures[temple];
if (lookup.ContainsKey(temple))
{
// Get the texture overrides
var textureChoices = lookup[temple];
int idx = slot.Index;
if (idx >= textureChoices.Count)
{
// Try to guess what the best index would be
int slotCount = BoardManager.Instance.PlayerSlotsCopy.Count;
if (slot.Index == slotCount - 1) // the last slot
idx = textureChoices.Count - 1;
else // Use the next to last slot
idx = textureChoices.Count - 2;
}
if (idx < 0)
idx = 0;

if (textureChoices[idx] != null)
newTexture = textureChoices[idx];
}

slot.SetTexture(newTexture);
}
}
Loading

0 comments on commit 1b0fd23

Please sign in to comment.