Skip to main content

Overview

Nesting is the practice of calling one module from within another, creating layers of functionality. This is where IonTech’s Modular Systems truly shines—by composing simple modules, you can build incredibly complex spell behaviors.
Every module is designed to integrate with all other modules. This isn’t accidental—it’s the core design principle.

Basic Nesting

The simplest form of nesting is calling one module from another:
# Level 1: Your spell
my_spell-cast:
  Skills:
  - skill:Spell{id=my_spell;name=My Spell;spellcd=100}

# Level 2: Spell Handler (called automatically)
spell-cast:
  Skills:
  - vskill{s=<skill.var.id>}  # Calls your actual spell logic
  - skill{s=spell-cooldown}    # Applies cooldown
When you call skill:Spell{...}, you’re actually nesting your logic inside the Spell Handler module, which provides:
  • Cooldown management
  • Charge systems
  • Channeling/warmup
  • Cast validation
  • VFX and sound effects

Understanding Execution Flow

Example: Frost Shard

Let’s trace how frost_shard executes through nested modules:
# LEVEL 1: Initial cast
frost_shard-cast:
  Skills:
  - skill:Spell{id=[ - vskill{s=frost_shard-exec;branch=true} ];
      name=Frost Shard;
      charges=8;chargecd=2;chargedelay=0.75}
Execution path:
1

Player triggers cast

The item or input handler calls frost_shard-cast
2

Spell Handler takes over

skill:Spell redirects to the Spell Handler module:
spell:
  Skills:
  - skill{s=spell-init}      # Initialize variables
  - skill{s=spell-evaluate}  # Check conditions
3

Validation checks

spell-evaluate:
  Conditions:
  - hasaura{name=stun} false  # Not stunned?
  - varequals{var=charges;val=0} castinstead spell-on_cooldown
4

Execute spell logic

Since charges > 0, executes the id parameter:
spell-charges-cast:
  Skills:
  - vskill{s=<skill.var.id>}  # Runs frost_shard-exec
5

Your logic runs

frost_shard-exec:
  Skills:
  - skill:projectile_barrage{...}  # Nest another module!

Visualization

Common Nesting Patterns

Pattern 1: Spell → Projectile → Damage

The most common pattern in the demo spells:
# LEVEL 1: Spell Handler
frost_shard-cast:
  Skills:
  - skill:Spell{id=[ - vskill{s=frost_shard-exec} ];...}

# LEVEL 2: Projectile Logic
frost_shard-exec:
  Skills:
  - skill:projectile_barrage{id=frost_shard-fire;...} @forward{...}

# LEVEL 3: Projectile Mechanics
frost_shard-fire:
  Skills:
  - skill:setRandomID          # Damage tracking module
  - projectile{onHit=frost_shard-hit_entity;...}

# LEVEL 4: Damage Handler
frost_shard-hit_entity:
  Skills:
  - skill:damage-ability{immune=10;...}  # Damage Core module
Why this structure?
  • Level 1 handles player interaction (cooldowns, charges, casting)
  • Level 2 handles spell behavior (firing projectiles)
  • Level 3 handles projectile mechanics (movement, VFX)
  • Level 4 handles combat resolution (damage, effects)
Each level can be modified independently without breaking the others.

Pattern 2: Spell → Channel → Execute

For spells with warmup/channeling:
# LEVEL 1: Spell Handler with channeling
burning_blast-cast:
  Skills:
  - skill:Spell{id=burning_blast;
      warmup=0.6;                        # Trigger channeling
      onChannelStart=burning_blast-exec; # Execute when ready
      onChannelTick=[
        - vskill{s=channel-tick_fx}
        - potion{t=SLOWNESS;l=2;duration=4}
      ]}

# LEVEL 2: Spell execution after channel
burning_blast-exec:
  Skills:
  - skill:burning_blast-fire{...} @forward{...}

# LEVEL 3: Projectile with tracking
burning_blast-fire:
  Skills:
  - skill:setRandomID
  - projectile{onStart=burning_blast-start;  # Nested callbacks!
      onTick=burning_blast-tick;
      onHit=burning_blast-hit_entity;...}

# LEVEL 4: Complex projectile behavior
burning_blast-start:
  Skills:
  - wait{c=[ - hasaura{aura=<skill.var.id>-channel} false ]}
  - modifyprojectile{t=VELOCITY;a=SET;v=12}

# LEVEL 5: Damage on hit
burning_blast-hit_entity:
  Skills:
  - skill:damage-ability{damageMod=[ - variableMath{var=damage;equation="x*2.5"} ]}
Execution timeline:
  1. Player starts cast → Spell Handler begins channel (0.6s)
  2. onChannelStart fires projectile immediately
  3. Projectile waits for channel to complete (onStart)
  4. After channel, projectile accelerates and homes
  5. On hit, deals 250% damage via Damage Core

Pattern 3: Damage → Nested Effects

Nesting modules inside the onHit parameter:
glacial_spikes-hit_entity:
  Skills:
  - skill:damage-ability{
      damageMod=[ - variableMath{var=damage;equation="x*1.75"} ];
      onHit=[                           # Nest multiple modules
      - freeze{t=60}                    # Vanilla effect
      - vskill{s=knockback;v=2;vy=0.5;vertical=false}  # Knockback module!
      ]}
Here, the Damage Core module executes:
  1. Apply damage modifiers
  2. Deal damage
  3. Execute onHit skills
  4. Call nested Knockback module
  5. Display damage indicator

Advanced Nesting Techniques

Technique 1: Recursive Calls

frost_shard uses recursion to fire multiple projectiles:
frost_shard-cast:
  Skills:
  - skill:Spell{charges=8;...}  # 8 charges available

frost_shard-exec:
  Skills:
  - skill:projectile_barrage{...}
  - delay 1
  - skill:frost_shard-cast      # Recursively call the spell!
Result: The spell casts itself repeatedly until all charges are consumed, creating a rapid-fire effect.
Be careful with recursion! Always have a termination condition (in this case, running out of charges) or you’ll create infinite loops.

Technique 2: Branching Execution

The branch=true parameter allows parallel execution:
frost_shard-cast:
  Skills:
  - skill:Spell{id=[ - vskill{s=frost_shard-exec;branch=true} ];...}
Without branch=true, the spell would wait for frost_shard-exec to complete before continuing. With it, execution continues immediately. Use cases:
  • Firing projectiles without blocking
  • Applying effects asynchronously
  • Running multiple parallel processes

Technique 3: Callback Nesting

burning_blast demonstrates complex callback nesting:
burning_blast-cast:
  Skills:
  - skill:Spell{
      onChannelStart=burning_blast-exec;  # Callback 1
      onChannelTick=[                     # Callback 2 (inline)
        - vskill{s=channel-tick_fx}
        - potion{t=SLOWNESS;l=2;duration=4}
      ]}

burning_blast-fire:
  Skills:
  - projectile{
      onStart=burning_blast-start;        # Nested callback 1
      onTick=burning_blast-tick;          # Nested callback 2
      onHit=burning_blast-hit_entity}     # Nested callback 3
Callbacks within callbacks create sophisticated behaviors:
  • Spell Handler’s onChannelStart fires a projectile
  • That projectile has its own onStart, onTick, onHit callbacks
  • The onHit callback calls the Damage Core
  • Which has its own onHit callback for status effects

Technique 4: Module Chains

glacial_spikes shows how to chain multiple projectiles:
glacial_spikes-target:
  Skills:
  - skill:setRandomID              # Module 1: Damage tracking
  - projectile{                    # Module 2: Main projectile
      onTick=[
      - skill{s=glacial_spikes-fire;...;branch=true}  # Module 3: Sub-projectiles
      ]}

glacial_spikes-fire:
  Skills:
  - projectile{                    # Module 4: Sub-projectile
      onHit=glacial_spikes-hit_entity}  # Module 5: Damage handler

glacial_spikes-hit_entity:
  Skills:
  - skill:damage-ability{          # Module 6: Damage Core
      onHit=[
      - vskill{s=knockback;...}    # Module 7: Knockback Core
      ]}
Execution flow:
  1. Main projectile travels forward
  2. Each tick, spawns sub-projectiles upward
  3. Sub-projectiles arc down
  4. On hit, deal damage and knockback
Seven modules working together to create a single spell effect!

Real-World Example: Building a Custom Spell

Let’s build a spell from scratch using nested modules:
Thunderstrike Spell:
  • 5 second cooldown
  • 1 second warmup
  • Fires a lightning bolt at cursor
  • Deals 200% damage
  • Stuns for 2 seconds
  • Knocks back enemies
Final nesting structure:
thunderstrike-cast (Spell Handler)
└── thunderstrike-exec
    └── thunderstrike-fire (setRandomID)
        └── thunderstrike-hit_entity (Damage Core)
            └── knockback (Knockback Core)
Five distinct modules working together to create one spell!

Common Pitfalls

Problem:
spell_a:
  Skills:
  - skill:spell_b

spell_b:
  Skills:
  - skill:spell_a  # Infinite loop!
Solution: Always ensure nesting has a termination condition. The Spell Handler prevents this with cooldowns and charge systems.
Problem:
my_spell:
  Skills:
  - skill:damage-ability{...}  # Blocks execution
  - sound{s=...}               # Never plays!
Solution: Use branch=true when you don’t need to wait:
my_spell:
  Skills:
  - skill{s=damage-ability;branch=true}
  - sound{s=...}  # Plays immediately
Problem:
spell_a:
  Skills:
  - setvar{var=myvar;val=10}
  - skill:spell_b  # Can spell_b access myvar?
Solution: Use the correct variable scope:
  • skill.var.myvar - Accessible only within current skill tree
  • caster.var.myvar - Accessible across all skills for this caster
Most modules use skill.var for internal state and caster.var for shared state.
From the README:
“Do make sure not to call a modifier within a modifier, you will nuke your server.”
The Modifier Handler isn’t designed for recursion. Don’t nest modifiers inside other modifiers!

Debugging Nested Modules

Technique 1: Message Breadcrumbs

my_spell-cast:
  Skills:
  - message{m="[1] Casting spell"}
  - skill:Spell{...}

my_spell-exec:
  Skills:
  - message{m="[2] Executing spell"}
  - skill:projectile_barrage{...}

my_spell-fire:
  Skills:
  - message{m="[3] Firing projectile"}
Trace execution flow by watching chat messages.

Technique 2: Conditional Debug Mode

my_spell-cast:
  Skills:
  - skill:Spell{id=my_spell;debug=true;...}

my_spell-exec:
  Skills:
  - message{m="Damage: <skill.var.damage>"} ?varequals{var=debug;val=true}
Only shows debug messages when debug=true.

Technique 3: Particle Markers

my_spell-hit:
  Skills:
  - e:p{p=villager_happy;a=10}  # Green particles = hit registered
  - skill:damage-ability{...}
Visual confirmation that each nested level executes.

Best Practices

Keep nesting intentional. Every level should serve a purpose:
  • Spell Handler: Player interaction
  • Your logic: Spell behavior
  • Core modules: Reusable mechanics
  • Callbacks: Conditional behavior

1. Name Consistently

Use a naming convention that shows nesting:
thunderstrike-cast          # Entry point
thunderstrike-exec          # Main logic
thunderstrike-fire          # Projectile spawn
thunderstrike-tick          # Projectile behavior
thunderstrike-hit_entity    # Hit detection

2. Document Complex Nesting

# Execution flow:
# 1. glacial_spikes-cast (Spell Handler)
# 2. glacial_spikes-exec (Fires main projectile)
# 3. glacial_spikes-target (Main projectile spawns sub-projectiles)
# 4. glacial_spikes-fire (Sub-projectiles arc downward)
# 5. glacial_spikes-hit_entity (Damage Core + Knockback Core)

3. Separate Concerns

Each nested level should handle one responsibility:
# Good - each skill has one job
spell-cast:      # Handle player input
spell-exec:      # Fire projectile
spell-hit:       # Deal damage

# Bad - mixing concerns
spell-cast:
  Skills:
  - skill:Spell{...}
  - projectile{...}        # Should be in separate skill
  - damage{...}            # Should be in separate skill

Next Steps

Spell Handler Reference

Deep dive into the core Spell Handler module

Damage Core Reference

Learn about percentage-based damage handling

Build docs developers (and LLMs) love