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:
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:
This calls the cast_servant function in the Go code.
Handler Function Signature
Custom handlers are implemented in lib/spell_engine.go:
๐ JSON Examples
Simple Custom Spell
Complex Admin Spell
Legacy Spell (Aerial 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:
Step 3: Register the Handler
Add your handler to the switch statement in handleCustomSpell:
Step 4: Test Your Spell
Compile the server: go build
Start 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):
After (YAML with standard effects):
๐ฏ Advanced Patterns
Conditional Effects
Multi-Stage Spells
Integration with Game Systems
๐ 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.
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! โจ
{
"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"
}
}
}
// 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)
}
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")
}
}
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)
}
}
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)
}()
}
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)
}