diff options
| author | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
| commit | a226d72e51eecb768b13600303f73483118d9104 (patch) | |
| tree | 458d15ef049732c15dacc591a830d3beddbedfde /internal/action | |
| parent | 6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff) | |
| download | thehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz | |
feat: implemented janky object interaction model
Diffstat (limited to 'internal/action')
| -rw-r--r-- | internal/action/action.go | 32 | ||||
| -rw-r--r-- | internal/action/behavior.go | 68 | ||||
| -rw-r--r-- | internal/action/store.go | 174 |
3 files changed, 274 insertions, 0 deletions
diff --git a/internal/action/action.go b/internal/action/action.go new file mode 100644 index 0000000..9cd4c0f --- /dev/null +++ b/internal/action/action.go @@ -0,0 +1,32 @@ +package action + +type Action struct { + Type string + TargetID string + TargetName string + Step int + WaitLeft int + Data map[string]any +} + +func (a *Action) Advance() bool { + if a.WaitLeft > 0 { + a.WaitLeft-- + return false + } + return true +} + +type DropEntry struct { + ItemID string `yaml:"item_id"` + Table string `yaml:"table"` + Weight int `yaml:"weight"` + Quantity int `yaml:"quantity"` + Depletes bool `yaml:"depletes"` + Message string `yaml:"message"` +} + +type DropTableDef struct { + ID string `yaml:"id"` + Drops []DropEntry `yaml:"drops"` +} diff --git a/internal/action/behavior.go b/internal/action/behavior.go new file mode 100644 index 0000000..d7f683a --- /dev/null +++ b/internal/action/behavior.go @@ -0,0 +1,68 @@ +package action + +type GatherConfig struct { + Skill string `yaml:"skill"` + Level int `yaml:"level"` + BaseWait int `yaml:"base_wait"` + Tool string `yaml:"tool"` + Success SuccessFormula `yaml:"success"` + GatherMsg string `yaml:"gather_message"` + FailMsg string `yaml:"fail_message"` + Drops []DropEntry `yaml:"drops"` + DepleteDelay int `yaml:"deplete_delay"` + RespawnMsg string `yaml:"respawn_message"` + RespawnBroadcast string `yaml:"respawn_broadcast"` +} + +type SuccessFormula struct { + Base float64 `yaml:"base"` + PerLevel float64 `yaml:"per_level"` + Cap float64 `yaml:"cap"` +} + +type TalkConfig struct { + Nodes map[string]TalkNode `yaml:"nodes"` +} + +type TalkNode struct { + Message string `yaml:"message"` + Options []TalkOption `yaml:"options"` + Action *NodeAction `yaml:"action"` +} + +type TalkOption struct { + Text string `yaml:"text"` + Goto string `yaml:"goto"` + End bool `yaml:"end"` + Condition *Condition `yaml:"condition"` +} + +type NodeAction struct { + SetFlags map[string]any `yaml:"set_flags"` + GiveItem string `yaml:"give_item"` + TakeItem string `yaml:"take_item"` +} + +type Condition struct { + Flag string `yaml:"flag"` + Value any `yaml:"value"` + Not bool `yaml:"not"` + HasItem string `yaml:"has_item"` +} + +type UseConfig struct { + Message string `yaml:"message"` + Wait int `yaml:"wait"` + Consume map[string]int `yaml:"consume"` + Reward DropEntry `yaml:"reward"` + FailMsg string `yaml:"fail_message"` + Success *SuccessFormula `yaml:"success"` + Skill string `yaml:"skill"` + Level int `yaml:"level"` +} + +type ToggleConfig struct { + Message string `yaml:"message"` + SetFlags map[string]any `yaml:"set_flags"` + Check *Condition `yaml:"check"` +} diff --git a/internal/action/store.go b/internal/action/store.go new file mode 100644 index 0000000..fd58281 --- /dev/null +++ b/internal/action/store.go @@ -0,0 +1,174 @@ +package action + +import ( + "fmt" + "math/rand" + "os" + "path/filepath" + "sync" + + "gopkg.in/yaml.v3" +) + +type Store struct { + dataDir string + mu sync.Mutex + cache map[string]*RawBehavior +} + +type RawBehavior struct { + ID string + Type string + Raw map[string]any +} + +func NewStore(dataDir string) *Store { + return &Store{ + dataDir: dataDir, + cache: make(map[string]*RawBehavior), + } +} + +func (s *Store) Load(id string) (*RawBehavior, error) { + s.mu.Lock() + if b, ok := s.cache[id]; ok { + s.mu.Unlock() + return b, nil + } + s.mu.Unlock() + + path := filepath.Join(s.dataDir, "behaviors", id+".yaml") + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read behavior %s: %w", id, err) + } + + var raw map[string]any + if err := yaml.Unmarshal(data, &raw); err != nil { + return nil, fmt.Errorf("parse behavior %s: %w", id, err) + } + + rb := &RawBehavior{ID: id, Raw: raw} + if t, ok := raw["type"].(string); ok { + rb.Type = t + } + if rid, ok := raw["id"].(string); ok { + rb.ID = rid + } + + s.mu.Lock() + s.cache[id] = rb + s.mu.Unlock() + return rb, nil +} + +func unmarshalRaw[T any](rb *RawBehavior, expectedType string) (*T, error) { + if rb.Type != expectedType { + return nil, fmt.Errorf("behavior %s is type %s, expected %s", rb.ID, rb.Type, expectedType) + } + var cfg T + raw, _ := yaml.Marshal(rb.Raw) + if err := yaml.Unmarshal(raw, &cfg); err != nil { + return nil, fmt.Errorf("parse %s config %s: %w", expectedType, rb.ID, err) + } + return &cfg, nil +} + +func (s *Store) LoadGather(id string) (*GatherConfig, error) { + rb, err := s.Load(id) + if err != nil { + return nil, err + } + return unmarshalRaw[GatherConfig](rb, "gather") +} + +func (s *Store) LoadTalk(id string) (*TalkConfig, error) { + rb, err := s.Load(id) + if err != nil { + return nil, err + } + return unmarshalRaw[TalkConfig](rb, "talk") +} + +func (s *Store) LoadUse(id string) (*UseConfig, error) { + rb, err := s.Load(id) + if err != nil { + return nil, err + } + return unmarshalRaw[UseConfig](rb, "use") +} + +func (s *Store) LoadToggle(id string) (*ToggleConfig, error) { + rb, err := s.Load(id) + if err != nil { + return nil, err + } + return unmarshalRaw[ToggleConfig](rb, "toggle") +} + +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 +} + +func (s *Store) LoadDropTable(id string) (*DropTableDef, error) { + path := filepath.Join(s.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 (s *Store) ResolveDrop(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 := s.LoadDropTable(drops[i].Table) + if err == nil { + if resolved := s.ResolveDrop(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] +} |
