# 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 ```go 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: ```go 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 ```go 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 ```go 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: ```go 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 ```go // 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 ```go // 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: ```go 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: ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go // 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 ```go // 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 ```go // 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 ```go // 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 ```go // 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 ```go // 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 ```go 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 ```go // 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 ```go 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 ```go 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 ```go 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.