eq2go/internal/recipes/recipe.go

362 lines
8.7 KiB
Go

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[int8(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<<classID)&r.Classes != 0
}
// IsValid checks if the recipe has valid data
func (r *Recipe) IsValid() bool {
r.mutex.RLock()
defer r.mutex.RUnlock()
if r.ID < MinRecipeID || r.ID > 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]any {
r.mutex.RLock()
defer r.mutex.RUnlock()
info := make(map[string]any)
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)
}