diff options
| author | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
| commit | abd612c15799f604e671e83dc7c410ed2b44185f (patch) | |
| tree | 0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/item | |
| parent | 2725e2927a1595c7b100d942d1f14146252adeb7 (diff) | |
| download | thehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz | |
slop refactor
Diffstat (limited to 'internal/item')
| -rw-r--r-- | internal/item/doc.go | 4 | ||||
| -rw-r--r-- | internal/item/item.go | 191 | ||||
| -rw-r--r-- | internal/item/store.go | 78 |
3 files changed, 273 insertions, 0 deletions
diff --git a/internal/item/doc.go b/internal/item/doc.go new file mode 100644 index 0000000..8fef8a7 --- /dev/null +++ b/internal/item/doc.go @@ -0,0 +1,4 @@ +// Package item defines item definitions (ItemDef), equipment stats, weapon +// types, and the ItemStore YAML loader. Items are read from data/items/ on +// demand and cached; editing a YAML takes effect immediately. +package item diff --git a/internal/item/item.go b/internal/item/item.go new file mode 100644 index 0000000..890fa8d --- /dev/null +++ b/internal/item/item.go @@ -0,0 +1,191 @@ +package item + +import ( + "fmt" + "strings" + + "gopkg.in/yaml.v3" + "thehouseoficarus/internal/behavior" +) + +type EquipSlot string + +const ( + SlotHead EquipSlot = "head" + SlotNeck EquipSlot = "neck" + SlotTorso EquipSlot = "torso" + SlotLegs EquipSlot = "legs" + SlotHands EquipSlot = "hands" + SlotFeet EquipSlot = "feet" + SlotBack EquipSlot = "back" + SlotAmmo EquipSlot = "ammo" + SlotMainHand EquipSlot = "main_hand" + SlotOffHand EquipSlot = "off_hand" + SlotRing EquipSlot = "ring" +) + +type WeaponType string + +const ( + WeaponMelee WeaponType = "melee" + WeaponRanged WeaponType = "ranged" + WeaponScience WeaponType = "science" +) + +type ItemDef struct { + ID string `yaml:"id"` + Name string `yaml:"name"` + Color string `yaml:"color"` + Description string `yaml:"description"` + Value int `yaml:"value"` + Stackable bool `yaml:"stackable"` + EquipSlot EquipSlot `yaml:"equip_slot"` + WeaponType WeaponType `yaml:"weapon_type"` + AttackType string `yaml:"attack_type"` + Stats ItemStats `yaml:"stats"` + Speed float64 `yaml:"speed"` + Requirements map[string]int `yaml:"requirements,omitempty"` + ToolType string `yaml:"tool_type"` + ToolSpeed float64 `yaml:"tool_speed"` + BurnTicks float64 `yaml:"burn_ticks"` + FireLevel int `yaml:"fire_level"` + FireXP int `yaml:"fire_xp"` + Quality int `yaml:"quality"` + MaxQuality int `yaml:"max_quality"` + SearchTable string `yaml:"search_table"` + SearchMiscTable string `yaml:"search_misc_table"` + SearchTicks float64 `yaml:"search_ticks"` + SearchMessage string `yaml:"search_message"` + HealValue int `yaml:"heal_value"` + EatMessage string `yaml:"eat_message"` + Craft CraftList `yaml:"craft,omitempty"` + ProvidesJunk string `yaml:"provides_junk,omitempty"` + FarmPatchType string `yaml:"farm_patch_type"` + FarmLevel int `yaml:"farm_level"` + FarmPlantXP int `yaml:"farm_plant_xp"` + FarmHarvestXP int `yaml:"farm_harvest_xp"` + FarmStages int `yaml:"farm_stages"` + FarmProduct string `yaml:"farm_product"` + FarmMinYield int `yaml:"farm_min_yield"` + FarmMaxYield int `yaml:"farm_max_yield"` + PotionEffect string `yaml:"potion_effect"` + PotionBonus int `yaml:"potion_bonus"` + PotionDuration float64 `yaml:"potion_duration"` + Recoverable bool `yaml:"recoverable"` +} + +type CraftList []CraftDef + +func (cl *CraftList) UnmarshalYAML(value *yaml.Node) error { + switch value.Kind { + case yaml.SequenceNode: + return value.Decode((*[]CraftDef)(cl)) + case yaml.MappingNode: + var single CraftDef + if err := value.Decode(&single); err != nil { + return err + } + *cl = []CraftDef{single} + return nil + } + return fmt.Errorf("craft: expected mapping or sequence") +} + +func (d *ItemDef) FirstCraft() *CraftDef { + if len(d.Craft) == 0 { + return nil + } + return &d.Craft[0] +} + +func (d *ItemDef) FindCraft(fn func(CraftDef) bool) *CraftDef { + for i := range d.Craft { + if fn(d.Craft[i]) { + return &d.Craft[i] + } + } + return nil +} + +func (d *ItemDef) MatchingCrafts(fn func(CraftDef) bool) []*CraftDef { + var result []*CraftDef + for i := range d.Craft { + if fn(d.Craft[i]) { + result = append(result, &d.Craft[i]) + } + } + return result +} + +type ConsumeEntry struct { + Items []string `yaml:"items"` + Quantity int `yaml:"quantity"` + Byproducts []string `yaml:"byproducts"` +} + +type CraftStep struct { + Tick float64 `yaml:"tick"` + Message string `yaml:"message"` +} + +type SuccessFormula struct { + Base float64 `yaml:"base"` + PerLevel float64 `yaml:"per_level"` + Cap float64 `yaml:"cap"` +} + +type CraftDef struct { + Type string `yaml:"type"` + Skill string `yaml:"skill"` + Level int `yaml:"level"` + XP int `yaml:"xp"` + Wait float64 `yaml:"wait"` + Station []string `yaml:"station"` + Tool string `yaml:"tool"` + Consume []ConsumeEntry `yaml:"consume"` + OutputQty int `yaml:"output_qty"` + Fail string `yaml:"fail"` + Message string `yaml:"message"` + FailMessage string `yaml:"fail_message"` + StartMessage string `yaml:"start_message"` + EndMessage string `yaml:"end_message"` + Steps []CraftStep `yaml:"steps"` + Success *SuccessFormula `yaml:"success"` +} + +func (c *CraftDef) EffectiveSkill() string { + if c.Skill != "" { + return c.Skill + } + return c.Type +} + +type ItemStats struct { + StabAttack int `yaml:"stab_attack"` + SlashAttack int `yaml:"slash_attack"` + CrushAttack int `yaml:"crush_attack"` + ScienceAttack int `yaml:"science_attack"` + RangedAttack int `yaml:"ranged_attack"` + + StabDefense int `yaml:"stab_defense"` + SlashDefense int `yaml:"slash_defense"` + CrushDefense int `yaml:"crush_defense"` + ScienceDefense int `yaml:"science_defense"` + RangedDefense int `yaml:"ranged_defense"` + + StrengthBonus int `yaml:"strength_bonus"` + RangedStrength int `yaml:"ranged_strength"` + ScienceDamage int `yaml:"science_damage"` + TechnologyBonus int `yaml:"technology_bonus"` +} + +func (d *ItemDef) MatchesName(input string) bool { + lower := strings.ToLower(strings.TrimSpace(input)) + if lower == "" { + return false + } + if strings.ToLower(d.Name) == lower { + return true + } + return behavior.WordPrefixMatch(lower, d.Name) +} diff --git a/internal/item/store.go b/internal/item/store.go new file mode 100644 index 0000000..d73d6fe --- /dev/null +++ b/internal/item/store.go @@ -0,0 +1,78 @@ +package item + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" + "thehouseoficarus/internal/behavior" +) + +type ItemStore struct { + dataDir string + pathIndex map[string]string + cache map[string]*ItemDef +} + +func NewItemStore(dataDir string) *ItemStore { + s := &ItemStore{ + dataDir: dataDir, + pathIndex: behavior.BuildPathIndex(filepath.Join(dataDir, "items")), + cache: make(map[string]*ItemDef), + } + return s +} + +func (s *ItemStore) Load(id string) (*ItemDef, error) { + if def, ok := s.cache[id]; ok { + return def, nil + } + path, ok := s.pathIndex[id] + if !ok { + return nil, fmt.Errorf("read item %s: no such item", id) + } + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read item %s: %w", id, err) + } + var def ItemDef + if err := yaml.Unmarshal(data, &def); err != nil { + return nil, fmt.Errorf("parse item %s: %w", id, err) + } + def.ID = id + s.cache[id] = &def + return &def, nil +} + +func (s *ItemStore) LoadAll() ([]*ItemDef, error) { + dir := filepath.Join(s.dataDir, "items") + var defs []*ItemDef + err := behavior.WalkYAMLDir(dir, func(path, id string, data []byte) error { + if def, ok := s.cache[id]; ok { + defs = append(defs, def) + return nil + } + var def ItemDef + if err := yaml.Unmarshal(data, &def); err != nil { + return fmt.Errorf("parse item %s: %w", id, err) + } + def.ID = id + s.cache[id] = &def + defs = append(defs, &def) + return nil + }) + return defs, err +} + +func (s *ItemStore) PathIndex() map[string]string { + return s.pathIndex +} + +func (s *ItemStore) IDSet() map[string]bool { + ids := make(map[string]bool) + for id := range s.pathIndex { + ids[id] = true + } + return ids +} |
