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 damageadv
: 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 rolleddemonic
: Apply Knight of St. Ydris' demonic damageassassin
: Apply Rad-Godai's assassinate damageverbose
: 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¶
- Base Ability Modifiers (attack only)
- STR for melee weapons
- DEX for ranged weapons
- For finesse weapons (property "F"), use the higher of STR or DEX
- Stat bonuses never apply to damage
- Magic weapon bonuses (apply to both attack and damage)
- Ancestry-specific bonuses
- HalfOrc: Mighty (bonus to melee attacks)
- Class-specific bonuses
- Fighter: WeaponMastery (bonus to specific weapons)
- Fighter: Plus1ToHit (stacking bonus to attack rolls)
- User-provided modifiers
- Custom modifiers provided via the
+<modifier>
or-<modifier>
syntax - Can be numeric (+2) or dice expressions (+1d6)
- 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 modulesStingbatbot.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:
- The
Stingbatbot.Attack
module defines a behavior with callbacks for modifying attacks - Ancestry and class modules implement this behavior
- Implementing modules are explicitly listed in the
@implementation_modules
attribute - 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:
- Behavior Implementation:
- Each ancestry and class module uses
use Stingbatbot.Attack
at the top of the module - This sets up the module to implement the required behavior callbacks
-
The module then implements the
apply_attack_modifiers/1
callback -
Manual Registration:
- Modules are manually added to the
@implementation_modules
list in the Attack module - This provides explicit control over which modules are included
-
The execution order is determined by the order in the list
-
Default Implementations:
- The
use
directive provides sensible defaults that can be overridden - 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¶
- When a new attack is created:
- Base ability modifiers are applied based on weapon type and character stats
- Magic weapon bonuses are applied if applicable
- All implementation modules' modifiers are applied in sequence
-
The attack description is generated
-
Each implementation module:
- Receives the attack struct as input
- Applies its specific modifiers
-
Returns the modified attack
-
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¶
- RollComponents Struct: Encapsulates the core elements of a dice roll (count, dice, modifier)
- Pipeline Transformations: Functions that modify roll components based on game mechanics
- 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.