Skip to content

Tutorial Part 6: Combat & Skills

Estimated time: 30 minutes


Welcome back! In Part 5, you learned how to create NPCs that bring your world to life through dialogue and behaviors. Now it is time to add action to your game with combat and skills - the systems that let players fight monsters, grow stronger, and learn new abilities.

What You Will Learn

By the end of this tutorial, you will:

  1. Understand MAID's tick-based combat system
  2. Work with combat states and targeting
  3. Calculate damage with various modifiers
  4. Use armor and damage mitigation
  5. Implement healing mechanics
  6. Create skills with experience and leveling
  7. Build custom abilities with cooldowns and resource costs
  8. Complete a hands-on exercise: creating a "Power Strike" ability

Combat in MUDs: An Overview

Traditional MUDs use two main combat styles:

Style Description Pros Cons
Turn-Based Players take turns, like classic RPGs Strategic, fair Slow, less immersive
Real-Time Combat happens continuously Exciting, immersive Can be chaotic

MAID uses a tick-based real-time system that combines the best of both:

  • Combat happens in real-time, but actions are resolved in discrete "ticks"
  • The game updates 4 times per second by default (configurable via MAID_GAME__TICK_RATE)
  • Players can queue commands while combat progresses
  • Systems process actions in priority order each tick

This approach provides the excitement of real-time combat while maintaining predictable mechanics that can be balanced and tested.


Combat System Architecture

MAID's combat is built from several cooperating systems:

+------------------+     +------------------+     +------------------+
|   MeleeCombat    |     |  RangedCombat    |     |   SpellSystem    |
|   System         |     |   System         |     |                  |
+--------+---------+     +--------+---------+     +--------+---------+
         |                        |                        |
         v                        v                        v
+-------------------------------------------------------------------------+
|                         DamageSystem                                     |
|   - Applies attack results to characters                                 |
|   - Handles location-based damage (body parts)                           |
|   - Triggers status effects                                              |
+--------------------------------+----------------------------------------+
                                 |
                                 v
+-------------------------------------------------------------------------+
|                      StatusEffectManager                                 |
|   - Tracks active effects (bleed, poison, stun, etc.)                    |
|   - Processes damage over time                                           |
|   - Manages effect stacking and expiration                               |
+-------------------------------------------------------------------------+

Combat Components

Entities involved in combat need appropriate components:

Component Purpose Key Fields
HealthComponent Hit points current, maximum, regeneration_rate
StaminaComponent Physical abilities current, maximum, regeneration_rate
ManaComponent Magical abilities current, maximum, regeneration_rate
CombatComponent Combat stats attack_power, defense, speed, accuracy
StatsComponent Base attributes strength, dexterity, constitution, etc.

Combat States

Entities can be in different combat states that affect what actions they can take:

CombatComponent States

from maid_stdlib.components import CombatComponent

combat = CombatComponent(
    attack_power=15,
    defense=10,
    speed=12,
    accuracy=80,        # Base hit chance (percentage)
    evasion=10,         # Dodge chance (percentage)
    critical_chance=5,  # Critical hit (percentage)
    critical_multiplier=2.0,
    in_combat=False,    # Currently fighting?
    target_id=None,     # Current target UUID
)

The in_combat flag determines:

  • Whether stamina regenerates at combat or out-of-combat rate
  • Which spells can be cast (some are combat-only or non-combat-only)
  • How NPCs behave (hostile NPCs may attack, merchants may refuse to trade)

Engagement and Targeting

When combat begins, entities become engaged:

# Entity attacks a target
combat_comp = world.get_component(attacker_id, CombatComponent)
combat_comp.in_combat = True
combat_comp.target_id = target_id

# Target responds
target_combat = world.get_component(target_id, CombatComponent)
target_combat.in_combat = True
# Target may auto-target the attacker if not already targeting
if target_combat.target_id is None:
    target_combat.target_id = attacker_id

The Combat Loop

Each tick, the combat systems process in this order:

  1. SpellSystem (priority 40) - Process spell casting and completions
  2. MeleeCombatSystem (priority 50) - Resolve melee attacks
  3. RangedCombatSystem (priority 52) - Resolve ranged attacks
  4. DamageSystem (priority 55) - Apply damage to characters
  5. SkillSystem (priority 55) - Process skill usage
  6. StatusEffectManager (priority 60) - Process effects and DOTs

Damage Calculation

MAID supports multiple damage types with different mechanics:

Damage Types

from maid_classic_rpg.models.combat.damage import DamageType

# Physical damage types
DamageType.SLASHING   # Swords, axes
DamageType.PIERCING   # Spears, arrows
DamageType.BLUDGEON   # Maces, hammers
DamageType.UNARMED    # Fists, claws

# Magical damage types
DamageType.FIRE       # Fire spells
DamageType.COLD       # Ice spells
DamageType.LIGHTNING  # Electric attacks
DamageType.HOLY       # Divine magic
DamageType.DARK       # Shadow magic

# Special damage types
DamageType.POISON     # Damage over time
DamageType.BLEED      # Physical DOT
DamageType.TRUE       # Ignores armor

Basic Damage Formula

The damage calculation follows this flow:

Base Damage
    |
    v
+ Strength/Stat Modifier
    |
    v
+ Weapon Damage (dice roll)
    |
    v
* Position/Angle Modifiers
    |
    v
* Critical Hit Multiplier (if crit)
    |
    v
- Armor Reduction
    |
    v
= Final Damage

Example Damage Calculation

from maid_stdlib.components import HealthComponent, StatsComponent, CombatComponent


def calculate_melee_damage(
    attacker_stats: StatsComponent,
    attacker_combat: CombatComponent,
    defender_stats: StatsComponent,
    weapon_base_damage: int,
    is_critical: bool = False,
) -> int:
    """Calculate damage for a melee attack.

    Args:
        attacker_stats: Attacker's base stats
        attacker_combat: Attacker's combat stats
        defender_stats: Defender's stats (for armor)
        weapon_base_damage: Base damage from weapon
        is_critical: Whether this is a critical hit

    Returns:
        Final damage after all modifiers
    """
    # Start with weapon base damage
    damage = weapon_base_damage

    # Add strength modifier: (STR - 10) / 2
    str_modifier = attacker_stats.get_modifier("strength")
    damage += str_modifier

    # Add attack power
    damage += attacker_combat.attack_power // 5

    # Apply critical hit multiplier
    if is_critical:
        damage = int(damage * attacker_combat.critical_multiplier)

    # Subtract armor (defender's constitution provides some reduction)
    armor = attacker_combat.defense
    con_reduction = defender_stats.get_modifier("constitution")
    total_reduction = armor + con_reduction

    # Final damage (minimum 1)
    final_damage = max(1, damage - total_reduction)

    return final_damage


# Usage example
attacker_stats = StatsComponent(strength=16, dexterity=14)  # +3 STR mod
attacker_combat = CombatComponent(attack_power=15, critical_multiplier=2.0)
defender_stats = StatsComponent(constitution=14)  # +2 CON mod
defender_combat = CombatComponent(defense=8)

damage = calculate_melee_damage(
    attacker_stats,
    attacker_combat,
    defender_stats,
    weapon_base_damage=8,  # Rolled from weapon dice
    is_critical=False,
)
# damage = 8 + 3 + 3 - (8 + 2) = 4

Armor and Damage Reduction

Armor reduces incoming damage based on damage type:

from maid_classic_rpg.models.combat.damage import calculate_armor_reduction, DamageType


def apply_armor(
    raw_damage: int,
    armor_value: int,
    damage_type: DamageType,
    armor_penetration: float = 0.0,
) -> int:
    """Apply armor reduction to damage.

    Args:
        raw_damage: Damage before armor
        armor_value: Target's armor value
        damage_type: Type of damage
        armor_penetration: Percentage of armor ignored (0.0 to 1.0)

    Returns:
        Damage after armor reduction
    """
    # Calculate effective armor after penetration
    effective_armor = int(armor_value * (1.0 - armor_penetration))

    # Get armor reduction (armor is more effective against physical)
    reduction = calculate_armor_reduction(
        raw_damage,
        effective_armor,
        damage_type,
        armor_penetration,
    )

    return max(0, raw_damage - reduction)

Different damage types interact with armor differently:

Damage Type Armor Effectiveness Notes
Physical (slash/pierce/bludgeon) 100% Standard reduction
Fire/Cold/Lightning 50% Elemental partially bypasses
Holy/Dark 25% Magical mostly bypasses
Poison/Bleed 0% Ignores armor entirely
True 0% Always deals full damage

Healing Mechanics

Healing restores health to damaged characters:

Using HealthComponent

from maid_stdlib.components import HealthComponent

# Create a wounded character
health = HealthComponent(
    current=35,
    maximum=100,
    regeneration_rate=1.0,  # HP per second out of combat
)

# Check status
print(f"Health: {health.current}/{health.maximum}")  # 35/100
print(f"Percentage: {health.percentage:.1f}%")        # 35.0%
print(f"Is alive: {health.is_alive}")                 # True

# Apply healing
actual_healed = health.heal(50)
print(f"Healed {actual_healed} HP")  # Healed 50 HP
print(f"New health: {health.current}")  # 85

# Cannot overheal
overflow_healed = health.heal(100)
print(f"Healed {overflow_healed} HP")  # Healed 15 HP (capped at max)
print(f"Final health: {health.current}")  # 100

Healing Through the DamageSystem

For proper game integration, use the DamageSystem:

from maid_classic_rpg.systems.combat import DamageSystem

# Get the damage system from the world
damage_system = world.systems.get(DamageSystem)

# Heal a character
actual_healed = damage_system.heal_character(
    target=character,
    amount=50,
    location=None,  # Or specify a body part
)

print(f"Healed {actual_healed} HP")

Health Status Descriptions

Get descriptive health status for display:

status = damage_system.get_health_status(character)
# Returns: "in perfect health", "slightly wounded", "wounded",
#          "badly wounded", "severely wounded", "near death", "dead"

The Skill System

Skills represent learned abilities that improve with use. MAID's skill system supports:

  • Learning and forgetting skills
  • Experience-based leveling
  • Skill checks with varying difficulty
  • Training at guilds

Skill Levels and Experience

from maid_classic_rpg.systems.skills import SkillSystem
from uuid import UUID

# Get the skill system
skill_system = world.systems.get(SkillSystem)

# Teach a skill to a character
character_id: UUID = ...
skill_system.learn_skill(character_id, "one_handed_swords")

# Check skill level
level = skill_system.get_skill_level(character_id, "one_handed_swords")
print(f"Sword skill: Level {level}")  # Level 1

# Add experience (from using the skill)
exp_gained, levels_gained = skill_system.add_experience(
    character_id,
    "one_handed_swords",
    amount=25.0,
)
print(f"Gained {exp_gained} XP, {levels_gained} level(s)")

Skill Checks

Skill checks determine success or failure for actions:

from maid_classic_rpg.systems.skills import SkillSystem, CheckDifficulty

# Difficulty levels and their target numbers
# TRIVIAL = 10, EASY = 25, MEDIUM = 50, HARD = 75, EXTREME = 90, LEGENDARY = 100

# Perform a skill check
result = skill_system.skill_check(
    character=character,
    skill_name="lockpicking",
    difficulty=CheckDifficulty.MEDIUM,  # Target: 50
    stat_bonus=True,  # Include stat modifiers
)

if result.success:
    if result.critical:
        print("Critical success! You pick the lock with exceptional skill.")
    else:
        print("You successfully pick the lock.")
else:
    if result.critical:
        print("Critical failure! You break your lockpick.")
    else:
        print(f"You fail to pick the lock. (Rolled {result.roll} vs {result.target})")

# Result contains detailed information
print(f"Roll: {result.roll}")
print(f"Target: {result.target}")
print(f"Margin: {result.margin}")  # How much above/below target
print(f"Skill level: {result.skill_level}")

Opposed Skill Checks

For contests between characters:

# Attacker tries to hit, defender tries to dodge
attack_result, defend_result = skill_system.opposed_check(
    attacker=attacker,
    defender=defender,
    attack_skill="one_handed_swords",
    defend_skill="dodging",
)

if attack_result.success:
    print(f"{attacker.name} hits {defender.name}!")
else:
    print(f"{defender.name} dodges the attack!")

Training Skills at Guilds

Characters can train skills using training points:

# Train at a guild (costs training points)
success, levels_gained = skill_system.train_skill(
    character_id=character.id,
    skill_name="one_handed_swords",
    training_points=5,  # Points to spend
)

if success:
    print(f"Training complete! Gained {levels_gained} level(s).")
else:
    print("This skill cannot be trained here.")

Creating Abilities

Abilities are special actions characters can perform, often with resource costs and cooldowns.

Ability Structure

A typical ability has:

  • Name and description - What it is and what it does
  • Resource cost - Mana, stamina, or other resources consumed
  • Cooldown - Time before it can be used again (in ticks or seconds)
  • Requirements - Level, skill, class restrictions
  • Effects - What happens when used (damage, healing, buffs, etc.)

Example: Creating a Fireball Spell

Using the magic system:

from maid_classic_rpg.models.magic.spell import (
    SpellDefinition,
    SpellEffect,
    TargetType,
    SpellSchool,
)

fireball = SpellDefinition(
    internal_name="fireball",
    name="Fireball",
    description="Hurls a ball of fire at the target, dealing fire damage.",
    school=SpellSchool.EVOCATION,
    mana_cost=25,
    cast_time=1.5,  # Seconds to cast
    cooldown=3.0,   # Seconds before reuse
    required_level=5,
    target_type=TargetType.SINGLE,
    is_hostile=True,
    effects=[
        SpellEffect(
            effect_type="damage",
            value=15,           # Base damage
            dice=3,             # Roll 3d8
            sides=8,
            scaling_stat="intelligence",
            scaling_factor=0.5,  # 50% INT scaling
        ),
    ],
    cast_message_self="You hurl a ball of fire at {target}!",
    cast_message_room="{caster} hurls a ball of fire at {target}!",
)

Example: Creating a Heal Spell

healing_light = SpellDefinition(
    internal_name="healing_light",
    name="Healing Light",
    description="Channels divine energy to heal wounds.",
    school=SpellSchool.RESTORATION,
    mana_cost=20,
    cast_time=2.0,
    cooldown=5.0,
    required_level=3,
    target_type=TargetType.SINGLE,
    is_hostile=False,
    effects=[
        SpellEffect(
            effect_type="heal",
            value=10,
            dice=2,
            sides=6,
            scaling_stat="wisdom",
            scaling_factor=0.75,
        ),
    ],
    cast_message_self="You channel healing light into {target}.",
    cast_message_room="{caster} channels healing light into {target}.",
)

Using the SpellSystem

from maid_classic_rpg.systems.magic import SpellSystem

spell_system = world.systems.get(SpellSystem)

# Teach a spell to a character
spell_system.learn_spell(character.id, "fireball")

# Cast the spell
success, message = await spell_system.start_cast(
    caster=character,
    spell_name="fireball",
    target=enemy,
)

if success:
    print(message)  # "You begin casting Fireball..."
else:
    print(f"Cast failed: {message}")  # "You don't have enough mana."

Combat Abilities (Non-Magic)

For physical abilities that use stamina instead of mana:

StaminaComponent

from maid_stdlib.components import StaminaComponent

stamina = StaminaComponent(
    current=100,
    maximum=100,
    regeneration_rate=5.0,      # Per second out of combat
    combat_regen_rate=1.0,      # Per second in combat
)

# Check if we can use an ability
ability_cost = 25
if stamina.consume(ability_cost):
    print("Ability used!")
else:
    print("Not enough stamina!")

# Restore stamina
restored = stamina.restore(10)
print(f"Recovered {restored} stamina")

Defining Combat Abilities

Here is a pattern for creating melee combat abilities:

from dataclasses import dataclass
from typing import Callable
from uuid import UUID


@dataclass
class CombatAbility:
    """A combat ability that uses stamina."""

    name: str
    description: str
    stamina_cost: int
    cooldown_ticks: int  # Cooldown in game ticks
    damage_multiplier: float = 1.0
    accuracy_modifier: float = 0.0
    effects: list[str] | None = None  # Status effects to apply

    def can_use(self, stamina_current: int, on_cooldown: bool) -> tuple[bool, str]:
        """Check if the ability can be used."""
        if on_cooldown:
            return False, "That ability is on cooldown."
        if stamina_current < self.stamina_cost:
            return False, "You don't have enough stamina."
        return True, ""


# Define some abilities
power_strike = CombatAbility(
    name="Power Strike",
    description="A powerful blow that deals extra damage.",
    stamina_cost=30,
    cooldown_ticks=12,  # 3 seconds at 4 ticks/second
    damage_multiplier=1.75,
    accuracy_modifier=-0.1,  # Harder to hit
)

swift_strike = CombatAbility(
    name="Swift Strike",
    description="A quick attack that is easier to land.",
    stamina_cost=15,
    cooldown_ticks=4,  # 1 second
    damage_multiplier=0.75,
    accuracy_modifier=0.2,  # Easier to hit
)

stunning_blow = CombatAbility(
    name="Stunning Blow",
    description="Strike that may stun the target.",
    stamina_cost=40,
    cooldown_ticks=20,  # 5 seconds
    damage_multiplier=1.0,
    effects=["stun"],  # Apply stun on hit
)

Exercise: Create a Power Strike Ability

Now it is your turn! Create a "Power Strike" ability that:

  1. Costs 35 stamina to use
  2. Has a 4-second cooldown (16 ticks at 4 ticks/second)
  3. Deals 150% normal damage
  4. Has a -15% accuracy penalty (harder to hit)
  5. Can be learned by characters with at least level 5 in one_handed_swords

Step 1: Define the Ability

from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING
from uuid import UUID

if TYPE_CHECKING:
    from maid_classic_rpg.models.entities.character import Character


class AbilityResult(Enum):
    """Result of using an ability."""
    SUCCESS = "success"
    NOT_ENOUGH_RESOURCE = "not_enough_resource"
    ON_COOLDOWN = "on_cooldown"
    MISSING_REQUIREMENT = "missing_requirement"
    MISS = "miss"


@dataclass
class AbilityOutcome:
    """Outcome of an ability use."""
    result: AbilityResult
    message: str
    damage_dealt: int = 0
    effects_applied: list[str] = field(default_factory=list)


@dataclass
class PowerStrikeAbility:
    """Power Strike - A mighty blow that deals increased damage."""

    name: str = "Power Strike"
    description: str = "Channel your strength into a devastating attack."
    stamina_cost: int = 35
    cooldown_ticks: int = 16  # 4 seconds
    damage_multiplier: float = 1.5
    accuracy_penalty: float = 0.15
    required_skill: str = "one_handed_swords"
    required_skill_level: int = 5

    def check_requirements(
        self,
        character: "Character",
        skill_system,
    ) -> tuple[bool, str]:
        """Check if character meets requirements to use this ability."""
        skill_level = skill_system.get_skill_level(
            character.id,
            self.required_skill,
        )
        if skill_level < self.required_skill_level:
            return (
                False,
                f"Requires {self.required_skill} level {self.required_skill_level}. "
                f"(You have level {skill_level})",
            )
        return True, ""

Step 2: Implement Ability Usage

from maid_stdlib.components import StaminaComponent, CombatComponent


class AbilityManager:
    """Manages combat ability usage and cooldowns."""

    def __init__(self):
        # Track cooldowns: (character_id, ability_name) -> expires_at_tick
        self._cooldowns: dict[tuple[UUID, str], int] = {}
        self._current_tick: int = 0

    def update_tick(self) -> None:
        """Called each game tick to advance time."""
        self._current_tick += 1
        # Clean up expired cooldowns
        expired = [
            key for key, expires in self._cooldowns.items()
            if self._current_tick >= expires
        ]
        for key in expired:
            del self._cooldowns[key]

    def is_on_cooldown(self, character_id: UUID, ability_name: str) -> bool:
        """Check if an ability is on cooldown."""
        key = (character_id, ability_name)
        expires = self._cooldowns.get(key, 0)
        return self._current_tick < expires

    def get_cooldown_remaining(
        self,
        character_id: UUID,
        ability_name: str,
        ticks_per_second: float = 4.0,
    ) -> float:
        """Get remaining cooldown in seconds."""
        key = (character_id, ability_name)
        expires = self._cooldowns.get(key, 0)
        remaining_ticks = max(0, expires - self._current_tick)
        return remaining_ticks / ticks_per_second

    def set_cooldown(
        self,
        character_id: UUID,
        ability_name: str,
        cooldown_ticks: int,
    ) -> None:
        """Set an ability on cooldown."""
        key = (character_id, ability_name)
        self._cooldowns[key] = self._current_tick + cooldown_ticks

    async def use_power_strike(
        self,
        character: "Character",
        target: "Character",
        ability: PowerStrikeAbility,
        skill_system,
        melee_system,
    ) -> AbilityOutcome:
        """Execute Power Strike ability."""
        # Check requirements
        meets_req, req_msg = ability.check_requirements(character, skill_system)
        if not meets_req:
            return AbilityOutcome(
                result=AbilityResult.MISSING_REQUIREMENT,
                message=req_msg,
            )

        # Check cooldown
        if self.is_on_cooldown(character.id, ability.name):
            remaining = self.get_cooldown_remaining(character.id, ability.name)
            return AbilityOutcome(
                result=AbilityResult.ON_COOLDOWN,
                message=f"{ability.name} is on cooldown ({remaining:.1f}s remaining).",
            )

        # Check stamina
        stamina = character.get_component(StaminaComponent)
        if not stamina or stamina.current < ability.stamina_cost:
            return AbilityOutcome(
                result=AbilityResult.NOT_ENOUGH_RESOURCE,
                message=f"Not enough stamina. Need {ability.stamina_cost}, have {stamina.current if stamina else 0}.",
            )

        # Consume stamina
        stamina.consume(ability.stamina_cost)

        # Set cooldown
        self.set_cooldown(character.id, ability.name, ability.cooldown_ticks)

        # Resolve the attack with modifiers
        combat = character.get_component(CombatComponent)
        original_accuracy = combat.accuracy

        # Apply accuracy penalty temporarily
        combat.accuracy = int(original_accuracy * (1.0 - ability.accuracy_penalty))

        # Resolve attack (this would use the melee combat system)
        attack_result = await melee_system.resolve_attack(
            attacker=character,
            target=target,
            weapon=character.equipped_weapon,  # Assuming this exists
        )

        # Restore original accuracy
        combat.accuracy = original_accuracy

        if not attack_result.hit:
            return AbilityOutcome(
                result=AbilityResult.MISS,
                message=f"Your {ability.name} misses {target.name}!",
            )

        # Apply damage multiplier
        boosted_damage = int(attack_result.damage_dealt * ability.damage_multiplier)

        return AbilityOutcome(
            result=AbilityResult.SUCCESS,
            message=f"Your {ability.name} hits {target.name} for {boosted_damage} damage!",
            damage_dealt=boosted_damage,
        )

Step 3: Register the Ability with a Character

# In your content pack or game setup:

# Create the ability
power_strike = PowerStrikeAbility()

# Create an ability manager
ability_manager = AbilityManager()

# When a character wants to use Power Strike:
outcome = await ability_manager.use_power_strike(
    character=player_character,
    target=enemy_npc,
    ability=power_strike,
    skill_system=world.systems.get(SkillSystem),
    melee_system=world.systems.get(MeleeCombatSystem),
)

# Handle the outcome
if outcome.result == AbilityResult.SUCCESS:
    # Apply the damage through DamageSystem
    damage_system = world.systems.get(DamageSystem)
    damage_system.apply_direct_damage(
        target=enemy_npc,
        damage=outcome.damage_dealt,
        damage_type=DamageType.SLASHING,
    )

# Send message to player
await player_session.send(outcome.message)

Step 4: Create a Command for the Ability

from maid_engine.commands import CommandContext, command


@command(
    name="powerstrike",
    aliases=["ps"],
    category="combat",
    help_text="Perform a Power Strike on your target.",
)
async def cmd_power_strike(ctx: CommandContext) -> bool:
    """Execute Power Strike ability."""
    character = ctx.character
    if not character:
        await ctx.send("You need a character to use abilities.")
        return False

    # Check if in combat
    combat = character.get_component(CombatComponent)
    if not combat or not combat.in_combat:
        await ctx.send("You must be in combat to use Power Strike.")
        return False

    # Get target
    if not combat.target_id:
        await ctx.send("You have no target.")
        return False

    target = await ctx.world.get_character(combat.target_id)
    if not target:
        await ctx.send("Your target is no longer valid.")
        return False

    # Use the ability
    outcome = await ctx.ability_manager.use_power_strike(
        character=character,
        target=target,
        ability=power_strike,
        skill_system=ctx.world.systems.get(SkillSystem),
        melee_system=ctx.world.systems.get(MeleeCombatSystem),
    )

    await ctx.send(outcome.message)

    if outcome.result == AbilityResult.SUCCESS:
        # Apply damage
        damage_system = ctx.world.systems.get(DamageSystem)
        damage_system.apply_direct_damage(target, outcome.damage_dealt)

        # Announce to room
        await ctx.room.broadcast(
            f"{character.name} lands a powerful strike on {target.name}!",
            exclude=[character.id],
        )

    return outcome.result == AbilityResult.SUCCESS

Congratulations!

You have created a complete combat ability with:

  • Resource cost (stamina)
  • Cooldown management
  • Skill requirements
  • Damage modifiers
  • Full integration with the combat system

What's Next?

You now know how to:

  • Work with MAID's tick-based combat system
  • Calculate damage with stats, weapons, and armor
  • Implement healing mechanics
  • Use the skill system for experience and leveling
  • Create custom abilities with cooldowns and resource costs
  • Integrate abilities into the combat flow

In the next tutorial, you will learn how to package all your creations into a content pack that can be shared and reused.

Coming up in Part 7: Your First Content Pack

  • Content pack structure
  • Manifest files and dependencies
  • Registering systems and commands
  • Packaging and distribution
  • Testing your content pack

< Previous: Part 5 - NPCs & AI Back to Tutorial Index Next: Part 7 - Content Packs >


Quick Reference

Combat Components

Component Purpose Key Fields
HealthComponent Hit points current, maximum, regeneration_rate
StaminaComponent Physical resource current, maximum, regeneration_rate, combat_regen_rate
ManaComponent Magical resource current, maximum, regeneration_rate
CombatComponent Combat stats attack_power, defense, speed, accuracy, evasion, critical_chance, in_combat, target_id
StatsComponent Base attributes strength, dexterity, constitution, intelligence, wisdom, charisma, level, experience

Damage Types

Type Armor Interaction Common Sources
SLASHING Full armor Swords, axes
PIERCING Full armor Arrows, spears
BLUDGEON Full armor Maces, hammers
FIRE 50% armor Fire magic
COLD 50% armor Ice magic
LIGHTNING 50% armor Electric magic
POISON Ignores armor Poison effects
BLEED Ignores armor Bleeding wounds
TRUE Ignores armor Special abilities

Skill Check Difficulties

Difficulty Target Number Description
TRIVIAL 10 Almost automatic
EASY 25 Minor challenge
MEDIUM 50 Standard task
HARD 75 Difficult task
EXTREME 90 Near impossible
LEGENDARY 100 Heroic feat

Combat Systems (by Priority)

System Priority Purpose
SpellSystem 40 Spell casting and completion
MeleeCombatSystem 50 Melee attack resolution
RangedCombatSystem 52 Ranged attack resolution
DamageSystem 55 Apply damage to characters
SkillSystem 55 Process skill usage
StatusEffectManager 60 Process effects and DOTs

Ability Template

@dataclass
class CombatAbility:
    name: str
    description: str
    stamina_cost: int       # Or mana_cost for magical
    cooldown_ticks: int     # At 4 ticks/second
    damage_multiplier: float = 1.0
    accuracy_modifier: float = 0.0
    effects: list[str] | None = None
    required_skill: str | None = None
    required_skill_level: int = 0