Skip to content

Attack Roll System

Overview

The Attack Roll System provides a modular, extensible architecture for handling weapon attacks in Stingbatbot. It connects character data, weapons, and dice rolling to create a seamless combat experience for users. The system follows these architectural principles:

  • Separation of concerns: Each module has a specific responsibility
  • Extensibility: Designed to support future features like advantage/disadvantage
  • Integration: Works with existing Character and Dice systems
  • User experience: Provides clear, informative roll results

Command Interface

Command Overview

The !attack command (with shortcut !a) provides a way for players to quickly view their character's attack information:

  • Basic Usage: !attack - Shows a list of all character's attacks in an embed
  • Search Usage: !attack <search_term> - Find specific weapons by name with fuzzy matching

Command Usage

!attack [weapon_name] [option1] [option2] ... [+<modifier>]
  • weapon_name: Name of the weapon to attack with (optional - if omitted, shows all available weapons)
  • Options (can be provided in any order after weapon_name):
  • 2H: Use a versatile weapon in two-handed mode, providing increased damage
  • adv: Roll the attack with advantage (roll twice and take the higher result)
  • dis: Roll the attack with disadvantage (roll twice and take the lower result)
  • backstab: Apply Thief backstab damage affecting the number of dice rolled
  • demonic: Apply Knight of St. Ydris' demonic damage
  • assassin: Apply Rad-Godai's assassinate damage
  • verbose: Show all the modifiers for the dice expression
  • +<modifier>: Add a custom modifier to the attack roll (e.g., +1d6, +2)

Examples:

!attack                  # Lists all weapons
!attack longsword        # Attacks with a longsword in one-handed mode
!attack longsword 2H     # Attacks with a longsword in two-handed mode (if versatile)
!attack longsword adv    # Attacks with a longsword with advantage
!attack longsword dis    # Attacks with a longsword with disadvantage
!attack longsword +1d6   # Attacks with a longsword with an additional 1d6 damage
!attack longsword +2 adv # Attacks with a longsword with +2 bonus and advantage

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

Command Responses

No Arguments

When used without arguments, the command displays an embed with a complete list of the character's attacks.

Single Weapon Match

When a search term matches exactly one weapon: - Actually rolls the attack for that specific weapon - Returns as a formatted Embed - With the verbose option, shows how the roll was calculated

Multiple Weapon Matches

When a search term matches multiple weapons: - Returns a plain text message listing all matching weapons - Allows users to see what options match their search - Users can refine their search to get more specific results

No Matches

When no weapons match the search term: - Returns an error message - Suggests checking weapon names or viewing the full attack list

Fuzzy Matching

The search functionality uses fuzzy matching to find weapons: - Case-insensitive matching (e.g., "axe" matches "Greataxe") - Substring matching anywhere in the name (e.g., "axe" matches "Battleaxe") - Partial word matching (e.g., "horde" matches "Greataxe of the Horde")

System Architecture

Core Structures

Weapon

  • stingbatbot/weapon.ex

Attack

%Attack{
  character: %Character{},
  weapon: %Weapon{},
  attack_modifier: integer(),
  attack_modifiers: [
    %{source: String.t(), value: integer()}
  ],
  damage_modifier: integer(),
  damage_modifiers: [
    %{source: String.t(), value: integer()}
  ],
  special_effects: [String.t()],
  description: String.t()
}

Modifier Sources

  1. Base Ability Modifiers (attack only)
  2. STR for melee weapons
  3. DEX for ranged weapons
  4. For finesse weapons (property "F"), use the higher of STR or DEX
  5. Stat bonuses never apply to damage
  6. Magic weapon bonuses (apply to both attack and damage)
  7. Ancestry-specific bonuses
  8. HalfOrc: Mighty (bonus to melee attacks)
  9. Class-specific bonuses
  10. Fighter: WeaponMastery (bonus to specific weapons)
  11. Fighter: Plus1ToHit (stacking bonus to attack rolls)
  12. User-provided modifiers
  13. Custom modifiers provided via the +<modifier> or -<modifier> syntax
  14. Can be numeric (+2) or dice expressions (+1d6)
  15. Applied after all other modifiers

Critical Hits

  • Double the number of damage dice
  • Example: 1d8 becomes 2d8

Module Structure

Core Attack System

  • Stingbatbot.Attack - Main module defining the attack behavior and implementation list
  • Defines the callback interface for attack modifiers
  • Maintains a hardcoded list of implementing modules
  • Contains the main logic for calculating attacks
  • Applies modifiers from all implementation modules

Character Component Modules

  • Stingbatbot.Ancestry - Namespace for ancestry-specific modules
  • Stingbatbot.Ancestry.HalfOrc
  • etc.

  • Stingbatbot.Class - Namespace for class-specific modules

  • Stingbatbot.Class.Fighter
  • etc.

Each character component module implements the attack behavior and is included in the implementation modules list.

Implementation Details

Implementation Architecture

The system uses a static list architecture where:

  1. The Stingbatbot.Attack module defines a behavior with callbacks for modifying attacks
  2. Ancestry and class modules implement this behavior
  3. Implementing modules are explicitly listed in the @implementation_modules attribute
  4. When calculating an attack, all listed modifiers are applied in sequence

This architecture provides several benefits: - Clear separation between the attack system and character components - Explicit control over which modules are included and their application order - Single implementation point for each ancestry and class - Simple to understand and maintain

Module Implementation

Character component modules implement the attack behavior using the use Stingbatbot.Attack directive:

  1. Behavior Implementation:
  2. Each ancestry and class module uses use Stingbatbot.Attack at the top of the module
  3. This sets up the module to implement the required behavior callbacks
  4. The module then implements the apply_attack_modifiers/1 callback

  5. Manual Registration:

  6. Modules are manually added to the @implementation_modules list in the Attack module
  7. This provides explicit control over which modules are included
  8. The execution order is determined by the order in the list

  9. Default Implementations:

  10. The use directive provides sensible defaults that can be overridden
  11. This reduces boilerplate in modules that make minimal modifications

Example implementation:

defmodule Stingbatbot.Class.Fighter do
  use Stingbatbot.Attack

  @impl true
  def apply_attack_modifiers(attack) do
    attack
    |> apply_plus1_to_hit()
    |> apply_weapon_mastery()
  end

  # Implementation details...
end

Implementation Flow

  1. When a new attack is created:
  2. Base ability modifiers are applied based on weapon type and character stats
  3. Magic weapon bonuses are applied if applicable
  4. All implementation modules' modifiers are applied in sequence
  5. The attack description is generated

  6. Each implementation module:

  7. Receives the attack struct as input
  8. Applies its specific modifiers
  9. Returns the modified attack

  10. All modifiers are tracked with their source and value for transparency

This implementation provides a clear, maintainable system for calculating attacks with full visibility into how each modifier affects the final result.

Roll Modifiers Framework

The Roll Modifiers Framework is a core architectural pattern that enables situational modifications to dice mechanics themselves (not just the static modifiers added to rolls) for features like Thief backstab damage and other class/ancestry-specific abilities that apply conditionally based on command options.

Core Components

  1. RollComponents Struct: Encapsulates the core elements of a dice roll (count, dice, modifier)
  2. Pipeline Transformations: Functions that modify roll components based on game mechanics
  3. Callback Interface: A standardized interface for class and ancestry modules to modify roll mechanics

The pipeline processes roll components through several transformation stages: 1. Initial roll component creation 2. Versatile weapon handling (for two-handed weapons) 3. Critical hit processing (doubling dice) 4. Final expression building with context preservation

Implementation Callbacks

Each character component module can implement the apply_roll_modifiers/4 callback:

@callback apply_roll_modifiers(
  context :: any(),
  roll_type :: atom(),
  components :: RollComponents.t(),
  options :: map()
) :: {context :: any(), roll_type :: atom(), RollComponents.t(), options :: map()}

This callback receives: - The attack context - The roll type (e.g., :attack, :damage) - The current roll components - Options including command flags (e.g., backstab)

Modules can pattern match on these parameters to apply specific game mechanics to the dice roll itself.

Real-World Examples

This pattern enables features like: - Thief Backstab: Doubles weapon dice on successful backstab attacks - Ras-Godai Assassinate: Adds additional damage based on level

Architectural Benefits

This pattern provides several key benefits: 1. Separation of Concerns: Cleanly separates static modifiers from dice mechanics 2. Extensibility: New roll modifiers can be added without changing core code 3. Consistency: The same pattern is used across all roll-based subsystems 4. Testability: Each transformation can be tested in isolation 5. Transparency: Provides visibility into how dice expressions are constructed

This extension to the attack system represents a significant architectural advancement, creating a foundation for complex game mechanics across all roll-based systems including attacks, spell casting, and skill checks.