BlackMagicAPI Guide

Updated 2 weeks ago

BlackMagicAPI Guide

A comprehensive framework for Mage Arena modding

Table of Contents

  1. Basic Setup
  2. Spell Creation
  3. Item System
  4. Crafting Recipes
  5. Soup System
  6. API Reference
  7. Troubleshooting

Basic Setup

Core Requirements

  • BepInEx 5.4.21+
  • .NET Framework 4.8
  • BlackMagicAPI.dll reference

Plugin Template

[BepInPlugin("your.mod.id", "Your Mod Name", "1.0.0")]
public class MyMod : BaseUnityPlugin
{
    private void Awake()
    {
        // Registration examples
        BlackMagicManager.RegisterSpell(this, typeof(YourSpellData), typeof(YourSpellLogic));
        BlackMagicManager.RegisterItem(this, typeof(YourItemData), typeof(YourItemBehavior));
        BlackMagicManager.RegisterCraftingRecipe(this, typeof(IngredientA), typeof(IngredientB), typeof(Result));
        BlackMagicManager.RegisterDeathIcon(this, "custom.death", "death_icon.png");
        BlackMagicManager.RegisterSoup(this, typeof(LogItem), typeof(YourSoupData), typeof(YourSoupEffect));
    }
}

Soup System

Soup Registration

/// <summary>
/// Registers a new soup with the soup management system.
/// </summary>
/// <param name="plugin">The plugin registering the soup.</param>
/// <param name="IItemInteraction">The type of the item used to create the soup (must implement IItemInteraction).</param>
/// <param name="SoupDataType">The type of the soup data (must inherit from SoupData).</param>
/// <param name="SoupEffectType">The type of the soup effect (must inherit from SoupEffect, optional if SoupData can load a prefab).</param>
/// <exception cref="InvalidCastException">Thrown if soup data cannot be created or cast to SoupData.</exception>
BlackMagicManager.RegisterSoup(
    BaseUnityPlugin plugin, 
    Type IItemInteraction, 
    Type SoupDataType, 
    Type? SoupEffectType = null
)

Soup Data Class

internal class HealthSoupData : SoupData
{
    public override string Name => "Health Soup";
    public override string ConsumeDescription => "Restores health over time";
    public override Color SoupColor => Color.red;
    
    // Optional: Load custom effect prefab
    public override Task<SoupEffect?> GetEffectPrefab()
    {
        // Load your custom prefab here
        return Task.FromResult<SoupEffect?>(null);
    }
    
    // Optional: Customize visual transform
    public override void SetObjectVisualTransform(GameObject render)
    {
        render.transform.localPosition = new Vector3(-0.0003f, 0.004f, 0.004f);
        render.transform.rotation = Quaternion.Euler(-3f, 0f, 0f);
        render.transform.localScale = render.transform.localScale * 0.03f;
    }
}

Soup Effect Class

internal class HealthSoupEffect : SoupEffect
{
    public override void ApplyEffect(PlayerMovement player)
    {
        // Implement your soup effect logic here
        // Example: Heal player over time
        StartCoroutine(HealOverTime(player));
    }
    
    private IEnumerator HealOverTime(PlayerMovement player)
    {
        float duration = 10f;
        float healPerSecond = 2f;
        float elapsed = 0f;
        
        while (elapsed < duration)
        {
            if (player != null)
            {
                // Apply healing logic
                // player.health += healPerSecond * Time.deltaTime;
            }
            elapsed += Time.deltaTime;
            yield return null;
        }
        
        DisposeEffect();
    }
    
    // Optional: Custom prefab initialization
    public override void OnPrefabCreatedAutomatically(GameObject prefab)
    {
        // Add custom components or modify the prefab
    }
}

Sprite Requirements

  • Place soup UI sprites in Sprites/{SoupName}_Ui.png within your plugin directory
  • Sprite filename should have spaces removed (e.g., "HealthSoup_Ui.png")
  • Fallback to default empty UI sprite if custom sprite not found

Complete Soup Example

// Registration
BlackMagicManager.RegisterSoup(
    this,
    typeof(SoupBowlItem), 
    typeof(HealthSoupData),
    typeof(HealthSoupEffect)
);

// Soup Data
internal class HealthSoupData : SoupData
{
    public override string Name => "Health Restoration Soup";
    public override string ConsumeDescription => "Gradually restores 20 health over 10 seconds";
    public override Color SoupColor => new Color(1f, 0.2f, 0.2f, 1f);
}

// Soup Effect
internal class HealthSoupEffect : SoupEffect
{
    public override void ApplyEffect(PlayerMovement player)
    {
        StartCoroutine(HealOverTime(player));
    }
    
    private IEnumerator HealOverTime(PlayerMovement player)
    {
        float totalHealing = 20f;
        float duration = 10f;
        float elapsed = 0f;
        
        while (elapsed < duration && player != null)
        {
            // Apply healing logic here
            elapsed += Time.deltaTime;
            yield return null;
        }
        
        DisposeEffect();
    }
}

Spell Creation

Spell Data Class

internal class FireballData : SpellData
{
    public override SpellType SpellType => SpellType.Page;
    public override string Name => "Fireball";
    public override float Cooldown => 3f;
    public override Color GlowColor => Color.red;
    
    // Additional voice command aliases
    public override string[] SubNames => ["flameball", "inferno"];
}

Spell Logic Class

internal class FireballLogic : SpellLogic
{
    public override bool CastSpell(PlayerMovement caster, PageController page, Vector3 spawnPos, Vector3 viewDirectionVector, int castingLevel);
    {
        return true; // return true to go on cooldown
    }

    // Optional page item interaction
    public override void OnPageItemUse(PlayerMovement itemOwner, PageController page)
    {
    }
}

Spell Prefab Access

// Get all active instances
var activeFireballs = Spell<FireballData>.GetLogicInstances();

// Get specific prefab component
var fireballPrefab = Spell<FireballData>.GetLogicPrefab<FireballLogic>();

// Check prefab existence
if(Spell<FireballData>.GetPagePrefab() != null) 
{
    // Prefab is loaded
}

Registration

BlackMagicManager.RegisterSpell(
    this, 
    typeof(FireballData), 
    typeof(FireballLogic)
);

Item System

Complete Item Example

internal class HealthPotionData : ItemData
{
    public override string Name => "Health Potion";
}

internal class HealthPotionBehavior : ItemBehavior
{
    protected override void OnItemUse(PlayerMovement itemOwner)
    {
    }
}

Crafting Recipes

Advanced Recipe Configuration

// Basic recipe
BlackMagicManager.RegisterCraftingRecipe(
    this,
    typeof(FireEssence),
    typeof(IceShard), 
    typeof(SteamSpirit)
);

Recipe Requirements

Component Requirements
Ingredients Must implement IItemInteraction
Result Must be a prefab of IItemInteraction

API Reference

SpellLogic Class

/// <summary>
/// Base class for all spell behaviors with lifecycle management
/// </summary>
public abstract class SpellLogic : MonoBehaviour, ISpell
{
    /// <summary>
    /// Core spell casting implementation
    /// </summary>
    public abstract void CastSpell(GameObject playerObj, PageController page, 
        Vector3 spawnPos, Vector3 viewDirectionVector, int castingLevel);

    /// <summary>
    /// Network data serialization for Castor
    /// </summary>
    public virtual void WriteData(DataWriter writer, PageController page,
        GameObject player, Vector3 spawnPos, Vector3 direction, int level) {}

    /// <summary>
    /// Client-side data synchronization
    /// </summary>
    public virtual void SyncData(object[] values) {}
}

Spell<SD> Utility Class

/// <summary>
/// Generic spell accessor providing runtime management utilities
/// </summary>
public class Spell<SD> where SD : SpellData
{
    /// <summary>
    /// Retrieves the PageController prefab for this spell type
    /// </summary>
    public static PageController? GetPagePrefab() { ... }

    /// <summary>
    /// Gets all active instances of this spell type
    /// </summary>
    public static SpellLogic?[] GetLogicInstances() { ... }

    /// <summary>
    /// Gets typed instances filtered by specific SpellLogic subtype
    /// </summary>
    public static SL?[] GetLogicInstances<SL>() where SL : SpellLogic { ... }
}

SoupData Class

/// <summary>
/// Abstract base class representing soup data and configuration
/// </summary>
public abstract class SoupData
{
    public abstract string Name { get; }
    public abstract string ConsumeDescription { get; }
    public abstract Color SoupColor { get; }
    public int ItemId { get; internal set; }
    public int SoupId { get; internal set; }
    public BaseUnityPlugin? Plugin { get; internal set; }
    
    public virtual Task<SoupEffect?> GetEffectPrefab() => Task.FromResult<SoupEffect?>(null);
    public virtual void SetObjectVisualTransform(GameObject render) { }
}

SoupEffect Class

/// <summary>
/// Abstract base class for soup effect behavior logic
/// </summary>
public abstract class SoupEffect : MonoBehaviour
{
    internal int Id { get; set; }
    public abstract void ApplyEffect(PlayerMovement player);
    public virtual void OnPrefabCreatedAutomatically(GameObject prefab) { }
    public void DisposeEffect() { Destroy(gameObject); }
}

Troubleshooting

Common Soup Issues

  • Missing Sprites: Ensure sprite files are in Sprites/ folder with correct naming
  • Registration Errors: Ensure all types implement required interfaces