Files
panel/pkg/empyrionitems/templates.go
T
dbledeez 4ccccc6fe2 panel v0.9.2 — public release
Self-hostable game server control panel: Go controller + agent, 26 game
modules, embedded web UI. One-line install via install.sh / install.ps1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:43:35 -07:00

171 lines
4.1 KiB
Go

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
}