JSON Spell Guide

This guide covers working with JSON spell definitions in OpenRoads, which are primarily used for hardcoded spells that require custom Go code logic.

πŸ“š Table of Contents

🎯 When to Use JSON Spells

Use JSON spells when you need:

βœ… Custom Logic

  • Complex spell behaviors that can't be achieved with standard effects

  • Integration with external systems

  • Dynamic spell behavior based on game state

  • Advanced AI or procedural generation

βœ… Performance Critical Spells

  • Spells that need to run very frequently

  • Spells with complex calculations

  • Spells that interact with multiple game systems

βœ… Legacy Support

  • Existing hardcoded spells (like Aerial Servant)

  • Maintaining backward compatibility

  • Gradual migration from hardcoded to YAML

❌ Don't Use JSON For

  • Simple damage/heal/teleport spells (use YAML instead)

  • Standard spell effects (YAML is easier to maintain)

  • Spells that non-programmers need to modify

πŸ“Š JSON vs YAML Comparison

Feature
YAML
JSON

Ease of Use

βœ… Very Easy

⚠️ Moderate

Human Readable

βœ… Excellent

⚠️ Good

Custom Logic

❌ Limited

βœ… Full Control

Performance

⚠️ Good

βœ… Excellent

Maintenance

βœ… Easy

⚠️ Requires Programming

Hot Reload

βœ… Planned

⚠️ Requires Restart

Non-Programmer Friendly

βœ… Yes

❌ No

πŸ—οΈ JSON Spell Structure

JSON spells are defined in world/spells.json:

{
  "spells": {
    "spell_name": {
      "name": "Display Name",
      "description": "Spell description",
      "element_required": "fire",
      "min_spell_level": 10,
      "min_phys_level": 0,
      "sp_cost": 15,
      "cast_time": 0,
      "cooldown": 3,
      "target_type": "enemy",
      "required_items": [],
      "effects": [],
      "messages": {
        "success": "Your spell succeeds!",
        "failure_element": "Only fire mages can cast this."
      },
      "restrictions": {
        "combat_only": false,
        "non_combat_only": false
      },
      "custom_handler": "function_name"
    }
  }
}

Key Differences from YAML

  • Uses "custom_handler" field to specify Go function

  • effects array is usually empty (handled by custom code)

  • All strings must be in double quotes

  • No comments allowed

  • Stricter syntax requirements

πŸ”§ Custom Handlers

The custom_handler field tells the system which Go function to call:

{
  "spells": {
    "servant": {
      "name": "Aerial Servant",
      "custom_handler": "cast_servant"
    }
  }
}

This calls the cast_servant function in the Go code.

Handler Function Signature

Custom handlers are implemented in lib/spell_engine.go:

func handleCustomSpell(s *Session, player *Player, spell SpellDefinition, args []string) {
    switch strings.ToLower(spell.CustomHandler) {
    case "cast_servant":
        CastServant(s, player, args[0])
    case "your_custom_handler":
        YourCustomFunction(s, player, spell, args)
    default:
        log.Printf("SPELL: Unknown custom handler: %s", spell.CustomHandler)
        fmt.Fprint(s.Conn, "This spell is not implemented.\r\n")
    }
}

πŸ“ JSON Examples

Simple Custom Spell

{
  "spells": {
    "identify": {
      "name": "Identify",
      "description": "Reveals detailed information about an item",
      "element_required": "",
      "min_spell_level": 8,
      "sp_cost": 5,
      "cast_time": 2,
      "cooldown": 0,
      "target_type": "none",
      "required_items": [],
      "effects": [],
      "messages": {
        "success": "You sense the magical properties of the item!",
        "failure_level": "You need spell level %d or higher. (Current: %d)",
        "failure_sp": "Not enough spell points. (Need: %d, Have: %d)"
      },
      "restrictions": {
        "non_combat_only": true
      },
      "custom_handler": "cast_identify"
    }
  }
}

Complex Admin Spell

{
  "spells": {
    "admin_teleport": {
      "name": "Admin Teleport",
      "description": "Admin-only instant teleportation",
      "element_required": "",
      "min_spell_level": 1,
      "sp_cost": 0,
      "cast_time": 0,
      "cooldown": 0,
      "target_type": "room",
      "required_items": [],
      "effects": [],
      "messages": {
        "success": "Admin teleported to room %s!",
        "failure_level": "Admin access required.",
        "failure_target": "Specify target room."
      },
      "restrictions": {},
      "custom_handler": "admin_teleport"
    }
  }
}

Legacy Spell (Aerial Servant)

{
  "spells": {
    "servant": {
      "name": "Aerial Servant",
      "description": "Summons an aerial servant to a target room",
      "element_required": "air",
      "min_spell_level": 26,
      "sp_cost": 20,
      "cast_time": 0,
      "cooldown": 0,
      "target_type": "room",
      "required_items": [],
      "effects": [],
      "messages": {
        "success": "You successfully summon an aerial servant in room %s!",
        "failure_element": "Only air mages can summon aerial servants.",
        "failure_level": "You need spell level %d or higher. (Current: %d)",
        "failure_sp": "You don't have enough spell points. (Need: %d, Have: %d)",
        "failure_target": "Cast servant where? (Usage: cast servant <room_number>)"
      },
      "restrictions": {
        "max_instances": 1
      },
      "custom_handler": "cast_servant"
    }
  }
}

πŸ› οΈ Creating Custom Handlers

Step 1: Define Your Spell in JSON

Add your spell to world/spells.json with a unique custom_handler name.

Step 2: Implement the Handler Function

Create your custom function in the appropriate Go file:

// Example: Custom identify spell
func CastIdentify(s *Session, player *Player, args []string) {
    // Custom logic here
    if len(player.Inventory) == 0 {
        fmt.Fprint(s.Conn, "You have no items to identify.\r\n")
        return
    }
    
    // Deduct SP (if not already handled by validation)
    player.CombatStats.CurrentSP -= 5
    
    // Custom identify logic
    for i, item := range player.Inventory {
        if name, ok := item["name"].(string); ok {
            fmt.Fprintf(s.Conn, "Item %d: %s\r\n", i+1, name)
            // Add more detailed item information
        }
    }
    
    // Save player state
    UpdatePlayer(player.ClientUsername, player)
}

Step 3: Register the Handler

Add your handler to the switch statement in handleCustomSpell:

func handleCustomSpell(s *Session, player *Player, spell SpellDefinition, args []string) {
    switch strings.ToLower(spell.CustomHandler) {
    case "cast_servant":
        CastServant(s, player, args[0])
    case "cast_identify":  // Add your handler here
        CastIdentify(s, player, args)
    default:
        log.Printf("SPELL: Unknown custom handler: %s", spell.CustomHandler)
        fmt.Fprint(s.Conn, "This spell is not implemented.\r\n")
    }
}

Step 4: Test Your Spell

  1. Compile the server: go build

  2. Start the server

  3. Test your spell in-game

πŸ“‹ Best Practices

JSON Formatting

  • Use proper indentation (2 or 4 spaces)

  • Validate JSON syntax before committing

  • Use meaningful handler names

  • Include comprehensive messages

Code Organization

  • Keep custom handlers in appropriate files

  • Use descriptive function names

  • Add proper error handling

  • Include logging for debugging

Performance

  • Minimize database operations in handlers

  • Cache frequently accessed data

  • Use efficient algorithms

  • Consider concurrency implications

Security

  • Validate all input parameters

  • Check player permissions

  • Prevent resource exhaustion

  • Sanitize user input

πŸ”„ Migration Guide

From Hardcoded to JSON

  1. Extract spell properties into JSON format

  2. Create custom handler function

  3. Register handler in switch statement

  4. Test thoroughly to ensure behavior matches

  5. Remove old hardcoded logic once confirmed working

From JSON to YAML

  1. Analyze custom logic - can it be replaced with standard effects?

  2. Create YAML equivalent if possible

  3. Test YAML version thoroughly

  4. Remove JSON version once YAML is confirmed working

  5. Update documentation to reflect changes

Example Migration: Simple Damage Spell

Before (JSON with custom handler):

{
  "magic_missile": {
    "name": "Magic Missile",
    "custom_handler": "cast_magic_missile"
  }
}

After (YAML with standard effects):

name: "Magic Missile"
description: "Launches a magical projectile"
sp_cost: 8
target_type: "enemy"
effects:
  - type: "damage"
    damage_min: 12
    damage_max: 18
    damage_type: "magic"

🎯 Advanced Patterns

Conditional Effects

func CastConditionalSpell(s *Session, player *Player, args []string) {
    if player.CombatState.InCombat {
        // Different behavior in combat
        dealDamage(s, player, args, 50)
    } else {
        // Different behavior out of combat
        healPlayer(s, player, 25)
    }
}

Multi-Stage Spells

func CastChanneledSpell(s *Session, player *Player, args []string) {
    // Stage 1: Start channeling
    fmt.Fprint(s.Conn, "You begin channeling...\r\n")
    
    // Stage 2: Continue in background goroutine
    go func() {
        time.Sleep(5 * time.Second)
        // Stage 3: Complete spell
        completeChanneledSpell(s, player, args)
    }()
}

Integration with Game Systems

func CastSystemIntegrationSpell(s *Session, player *Player, args []string) {
    // Interact with world system
    room := gameWorld[player.Location]
    
    // Interact with enemy system
    enemies := room.Enemies
    
    // Interact with player system
    otherPlayers := getPlayersInRoom(player.Location)
    
    // Custom logic combining multiple systems
    performComplexSpellLogic(room, enemies, otherPlayers)
}

πŸŽ“ Conclusion

JSON spells provide powerful customization capabilities for complex spell behaviors that can't be achieved with standard YAML effects. Use them wisely for spells that truly need custom logic, and prefer YAML for standard spell effects.

Next Steps

  1. Spell Effects Reference - Learn about standard effects

  2. YAML Spell Tutorial - Master YAML spell creation

  3. Custom Spells Directory - See practical examples

  4. Experiment - Try creating your own custom handlers!

Remember: With great power comes great responsibility. JSON spells require programming knowledge and careful testing. When in doubt, try to achieve your goals with YAML first! ✨

Last updated

Was this helpful?