362 lines
8.7 KiB
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)
|
|
}
|