package behavior import ( "fmt" "math/rand" "os" "path/filepath" "strings" "sync" "gopkg.in/yaml.v3" ) func BuildPathIndex(dir string) map[string]string { index := make(map[string]string) filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") { return nil } id := strings.TrimSuffix(d.Name(), ".yaml") index[id] = path return nil }) return index } func BuildRoomIndex(dir string) map[int]string { index := make(map[int]string) filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") { return nil } id := strings.TrimSuffix(d.Name(), ".yaml") var n int if _, scanErr := fmt.Sscanf(id, "%d", &n); scanErr == nil { index[n] = path } return nil }) return index } func WalkYAMLDir(dir string, fn func(path, id string, data []byte) error) error { return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") { return nil } data, readErr := os.ReadFile(path) if readErr != nil { return nil } id := strings.TrimSuffix(d.Name(), ".yaml") return fn(path, id, data) }) } type Duplicate struct { ID string Paths []string } func CheckDuplicateIDs(dir string) []Duplicate { seen := make(map[string][]string) filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") { return nil } id := strings.TrimSuffix(d.Name(), ".yaml") seen[id] = append(seen[id], path) return nil }) var dups []Duplicate for id, paths := range seen { if len(paths) > 1 { dups = append(dups, Duplicate{ID: id, Paths: paths}) } } return dups } func SuccessChance(cfg SuccessFormula, level int, requiredLevel int) float64 { chance := cfg.Base + float64(level-requiredLevel)*cfg.PerLevel if chance > cfg.Cap { chance = cfg.Cap } if chance < 0 { chance = 0 } return chance } var ( dropIndex map[string]string dropIndexMu sync.Mutex ) func loadDropIndex(dataDir string) map[string]string { dropIndexMu.Lock() defer dropIndexMu.Unlock() if dropIndex == nil { dropIndex = BuildPathIndex(filepath.Join(dataDir, "drops")) } return dropIndex } func ClearDropIndex() { dropIndexMu.Lock() defer dropIndexMu.Unlock() dropIndex = nil } func LoadDropTable(dataDir, id string) (*DropTableDef, error) { index := loadDropIndex(dataDir) path, ok := index[id] if !ok { path = filepath.Join(dataDir, "drops", id+".yaml") } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read drop table %s: %w", id, err) } var dt DropTableDef if err := yaml.Unmarshal(data, &dt); err != nil { return nil, fmt.Errorf("parse drop table %s: %w", id, err) } return &dt, nil } func ResolveDrop(dataDir string, drops []DropEntry) *DropEntry { if len(drops) == 0 { return nil } total := 0 for _, d := range drops { total += d.Weight } if total <= 0 { return nil } roll := rand.Intn(total) cumulative := 0 for i := range drops { cumulative += drops[i].Weight if roll < cumulative { if drops[i].Table != "" { sub, err := LoadDropTable(dataDir, drops[i].Table) if err == nil { if resolved := ResolveDrop(dataDir, sub.Drops); resolved != nil { qty := drops[i].Quantity if qty <= 0 { qty = resolved.Quantity } if qty <= 0 { qty = 1 } return &DropEntry{ ItemID: resolved.ItemID, Quantity: qty, Depletes: drops[i].Depletes, Message: drops[i].Message, } } } return nil } return &drops[i] } } return &drops[0] }