Magic System Tutorial¶
This tutorial guides you through building a complete magic system content pack for MAID. You will learn how to create spell components, implement mana management, handle spell effects, and integrate with the combat system.
What We're Building¶
The magic system will include:
- ManaComponent: Resource pool for casting spells
- SpellComponent: Definition of individual spells
- SpellbookComponent: Collection of learned spells
- MagicSystem: Spell casting, mana regeneration, spell effects
- Commands:
cast,spells,learncommands - Effects: Damage, healing, buffs, debuffs
Prerequisites¶
Before starting this tutorial, you should:
- Have completed the Combat System Tutorial
- Understand MAID's ECS architecture
- Be familiar with event-driven systems
Project Structure¶
maid-magic-system/
src/
maid_magic_system/
__init__.py
pack.py
components/
__init__.py
mana.py
spells.py
systems/
__init__.py
magic_system.py
mana_regen_system.py
commands/
__init__.py
magic_commands.py
events/
__init__.py
magic_events.py
data/
spells.py # Spell definitions
tests/
conftest.py
test_components.py
test_system.py
test_commands.py
pyproject.toml
Part 1: Mana Component¶
The ManaComponent tracks magical energy:
# src/maid_magic_system/components/mana.py
"""Mana component for magical energy management."""
from typing import ClassVar
from maid_engine.core.ecs import Component
from pydantic import Field
class ManaComponent(Component):
"""Component for entities that use magical energy.
Mana is consumed when casting spells and regenerates over time.
Some effects may also restore or drain mana.
Attributes:
current: Current mana points
maximum: Maximum mana points
regeneration_rate: Mana regenerated per second
casting_modifier: Bonus/penalty to spell power
"""
component_type: ClassVar[str] = "ManaComponent"
current: int = Field(default=100, ge=0, description="Current mana")
maximum: int = Field(default=100, ge=1, description="Maximum mana")
regeneration_rate: float = Field(
default=2.0, ge=0.0, description="Mana per second"
)
casting_modifier: int = Field(
default=0, description="Bonus to spell power"
)
# Tracking
time_since_cast: float = Field(
default=5.0, description="Seconds since last cast"
)
combat_regen_multiplier: float = Field(
default=0.5, description="Regen rate multiplier in combat"
)
@property
def percentage(self) -> float:
"""Get mana as a percentage (0-100)."""
if self.maximum <= 0:
return 0.0
return (self.current / self.maximum) * 100.0
@property
def is_empty(self) -> bool:
"""Check if mana is depleted."""
return self.current <= 0
@property
def is_full(self) -> bool:
"""Check if mana is at maximum."""
return self.current >= self.maximum
def can_cast(self, cost: int) -> bool:
"""Check if there is enough mana to cast a spell.
Args:
cost: The mana cost of the spell
Returns:
True if there is sufficient mana.
"""
return self.current >= cost
def consume(self, amount: int) -> bool:
"""Consume mana for a spell cast.
Args:
amount: Amount of mana to consume
Returns:
True if mana was successfully consumed.
"""
if not self.can_cast(amount):
return False
self.current -= amount
self.time_since_cast = 0.0
return True
def restore(self, amount: int) -> int:
"""Restore mana.
Args:
amount: Amount to restore
Returns:
Actual amount restored.
"""
old_value = self.current
self.current = min(self.maximum, self.current + amount)
return self.current - old_value
def drain(self, amount: int) -> int:
"""Drain mana (e.g., from enemy attack).
Args:
amount: Amount to drain
Returns:
Actual amount drained.
"""
old_value = self.current
self.current = max(0, self.current - amount)
return old_value - self.current
Part 2: Spell Components¶
Define spell data structures:
# src/maid_magic_system/components/spells.py
"""Spell-related components."""
from enum import Enum
from typing import ClassVar
from uuid import UUID
from maid_engine.core.ecs import Component
from pydantic import Field
class SpellSchool(str, Enum):
"""Schools of magic."""
EVOCATION = "evocation" # Damage spells
RESTORATION = "restoration" # Healing spells
ABJURATION = "abjuration" # Protective spells
ENCHANTMENT = "enchantment" # Buff/debuff spells
CONJURATION = "conjuration" # Summoning spells
DIVINATION = "divination" # Information spells
class SpellTarget(str, Enum):
"""Valid spell targets."""
SELF = "self"
SINGLE = "single"
AREA = "area"
ALL_ENEMIES = "all_enemies"
ALL_ALLIES = "all_allies"
class Spell:
"""Definition of a spell.
This is not a component but a data class for spell definitions.
Spells are stored in SpellbookComponent.
"""
def __init__(
self,
id: str,
name: str,
description: str,
school: SpellSchool,
mana_cost: int,
cooldown: float = 0.0,
cast_time: float = 0.0,
target_type: SpellTarget = SpellTarget.SINGLE,
base_power: int = 0,
level_requirement: int = 1,
effects: list[dict] | None = None,
):
self.id = id
self.name = name
self.description = description
self.school = school
self.mana_cost = mana_cost
self.cooldown = cooldown
self.cast_time = cast_time
self.target_type = target_type
self.base_power = base_power
self.level_requirement = level_requirement
self.effects = effects or []
class SpellbookComponent(Component):
"""Component that stores learned spells.
Attributes:
known_spells: List of spell IDs the entity knows
spell_cooldowns: Remaining cooldown for each spell
active_spell: Currently channeling spell (if any)
"""
component_type: ClassVar[str] = "SpellbookComponent"
known_spells: list[str] = Field(
default_factory=list,
description="IDs of known spells"
)
spell_cooldowns: dict[str, float] = Field(
default_factory=dict,
description="Remaining cooldowns"
)
active_spell: str | None = Field(
default=None,
description="Spell being channeled"
)
channel_progress: float = Field(
default=0.0,
description="Channel time accumulated"
)
def knows_spell(self, spell_id: str) -> bool:
"""Check if entity knows a spell."""
return spell_id in self.known_spells
def learn_spell(self, spell_id: str) -> bool:
"""Learn a new spell.
Returns:
True if spell was learned, False if already known.
"""
if spell_id in self.known_spells:
return False
self.known_spells.append(spell_id)
return True
def forget_spell(self, spell_id: str) -> bool:
"""Forget a spell.
Returns:
True if spell was forgotten.
"""
if spell_id not in self.known_spells:
return False
self.known_spells.remove(spell_id)
return True
def is_on_cooldown(self, spell_id: str) -> bool:
"""Check if a spell is on cooldown."""
return self.spell_cooldowns.get(spell_id, 0.0) > 0.0
def get_cooldown(self, spell_id: str) -> float:
"""Get remaining cooldown for a spell."""
return self.spell_cooldowns.get(spell_id, 0.0)
def start_cooldown(self, spell_id: str, duration: float) -> None:
"""Start a spell cooldown."""
self.spell_cooldowns[spell_id] = duration
def reduce_cooldowns(self, delta: float) -> None:
"""Reduce all cooldowns by time passed."""
for spell_id in list(self.spell_cooldowns.keys()):
self.spell_cooldowns[spell_id] -= delta
if self.spell_cooldowns[spell_id] <= 0:
del self.spell_cooldowns[spell_id]
class SpellEffectComponent(Component):
"""Component for active spell effects on an entity.
Tracks buffs, debuffs, and damage-over-time effects.
"""
component_type: ClassVar[str] = "SpellEffectComponent"
effects: list[dict] = Field(
default_factory=list,
description="Active spell effects"
)
def add_effect(
self,
effect_type: str,
power: int,
duration: float,
source_id: UUID | None = None,
spell_id: str = "",
) -> None:
"""Add a spell effect."""
self.effects.append({
"type": effect_type,
"power": power,
"duration": duration,
"remaining": duration,
"source_id": str(source_id) if source_id else None,
"spell_id": spell_id,
})
def remove_expired(self) -> list[dict]:
"""Remove and return expired effects."""
expired = [e for e in self.effects if e["remaining"] <= 0]
self.effects = [e for e in self.effects if e["remaining"] > 0]
return expired
def tick(self, delta: float) -> None:
"""Reduce all effect durations."""
for effect in self.effects:
effect["remaining"] -= delta
def get_effects_by_type(self, effect_type: str) -> list[dict]:
"""Get all effects of a specific type."""
return [e for e in self.effects if e["type"] == effect_type]
def clear_effects(self) -> None:
"""Remove all effects."""
self.effects.clear()
Part 3: Spell Registry¶
Create a spell registry with predefined spells:
# src/maid_magic_system/data/spells.py
"""Predefined spell definitions."""
from ..components.spells import Spell, SpellSchool, SpellTarget
# Evocation (Damage) Spells
FIREBALL = Spell(
id="fireball",
name="Fireball",
description="Hurls a ball of fire at the target",
school=SpellSchool.EVOCATION,
mana_cost=25,
cooldown=3.0,
target_type=SpellTarget.SINGLE,
base_power=30,
level_requirement=1,
effects=[
{"type": "damage", "damage_type": "fire"},
],
)
LIGHTNING_BOLT = Spell(
id="lightning_bolt",
name="Lightning Bolt",
description="Strikes the target with lightning",
school=SpellSchool.EVOCATION,
mana_cost=35,
cooldown=5.0,
target_type=SpellTarget.SINGLE,
base_power=45,
level_requirement=3,
effects=[
{"type": "damage", "damage_type": "lightning"},
],
)
ICE_STORM = Spell(
id="ice_storm",
name="Ice Storm",
description="Unleashes a storm of ice on all enemies",
school=SpellSchool.EVOCATION,
mana_cost=50,
cooldown=10.0,
target_type=SpellTarget.ALL_ENEMIES,
base_power=20,
level_requirement=5,
effects=[
{"type": "damage", "damage_type": "cold"},
{"type": "slow", "power": 30, "duration": 5.0},
],
)
# Restoration (Healing) Spells
HEAL = Spell(
id="heal",
name="Heal",
description="Restores health to the target",
school=SpellSchool.RESTORATION,
mana_cost=20,
cooldown=2.0,
target_type=SpellTarget.SINGLE,
base_power=25,
level_requirement=1,
effects=[
{"type": "heal"},
],
)
GREATER_HEAL = Spell(
id="greater_heal",
name="Greater Heal",
description="Powerfully restores health",
school=SpellSchool.RESTORATION,
mana_cost=45,
cooldown=5.0,
cast_time=2.0, # Channeled spell
target_type=SpellTarget.SINGLE,
base_power=60,
level_requirement=4,
effects=[
{"type": "heal"},
],
)
REGENERATION = Spell(
id="regeneration",
name="Regeneration",
description="Gradually restores health over time",
school=SpellSchool.RESTORATION,
mana_cost=30,
cooldown=15.0,
target_type=SpellTarget.SINGLE,
base_power=5,
level_requirement=2,
effects=[
{"type": "heal_over_time", "duration": 10.0, "tick_rate": 1.0},
],
)
# Abjuration (Protection) Spells
SHIELD = Spell(
id="shield",
name="Shield",
description="Creates a protective barrier",
school=SpellSchool.ABJURATION,
mana_cost=30,
cooldown=20.0,
target_type=SpellTarget.SELF,
base_power=50,
level_requirement=2,
effects=[
{"type": "absorb", "duration": 15.0},
],
)
# Enchantment (Buff/Debuff) Spells
STRENGTH = Spell(
id="strength",
name="Strength",
description="Increases attack power",
school=SpellSchool.ENCHANTMENT,
mana_cost=25,
cooldown=30.0,
target_type=SpellTarget.SINGLE,
base_power=10,
level_requirement=2,
effects=[
{"type": "buff", "stat": "attack_power", "duration": 60.0},
],
)
WEAKEN = Spell(
id="weaken",
name="Weaken",
description="Reduces target's defense",
school=SpellSchool.ENCHANTMENT,
mana_cost=20,
cooldown=15.0,
target_type=SpellTarget.SINGLE,
base_power=5,
level_requirement=1,
effects=[
{"type": "debuff", "stat": "defense", "duration": 30.0},
],
)
# Spell Registry
SPELL_REGISTRY: dict[str, Spell] = {
spell.id: spell for spell in [
FIREBALL,
LIGHTNING_BOLT,
ICE_STORM,
HEAL,
GREATER_HEAL,
REGENERATION,
SHIELD,
STRENGTH,
WEAKEN,
]
}
def get_spell(spell_id: str) -> Spell | None:
"""Get a spell by ID."""
return SPELL_REGISTRY.get(spell_id)
def get_all_spells() -> list[Spell]:
"""Get all registered spells."""
return list(SPELL_REGISTRY.values())
def get_spells_by_school(school: SpellSchool) -> list[Spell]:
"""Get all spells of a specific school."""
return [s for s in SPELL_REGISTRY.values() if s.school == school]
Part 4: Magic System¶
Create the main magic system:
# src/maid_magic_system/systems/magic_system.py
"""Magic system for spell casting and effects."""
from __future__ import annotations
from typing import TYPE_CHECKING, ClassVar
from uuid import UUID
from maid_engine.core.ecs import System
from maid_stdlib.components import DescriptionComponent, HealthComponent, PositionComponent
from ..components.mana import ManaComponent
from ..components.spells import SpellbookComponent, SpellEffectComponent, SpellTarget
from ..data.spells import get_spell
from ..events import SpellCastEvent, SpellEffectEvent, SpellFailedEvent
if TYPE_CHECKING:
from maid_engine.core.ecs import Entity
class MagicSystem(System):
"""System that handles spell casting and magic effects.
Responsibilities:
- Process spell cast requests
- Validate mana and cooldowns
- Calculate spell effects
- Apply damage, healing, buffs, debuffs
- Process active spell effects each tick
"""
priority: ClassVar[int] = 55 # Run after combat
async def startup(self) -> None:
"""Subscribe to spell events."""
self.events.subscribe(SpellCastEvent, self._handle_spell_cast)
async def update(self, delta: float) -> None:
"""Process active spell effects."""
# Update cooldowns
for entity in self.entities.with_components(SpellbookComponent):
spellbook = entity.get(SpellbookComponent)
spellbook.reduce_cooldowns(delta)
# Process active effects
for entity in self.entities.with_components(SpellEffectComponent):
effects = entity.get(SpellEffectComponent)
await self._process_effects(entity, effects, delta)
async def _handle_spell_cast(self, event: SpellCastEvent) -> None:
"""Handle a spell cast request."""
caster = self.entities.get(event.caster_id)
if not caster:
return
# Get spell definition
spell = get_spell(event.spell_id)
if not spell:
await self.events.emit(SpellFailedEvent(
caster_id=event.caster_id,
spell_id=event.spell_id,
reason="Unknown spell",
))
return
# Check requirements
fail_reason = self._check_cast_requirements(caster, spell)
if fail_reason:
await self.events.emit(SpellFailedEvent(
caster_id=event.caster_id,
spell_id=event.spell_id,
reason=fail_reason,
))
return
# Get targets
targets = self._get_targets(caster, event.target_id, spell.target_type)
if not targets and spell.target_type != SpellTarget.SELF:
await self.events.emit(SpellFailedEvent(
caster_id=event.caster_id,
spell_id=event.spell_id,
reason="No valid targets",
))
return
# Consume mana
mana = caster.get(ManaComponent)
mana.consume(spell.mana_cost)
# Start cooldown
spellbook = caster.get(SpellbookComponent)
if spell.cooldown > 0:
spellbook.start_cooldown(spell.id, spell.cooldown)
# Calculate power
power = spell.base_power + mana.casting_modifier
# Apply effects to all targets
for target in targets:
await self._apply_spell_effects(caster, target, spell, power)
def _check_cast_requirements(self, caster: Entity, spell) -> str | None:
"""Check if caster meets requirements to cast a spell.
Returns:
Error message if requirements not met, None otherwise.
"""
# Check mana
mana = caster.try_get(ManaComponent)
if not mana:
return "You have no magical ability"
if not mana.can_cast(spell.mana_cost):
return "Not enough mana"
# Check spellbook
spellbook = caster.try_get(SpellbookComponent)
if not spellbook:
return "You have no spellbook"
if not spellbook.knows_spell(spell.id):
return "You don't know that spell"
# Check cooldown
if spellbook.is_on_cooldown(spell.id):
remaining = spellbook.get_cooldown(spell.id)
return f"Spell on cooldown ({remaining:.1f}s)"
return None
def _get_targets(
self,
caster: Entity,
target_id: UUID | None,
target_type: SpellTarget
) -> list[Entity]:
"""Get valid targets for a spell."""
if target_type == SpellTarget.SELF:
return [caster]
caster_pos = caster.try_get(PositionComponent)
if not caster_pos:
return []
if target_type == SpellTarget.SINGLE:
if target_id:
target = self.entities.get(target_id)
if target:
target_pos = target.try_get(PositionComponent)
if target_pos and target_pos.room_id == caster_pos.room_id:
return [target]
return []
# Area/group targeting
targets = []
for entity in self.entities.with_components(PositionComponent):
if entity.id == caster.id:
continue
pos = entity.get(PositionComponent)
if pos.room_id != caster_pos.room_id:
continue
if target_type == SpellTarget.ALL_ENEMIES:
if entity.has_tag("hostile"):
targets.append(entity)
elif target_type == SpellTarget.ALL_ALLIES:
if entity.has_tag("player") or entity.has_tag("ally"):
targets.append(entity)
elif target_type == SpellTarget.AREA:
targets.append(entity)
return targets
async def _apply_spell_effects(
self,
caster: Entity,
target: Entity,
spell,
power: int
) -> None:
"""Apply spell effects to a target."""
for effect in spell.effects:
effect_type = effect["type"]
if effect_type == "damage":
await self._apply_damage(caster, target, power, effect)
elif effect_type == "heal":
await self._apply_healing(caster, target, power)
elif effect_type == "heal_over_time":
self._apply_hot(caster, target, power, effect, spell.id)
elif effect_type in ("buff", "debuff"):
self._apply_stat_modifier(caster, target, power, effect, spell.id)
elif effect_type == "absorb":
self._apply_shield(caster, target, power, effect, spell.id)
# Emit spell effect event
await self.events.emit(SpellEffectEvent(
caster_id=caster.id,
target_id=target.id,
spell_id=spell.id,
effect_type=spell.effects[0]["type"] if spell.effects else "none",
))
async def _apply_damage(
self,
caster: Entity,
target: Entity,
power: int,
effect: dict
) -> None:
"""Apply magical damage to a target."""
health = target.try_get(HealthComponent)
if not health:
return
damage_type = effect.get("damage_type", "magical")
# Could apply resistances here
actual_damage = health.damage(power)
# Emit damage event for combat log
from maid_combat_system.events import DamageDealtEvent
await self.events.emit(DamageDealtEvent(
source_id=caster.id,
target_id=target.id,
damage=actual_damage,
damage_type=damage_type,
))
async def _apply_healing(
self,
caster: Entity,
target: Entity,
power: int
) -> None:
"""Apply healing to a target."""
health = target.try_get(HealthComponent)
if not health:
return
actual_healing = health.heal(power)
def _apply_hot(
self,
caster: Entity,
target: Entity,
power: int,
effect: dict,
spell_id: str
) -> None:
"""Apply heal-over-time effect."""
effects = target.try_get(SpellEffectComponent)
if not effects:
target.add(SpellEffectComponent())
effects = target.get(SpellEffectComponent)
effects.add_effect(
effect_type="heal_over_time",
power=power,
duration=effect.get("duration", 10.0),
source_id=caster.id,
spell_id=spell_id,
)
def _apply_stat_modifier(
self,
caster: Entity,
target: Entity,
power: int,
effect: dict,
spell_id: str
) -> None:
"""Apply buff or debuff."""
effects = target.try_get(SpellEffectComponent)
if not effects:
target.add(SpellEffectComponent())
effects = target.get(SpellEffectComponent)
effect_power = power if effect["type"] == "buff" else -power
effects.add_effect(
effect_type=f"stat_{effect['stat']}",
power=effect_power,
duration=effect.get("duration", 30.0),
source_id=caster.id,
spell_id=spell_id,
)
def _apply_shield(
self,
caster: Entity,
target: Entity,
power: int,
effect: dict,
spell_id: str
) -> None:
"""Apply damage absorption shield."""
effects = target.try_get(SpellEffectComponent)
if not effects:
target.add(SpellEffectComponent())
effects = target.get(SpellEffectComponent)
effects.add_effect(
effect_type="absorb",
power=power,
duration=effect.get("duration", 15.0),
source_id=caster.id,
spell_id=spell_id,
)
async def _process_effects(
self,
entity: Entity,
effects: SpellEffectComponent,
delta: float
) -> None:
"""Process active spell effects on an entity."""
# Process heal-over-time
for hot in effects.get_effects_by_type("heal_over_time"):
# Apply healing tick
health = entity.try_get(HealthComponent)
if health:
tick_healing = int(hot["power"] * delta)
if tick_healing > 0:
health.heal(tick_healing)
# Tick durations
effects.tick(delta)
# Remove expired effects
expired = effects.remove_expired()
# Could emit events for expired effects
Part 5: Magic Commands¶
Create the cast command:
# src/maid_magic_system/commands/magic_commands.py
"""Commands for the magic system."""
from maid_engine.commands.registry import AccessLevel, CommandContext, LayeredCommandRegistry
from maid_stdlib.components import DescriptionComponent, PositionComponent
from ..components.mana import ManaComponent
from ..components.spells import SpellbookComponent
from ..data.spells import get_spell, get_all_spells
from ..events import SpellCastEvent
async def cast_command(ctx: CommandContext) -> bool:
"""Cast a spell.
Usage:
cast <spell> [target]
cast fireball goblin
cast heal self
"""
if not ctx.args:
await ctx.session.send("Cast what spell?")
await ctx.session.send("Usage: cast <spell> [target]")
return True
spell_name = ctx.args[0].lower()
target_name = ctx.args[1].lower() if len(ctx.args) > 1 else None
# Get player
player = ctx.world.entities.get(ctx.player_id)
if not player:
return False
# Check player has magic ability
mana = player.try_get(ManaComponent)
if not mana:
await ctx.session.send("You have no magical ability.")
return True
spellbook = player.try_get(SpellbookComponent)
if not spellbook:
await ctx.session.send("You don't have a spellbook.")
return True
# Find spell
spell = get_spell(spell_name)
if not spell:
# Try to find by partial name
for s in get_all_spells():
if spell_name in s.name.lower():
spell = s
break
if not spell:
await ctx.session.send(f"Unknown spell: {spell_name}")
return True
# Check requirements
if not spellbook.knows_spell(spell.id):
await ctx.session.send(f"You don't know {spell.name}.")
return True
if not mana.can_cast(spell.mana_cost):
await ctx.session.send(
f"Not enough mana. Need {spell.mana_cost}, have {mana.current}."
)
return True
if spellbook.is_on_cooldown(spell.id):
remaining = spellbook.get_cooldown(spell.id)
await ctx.session.send(f"{spell.name} is on cooldown ({remaining:.1f}s).")
return True
# Find target
target_id = None
if target_name and target_name != "self":
target = await find_target(ctx, target_name)
if not target:
await ctx.session.send(f"You don't see '{target_name}' here.")
return True
target_id = target.id
# Cast the spell
await ctx.session.send(f"You cast {spell.name}!")
await ctx.world.events.emit(SpellCastEvent(
caster_id=ctx.player_id,
spell_id=spell.id,
target_id=target_id,
))
return True
async def spells_command(ctx: CommandContext) -> bool:
"""List known spells.
Usage:
spells
"""
player = ctx.world.entities.get(ctx.player_id)
if not player:
return False
spellbook = player.try_get(SpellbookComponent)
if not spellbook:
await ctx.session.send("You don't have a spellbook.")
return True
mana = player.try_get(ManaComponent)
if not spellbook.known_spells:
await ctx.session.send("You don't know any spells.")
return True
await ctx.session.send("Known Spells:")
await ctx.session.send("-" * 40)
for spell_id in spellbook.known_spells:
spell = get_spell(spell_id)
if not spell:
continue
# Build spell line
status = ""
if spellbook.is_on_cooldown(spell_id):
cd = spellbook.get_cooldown(spell_id)
status = f" [CD: {cd:.1f}s]"
elif mana and not mana.can_cast(spell.mana_cost):
status = " [No mana]"
await ctx.session.send(
f" {spell.name} ({spell.mana_cost} mana) - {spell.description}{status}"
)
if mana:
await ctx.session.send("-" * 40)
await ctx.session.send(f"Mana: {mana.current}/{mana.maximum}")
return True
async def find_target(ctx: CommandContext, name: str):
"""Find a target by name in the player's room."""
player = ctx.world.entities.get(ctx.player_id)
if not player:
return None
player_pos = player.try_get(PositionComponent)
if not player_pos:
return None
for entity in ctx.world.entities.with_components(PositionComponent, DescriptionComponent):
pos = entity.get(PositionComponent)
if pos.room_id != player_pos.room_id:
continue
desc = entity.get(DescriptionComponent)
if name in str(desc.name).lower():
return entity
return None
def register_commands(registry: LayeredCommandRegistry, pack_name: str) -> None:
"""Register magic commands."""
registry.register(
name="cast",
handler=cast_command,
pack_name=pack_name,
aliases=["c"],
category="magic",
description="Cast a spell",
usage="cast <spell> [target]",
access_level=AccessLevel.PLAYER,
)
registry.register(
name="spells",
handler=spells_command,
pack_name=pack_name,
aliases=["spellbook", "sb"],
category="magic",
description="List known spells",
usage="spells",
access_level=AccessLevel.PLAYER,
)
Summary¶
This tutorial covered:
- Mana Component: Resource management for spell casting
- Spell Components: Spell definitions and spellbooks
- Spell Registry: Predefined spell data
- Magic System: Spell casting, targeting, and effect processing
- Commands: Player interface for casting spells
Integration with Combat¶
The magic system integrates with the combat system by:
- Emitting
DamageDealtEventfor damage spells - Using the same targeting system
- Sharing components like
HealthComponent
Next Steps¶
- Add more spell effects (stun, teleport, summon)
- Implement spell combos
- Create a learning/leveling system for spells
- Add equipment that boosts magic
Continue to the Complete Game Tutorial to see how all systems work together.