package action import ( "path/filepath" "gopkg.in/yaml.v3" ) type ConsumeEntry struct { Items []string `yaml:"items"` Quantity int `yaml:"quantity"` Byproducts []string `yaml:"byproducts"` } type RecipeDef struct { ID string `yaml:"id"` Type string `yaml:"type"` Skill string `yaml:"skill"` Level int `yaml:"level"` XP int `yaml:"xp"` Station []string `yaml:"station"` Tool string `yaml:"tool"` Consume []ConsumeEntry `yaml:"consume"` Output string `yaml:"output"` OutputQty int `yaml:"output_qty"` Fail string `yaml:"fail"` Message string `yaml:"message"` FailMessage string `yaml:"fail_message"` Wait float64 `yaml:"wait"` Success *SuccessFormula `yaml:"success"` } type RecipeStore struct { dataDir string cache map[string][]RecipeDef } func NewRecipeStore(dataDir string) *RecipeStore { return &RecipeStore{dataDir: dataDir, cache: make(map[string][]RecipeDef)} } func (s *RecipeStore) LoadAll() ([]RecipeDef, error) { return s.LoadByType("") } func (s *RecipeStore) LoadByType(recipeType string) ([]RecipeDef, error) { if recipeType == "" { recipeType = "_all" } if cached, ok := s.cache[recipeType]; ok { result := make([]RecipeDef, len(cached)) copy(result, cached) return result, nil } all, err := s.loadAllRaw() if err != nil { return nil, err } if recipeType == "_all" { s.cache[recipeType] = all result := make([]RecipeDef, len(all)) copy(result, all) return result, nil } var filtered []RecipeDef for _, r := range all { if r.Type == recipeType { filtered = append(filtered, r) } } s.cache[recipeType] = filtered result := make([]RecipeDef, len(filtered)) copy(result, filtered) return result, nil } func (s *RecipeStore) loadAllRaw() ([]RecipeDef, error) { dir := filepath.Join(s.dataDir, "recipes") var recipes []RecipeDef WalkYAMLDir(dir, func(path, id string, data []byte) error { var r RecipeDef if err := yaml.Unmarshal(data, &r); err != nil { return nil } recipes = append(recipes, r) return nil }) return recipes, nil } func (r *RecipeDef) MatchesEntry(itemID string) bool { for _, e := range r.Consume { for _, id := range e.Items { if id == itemID { return true } } } return false } func (s *RecipeStore) FindByInput(recipeType string, itemID string) *RecipeDef { all, err := s.LoadByType(recipeType) if err != nil { return nil } for i := range all { for _, e := range all[i].Consume { for _, id := range e.Items { if id == itemID { return &all[i] } } } } return nil } func (s *RecipeStore) FindByStation(itemID string, stationIDs []string, recipeType string) ([]RecipeDef, error) { all, err := s.LoadByType(recipeType) if err != nil { return nil, err } var matches []RecipeDef for _, r := range all { if recipeType != "" && r.Type != recipeType { continue } if !r.MatchesEntry(itemID) { continue } if len(r.Station) == 0 { continue } for _, station := range stationIDs { for _, rs := range r.Station { if rs == station { matches = append(matches, r) goto next } } } next: } return matches, nil } func (r *RecipeDef) DisplayName() string { return r.Output } func (r *RecipeDef) EffectiveSkill() string { if r.Skill != "" { return r.Skill } return r.Type } func (r *RecipeDef) FirstItemName(load func(string) (string, bool)) string { for _, e := range r.Consume { for _, id := range e.Items { if name, ok := load(id); ok { return name } return id } } return "" } func (r *RecipeDef) ConsumeAll(hasItem func(string) bool, removeItem func(string, int) bool) bool { for _, e := range r.Consume { found := false for _, id := range e.Items { if hasItem(id) { removeItem(id, e.Quantity) found = true break } } if !found { return false } } return true } func (r *RecipeDef) HasAllItemsQty(countItem func(string) int) bool { for _, e := range r.Consume { found := false for _, id := range e.Items { qty := e.Quantity if qty <= 0 { qty = 1 } if countItem(id) >= qty { found = true break } } if !found { return false } } return true } func (r *RecipeDef) HasAllItems(hasItem func(string) bool) bool { for _, e := range r.Consume { found := false for _, id := range e.Items { if hasItem(id) { found = true break } } if !found { return false } } return true }