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

Slot Modification Manager #306

Merged
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
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
Loading