397 lines
9.7 KiB
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() StatisticsSnapshot {
|
|
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
|
|
}
|