diff options
| author | historia <[not public]> | 2026-06-24 20:58:25 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-24 20:58:25 -0400 |
| commit | 9d4b799db4868bcab291b83599ff088763cd4ed6 (patch) | |
| tree | 252f0fcc779acf35243983d643d6399ee2e0cd0b /internal/world/hazard.go | |
| parent | d25638d98fe63efdeab570ef77e6a6a97f2d7a60 (diff) | |
| download | thehouseoficarus-9d4b799db4868bcab291b83599ff088763cd4ed6.tar.gz | |
feat: new type of non-violent 'combat': work
Diffstat (limited to 'internal/world/hazard.go')
| -rw-r--r-- | internal/world/hazard.go | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/internal/world/hazard.go b/internal/world/hazard.go new file mode 100644 index 0000000..396343b --- /dev/null +++ b/internal/world/hazard.go @@ -0,0 +1,82 @@ +package world + +import ( + "fmt" + "os" + "path/filepath" + + "thehouseoficarus/internal/action" + "gopkg.in/yaml.v3" +) + +// 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/<id>.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 { + ID string `yaml:"id"` + 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 = action.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) + } + def.ID = id + + 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 = action.BuildPathIndex(filepath.Join(w.dataDir, "hazards")) + } + ids := make(map[string]bool, len(w.hazardPathIndex)) + for id := range w.hazardPathIndex { + ids[id] = true + } + return ids +} |
