eq2go/internal/recipes/master_recipe_list.go

397 lines
9.7 KiB
Go

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
}