package recipes import ( "fmt" "strings" ) // NewRecipe creates a new recipe with default values // Converted from C++ Recipe::Recipe constructor func NewRecipe() *Recipe { return &Recipe{ Components: make(map[int8][]int32), Products: make(map[int8]*RecipeProducts), } } // NewRecipeFromRecipe creates a copy of another recipe // Converted from C++ Recipe::Recipe(Recipe *in) copy constructor func NewRecipeFromRecipe(source *Recipe) *Recipe { if source == nil { return NewRecipe() } source.mutex.RLock() defer source.mutex.RUnlock() recipe := &Recipe{ // Core data ID: source.ID, SoeID: source.SoeID, BookID: source.BookID, Name: source.Name, Description: source.Description, BookName: source.BookName, Book: source.Book, Device: source.Device, // Properties Level: source.Level, Tier: source.Tier, Icon: source.Icon, Skill: source.Skill, Technique: source.Technique, Knowledge: source.Knowledge, Classes: source.Classes, DeviceSubType: source.DeviceSubType, // Unknown fields Unknown1: source.Unknown1, Unknown2: source.Unknown2, Unknown3: source.Unknown3, Unknown4: source.Unknown4, // Product information ProductItemID: source.ProductItemID, ProductName: source.ProductName, ProductQty: source.ProductQty, // Component titles PrimaryBuildCompTitle: source.PrimaryBuildCompTitle, Build1CompTitle: source.Build1CompTitle, Build2CompTitle: source.Build2CompTitle, Build3CompTitle: source.Build3CompTitle, Build4CompTitle: source.Build4CompTitle, FuelCompTitle: source.FuelCompTitle, // Component quantities Build1CompQty: source.Build1CompQty, Build2CompQty: source.Build2CompQty, Build3CompQty: source.Build3CompQty, Build4CompQty: source.Build4CompQty, FuelCompQty: source.FuelCompQty, PrimaryCompQty: source.PrimaryCompQty, // Stage information HighestStage: source.HighestStage, // Initialize maps Components: make(map[int8][]int32), Products: make(map[int8]*RecipeProducts), } // Deep copy components for slot, components := range source.Components { recipe.Components[slot] = make([]int32, len(components)) copy(recipe.Components[slot], components) } // Deep copy products for stage, products := range source.Products { if products != nil { recipe.Products[stage] = &RecipeProducts{ ProductID: products.ProductID, ByproductID: products.ByproductID, ProductQty: products.ProductQty, ByproductQty: products.ByproductQty, } } } return recipe } // AddBuildComponent adds a component to a specific slot for this recipe // Converted from C++ Recipe::AddBuildComp func (r *Recipe) AddBuildComponent(itemID int32, slot int8, preferred bool) { r.mutex.Lock() defer r.mutex.Unlock() if slot < 0 || slot >= MaxSlots { return } // Initialize the slot if it doesn't exist if r.Components[slot] == nil { r.Components[slot] = make([]int32, 0) } // Check if the item is already in this slot for _, existingID := range r.Components[slot] { if existingID == itemID { return // Already exists } } // Add the component if preferred { // Add at the beginning for preferred components r.Components[slot] = append([]int32{itemID}, r.Components[slot]...) } else { r.Components[slot] = append(r.Components[slot], itemID) } } // GetTotalBuildComponents returns the total number of component slots used // Converted from C++ Recipe::GetTotalBuildComponents func (r *Recipe) GetTotalBuildComponents() int8 { r.mutex.RLock() defer r.mutex.RUnlock() count := int8(0) for slot := SlotBuild1; slot <= SlotBuild4; slot++ { if len(r.Components[slot]) > 0 { count++ } } return count } // GetItemRequiredQuantity returns the required quantity for a specific item // Converted from C++ Recipe::GetItemRequiredQuantity func (r *Recipe) GetItemRequiredQuantity(itemID int32) int8 { r.mutex.RLock() defer r.mutex.RUnlock() // Check each slot for the item for slot, components := range r.Components { for _, componentID := range components { if componentID == itemID { // Return the quantity based on the slot switch slot { case SlotPrimary: return int8(r.PrimaryCompQty) case SlotBuild1: return int8(r.Build1CompQty) case SlotBuild2: return int8(r.Build2CompQty) case SlotBuild3: return int8(r.Build3CompQty) case SlotBuild4: return int8(r.Build4CompQty) case SlotFuel: return int8(r.FuelCompQty) } } } } return 0 // Not found } // CanUseRecipeByClass checks if a tradeskill class can use this recipe // Converted from C++ Recipe::CanUseRecipeByClass (simplified) func (r *Recipe) CanUseRecipeByClass(classID int8) bool { r.mutex.RLock() defer r.mutex.RUnlock() // Any can use: bit combination of 1+2 (adornments + artisan) if r.Classes < 4 { return true } // Check if the class bit is set return (1< MaxRecipeID { return false } if strings.TrimSpace(r.Name) == "" { return false } if r.Level < MinRecipeLevel || r.Level > MaxRecipeLevel { return false } if r.Tier < MinTier || r.Tier > MaxTier { return false } return true } // GetComponentsBySlot returns the component items for a specific slot func (r *Recipe) GetComponentsBySlot(slot int8) []int32 { r.mutex.RLock() defer r.mutex.RUnlock() if components, exists := r.Components[slot]; exists { // Return a copy to prevent external modification result := make([]int32, len(components)) copy(result, components) return result } return nil } // GetProductsForStage returns the products for a specific crafting stage func (r *Recipe) GetProductsForStage(stage int8) *RecipeProducts { r.mutex.RLock() defer r.mutex.RUnlock() if products, exists := r.Products[stage]; exists { // Return a copy to prevent external modification return &RecipeProducts{ ProductID: products.ProductID, ByproductID: products.ByproductID, ProductQty: products.ProductQty, ByproductQty: products.ByproductQty, } } return nil } // SetProductsForStage sets the products for a specific crafting stage func (r *Recipe) SetProductsForStage(stage int8, products *RecipeProducts) { r.mutex.Lock() defer r.mutex.Unlock() if stage < Stage0 || stage > Stage4 { return } if products == nil { delete(r.Products, stage) return } r.Products[stage] = &RecipeProducts{ ProductID: products.ProductID, ByproductID: products.ByproductID, ProductQty: products.ProductQty, ByproductQty: products.ByproductQty, } } // GetComponentTitleForSlot returns the component title for a specific slot func (r *Recipe) GetComponentTitleForSlot(slot int8) string { r.mutex.RLock() defer r.mutex.RUnlock() switch slot { case SlotPrimary: return r.PrimaryBuildCompTitle case SlotBuild1: return r.Build1CompTitle case SlotBuild2: return r.Build2CompTitle case SlotBuild3: return r.Build3CompTitle case SlotBuild4: return r.Build4CompTitle case SlotFuel: return r.FuelCompTitle default: return "" } } // GetComponentQuantityForSlot returns the component quantity for a specific slot func (r *Recipe) GetComponentQuantityForSlot(slot int8) int16 { r.mutex.RLock() defer r.mutex.RUnlock() switch slot { case SlotPrimary: return r.PrimaryCompQty case SlotBuild1: return r.Build1CompQty case SlotBuild2: return r.Build2CompQty case SlotBuild3: return r.Build3CompQty case SlotBuild4: return r.Build4CompQty case SlotFuel: return r.FuelCompQty default: return 0 } } // GetInfo returns comprehensive information about the recipe func (r *Recipe) GetInfo() map[string]interface{} { r.mutex.RLock() defer r.mutex.RUnlock() info := make(map[string]interface{}) info["id"] = r.ID info["soe_id"] = r.SoeID info["book_id"] = r.BookID info["name"] = r.Name info["description"] = r.Description info["book_name"] = r.BookName info["book"] = r.Book info["device"] = r.Device info["level"] = r.Level info["tier"] = r.Tier info["icon"] = r.Icon info["skill"] = r.Skill info["technique"] = r.Technique info["knowledge"] = r.Knowledge info["classes"] = r.Classes info["product_id"] = r.ProductItemID info["product_name"] = r.ProductName info["product_qty"] = r.ProductQty info["highest_stage"] = r.HighestStage info["total_components"] = r.GetTotalBuildComponents() info["valid"] = r.IsValid() return info } // String returns a string representation of the recipe func (r *Recipe) String() string { r.mutex.RLock() defer r.mutex.RUnlock() return fmt.Sprintf("Recipe{ID: %d, Name: %s, Level: %d, Tier: %d, Skill: %d}", r.ID, r.Name, r.Level, r.Tier, r.Skill) }