Skip to content

Ability Check System

Overview

The Ability Check System provides a streamlined way for characters to make ability checks in Shadowdark RPG. It integrates with the character system to automatically calculate and apply appropriate modifiers based on the character's ability scores.

This system follows the same architectural patterns established by the Attack Roll System, leveraging the generic RollResult and RollFormatter modules to maintain a consistent user experience across all roll types.

Command Usage

!check <stat> [option1] [option2] ... [descriptive text]
  • stat: One of the six core statistics (required)
  • str - Strength
  • dex - Dexterity
  • con - Constitution
  • int - Intelligence
  • wis - Wisdom
  • cha - Charisma

  • Options (can be provided in any order after the stat):

  • adv: Roll the check with advantage (roll twice and take the higher result)
  • dis: Roll the check with disadvantage (roll twice and take the lower result)
  • verb: Show detailed modifier breakdown

  • descriptive text: Optional text describing what the check is for (e.g., "picking a lock", "climbing a wall")

Examples:

!check str           # Basic Strength check
!check dex           # Basic Dexterity check
!check wis adv       # Wisdom check with advantage
!check int dis       # Intelligence check with disadvantage
!check dex picking a lock  # Dexterity check for picking a lock
!check str adv climbing a wall  # Strength check with advantage for climbing

Note: If both adv and dis are specified, they cancel each other out and a normal roll is made.

System Components

Core Modules

Character Module Extension

Responsibility: Provides pre-calculated stat check data

  • Already contains character stats, class, ancestry data
  • Calculates modifiers based on stats and other factors
  • Returns StatCheck structure with pre-calculated modifiers

Key Functions: - get_stat_check/2: Returns a pre-calculated StatCheck structure for a specific stat

StatCheck

Responsibility: Structured data representation of a stat check with pre-calculated modifiers

  • Contains the stat being checked
  • Stores the character making the check
  • Includes pre-calculated modifiers and bonuses from all sources
  • Similar to how Attack structures work for weapons

Key Fields: - character: The character making the check - stat: The stat being checked (str, dex, etc.) - stat_name: Full name of the stat (e.g., "Strength") - stat_score: Raw stat value (3-18) - modifier: The calculated total stat modifier (including all bonuses) - modifiers: List of all modifier sources (base stat, class bonuses, etc.)

StatCheckCommand

Responsibility: Processes the !check command from Discord

  • Implements the Stingbatbot.Commands.Command behavior
  • Located in the Stingbatbot.Commands namespace
  • Registered in the Command Registry for bot access
  • Validates the specified stat
  • Extracts options like advantage/disadvantage
  • Retrieves the pre-calculated StatCheck structure from the Character
  • Passes the StatCheck to StatCheckRoll for dice mechanics
  • Uses RollFormatter to format the results
  • Sends the formatted message back to Discord via MessageApi
  • Follows standard command pattern for error handling and responses

Key Functions: - name/0: Returns "check" as the command name - shortcut/0: Returns "c" as the command shortcut (optional) - description/0: Provides a description of the command - usage/0: Provides usage instructions - execute/2: Main function that handles command execution, returns {:ok, :sent} or {:error, reason} - extract_args/1: Extracts stat and options from command arguments

StatCheckRoll

Responsibility: Handles the dice rolling logic for stat checks

  • Takes a pre-calculated StatCheck structure
  • Creates a die expression using the pre-calculated modifier
  • Uses the Dice module for actual dice rolling
  • Creates structured results for formatting
  • Supports advantage/disadvantage mechanics

Key Functions: - roll/2: Main function that performs the check with the StatCheck and options - build_check_expression/2: Creates dice expressions with proper modifiers

RollResult Integration

The existing RollResult struct will be used to store check results, with:

  • context: A %StatCheck{} structure with check details
  • roll_expression: The dice expression (e.g., "1d20+2")
  • roll_result: The formatted result (e.g., "[15]+2 = 17")
  • roll_total: The numerical total
  • critical: Status of critical success/failure
  • No damage-related fields will be used
  • options: Map of options used for the roll (e.g., %{advantage: true})

RollFormatter Integration

The RollFormatter module will be extended with pattern matching for StatCheck contexts:

# Format title for stat checks
def format_title_part(%RollResult{context: %StatCheck{}} = result) do
  character_name = result.context.character.name
  stat_name = result.context.stat_name

  title = "🎲 #{character_name}'s #{stat_name} Check"

  # Add options if present
  option_text = build_option_text(result.options)
  if option_text != "", do: "#{title} (#{option_text})", else: title
end

# Format description for stat checks
def format_description_part(%RollResult{context: %StatCheck{}} = result) do
  check_part = format_check_part(result)

  "#{check_part}"
end

# Helper for formatting stat checks
def format_check_part(%RollResult{} = result) do
  # Base check text
  check_text = "**Stat Check:** #{result.roll_result}"

  # Add critical success/failure indicator
  case result.critical do
    :crit -> check_text <> "\n**CRITICAL SUCCESS!** 🎯"
    :fail -> check_text <> "\n**CRITICAL FAILURE!** 💥"
    _ -> check_text
  end
end

Data Flow

The stat check process follows these steps:

  1. User invokes !check <stat> command
  2. Bot's command processor identifies "check" and routes to StatCheckCommand
  3. StatCheckCommand validates the stat and extracts options
  4. Command retrieves the pre-calculated StatCheck structure from Character module
  5. Command passes StatCheck to StatCheckRoll with options
  6. StatCheckRoll:
  7. Uses the pre-calculated modifier from StatCheck
  8. Generates dice expression based on modifiers and options
  9. Uses Dice module to execute the roll
  10. Creates a structured RollResult with the StatCheck as context
  11. StatCheckCommand passes the RollResult to RollFormatter
  12. RollFormatter converts the result into a user-friendly message
  13. Message is sent back to Discord via MessageApi

Implementation Details

Stat Score Calculation

These calculations are performed once in the Character module when generating the StatCheck structure, rather than calculating them at roll time.

Class and Ancestry Bonuses

The StatCheck structures will include any relevant bonuses from: - Character class features affecting stat checks - Ancestry traits affecting stat checks - Magic items or other equipment - Temporary effects or status conditions

This ensures that all modifiers are pre-calculated and available when rolling.

Advantage Implementation

Like attack rolls, advantage uses the 2d20kh1 dice notation, which rolls two d20s and keeps the higher result.

Disadvantage Implementation

Like attack rolls, disadvantage uses the 2d20kl1 dice notation, which rolls two d20s and keeps the lower result.

When both advantage and disadvantage are specified on the same check, they cancel each other out, resulting in a normal d20 roll.

Critical Success/Failure

The system will detect natural 20s as critical successes and natural 1s as critical failures, marking them in the output with special formatting and emoji.

Extension Points

Key extension points in the current architecture:

  • StatCheckRoll can be extended with additional options
  • The RollResult structure already supports different types of contexts
  • RollFormatter already has pattern matching for different context types
  • Roll modifiers framework can be used for class/ancestry features affecting stat checks

Implementation Plan

  1. Extend the Character module to generate StatCheck structures via get_stat_check/2
  2. Create the StatCheck struct with relevant fields
  3. Implement the StatCheckRoll module to handle dice expressions and rolling
  4. Extend RollFormatter with pattern matching for StatCheck contexts
  5. Implement the StatCheckCommand to handle the !check command
  6. Register the command in the command registry
  7. Add tests to ensure all functionality works as expected
  8. Update documentation