aboutsummaryrefslogtreecommitdiff
path: root/internal/action
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-10 01:48:28 -0400
committerhistoria <[not public]>2026-06-10 01:48:28 -0400
commita226d72e51eecb768b13600303f73483118d9104 (patch)
tree458d15ef049732c15dacc591a830d3beddbedfde /internal/action
parent6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff)
downloadthehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz
feat: implemented janky object interaction model
Diffstat (limited to 'internal/action')
-rw-r--r--internal/action/action.go32
-rw-r--r--internal/action/behavior.go68
-rw-r--r--internal/action/store.go174
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]
+}