package recipes import ( "strings" "sync" ) // MasterRecipeList manages all recipes in the system // Converted from C++ MasterRecipeList class type MasterRecipeList struct { recipes map[int32]*Recipe // Recipe ID -> Recipe recipesCRC map[int32]*Recipe // SOE CRC ID -> Recipe nameIndex map[string]*Recipe // Lowercase name -> Recipe bookIndex map[string][]*Recipe // Lowercase book name -> Recipes skillIndex map[int32][]*Recipe // Skill ID -> Recipes tierIndex map[int8][]*Recipe // Tier -> Recipes mutex sync.RWMutex stats *Statistics } // NewMasterRecipeList creates a new master recipe list // Converted from C++ MasterRecipeList::MasterRecipeList constructor func NewMasterRecipeList() *MasterRecipeList { return &MasterRecipeList{ recipes: make(map[int32]*Recipe), recipesCRC: make(map[int32]*Recipe), nameIndex: make(map[string]*Recipe), bookIndex: make(map[string][]*Recipe), skillIndex: make(map[int32][]*Recipe), tierIndex: make(map[int8][]*Recipe), stats: NewStatistics(), } } // AddRecipe adds a recipe to the master list // Converted from C++ MasterRecipeList::AddRecipe func (mrl *MasterRecipeList) AddRecipe(recipe *Recipe) bool { if recipe == nil || !recipe.IsValid() { return false } mrl.mutex.Lock() defer mrl.mutex.Unlock() // Check for duplicate ID if _, exists := mrl.recipes[recipe.ID]; exists { return false } // Add to main map mrl.recipes[recipe.ID] = recipe // Add to CRC map if SOE ID is set if recipe.SoeID != 0 { mrl.recipesCRC[recipe.SoeID] = recipe } // Add to name index nameLower := strings.ToLower(strings.TrimSpace(recipe.Name)) if nameLower != "" { mrl.nameIndex[nameLower] = recipe } // Add to book index bookLower := strings.ToLower(strings.TrimSpace(recipe.Book)) if bookLower != "" { mrl.bookIndex[bookLower] = append(mrl.bookIndex[bookLower], recipe) } // Add to skill index if recipe.Skill != 0 { mrl.skillIndex[recipe.Skill] = append(mrl.skillIndex[recipe.Skill], recipe) } // Add to tier index if recipe.Tier > 0 { mrl.tierIndex[recipe.Tier] = append(mrl.tierIndex[recipe.Tier], recipe) } // Update statistics mrl.stats.TotalRecipes++ mrl.stats.RecipesByTier[recipe.Tier]++ mrl.stats.RecipesBySkill[recipe.Skill]++ return true } // GetRecipe retrieves a recipe by ID // Converted from C++ MasterRecipeList::GetRecipe func (mrl *MasterRecipeList) GetRecipe(recipeID int32) *Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() if recipe, exists := mrl.recipes[recipeID]; exists { return recipe } return nil } // GetRecipeByCRC retrieves a recipe by SOE CRC ID // Converted from C++ MasterRecipeList::GetRecipeByCRC func (mrl *MasterRecipeList) GetRecipeByCRC(recipeCRC int32) *Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() if recipe, exists := mrl.recipesCRC[recipeCRC]; exists { return recipe } return nil } // GetRecipeByName retrieves a recipe by name (case-insensitive) // Converted from C++ MasterRecipeList::GetRecipeByName func (mrl *MasterRecipeList) GetRecipeByName(name string) *Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() nameLower := strings.ToLower(strings.TrimSpace(name)) if recipe, exists := mrl.nameIndex[nameLower]; exists { return recipe } return nil } // GetRecipesByBook retrieves all recipes for a given book name // Converted from C++ MasterRecipeList::GetRecipes func (mrl *MasterRecipeList) GetRecipesByBook(bookName string) []*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() bookLower := strings.ToLower(strings.TrimSpace(bookName)) if recipes, exists := mrl.bookIndex[bookLower]; exists { // Return a copy to prevent external modification result := make([]*Recipe, len(recipes)) copy(result, recipes) return result } return nil } // GetRecipesBySkill retrieves all recipes for a given skill func (mrl *MasterRecipeList) GetRecipesBySkill(skillID int32) []*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() if recipes, exists := mrl.skillIndex[skillID]; exists { // Return a copy to prevent external modification result := make([]*Recipe, len(recipes)) copy(result, recipes) return result } return nil } // GetRecipesByTier retrieves all recipes for a given tier func (mrl *MasterRecipeList) GetRecipesByTier(tier int8) []*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() if recipes, exists := mrl.tierIndex[tier]; exists { // Return a copy to prevent external modification result := make([]*Recipe, len(recipes)) copy(result, recipes) return result } return nil } // GetRecipesByClass retrieves all recipes that can be used by a tradeskill class func (mrl *MasterRecipeList) GetRecipesByClass(classID int8) []*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() var result []*Recipe for _, recipe := range mrl.recipes { if recipe.CanUseRecipeByClass(classID) { result = append(result, recipe) } } return result } // GetRecipesByLevel retrieves all recipes within a level range func (mrl *MasterRecipeList) GetRecipesByLevel(minLevel, maxLevel int8) []*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() mrl.stats.IncrementRecipeLookups() var result []*Recipe for _, recipe := range mrl.recipes { if recipe.Level >= minLevel && recipe.Level <= maxLevel { result = append(result, recipe) } } return result } // RemoveRecipe removes a recipe from the master list func (mrl *MasterRecipeList) RemoveRecipe(recipeID int32) bool { mrl.mutex.Lock() defer mrl.mutex.Unlock() recipe, exists := mrl.recipes[recipeID] if !exists { return false } // Remove from main map delete(mrl.recipes, recipeID) // Remove from CRC map if recipe.SoeID != 0 { delete(mrl.recipesCRC, recipe.SoeID) } // Remove from name index nameLower := strings.ToLower(strings.TrimSpace(recipe.Name)) if nameLower != "" { delete(mrl.nameIndex, nameLower) } // Remove from book index bookLower := strings.ToLower(strings.TrimSpace(recipe.Book)) if bookLower != "" { if recipes, exists := mrl.bookIndex[bookLower]; exists { for i, r := range recipes { if r.ID == recipeID { mrl.bookIndex[bookLower] = append(recipes[:i], recipes[i+1:]...) break } } if len(mrl.bookIndex[bookLower]) == 0 { delete(mrl.bookIndex, bookLower) } } } // Remove from skill index if recipe.Skill != 0 { if recipes, exists := mrl.skillIndex[recipe.Skill]; exists { for i, r := range recipes { if r.ID == recipeID { mrl.skillIndex[recipe.Skill] = append(recipes[:i], recipes[i+1:]...) break } } if len(mrl.skillIndex[recipe.Skill]) == 0 { delete(mrl.skillIndex, recipe.Skill) } } } // Remove from tier index if recipe.Tier > 0 { if recipes, exists := mrl.tierIndex[recipe.Tier]; exists { for i, r := range recipes { if r.ID == recipeID { mrl.tierIndex[recipe.Tier] = append(recipes[:i], recipes[i+1:]...) break } } if len(mrl.tierIndex[recipe.Tier]) == 0 { delete(mrl.tierIndex, recipe.Tier) } } } // Update statistics mrl.stats.TotalRecipes-- mrl.stats.RecipesByTier[recipe.Tier]-- mrl.stats.RecipesBySkill[recipe.Skill]-- return true } // ClearRecipes removes all recipes from the master list // Converted from C++ MasterRecipeList::ClearRecipes func (mrl *MasterRecipeList) ClearRecipes() { mrl.mutex.Lock() defer mrl.mutex.Unlock() mrl.recipes = make(map[int32]*Recipe) mrl.recipesCRC = make(map[int32]*Recipe) mrl.nameIndex = make(map[string]*Recipe) mrl.bookIndex = make(map[string][]*Recipe) mrl.skillIndex = make(map[int32][]*Recipe) mrl.tierIndex = make(map[int8][]*Recipe) // Reset statistics mrl.stats.TotalRecipes = 0 mrl.stats.RecipesByTier = make(map[int8]int32) mrl.stats.RecipesBySkill = make(map[int32]int32) } // Size returns the total number of recipes // Converted from C++ MasterRecipeList::Size func (mrl *MasterRecipeList) Size() int32 { mrl.mutex.RLock() defer mrl.mutex.RUnlock() return int32(len(mrl.recipes)) } // GetAllRecipes returns all recipes (use with caution for large lists) func (mrl *MasterRecipeList) GetAllRecipes() map[int32]*Recipe { mrl.mutex.RLock() defer mrl.mutex.RUnlock() // Return a copy to prevent external modification result := make(map[int32]*Recipe) for id, recipe := range mrl.recipes { result[id] = recipe } return result } // GetRecipeIDs returns all recipe IDs func (mrl *MasterRecipeList) GetRecipeIDs() []int32 { mrl.mutex.RLock() defer mrl.mutex.RUnlock() result := make([]int32, 0, len(mrl.recipes)) for id := range mrl.recipes { result = append(result, id) } return result } // GetStatistics returns a snapshot of the current statistics func (mrl *MasterRecipeList) GetStatistics() Statistics { return mrl.stats.GetSnapshot() } // GetSkills returns all skills that have recipes func (mrl *MasterRecipeList) GetSkills() []int32 { mrl.mutex.RLock() defer mrl.mutex.RUnlock() result := make([]int32, 0, len(mrl.skillIndex)) for skill := range mrl.skillIndex { result = append(result, skill) } return result } // GetTiers returns all tiers that have recipes func (mrl *MasterRecipeList) GetTiers() []int8 { mrl.mutex.RLock() defer mrl.mutex.RUnlock() result := make([]int8, 0, len(mrl.tierIndex)) for tier := range mrl.tierIndex { result = append(result, tier) } return result } // GetBookNames returns all book names that have recipes func (mrl *MasterRecipeList) GetBookNames() []string { mrl.mutex.RLock() defer mrl.mutex.RUnlock() result := make([]string, 0, len(mrl.bookIndex)) for book := range mrl.bookIndex { result = append(result, book) } return result }