..
2025-07-31 11:22:03 -05:00
2025-08-05 21:02:00 -05:00
2025-08-05 21:02:00 -05:00
2025-08-06 22:13:32 -05:00
2025-08-05 21:02:00 -05:00
2025-07-31 11:22:03 -05:00
2025-08-05 21:02:00 -05:00
2025-08-05 21:02:00 -05:00
2025-08-05 21:02:00 -05:00
2025-08-05 21:02:00 -05:00

Recipe System

Complete tradeskill recipe management system for EverQuest II, converted from C++ EQ2EMu codebase. Handles recipe definitions, player recipe knowledge, crafting components, and database persistence with modern Go concurrency patterns.

Overview

The recipe system manages all aspects of tradeskill crafting in EQ2:

  • Recipe Management: Master recipe lists with complex component relationships
  • Player Recipe Knowledge: Individual player recipe collections and progress tracking
  • Recipe Books: Tradeskill recipe book items and learning mechanics
  • Database Integration: Complete SQLite persistence with efficient loading
  • Component System: Multi-slot component requirements (primary, fuel, build slots)
  • Crafting Stages: 5-stage crafting progression with products and byproducts
  • Tradeskill Classes: Class-based recipe access control and validation
  • Integration Interfaces: Seamless integration with player, item, client, and event systems

Core Components

Recipe Data Structure

type Recipe struct {
    // Core recipe identification
    ID          int32  // Unique recipe ID
    SoeID       int32  // SOE recipe CRC ID
    Name        string // Recipe display name
    Description string // Recipe description

    // Requirements and properties
    Level          int8   // Required level
    Tier           int8   // Recipe tier (1-10)
    Skill          int32  // Required tradeskill ID
    Technique      int32  // Technique requirement
    Knowledge      int32  // Knowledge requirement
    Classes        int32  // Tradeskill class bitmask

    // Product information
    ProductItemID int32  // Resulting item ID
    ProductName   string // Product display name
    ProductQty    int8   // Quantity produced

    // Component titles and quantities (6 slots)
    PrimaryBuildCompTitle string // Primary component name
    Build1CompTitle       string // Build slot 1 name
    Build2CompTitle       string // Build slot 2 name
    Build3CompTitle       string // Build slot 3 name
    Build4CompTitle       string // Build slot 4 name
    FuelCompTitle         string // Fuel component name

    // Components map: slot -> list of valid item IDs
    // Slots: 0=primary, 1-4=build slots, 5=fuel
    Components map[int8][]int32

    // Products map: stage -> products/byproducts (5 stages)
    Products map[int8]*RecipeProducts

    // Player progression
    HighestStage int8 // Highest completed stage for player recipes
}

Component Slots

The recipe system uses 6 component slots:

  • Slot 0 (Primary): Primary crafting material
  • Slot 1-4 (Build): Secondary build components
  • Slot 5 (Fuel): Fuel component for crafting device

Each slot can contain multiple valid item IDs, providing flexibility in component selection.

Crafting Stages

Recipes support 5 crafting stages (0-4), each with potential products and byproducts:

type RecipeProducts struct {
    ProductID    int32 // Main product item ID
    ProductQty   int8  // Product quantity
    ByproductID  int32 // Byproduct item ID
    ByproductQty int8  // Byproduct quantity
}

Master Recipe Lists

type MasterRecipeList struct {
    recipes map[int32]*Recipe // Recipe ID -> Recipe
    mutex   sync.RWMutex      // Thread-safe access
}

type MasterRecipeBookList struct {
    recipeBooks map[int32]*Recipe // Book ID -> Recipe
    mutex       sync.RWMutex      // Thread-safe access
}

Player Recipe Collections

type PlayerRecipeList struct {
    recipes map[int32]*Recipe // Player's known recipes
    mutex   sync.RWMutex      // Thread-safe access
}

type PlayerRecipeBookList struct {
    recipeBooks map[int32]*Recipe // Player's recipe books
    mutex       sync.RWMutex      // Thread-safe access
}

Database Integration

RecipeManager

High-level recipe system management with complete database integration:

type RecipeManager struct {
    db                    *database.DB
    masterRecipeList      *MasterRecipeList
    masterRecipeBookList  *MasterRecipeBookList
    loadedRecipes         map[int32]*Recipe
    loadedRecipeBooks     map[int32]*Recipe
    statisticsEnabled     bool
    stats                 RecipeManagerStats
}

Key Operations

Loading Recipes

// Load all recipes from database with complex component relationships
manager.LoadRecipes()

// Load all recipe books
manager.LoadRecipeBooks()

// Load player-specific recipes
manager.LoadPlayerRecipes(playerRecipeList, characterID)

// Load player recipe books
manager.LoadPlayerRecipeBooks(playerRecipeBookList, characterID)

Saving Recipe Progress

// Save new recipe for player
manager.SavePlayerRecipe(characterID, recipeID)

// Save recipe book for player
manager.SavePlayerRecipeBook(characterID, recipebookID)

// Update recipe progress
manager.UpdatePlayerRecipe(characterID, recipeID, highestStage)

Database Schema

The system integrates with these key database tables:

  • recipe: Master recipe definitions
  • recipe_comp_list: Component list definitions
  • recipe_comp_list_item: Individual component items
  • recipe_secondary_comp: Secondary component mappings
  • character_recipes: Player recipe knowledge
  • character_recipe_books: Player recipe book ownership
  • items: Item definitions and recipe books

Integration Interfaces

RecipeSystemAdapter

Primary integration interface for external systems:

type RecipeSystemAdapter interface {
    // Player Integration
    GetPlayerRecipeList(characterID int32) *PlayerRecipeList
    LoadPlayerRecipes(characterID int32) error

    // Recipe Management
    GetRecipe(recipeID int32) *Recipe
    ValidateRecipe(recipe *Recipe) bool

    // Progress Tracking
    UpdateRecipeProgress(characterID int32, recipeID int32, stage int8) error
    GetRecipeProgress(characterID int32, recipeID int32) int8
}

Database Adapter

Abstracts database operations for testing and multiple database support:

type DatabaseRecipeAdapter interface {
    LoadAllRecipes() ([]*Recipe, error)
    LoadPlayerRecipes(characterID int32) ([]*Recipe, error)
    SavePlayerRecipe(characterID int32, recipeID int32) error
    UpdatePlayerRecipe(characterID int32, recipeID int32, highestStage int8) error
}

Player Integration

type PlayerRecipeAdapter interface {
    CanPlayerLearnRecipe(characterID int32, recipeID int32) bool
    GetPlayerTradeskillLevel(characterID int32, skillID int32) int32
    AwardTradeskillExperience(characterID int32, skillID int32, experience int32) error
}

Item Integration

type ItemRecipeAdapter interface {
    ValidateRecipeComponents(recipeID int32) bool
    PlayerHasComponents(characterID int32, recipeID int32) bool
    ConsumeRecipeComponents(characterID int32, recipeID int32) error
    AwardRecipeProduct(characterID int32, itemID int32, quantity int8) error
}

Client Integration

type ClientRecipeAdapter interface {
    SendRecipeList(characterID int32) error
    SendRecipeLearned(characterID int32, recipeID int32) error
    SendTradeskillWindow(characterID int32, deviceID int32) error
    SendCraftingResults(characterID int32, success bool, itemID int32, quantity int8) error
}

Event Integration

type EventRecipeAdapter interface {
    OnRecipeLearned(characterID int32, recipeID int32) error
    OnRecipeCrafted(characterID int32, recipeID int32, success bool) error
    CheckCraftingAchievements(characterID int32, recipeID int32) error
}

Usage Examples

Basic Recipe Management

// Create recipe manager
db, _ := database.Open("world.db")
manager := NewRecipeManager(db)

// Load all recipes and recipe books
manager.LoadRecipes()
manager.LoadRecipeBooks()

// Get a specific recipe
recipe := manager.GetRecipe(12345)
if recipe != nil {
    fmt.Printf("Recipe: %s (Level %d, Tier %d)\n",
        recipe.Name, recipe.Level, recipe.Tier)
}

// Check recipe validation
if recipe.IsValid() {
    fmt.Println("Recipe is valid")
}

Player Recipe Operations

// Load player recipes
playerRecipes := NewPlayerRecipeList()
manager.LoadPlayerRecipes(playerRecipes, characterID)

// Check if player knows a recipe
if playerRecipes.HasRecipe(recipeID) {
    fmt.Println("Player knows this recipe")
}

// Learn a new recipe
recipe := manager.GetRecipe(recipeID)
if recipe != nil {
    playerCopy := NewRecipeFromRecipe(recipe)
    playerRecipes.AddRecipe(playerCopy)
    manager.SavePlayerRecipe(characterID, recipeID)
}

Recipe Component Analysis

// Get components for each slot
for slot := int8(0); slot < 6; slot++ {
    components := recipe.GetComponentsBySlot(slot)
    title := recipe.GetComponentTitleForSlot(slot)
    quantity := recipe.GetComponentQuantityForSlot(slot)

    fmt.Printf("Slot %d (%s): %d needed\n", slot, title, quantity)
    for _, itemID := range components {
        fmt.Printf("  - Item ID: %d\n", itemID)
    }
}

Crafting Stage Processing

// Process each crafting stage
for stage := int8(0); stage < 5; stage++ {
    products := recipe.GetProductsForStage(stage)
    if products != nil {
        fmt.Printf("Stage %d produces:\n", stage)
        fmt.Printf("  Product: %d (qty: %d)\n",
            products.ProductID, products.ProductQty)
        if products.ByproductID > 0 {
            fmt.Printf("  Byproduct: %d (qty: %d)\n",
                products.ByproductID, products.ByproductQty)
        }
    }
}

Integration with Full System

// Create system dependencies
deps := &RecipeSystemDependencies{
    Database: &DatabaseRecipeAdapterImpl{},
    Player:   &PlayerRecipeAdapterImpl{},
    Item:     &ItemRecipeAdapterImpl{},
    Client:   &ClientRecipeAdapterImpl{},
    Event:    &EventRecipeAdapterImpl{},
    Crafting: &CraftingRecipeAdapterImpl{},
}

// Create integrated recipe system
adapter := NewRecipeManagerAdapter(db, deps)
adapter.Initialize()

// Handle recipe learning workflow
err := adapter.PlayerLearnRecipe(characterID, recipeID)
if err != nil {
    fmt.Printf("Failed to learn recipe: %v\n", err)
}

// Handle recipe book acquisition workflow
err = adapter.PlayerObtainRecipeBook(characterID, bookID)
if err != nil {
    fmt.Printf("Failed to obtain recipe book: %v\n", err)
}

Tradeskill Classes

The system supports all EQ2 tradeskill classes with bitmask-based access control:

Base Classes

  • Provisioner (food and drink)
  • Woodworker (wooden items, bows, arrows)
  • Carpenter (furniture and housing items)
  • Outfitter (light and medium armor)
  • Armorer (heavy armor and shields)
  • Weaponsmith (metal weapons)
  • Tailor (cloth armor and bags)
  • Scholar (spells and combat arts)
  • Jeweler (jewelry)
  • Sage (advanced spells)
  • Alchemist (potions and poisons)
  • Craftsman (general crafting)
  • Tinkerer (tinkered items)

Class Validation

// Check if a tradeskill class can use a recipe
canUse := recipe.CanUseRecipeByClass(classID)

// Special handling for "any class" recipes (adornments + artisan)
if recipe.Classes < 4 {
    // Any class can use this recipe
}

Crafting Devices

Recipes require specific crafting devices:

  • Stove & Keg: Provisioning recipes
  • Forge: Weaponsmithing and armoring
  • Sewing Table & Mannequin: Tailoring and outfitting
  • Woodworking Table: Woodworking and carpentry
  • Chemistry Table: Alchemy
  • Jeweler's Table: Jewelcrafting
  • Loom & Spinning Wheel: Advanced tailoring
  • Engraved Desk: Scribing and sage recipes
  • Work Bench: Tinkering

Statistics and Monitoring

RecipeManagerStats

type RecipeManagerStats struct {
    TotalRecipesLoaded     int32
    TotalRecipeBooksLoaded int32
    PlayersWithRecipes     int32
    LoadOperations         int32
    SaveOperations         int32
}

// Get current statistics
stats := manager.GetStatistics()
fmt.Printf("Loaded %d recipes, %d recipe books\n",
    stats.TotalRecipesLoaded, stats.TotalRecipeBooksLoaded)

Validation

// Comprehensive system validation
issues := manager.Validate()
for _, issue := range issues {
    fmt.Printf("Validation issue: %s\n", issue)
}

// Recipe-specific validation
if !recipe.IsValid() {
    fmt.Println("Recipe has invalid data")
}

Thread Safety

All recipe system components are fully thread-safe:

  • RWMutex Protection: All maps and collections use read-write mutexes
  • Atomic Operations: Statistics counters use atomic operations where appropriate
  • Safe Copying: Recipe copying creates deep copies of all nested data
  • Concurrent Access: Multiple goroutines can safely read recipe data simultaneously
  • Write Synchronization: All write operations are properly synchronized

Performance Considerations

Efficient Loading

  • Batch Loading: All recipes loaded in single database query with complex joins
  • Component Resolution: Components loaded separately and linked to recipes
  • Memory Management: Recipes cached in memory for fast access
  • Statistics Tracking: Optional statistics collection for performance monitoring

Memory Usage

  • Component Maps: Pre-allocated component arrays for each recipe
  • Product Arrays: Fixed-size arrays for 5 crafting stages
  • Player Copies: Player recipe instances are copies, not references
  • Cleanup: Proper cleanup of database connections and prepared statements

Error Handling

Comprehensive Error Types

var (
    ErrRecipeNotFound        = errors.New("recipe not found")
    ErrRecipeBookNotFound    = errors.New("recipe book not found")
    ErrInvalidRecipeID       = errors.New("invalid recipe ID")
    ErrDuplicateRecipe       = errors.New("duplicate recipe ID")
    ErrMissingComponents     = errors.New("missing required components")
    ErrInsufficientSkill     = errors.New("insufficient skill level")
    ErrWrongTradeskillClass  = errors.New("wrong tradeskill class")
    ErrCannotLearnRecipe     = errors.New("cannot learn recipe")
    ErrCannotUseRecipeBook   = errors.New("cannot use recipe book")
)

Error Propagation

All database operations return detailed errors that can be handled appropriately by calling systems.

Database Schema Compatibility

C++ EQ2EMu Compatibility

The Go implementation maintains full compatibility with the existing C++ EQ2EMu database schema:

  • Recipe Table: Direct mapping of all C++ Recipe fields
  • Component System: Preserves complex component relationships
  • Player Data: Compatible with existing character recipe storage
  • Query Optimization: Uses same optimized queries as C++ version

Migration Support

The system can seamlessly work with existing EQ2EMu databases without requiring schema changes or data migration.

Future Enhancements

Areas marked for future implementation:

  • Lua Integration: Recipe validation and custom logic via Lua scripts
  • Advanced Crafting: Rare material handling and advanced crafting mechanics
  • Recipe Discovery: Dynamic recipe discovery and experimentation
  • Guild Recipes: Guild-specific recipe sharing and management
  • Seasonal Recipes: Time-based recipe availability
  • Recipe Sets: Collection-based achievements and bonuses

Testing

Unit Tests

func TestRecipeValidation(t *testing.T) {
    recipe := NewRecipe()
    recipe.ID = 12345
    recipe.Name = "Test Recipe"
    recipe.Level = 50
    recipe.Tier = 5

    assert.True(t, recipe.IsValid())
}

func TestPlayerRecipeOperations(t *testing.T) {
    playerRecipes := NewPlayerRecipeList()
    recipe := NewRecipe()
    recipe.ID = 12345

    assert.True(t, playerRecipes.AddRecipe(recipe))
    assert.True(t, playerRecipes.HasRecipe(12345))
    assert.Equal(t, 1, playerRecipes.Size())
}

Integration Tests

func TestDatabaseIntegration(t *testing.T) {
    db := setupTestDatabase()
    manager := NewRecipeManager(db)

    err := manager.LoadRecipes()
    assert.NoError(t, err)

    recipes, books := manager.Size()
    assert.Greater(t, recipes, int32(0))
}

This recipe system provides a complete, thread-safe, and efficient implementation of EverQuest II tradeskill recipes with modern Go patterns while maintaining compatibility with the existing C++ EQ2EMu architecture.