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:
- Understand MAID's tick-based combat system
- Work with combat states and targeting
- Calculate damage with various modifiers
- Use armor and damage mitigation
- Implement healing mechanics
- Create skills with experience and leveling
- Build custom abilities with cooldowns and resource costs
- 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:
- SpellSystem (priority 40) - Process spell casting and completions
- MeleeCombatSystem (priority 50) - Resolve melee attacks
- RangedCombatSystem (priority 52) - Resolve ranged attacks
- DamageSystem (priority 55) - Apply damage to characters
- SkillSystem (priority 55) - Process skill usage
- 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:
- Costs 35 stamina to use
- Has a 4-second cooldown (16 ticks at 4 ticks/second)
- Deals 150% normal damage
- Has a -15% accuracy penalty (harder to hit)
- 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