Skip to content

Spell System Architecture

This document outlines the architecture for the spell system in Stingbatbot, which provides a centralized source of spell data for the Shadowdark RPG.

Overview

The Spell System follows a centralized architecture pattern:

  1. Data Layer: Spell definitions stored in spells.json
  2. Service Layer: SpellList GenServer providing access to spell data
  3. Domain Layer: Spell struct representing individual spells
  4. Integration Layer: Character system and spell casting mechanics using the SpellList

This architecture ensures a single source of truth for all spell data while providing efficient access patterns.

Core Components

SpellList GenServer

Responsibility: Central registry and lookup service for all spells

  • Loads all spells from spells.json at startup
  • Provides lookup functions for finding spells by name, class, and tier
  • Acts as the single source of truth for all spell data
  • Maintains a map of spells indexed by name for efficient lookup
  • Used during character parsing to associate spells with characters

Key Functions: - get_all/0: Returns all available spells - get_spell/1: Retrieves a spell by name (case-insensitive, supports partial matches) - get_spells_for_class/1: Returns all spells available to a specific class - get_spells_for_class_and_tier/2: Returns all spells for a class at a specific tier - get_spell_for_class/2: Retrieves a specific spell for a specific class - reload/0: Reloads the spell list from the JSON file (for development/admin use)

Spell Struct

Responsibility: Represents individual spell data

  • Defines the structure of a spell with all properties from spells.json
  • Provides helper functions for checking class availability and tier information
  • Used throughout the application to represent spell data consistently

Key Fields: - name: The name of the spell - classes: List of class names that can use this spell - tierByClass: List of tier/level numbers corresponding to classes - duration: Duration of the spell - range: Range of the spell - desc: Description of the spell - castWithAdv: Whether the spell is cast with advantage - popularityWithClass: List of popularity scores corresponding to classes - sourceId: Optional - source of the spell - alignmentRestriction: Optional - alignment restrictions - class: Backward compatibility - first class in classes - tier: Backward compatibility - first tier in tierByClass

Key Functions: - get_tier_for_class/2: Gets the tier for a specific class - available_to_class?/2: Checks if a spell is available to a specific class

Integration with Character System

The spell system integrates with the character system during character parsing:

  1. When parsing character data, any spells referenced in the character's abilities are looked up in the SpellList
  2. Class-specific default spells (like Turn Undead for Priests) are also retrieved from the SpellList
  3. Spell references are resolved to complete spell objects with all properties from spells.json
  4. This ensures all characters have complete and consistent spell data

Character.Parse Integration

During character parsing, spells are looked up from the SpellList rather than being constructed directly:

# From a character ability/bonus to a proper Spell object
def ability_to_spell(ability) do
  SpellList.get_spell_for_class(ability.bonus_name, ability.source_name)
end

For class-specific default spells, the same pattern is used:

# In Priest class module
def apply_parsing_modification(%{class: "Priest"} = character) do
  # Add Turn Undead if not already present
  if not has_turn_undead?(character) do
    turn_undead = SpellList.get_spell_for_class("Turn Undead", "Priest")
    %{character | spells: [turn_undead | character.spells]}
  else
    character
  end
end

Testing Strategy

The testing strategy for the spell system focuses on verifying:

  1. Accuracy: SpellList correctly loads and represents spells from JSON
  2. Completeness: All expected spells are available
  3. Integration: Character parsing correctly associates spells with characters
  4. Performance: Lookups are efficient even with many spells

Tests use the actual SpellList service (no mocking) to ensure end-to-end validation of the system.

Benefits of Centralized Spell Data

This centralized architecture provides several key benefits:

  1. Single Source of Truth: All spell data comes from spells.json, ensuring consistency
  2. Complete Information: All spells have complete information (duration, range, etc.)
  3. Maintainability: Updating spell data only requires changing the JSON file
  4. Consistency: All spell references use the same data structure
  5. Efficiency: Fast lookups via indexed map structure

Implementation Requirements

To implement this architecture:

  1. Ensure all spells are properly defined in spells.json
  2. Implement get_spell_for_class/2 in SpellList
  3. Update character parsing to use SpellList lookups
  4. Update class modules to retrieve default spells from SpellList
  5. Verify that all spells are properly loaded at application startup

Migration Path

The migration path from direct Spell construction to SpellList lookups:

  1. Add backward compatibility fields (class, tier) to the Spell struct
  2. Implement SpellList with efficient lookup functions
  3. Replace direct Spell construction with SpellList lookups
  4. Update tests to verify SpellList integration
  5. (Future) Consider removing backward compatibility fields once all code is updated