package empyrionitems // Template (recipe) parser for Empyrion's `Templates.ecf`. // Format snippet: // // { +Template Name: PlasticMaterial // BaseItem: true // CraftTime: 2 // Target: "SurvC,SmallC,HoverC,BaseC,LargeC,AdvC" // { Child Inputs // CrushedStone: 1 // } // } // // We extract Name + outer props (CraftTime, Target, BaseItem, // OutputCount, etc.) and the Inputs map. import ( "bufio" "io" "os" "strings" ) type Template struct { Name string `json:"name"` BaseItem bool `json:"baseItem,omitempty"` CraftTime string `json:"craftTime,omitempty"` Target string `json:"target,omitempty"` OutputCount string `json:"outputCount,omitempty"` Ref string `json:"ref,omitempty"` Inputs map[string]string `json:"inputs"` // ingredientName → count token Extras map[string]string `json:"extras,omitempty"` } type TemplateIndex struct { Templates []Template byName map[string]*Template } func LoadTemplatesFromFile(path string) (*TemplateIndex, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return LoadTemplatesFromReader(f) } func LoadTemplatesFromReader(r io.Reader) (*TemplateIndex, error) { idx := &TemplateIndex{byName: map[string]*Template{}} sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 1024*1024), 1024*1024) var current *Template var inInputs bool var depth int for sc.Scan() { line := stripComment(sc.Text()) trim := strings.TrimSpace(line) if trim == "" { continue } if current == nil { if strings.HasPrefix(trim, "{ +Template ") || strings.HasPrefix(trim, "{+Template ") || strings.HasPrefix(trim, "{ Template ") { body := strings.TrimPrefix(trim, "{ +Template ") body = strings.TrimPrefix(body, "{+Template ") body = strings.TrimPrefix(body, "{ Template ") body = strings.TrimSpace(body) current = &Template{Inputs: map[string]string{}, Extras: map[string]string{}} for _, kv := range splitTopLevel(body, ',') { parts := strings.SplitN(strings.TrimSpace(kv), ":", 2) if len(parts) != 2 { continue } k := strings.TrimSpace(parts[0]) v := stripQuotes(strings.TrimSpace(parts[1])) switch strings.ToLower(k) { case "name": current.Name = v case "ref": current.Ref = v } } depth = 1 } continue } opens := strings.Count(trim, "{") closes := strings.Count(trim, "}") depth += opens - closes if depth <= 0 { if current.Name != "" { cp := *current idx.Templates = append(idx.Templates, cp) idx.byName[strings.ToLower(cp.Name)] = &idx.Templates[len(idx.Templates)-1] } current = nil inInputs = false depth = 0 continue } // Track child blocks like `{ Child Inputs`. if strings.HasPrefix(trim, "{ Child Inputs") || strings.HasPrefix(trim, "{Child Inputs") { inInputs = true continue } if inInputs && strings.HasPrefix(trim, "}") { inInputs = false continue } // Property line. parts := strings.SplitN(trim, ":", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := stripQuotes(strings.TrimSpace(parts[1])) val = strings.TrimSpace(stripComment(val)) if inInputs { current.Inputs[key] = val continue } switch strings.ToLower(key) { case "baseitem": current.BaseItem = strings.EqualFold(val, "true") case "crafttime": current.CraftTime = val case "target": current.Target = val case "outputcount": current.OutputCount = val default: current.Extras[key] = val } } if err := sc.Err(); err != nil { return nil, err } return idx, nil } func (idx *TemplateIndex) ByName(name string) *Template { if t, ok := idx.byName[strings.ToLower(name)]; ok { c := *t return &c } return nil } // UsedIn returns the list of templates whose inputs include the named item. func (idx *TemplateIndex) UsedIn(itemName string) []Template { want := strings.ToLower(itemName) out := []Template{} for _, t := range idx.Templates { for k := range t.Inputs { if strings.ToLower(k) == want { out = append(out, t) break } } } return out }