package world import ( "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" "thehouseoficarus/internal/behavior" ) // HazardDef describes an environmental threat attached to a room (a sandstorm, // solar radiation, falling rocks, etc.). It is a shared, ID-referenced // definition loaded from data/hazards/.yaml, so one hazard can be reused // across a whole area and resolved by name for a future `examine` command. // // A hazard rolls an attack against everyone in the room every Speed ticks using // the SAME combat formulas as a mob: accuracy comes from Attack/AttackBonus, max // hit from MaxHit, and the player defends with their Defense level + equipment // bonus selected by AttackType (so existing armour and the Defense skill apply). // Tech protection (Kinetic Barrier / Projectile Screen / Neural Firewall) keys // off AttackType automatically. type HazardDef struct { Name string `yaml:"name"` Description string `yaml:"description"` AttackType string `yaml:"attack_type"` // stab/slash/crush/ranged/science Attack int `yaml:"attack"` AttackBonus int `yaml:"attack_bonus"` MaxHit int `yaml:"max_hit"` Speed float64 `yaml:"speed"` // ticks between hazard rolls WarnMessage string `yaml:"warn_message"` HitMessage string `yaml:"hit_message"` // accepts a single %d for damage MissMessage string `yaml:"miss_message"` RequiredItem string `yaml:"required_item"` // equipped item that negates the hazard } // LoadHazard returns the shared hazard definition for id, caching results. func (w *World) LoadHazard(id string) (*HazardDef, error) { w.hazardMu.Lock() if w.hazardPathIndex == nil { w.hazardPathIndex = behavior.BuildPathIndex(filepath.Join(w.dataDir, "hazards")) } if def, ok := w.hazardDefs[id]; ok { w.hazardMu.Unlock() return def, nil } path, ok := w.hazardPathIndex[id] w.hazardMu.Unlock() if !ok { return nil, fmt.Errorf("read hazard %s: no such hazard", id) } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read hazard %s: %w", id, err) } var def HazardDef if err := yaml.Unmarshal(data, &def); err != nil { return nil, fmt.Errorf("parse hazard %s: %w", id, err) } w.hazardMu.Lock() w.hazardDefs[id] = &def w.hazardMu.Unlock() return &def, nil } // HazardIndex returns the set of known hazard IDs (used by startup validation). func (w *World) HazardIndex() map[string]bool { w.hazardMu.Lock() defer w.hazardMu.Unlock() if w.hazardPathIndex == nil { w.hazardPathIndex = behavior.BuildPathIndex(filepath.Join(w.dataDir, "hazards")) } ids := make(map[string]bool, len(w.hazardPathIndex)) for id := range w.hazardPathIndex { ids[id] = true } return ids }