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
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 functioneffectsarray 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
Compile the server:
go buildStart the server
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
Extract spell properties into JSON format
Create custom handler function
Register handler in switch statement
Test thoroughly to ensure behavior matches
Remove old hardcoded logic once confirmed working
From JSON to YAML
Analyze custom logic - can it be replaced with standard effects?
Create YAML equivalent if possible
Test YAML version thoroughly
Remove JSON version once YAML is confirmed working
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
Spell Effects Reference - Learn about standard effects
YAML Spell Tutorial - Master YAML spell creation
Custom Spells Directory - See practical examples
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?