package empyrionitems // Loot group parser for Empyrion's `LootGroups.ecf`. // Format: // // { +LootGroup Name: EscapePodEasy // Count: all // Item_0: EmergencyRations, param1: 1 // Item_1: WaterBottle, param1: 5 // ... // } // // param1 = count (or "low-high" range), param2 = optional probability. // Item names are canonical Item.Name tokens. import ( "bufio" "io" "os" "strings" ) type LootEntry struct { Item string `json:"item"` Count string `json:"count,omitempty"` Probability string `json:"probability,omitempty"` } type LootGroup struct { Name string `json:"name"` Count string `json:"count,omitempty"` Items []LootEntry `json:"items"` } type LootIndex struct { Groups []LootGroup byName map[string]*LootGroup byItem map[string][]string // ingredientLower → group names } func LoadLootFromFile(path string) (*LootIndex, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return LoadLootFromReader(f) } func LoadLootFromReader(r io.Reader) (*LootIndex, error) { idx := &LootIndex{byName: map[string]*LootGroup{}, byItem: map[string][]string{}} sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 1024*1024), 1024*1024) var current *LootGroup var depth int for sc.Scan() { line := stripComment(sc.Text()) trim := strings.TrimSpace(line) if trim == "" { continue } if current == nil { if strings.HasPrefix(trim, "{ +LootGroup ") || strings.HasPrefix(trim, "{+LootGroup ") || strings.HasPrefix(trim, "{ LootGroup ") { body := strings.TrimPrefix(trim, "{ +LootGroup ") body = strings.TrimPrefix(body, "{+LootGroup ") body = strings.TrimPrefix(body, "{ LootGroup ") current = &LootGroup{} 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])) if strings.EqualFold(k, "name") { current.Name = v } } depth = 1 } continue } opens := strings.Count(trim, "{") closes := strings.Count(trim, "}") depth += opens - closes if depth <= 0 { if current.Name != "" { cp := *current idx.Groups = append(idx.Groups, cp) gp := &idx.Groups[len(idx.Groups)-1] idx.byName[strings.ToLower(cp.Name)] = gp for _, it := range cp.Items { low := strings.ToLower(it.Item) idx.byItem[low] = append(idx.byItem[low], cp.Name) } } current = nil depth = 0 continue } parts := strings.SplitN(trim, ":", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := stripComment(strings.TrimSpace(parts[1])) if strings.HasPrefix(strings.ToLower(key), "item_") { // Item_N: Name, param1: count, param2: probability le := LootEntry{} pieces := splitTopLevel(val, ',') for i, p := range pieces { p = strings.TrimSpace(p) if i == 0 { le.Item = stripQuotes(p) continue } kv := strings.SplitN(p, ":", 2) if len(kv) != 2 { continue } kk := strings.ToLower(strings.TrimSpace(kv[0])) vv := strings.TrimSpace(kv[1]) switch kk { case "param1", "param": le.Count = vv case "param2", "xdata": le.Probability = vv } } if le.Item != "" { current.Items = append(current.Items, le) } } else if strings.EqualFold(key, "Count") { current.Count = strings.TrimSpace(val) } } if err := sc.Err(); err != nil { return nil, err } return idx, nil } func (idx *LootIndex) ByName(name string) *LootGroup { if g, ok := idx.byName[strings.ToLower(name)]; ok { c := *g return &c } return nil } func (idx *LootIndex) DroppedBy(itemName string) []string { return append([]string{}, idx.byItem[strings.ToLower(itemName)]...) }