aboutsummaryrefslogtreecommitdiff
path: root/skill_plans/combat.md
diff options
context:
space:
mode:
Diffstat (limited to 'skill_plans/combat.md')
-rw-r--r--skill_plans/combat.md3402
1 files changed, 0 insertions, 3402 deletions
diff --git a/skill_plans/combat.md b/skill_plans/combat.md
deleted file mode 100644
index 67037d6..0000000
--- a/skill_plans/combat.md
+++ /dev/null
@@ -1,3402 +0,0 @@
-# Combat System Overhaul — OSRS-Style Attack Types & Per-Type Defense
-
-## 1. Overview
-
-The current combat system uses a single `attack_bonus` / `defense_bonus` per item, ignoring attack types entirely. OSRS uses per-type accuracy bonuses (stab/slash/crush/ranged/magic) and per-type defense bonuses. This overhaul brings the game's combat inline with OSRS mechanics while adapting the naming to the sci-fi theme (Science replaces Magic, Technology replaces Prayer).
-
-**What changes:**
-- `ItemStats` struct: 5 attack bonuses, 5 defense bonuses, 4 damage bonuses (14 fields total)
-- Weapons get an `attack_type` field (stab/slash/crush/ranged/science)
-- Mobs get per-type defense bonuses and attack bonuses
-- Combat formulas use the true OSRS accuracy formula (not the current binary hit/miss)
-- `formulas.go` rewritten with proper OSRS accuracy curve
-- `cmd_attack.go` updated to use per-type bonuses for both player and mob attacks
-- Mobs get an `attack_type` field (default "crush")
-- New `stats` command shows total equipment bonuses
-- `look <item>` shows per-type combat stats
-- All ~66 combat item YAMLs updated with per-type stats
-- All 5 mob YAMLs updated with per-type defense/attack bonuses
-- 8 new example mob YAMLs for varied combat encounters
-
-**What stays the same:**
-- 4 attack styles (accurate/aggressive/defensive/balanced) — style determines level bonus
-- XP distribution formula (dmg * 4, split by style)
-- Death mechanics, flee mechanics, combat timing/tick system
-- WeaponType enum (melee/ranged/science) — still used for equip text + XP routing
-- Player combat level formula
-
----
-
-## 2. ItemStats Struct
-
-**File:** `internal/object/item.go:67-73`
-
-**Current:**
-```go
-type ItemStats struct {
- AttackBonus int `yaml:"attack_bonus"`
- StrengthBonus int `yaml:"strength_bonus"`
- DefenseBonus int `yaml:"defense_bonus"`
- ScienceBonus int `yaml:"science_bonus"`
- TechnologyBonus int `yaml:"technology_bonus"`
-}
-```
-
-**New:**
-```go
-type ItemStats struct {
- // Offensive accuracy bonuses
- 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"`
-
- // Defensive bonuses
- 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"`
-
- // Damage bonuses
- StrengthBonus int `yaml:"strength_bonus"`
- RangedStrength int `yaml:"ranged_strength"`
- ScienceDamage int `yaml:"science_damage"`
- TechnologyBonus int `yaml:"technology_bonus"`
-}
-```
-
-**Notes:**
-- `AttackBonus` and `DefenseBonus` YAML tags are removed entirely. All item YAMLs will be updated to use the new per-type fields.
-- `ScienceBonus` is removed. It was never used in combat. The new `ScienceDamage` field replaces it for future science combat.
-- `TechnologyBonus` stays — it will be used in the Technology skill (Prayer equivalent) in the future.
-- The old `attack` YAML tag (used by bows/arrows/bolts, silently ignored because the Go struct had `attack_bonus`) is also removed. Those items move to `ranged_attack` and `ranged_strength`.
-
-**Backward Compatibility:** None needed. All YAML files will be rewritten. The old YAML tags (`attack_bonus`, `defense_bonus`, `attack`) will no longer be recognized and will be silently ignored by `gopkg.in/yaml.v3` (which is the current behavior for unknown fields). No `UnmarshalYAML` shim needed.
-
----
-
-## 3. Weapon Attack Types
-
-**File:** `internal/object/item.go:33-59` — add field to `ItemDef`
-
-**Add this field to `ItemDef`:**
-```go
-AttackType string `yaml:"attack_type"` // "stab", "slash", "crush", "ranged", "science"
-```
-
-Insert it after `WeaponType` (line 41), before `Stats`:
-```go
-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"` // NEW
- Stats ItemStats `yaml:"stats"`
- Speed float64 `yaml:"speed"`
- // ... rest unchanged
-}
-```
-
-**Default attack type mapping** (used when `attack_type` is empty):
-- If `WeaponType == "ranged"` → `"ranged"`
-- If `WeaponType == "science"` → `"science"`
-- Unarmed (no weapon) → `"crush"`
-- Otherwise → `"crush"` (safest default for melee)
-
-Weapons should always have `attack_type` set explicitly in YAML. The default is only a fallback.
-
-**Attack type assignments for existing weapons:**
-
-| Weapon Category | attack_type | Reasoning |
-|-----------------|-------------|-----------|
-| Swords (all tiers) | slash | Swords are slashing weapons |
-| Daggers (all tiers) | stab | Daggers are stabbing weapons |
-| Axes (all tiers) | slash | Axes are slashing weapons (also tools) |
-| Pickaxes (all tiers) | stab | Pickaxes stab/pierce (also tools) |
-| Bows (all types) | ranged | Ranged weapons |
-| Unarmed | crush | Fists = crush |
-
----
-
-## 4. Combat Style Mapping
-
-The 4 existing styles remain. The style determines:
-1. **Level bonus** (which combat level gets +3 or +1)
-2. **XP distribution** (unchanged from current)
-
-The **weapon's `attack_type`** determines:
-1. Which of the player's 5 attack bonus totals is used for the accuracy roll
-2. Which of the mob's 5 defense bonus values is used for the defense roll
-
-**Style → Level Bonus (unchanged from current):**
-
-| Style | Attack Level | Strength Level | Defense Level |
-|-------|-------------|----------------|---------------|
-| Accurate | +3 | +0 | +0 |
-| Aggressive | +0 | +3 | +0 |
-| Defensive | +0 | +0 | +3 |
-| Balanced | +1 | +1 | +1 |
-
-**Style → XP Distribution (unchanged from current):**
-
-| Style | Distribution |
-|-------|-------------|
-| Accurate | 75% Attack, 25% Hitpoints |
-| Aggressive | 75% Strength, 25% Hitpoints |
-| Defensive | 75% Defense, 25% Hitpoints |
-| Balanced | 25% each Attack/Strength/Defense/Hitpoints |
-
-**Ranged style bonuses** (when weapon_type is "ranged"):
-
-| Style | Ranged Level | Defense Level | XP Distribution |
-|-------|-------------|---------------|-----------------|
-| Accurate | +3 | +0 | 75% Ranged, 25% Hitpoints |
-| Aggressive | +3 | +0 | 75% Ranged, 25% Hitpoints |
-| Defensive | +0 | +3 | 75% Ranged, 25% Hitpoints |
-| Balanced | +1 | +1 | 75% Ranged, 25% Hitpoints |
-
-Note: For ranged, accurate and aggressive both give +3 Ranged (matching OSRS "accurate" and "rapid" which both boost ranged). The attack/strength bonuses from `AttackStyleBonus()` are NOT used — Ranged level is used instead.
-
----
-
-## 5. Updated Combat Formulas
-
-**File:** `internal/combat/formulas.go` — complete rewrite
-
-```go
-package combat
-
-import "math/rand"
-
-// AttackRoll computes the maximum attack roll for accuracy.
-// effectiveLevel = level + styleBonus + 8
-// equipBonus = total equipment bonus for the relevant attack type
-func AttackRoll(level int, styleBonus int, equipBonus int) int {
- effective := level + styleBonus + 8
- return effective * (equipBonus + 64)
-}
-
-// DefenseRoll computes the maximum defense roll.
-// effectiveLevel = level + styleBonus + 8
-// equipBonus = total equipment bonus for the relevant defense type
-func DefenseRoll(level int, styleBonus int, equipBonus int) int {
- effective := level + styleBonus + 8
- return effective * (equipBonus + 64)
-}
-
-// HitChance returns the probability of hitting (0.0 to 1.0) using the
-// true OSRS accuracy formula.
-func HitChance(attackRoll, defenseRoll int) float64 {
- a := float64(attackRoll)
- d := float64(defenseRoll)
- if a > d {
- return 1.0 - (d+2.0)/(2.0*(a+1.0))
- }
- return a / (2.0*(d+1.0))
-}
-
-// HitCheck uses the OSRS accuracy formula to determine if an attack hits.
-// Returns true if the attack lands.
-func HitCheck(attackRoll, defenseRoll int) bool {
- chance := HitChance(attackRoll, defenseRoll)
- return rand.Float64() < chance
-}
-
-// MaxHit computes the maximum melee hit.
-// level = Strength level (or Ranged level for ranged)
-// styleBonus = strength style bonus (+3 aggressive, +1 balanced, 0 otherwise)
-// equipBonus = total equipment strength bonus (or ranged_strength for ranged)
-func MaxHit(level int, styleBonus int, equipBonus int) int {
- effective := level + styleBonus + 8
- hit := (effective * (equipBonus + 64)) / 512
- if hit < 1 {
- hit = 1
- }
- return hit
-}
-
-// RollDamage returns a random damage value from 0 to maxHit inclusive.
-// In OSRS, 0 is a valid damage (a "hit 0" or splash). We keep the
-// current behavior of 1..maxHit to match existing game feel, but this
-// can be changed to 0..maxHit for true OSRS.
-func RollDamage(maxHit int) int {
- if maxHit <= 0 {
- return 0
- }
- return 1 + rand.Intn(maxHit)
-}
-
-// AttackStyleBonus returns the level bonuses for melee combat styles.
-// For ranged weapons, the caller should use RangedStyleBonus instead.
-func AttackStyleBonus(style string) (attack, strength, defense int) {
- switch style {
- case "accurate":
- return 3, 0, 0
- case "aggressive":
- return 0, 3, 0
- case "defensive":
- return 0, 0, 3
- case "balanced":
- return 1, 1, 1
- default:
- return 0, 0, 0
- }
-}
-
-// RangedStyleBonus returns the level bonuses for ranged combat styles.
-func RangedStyleBonus(style string) (ranged, defense int) {
- switch style {
- case "accurate":
- return 3, 0
- case "aggressive":
- return 3, 0
- case "defensive":
- return 0, 3
- case "balanced":
- return 1, 1
- default:
- return 0, 0
- }
-}
-
-// MobCombatLevel computes a mob's combat level.
-// Uses the standard OSRS NPC formula:
-// floor((attack + strength + defense + hp) / 4)
-func MobCombatLevel(attack, strength, defense, hp int) int {
- return (attack + strength + defense + hp) / 4
-}
-
-// EquipBonusForAttackType selects the correct attack bonus from a
-// pre-summed equipment stats struct based on the weapon's attack type.
-// attackType is one of: "stab", "slash", "crush", "ranged", "science"
-//
-// This function lives here to avoid circular imports. The caller passes
-// the individual bonus values.
-func SelectAttackBonus(attackType string, stab, slash, crush, science, ranged int) int {
- switch attackType {
- case "stab":
- return stab
- case "slash":
- return slash
- case "crush":
- return crush
- case "science":
- return science
- case "ranged":
- return ranged
- default:
- return crush
- }
-}
-
-// SelectDefenseBonus picks the correct defense bonus to oppose a given
-// attack type.
-func SelectDefenseBonus(attackType string, stab, slash, crush, science, ranged int) int {
- switch attackType {
- case "stab":
- return stab
- case "slash":
- return slash
- case "crush":
- return crush
- case "science":
- return science
- case "ranged":
- return ranged
- default:
- return crush
- }
-}
-```
-
-**Key differences from current formulas.go:**
-1. `HitCheck` now uses the continuous OSRS accuracy formula instead of a binary comparison. Previously, if `attackRoll > defenseRoll` it was always a hit; if less, always a miss. Now it uses the proper probability curve.
-2. `HitChance` is exposed as a separate function (useful for debugging/display).
-3. `RangedStyleBonus` added for ranged weapons.
-4. `MobCombatLevel` moved here from `utils.go` (optional, can stay in utils.go).
-5. `SelectAttackBonus` and `SelectDefenseBonus` helper functions added.
-6. `AttackRoll`, `DefenseRoll`, `MaxHit` formulas are actually unchanged in structure — they were already OSRS-correct. The change is in what values are passed to them.
-
----
-
-## 6. Code Changes to cmd_attack.go
-
-**File:** `internal/game/cmd_attack.go`
-
-### 6.1 playerAttack (lines 174-236)
-
-**Current flow:**
-1. Gets style bonuses from `AttackStyleBonus`
-2. Reads main hand weapon's `AttackBonus` and `StrengthBonus`
-3. `attRoll = AttackRoll(Attack level, attBonus, equipAtt)`
-4. `defRoll = DefenseRoll(mob.Defense, 0, 0)` — mob has NO equipment defense
-5. Binary `HitCheck(attRoll, defRoll)`
-6. `MaxHit(Strength level, strBonus, equipStr)`
-
-**New flow:**
-```go
-func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- if g.processConsumeQueue(p, sess) {
- p.ActionState = &ActionState{Type: ActionEating, TargetName: "food"}
- return
- }
-
- // Determine weapon and attack type
- attackType := "crush" // unarmed default
- var weaponType object.WeaponType
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil {
- if def.AttackType != "" {
- attackType = def.AttackType
- } else if def.WeaponType == object.WeaponRanged {
- attackType = "ranged"
- } else if def.WeaponType == object.WeaponScience {
- attackType = "science"
- }
- weaponType = def.WeaponType
- }
- }
-
- // Sum all equipment bonuses
- totals := g.playerEquipBonuses(p)
-
- if weaponType == object.WeaponRanged {
- // --- RANGED ATTACK ---
- rangedBonus, defBonus := combat.RangedStyleBonus(string(p.AttackStyle))
-
- equipAttack := totals.RangedAttack
- attRoll := combat.AttackRoll(p.Level(player.Ranged), rangedBonus, equipAttack)
-
- mobDefBonus := combat.SelectDefenseBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
- defRoll := combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
-
- if combat.HitCheck(attRoll, defRoll) {
- maxHit := combat.MaxHit(p.Level(player.Ranged), rangedBonus, totals.RangedStrength)
- dmg := combat.RollDamage(maxHit)
- // ... apply damage, award XP (ranged style), consume ammo ...
- _ = defBonus // used if needed for defense-related ranged style
- }
- } else {
- // --- MELEE ATTACK ---
- attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
-
- equipAttack := combat.SelectAttackBonus(attackType,
- totals.StabAttack, totals.SlashAttack, totals.CrushAttack,
- totals.ScienceAttack, totals.RangedAttack)
- attRoll := combat.AttackRoll(p.Level(player.Attack), attBonus, equipAttack)
-
- mobDefBonus := combat.SelectDefenseBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
- defRoll := combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
-
- if combat.HitCheck(attRoll, defRoll) {
- maxHit := combat.MaxHit(p.Level(player.Strength), strBonus, totals.StrengthBonus)
- dmg := combat.RollDamage(maxHit)
- // ... apply damage, award XP (current style) ...
- }
- }
-}
-```
-
-**Detailed changes (line-by-line for melee path):**
-
-Replace lines 180-193:
-```go
-// OLD:
-attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
-equipAtt := 0
-equipStr := 0
-if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- def, err := g.ItemStore.Load(itemID)
- if err == nil {
- equipAtt = def.Stats.AttackBonus
- equipStr = def.Stats.StrengthBonus
- }
-}
-attRoll := combat.AttackRoll(p.Level(player.Attack), attBonus, equipAtt)
-defRoll := combat.DefenseRoll(mob.Defense, 0, 0)
-
-// NEW:
-attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
-
-attackType := "crush"
-if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil {
- if def.AttackType != "" {
- attackType = def.AttackType
- }
- }
-}
-
-totals := g.playerEquipBonuses(p)
-equipAttack := combat.SelectAttackBonus(attackType,
- totals.StabAttack, totals.SlashAttack, totals.CrushAttack,
- totals.ScienceAttack, totals.RangedAttack)
-attRoll := combat.AttackRoll(p.Level(player.Attack), attBonus, equipAttack)
-
-mobDefBonus := combat.SelectDefenseBonus(attackType,
- mob.StabDefense, mob.SlashDefense, mob.CrushDefense,
- mob.ScienceDefense, mob.RangedDefense)
-defRoll := combat.DefenseRoll(mob.Defense, 0, mobDefBonus)
-```
-
-Replace line 196 (`MaxHit` call):
-```go
-// OLD:
-maxHit := combat.MaxHit(p.Level(player.Strength), strBonus, equipStr)
-
-// NEW:
-maxHit := combat.MaxHit(p.Level(player.Strength), strBonus, totals.StrengthBonus)
-```
-
-### 6.2 mobAttack (lines 238-289)
-
-**Current flow:**
-1. Gets defense style bonus
-2. Sums `DefenseBonus` from ALL equipped items
-3. `attRoll = AttackRoll(mob.Attack, 0, 0)` — mob has NO attack bonus
-4. `defRoll = DefenseRoll(Defense level, defBonus, equipDef)`
-
-**New flow:**
-```go
-func (g *Game) mobAttack(sess *net.Session, p *player.Player, mob *world.MobInstance) {
- _, _, defStyleBonus := combat.AttackStyleBonus(string(p.AttackStyle))
-
- // Determine mob's attack type
- mobAttackType := mob.AttackType
- if mobAttackType == "" {
- mobAttackType = "crush"
- }
-
- // Mob attack roll: uses mob's attack level + mob's attack bonus
- attRoll := combat.AttackRoll(mob.Attack, 0, mob.AttackBonus)
-
- // Player defense roll: uses player's defense level + style bonus +
- // equipment defense matching the mob's attack type
- totals := g.playerEquipBonuses(p)
- equipDef := combat.SelectDefenseBonus(mobAttackType,
- totals.StabDefense, totals.SlashDefense, totals.CrushDefense,
- totals.ScienceDefense, totals.RangedDefense)
- defRoll := combat.DefenseRoll(p.Level(player.Defense), defStyleBonus, equipDef)
-
- if combat.HitCheck(attRoll, defRoll) {
- maxHit := combat.MaxHit(mob.Strength, 0, mob.StrengthBonus)
- dmg := combat.RollDamage(maxHit)
- // ... rest unchanged (apply damage, save, flee check, etc.)
- }
-}
-```
-
-**Detailed changes (line-by-line):**
-
-Replace lines 239-250:
-```go
-// OLD:
-_, _, defBonus := combat.AttackStyleBonus(string(p.AttackStyle))
-equipDef := 0
-for _, itemID := range p.Equipment {
- def, err := g.ItemStore.Load(itemID)
- if err == nil {
- equipDef += def.Stats.DefenseBonus
- }
-}
-attRoll := combat.AttackRoll(mob.Attack, 0, 0)
-defRoll := combat.DefenseRoll(p.Level(player.Defense), defBonus, equipDef)
-
-// NEW:
-_, _, defStyleBonus := combat.AttackStyleBonus(string(p.AttackStyle))
-
-mobAttackType := mob.AttackType
-if mobAttackType == "" {
- mobAttackType = "crush"
-}
-
-attRoll := combat.AttackRoll(mob.Attack, 0, mob.AttackBonus)
-
-totals := g.playerEquipBonuses(p)
-equipDef := combat.SelectDefenseBonus(mobAttackType,
- totals.StabDefense, totals.SlashDefense, totals.CrushDefense,
- totals.ScienceDefense, totals.RangedDefense)
-defRoll := combat.DefenseRoll(p.Level(player.Defense), defStyleBonus, equipDef)
-```
-
-Replace line 253 (`MaxHit` call):
-```go
-// OLD:
-maxHit := combat.MaxHit(mob.Strength, 0, 0)
-
-// NEW:
-maxHit := combat.MaxHit(mob.Strength, 0, mob.StrengthBonus)
-```
-
-### 6.3 awardCombatXP changes for Ranged
-
-**Current** (lines 378-406): Only awards to Attack/Strength/Defense/Hitpoints based on style.
-
-**New:** If the player's weapon_type is "ranged", XP goes to Ranged instead of melee skills.
-
-Add a new parameter to `awardCombatXP`:
-```go
-func (g *Game) awardCombatXP(p *player.Player, dmg int, isRanged bool) ([]xpGain, []player.SkillName) {
- baseXP := dmg * 4
- var gains []xpGain
- var leveledUp []player.SkillName
-
- if isRanged {
- // All ranged styles: 75% ranged, 25% hitpoints
- gains = []xpGain{
- {string(player.Ranged), baseXP * 3 / 4},
- {string(player.Hitpoints), baseXP / 4},
- }
- } else {
- switch p.AttackStyle {
- case player.Accurate:
- gains = []xpGain{{string(player.Attack), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Aggressive:
- gains = []xpGain{{string(player.Strength), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Defensive:
- gains = []xpGain{{string(player.Defense), baseXP * 3 / 4}, {string(player.Hitpoints), baseXP / 4}}
- case player.Balanced:
- quarter := baseXP / 4
- gains = []xpGain{
- {string(player.Attack), quarter},
- {string(player.Strength), quarter},
- {string(player.Defense), quarter},
- {string(player.Hitpoints), quarter},
- }
- }
- }
-
- for _, gain := range gains {
- if newLevel := p.AddSkillXP(player.SkillName(gain.Skill), gain.XP); newLevel > 0 {
- leveledUp = append(leveledUp, player.SkillName(gain.Skill))
- }
- }
- g.AccountStore.SaveCharacter(p)
- return gains, leveledUp
-}
-```
-
-Update call sites:
-- `playerAttack` melee path: `g.awardCombatXP(p, dmg, false)`
-- `playerAttack` ranged path: `g.awardCombatXP(p, dmg, true)`
-
-### 6.4 Ammo Consumption (Ranged)
-
-In `playerAttack`, after a successful ranged attack:
-```go
-if weaponType == object.WeaponRanged {
- p.AmmoQty--
- if p.AmmoQty <= 0 {
- delete(p.Equipment, object.SlotAmmo)
- p.AmmoQty = 0
- sess.WriteLine("You've run out of ammo!")
- combat.LeaveCombat(p.Name)
- // End combat
- }
- g.AccountStore.SaveCharacter(p)
-}
-```
-
-Before combat starts (in `startCombat` or `doAttack`), check for ammo:
-```go
-if weaponDef.WeaponType == object.WeaponRanged {
- if _, hasAmmo := p.Equipment[object.SlotAmmo]; !hasAmmo || p.AmmoQty <= 0 {
- sess.WriteLine("You don't have any ammo equipped.")
- return
- }
-}
-```
-
-### 6.5 startCombat Style Display
-
-In `startCombat` (line 120), the style display should also show the attack type:
-```go
-attackType := "crush"
-if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil && def.AttackType != "" {
- attackType = def.AttackType
- }
-}
-styleStr := fmt.Sprintf(" Style: %s (%s, %s)", p.AttackStyle, strings.Join(styleParts, ", "), attackType)
-```
-
----
-
-## 7. Code Changes to MobDef and MobInstance
-
-**File:** `internal/world/mob.go`
-
-### 7.1 MobDef (lines 20-37)
-
-**Add these fields:**
-```go
-type MobDef struct {
- ID string `yaml:"id"`
- Name string `yaml:"name"`
- Description string `yaml:"description"`
- BehaviorID string `yaml:"behavior"`
- IdleDescriptions []string `yaml:"idle_descriptions"`
- CombatDescriptions []string `yaml:"combat_descriptions"`
- Attack int `yaml:"attack"`
- Strength int `yaml:"strength"`
- Defense int `yaml:"defense"`
- HP int `yaml:"hp"`
- Ranged int `yaml:"ranged"` // NEW
- Science int `yaml:"science"` // NEW
- Speed float64 `yaml:"speed"`
- Aggressive bool `yaml:"aggressive"`
- Protected bool `yaml:"protected"`
- Unique bool `yaml:"unique"`
- RespawnTicks float64 `yaml:"respawn_ticks"`
- Drops DropTable `yaml:"drops"`
-
- // NEW: Attack bonuses
- AttackBonus int `yaml:"attack_bonus"` // equipment-equivalent attack bonus
- StrengthBonus int `yaml:"strength_bonus"` // equipment-equivalent strength bonus
- RangedBonus int `yaml:"ranged_bonus"` // ranged attack bonus
- ScienceBonus int `yaml:"science_bonus"` // science attack bonus
- AttackType string `yaml:"attack_type"` // "stab"/"slash"/"crush"/"ranged"/"science" (default "crush")
-
- // NEW: Defense bonuses
- 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"`
-
- // NEW: Elemental weakness
- Weakness string `yaml:"weakness"` // "solar"/"hydro"/"eco"/"bio"/""
-}
-```
-
-### 7.2 MobInstance (lines 39-62)
-
-**Add matching fields:**
-```go
-type MobInstance struct {
- InstanceID string
- DefID string
- Name string
- BehaviorID string
- HP int
- MaxHP int
- Attack int
- Strength int
- Defense int
- Ranged int // NEW
- Science int // NEW
- Speed float64
- Aggressive bool
- Protected bool
- Unique bool
- RespawnTicks float64
- RoomID int
- HomeRoomID int
- Drops DropTable
- IdleDescription string
- WanderRooms []int
- WanderInterval float64
- WanderTickCounter int
- regenerateTick int
-
- // NEW: Attack bonuses
- AttackBonus int
- StrengthBonus int
- RangedBonus int
- ScienceBonus int
- AttackType string
-
- // NEW: Defense bonuses
- StabDefense int
- SlashDefense int
- CrushDefense int
- ScienceDefense int
- RangedDefense int
-
- // NEW: Elemental weakness
- Weakness string
-}
-```
-
-### 7.3 SeedMobs (lines 213-259)
-
-**Add field propagation** in the `inst := &MobInstance{...}` block (lines 236-258):
-
-After line 245 (`Defense: dw.def.Defense,`), add:
-```go
-Ranged: dw.def.Ranged,
-Science: dw.def.Science,
-```
-
-After line 255 (`Drops: dw.def.Drops,`), add:
-```go
-AttackBonus: dw.def.AttackBonus,
-StrengthBonus: dw.def.StrengthBonus,
-RangedBonus: dw.def.RangedBonus,
-ScienceBonus: dw.def.ScienceBonus,
-AttackType: dw.def.AttackType,
-StabDefense: dw.def.StabDefense,
-SlashDefense: dw.def.SlashDefense,
-CrushDefense: dw.def.CrushDefense,
-ScienceDefense: dw.def.ScienceDefense,
-RangedDefense: dw.def.RangedDefense,
-Weakness: dw.def.Weakness,
-```
-
-### 7.4 mobCombatLevel Update
-
-**File:** `internal/game/utils.go:47-49`
-
-**Current:**
-```go
-func mobCombatLevel(m *world.MobInstance) int {
- return int(0.25*float64(m.Attack+m.Strength+m.Defense+m.MaxHP) + 0.5)
-}
-```
-
-**New** (unchanged — this formula is already correct for melee-only mobs):
-```go
-func mobCombatLevel(m *world.MobInstance) int {
- base := float64(m.Defense+m.MaxHP) / 4.0
- melee := float64(m.Attack+m.Strength) / 4.0
- ranged := float64(m.Ranged) * 3.0 / 8.0
- science := float64(m.Science) * 3.0 / 8.0
- best := melee
- if ranged > best {
- best = ranged
- }
- if science > best {
- best = science
- }
- return int(base + best + 0.5)
-}
-```
-
-This uses the OSRS NPC combat level formula:
-- `base = (def + hp) / 4`
-- `offensive = max(melee=(atk+str)/4, ranged=rng*3/8, magic=sci*3/8)`
-- `level = floor(base + offensive)`
-
-For existing mobs with no Ranged/Science, this produces the same results as the old formula.
-
----
-
-## 8. Equipment Bonus Aggregation
-
-**File:** `internal/game/cmd_attack.go` (or a new `internal/game/equip_stats.go`)
-
-Add a helper function that sums all equipped items' stats:
-
-```go
-func (g *Game) playerEquipBonuses(p *player.Player) object.ItemStats {
- var totals object.ItemStats
- for _, itemID := range p.Equipment {
- def, err := g.ItemStore.Load(itemID)
- if err != nil {
- continue
- }
- totals.StabAttack += def.Stats.StabAttack
- totals.SlashAttack += def.Stats.SlashAttack
- totals.CrushAttack += def.Stats.CrushAttack
- totals.ScienceAttack += def.Stats.ScienceAttack
- totals.RangedAttack += def.Stats.RangedAttack
- totals.StabDefense += def.Stats.StabDefense
- totals.SlashDefense += def.Stats.SlashDefense
- totals.CrushDefense += def.Stats.CrushDefense
- totals.ScienceDefense += def.Stats.ScienceDefense
- totals.RangedDefense += def.Stats.RangedDefense
- totals.StrengthBonus += def.Stats.StrengthBonus
- totals.RangedStrength += def.Stats.RangedStrength
- totals.ScienceDamage += def.Stats.ScienceDamage
- totals.TechnologyBonus += def.Stats.TechnologyBonus
- }
- return totals
-}
-```
-
-This function is called by `playerAttack`, `mobAttack`, the new `stats` command, and `doLookTarget` for comparing equipment.
-
----
-
-## 9. Equipment Stats Display
-
-**File:** `internal/game/cmd_look.go`
-
-### 9.1 Item Examination (doLookTarget)
-
-When looking at an item (inventory or ground), if the item has any non-zero combat stats, display them in a formatted block.
-
-**Location:** After displaying item name/description/value (lines 508-514 for ground items, lines 526-536 for inventory items).
-
-**Add this helper function:**
-```go
-func (g *Game) showItemStats(sess *net.Session, def *object.ItemDef) {
- s := def.Stats
- hasAttack := s.StabAttack != 0 || s.SlashAttack != 0 || s.CrushAttack != 0 ||
- s.ScienceAttack != 0 || s.RangedAttack != 0
- hasDefense := s.StabDefense != 0 || s.SlashDefense != 0 || s.CrushDefense != 0 ||
- s.ScienceDefense != 0 || s.RangedDefense != 0
- hasOther := s.StrengthBonus != 0 || s.RangedStrength != 0 ||
- s.ScienceDamage != 0 || s.TechnologyBonus != 0
-
- if !hasAttack && !hasDefense && !hasOther {
- return
- }
-
- sess.WriteLine("")
- if hasAttack || hasDefense {
- sess.WriteLine(" Attack bonuses: Defense bonuses:")
- sess.WriteLine(fmt.Sprintf(" Stab: %+4d Stab: %+4d", s.StabAttack, s.StabDefense))
- sess.WriteLine(fmt.Sprintf(" Slash: %+4d Slash: %+4d", s.SlashAttack, s.SlashDefense))
- sess.WriteLine(fmt.Sprintf(" Crush: %+4d Crush: %+4d", s.CrushAttack, s.CrushDefense))
- sess.WriteLine(fmt.Sprintf(" Science:%+4d Science:%+4d", s.ScienceAttack, s.ScienceDefense))
- sess.WriteLine(fmt.Sprintf(" Ranged: %+4d Ranged: %+4d", s.RangedAttack, s.RangedDefense))
- }
- if hasOther {
- sess.WriteLine("")
- sess.WriteLine(" Other bonuses:")
- if s.StrengthBonus != 0 {
- sess.WriteLine(fmt.Sprintf(" Melee strength: %+d", s.StrengthBonus))
- }
- if s.RangedStrength != 0 {
- sess.WriteLine(fmt.Sprintf(" Ranged strength: %+d", s.RangedStrength))
- }
- if s.ScienceDamage != 0 {
- sess.WriteLine(fmt.Sprintf(" Science damage: %+d", s.ScienceDamage))
- }
- if s.TechnologyBonus != 0 {
- sess.WriteLine(fmt.Sprintf(" Technology: %+d", s.TechnologyBonus))
- }
- }
- if def.AttackType != "" {
- sess.WriteLine(fmt.Sprintf(" Attack type: %s", def.AttackType))
- }
- if def.Speed > 0 {
- sess.WriteLine(fmt.Sprintf(" Speed: %.0f", def.Speed))
- }
-}
-```
-
-**Call it** in the ground item branch (after line 513):
-```go
-g.showItemStats(sess, def)
-```
-
-**Call it** in the inventory item branch (after line 531):
-```go
-g.showItemStats(sess, def)
-```
-
-### 9.2 Mob Examination
-
-Update the mob examination section in `doLookTarget` (lines 451-457) to show per-type bonuses:
-
-```go
-// Replace lines 451-457:
-sess.WriteLines(
- "",
- fmt.Sprintf(" Attack: %d Strength: %d Defense: %d", best.Attack, best.Strength, best.Defense),
- fmt.Sprintf(" HP: %d/%d", best.HP, best.MaxHP),
-)
-if best.StabDefense != 0 || best.SlashDefense != 0 || best.CrushDefense != 0 ||
- best.ScienceDefense != 0 || best.RangedDefense != 0 {
- sess.WriteLines(
- "",
- " Defense bonuses:",
- fmt.Sprintf(" Stab: %+d Slash: %+d Crush: %+d", best.StabDefense, best.SlashDefense, best.CrushDefense),
- fmt.Sprintf(" Science: %+d Ranged: %+d", best.ScienceDefense, best.RangedDefense),
- )
-}
-if best.Weakness != "" {
- sess.WriteLine(fmt.Sprintf(" Weakness: %s", best.Weakness))
-}
-```
-
----
-
-## 10. Equipment Totals Command — `stats`
-
-**New file:** `internal/game/cmd_stats.go`
-
-```go
-package game
-
-import (
- "fmt"
- "strings"
-
- "thehouseoficarus/internal/combat"
- "thehouseoficarus/internal/net"
- "thehouseoficarus/internal/object"
- "thehouseoficarus/internal/player"
-)
-
-func (g *Game) doStats(sess *net.Session) {
- p := sess.Player.(*player.Player)
- totals := g.playerEquipBonuses(p)
-
- attackType := "crush"
- weaponName := "unarmed"
- if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
- if def, err := g.ItemStore.Load(itemID); err == nil {
- weaponName = def.Name
- if def.AttackType != "" {
- attackType = def.AttackType
- }
- }
- }
-
- sess.WriteLines(
- "",
- fmt.Sprintf("Weapon: %s (attack type: %s)", weaponName, attackType),
- "",
- "Attack bonuses: Defense bonuses:",
- fmt.Sprintf(" Stab: %+4d Stab: %+4d", totals.StabAttack, totals.StabDefense),
- fmt.Sprintf(" Slash: %+4d Slash: %+4d", totals.SlashAttack, totals.SlashDefense),
- fmt.Sprintf(" Crush: %+4d Crush: %+4d", totals.CrushAttack, totals.CrushDefense),
- fmt.Sprintf(" Science:%+4d Science:%+4d", totals.ScienceAttack, totals.ScienceDefense),
- fmt.Sprintf(" Ranged: %+4d Ranged: %+4d", totals.RangedAttack, totals.RangedDefense),
- "",
- "Other bonuses:",
- fmt.Sprintf(" Melee strength: %+d", totals.StrengthBonus),
- fmt.Sprintf(" Ranged strength: %+d", totals.RangedStrength),
- fmt.Sprintf(" Science damage: %+d", totals.ScienceDamage),
- fmt.Sprintf(" Technology: %+d", totals.TechnologyBonus),
- )
-
- // Show effective accuracy and max hit for current setup
- attBonus, strBonus, _ := combat.AttackStyleBonus(string(p.AttackStyle))
- equipAtt := combat.SelectAttackBonus(attackType,
- totals.StabAttack, totals.SlashAttack, totals.CrushAttack,
- totals.ScienceAttack, totals.RangedAttack)
- attRoll := combat.AttackRoll(p.Level(player.Attack), attBonus, equipAtt)
- maxHit := combat.MaxHit(p.Level(player.Strength), strBonus, totals.StrengthBonus)
-
- sess.WriteLines(
- "",
- fmt.Sprintf("Style: %s Attack roll: %d Max hit: %d",
- p.AttackStyle, attRoll, maxHit),
- )
-
- _ = strings.Join // avoid unused import (the real code won't need this)
-}
-```
-
-**Register the command:**
-
-**File:** `internal/game/game.go`
-
-In `classifyCommand` (line 138), add `"stats"` to the `ClassInstant` case:
-```go
-case "say", "score", "sc", "inventory", "i", "inv",
- "look", "l", "exits", "help",
- "map", "option", "options", "alias", "unalias",
- "description", "desc", "queued", "color", "colors",
- "colortable", "prompt", "style", "stats": // ADD "stats"
- return ClassInstant
-```
-
-In `executeCommand` (find the switch statement for instant commands), add:
-```go
-case "stats":
- g.doStats(sess)
-```
-
----
-
-## 11. Item YAML Migration
-
-Every item with combat stats needs updated YAML. Below is the complete set.
-
-### 11.1 Melee Weapons — Swords (attack_type: slash)
-
-Stats are OSRS-proportional. Swords have high slash, moderate stab, no crush.
-
-**Bronze Sword** (`data/items/bronze_sword.yaml`):
-```yaml
-id: bronze_sword
-name: bronze sword
-color: "178"
-description: "A basic bronze sword."
-value: 12
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 4
- slash_attack: 10
- crush_attack: -2
- strength_bonus: 7
-speed: 4
-```
-
-**Iron Sword** (`data/items/iron_sword.yaml`):
-```yaml
-id: iron_sword
-name: iron sword
-color: "250"
-description: "An iron sword."
-value: 30
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 7
- slash_attack: 15
- crush_attack: -2
- strength_bonus: 10
-speed: 4
-```
-
-**Steel Sword** (`data/items/steel_sword.yaml`):
-```yaml
-id: steel_sword
-name: steel sword
-color: "253"
-description: "A steel sword."
-value: 100
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 10
- slash_attack: 22
- crush_attack: -2
- strength_bonus: 14
-speed: 4
-```
-
-**Mithril Sword** (`data/items/mithril_sword.yaml`):
-```yaml
-id: mithril_sword
-name: mithril sword
-color: "75"
-description: "A mithril sword."
-value: 250
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 14
- slash_attack: 30
- crush_attack: -2
- strength_bonus: 20
-speed: 4
-```
-
-**Adamant Sword** (`data/items/adamant_sword.yaml`):
-```yaml
-id: adamant_sword
-name: adamant sword
-color: "120"
-description: "An adamant sword."
-value: 700
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 20
- slash_attack: 40
- crush_attack: -2
- strength_bonus: 27
-speed: 4
-```
-
-**Rune Sword** (`data/items/rune_sword.yaml`):
-```yaml
-id: rune_sword
-name: rune sword
-color: "87"
-description: "A rune sword."
-value: 10000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: 27
- slash_attack: 52
- crush_attack: -2
- strength_bonus: 36
-speed: 4
-```
-
-### 11.2 Melee Weapons — Daggers (attack_type: stab)
-
-Daggers have high stab, moderate slash, negative crush. Fast speed.
-
-**Bronze Dagger** (`data/items/bronze_dagger.yaml`):
-```yaml
-id: bronze_dagger
-name: bronze dagger
-color: "178"
-description: "A small bronze dagger."
-value: 8
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 5
- slash_attack: 3
- crush_attack: -1
- strength_bonus: 4
-speed: 3
-```
-
-**Iron Dagger** (`data/items/iron_dagger.yaml`):
-```yaml
-id: iron_dagger
-name: iron dagger
-color: "250"
-description: "A small iron dagger."
-value: 20
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 8
- slash_attack: 5
- crush_attack: -1
- strength_bonus: 7
-speed: 3
-```
-
-**Steel Dagger** (`data/items/steel_dagger.yaml`):
-```yaml
-id: steel_dagger
-name: steel dagger
-color: "253"
-description: "A small steel dagger."
-value: 60
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 12
- slash_attack: 7
- crush_attack: -1
- strength_bonus: 10
-speed: 3
-```
-
-**Mithril Dagger** (`data/items/mithril_dagger.yaml`):
-```yaml
-id: mithril_dagger
-name: mithril dagger
-color: "75"
-description: "A small mithril dagger."
-value: 150
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 17
- slash_attack: 10
- crush_attack: -1
- strength_bonus: 14
-speed: 3
-```
-
-**Adamant Dagger** (`data/items/adamant_dagger.yaml`):
-```yaml
-id: adamant_dagger
-name: adamant dagger
-color: "120"
-description: "A small adamant dagger."
-value: 400
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 23
- slash_attack: 14
- crush_attack: -1
- strength_bonus: 19
-speed: 3
-```
-
-**Rune Dagger** (`data/items/rune_dagger.yaml`):
-```yaml
-id: rune_dagger
-name: rune dagger
-color: "87"
-description: "A small rune dagger."
-value: 5000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 30
- slash_attack: 18
- crush_attack: -1
- strength_bonus: 24
-speed: 3
-```
-
-### 11.3 Melee Weapons — Axes (attack_type: slash)
-
-Axes are also tools. They have moderate slash, some crush, weak stab. Slower than swords.
-
-**Bronze Axe** (`data/items/bronze_axe.yaml`):
-```yaml
-id: bronze_axe
-name: bronze axe
-color: "178"
-description: "A sturdy bronze axe, good for woodcutting."
-value: 10
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 7
- crush_attack: 5
- strength_bonus: 5
-speed: 5
-tool_type: axe
-tool_speed: 4
-```
-
-**Iron Axe** (`data/items/iron_axe.yaml`):
-```yaml
-id: iron_axe
-name: iron axe
-color: "250"
-description: "A sturdy iron axe, good for woodcutting."
-value: 25
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 10
- crush_attack: 7
- strength_bonus: 8
-speed: 5
-tool_type: axe
-tool_speed: 3.5
-```
-
-**Steel Axe** (`data/items/steel_axe.yaml`):
-```yaml
-id: steel_axe
-name: steel axe
-color: "253"
-description: "A sturdy steel axe, good for woodcutting."
-value: 75
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 15
- crush_attack: 11
- strength_bonus: 12
-speed: 5
-tool_type: axe
-tool_speed: 3
-```
-
-**Black Axe** (`data/items/black_axe.yaml`):
-```yaml
-id: black_axe
-name: black axe
-color: "240"
-description: "A black axe, good for woodcutting."
-value: 150
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 18
- crush_attack: 14
- strength_bonus: 14
-speed: 5
-tool_type: axe
-tool_speed: 2.5
-```
-
-**Mithril Axe** (`data/items/mithril_axe.yaml`):
-```yaml
-id: mithril_axe
-name: mithril axe
-color: "75"
-description: "A sturdy mithril axe, good for woodcutting."
-value: 200
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 21
- crush_attack: 16
- strength_bonus: 17
-speed: 5
-tool_type: axe
-tool_speed: 2.5
-```
-
-**Adamant Axe** (`data/items/adamant_axe.yaml`):
-```yaml
-id: adamant_axe
-name: adamant axe
-color: "120"
-description: "A sturdy adamant axe, good for woodcutting."
-value: 500
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 29
- crush_attack: 22
- strength_bonus: 24
-speed: 5
-tool_type: axe
-tool_speed: 2
-```
-
-**Rune Axe** (`data/items/rune_axe.yaml`):
-```yaml
-id: rune_axe
-name: rune axe
-color: "87"
-description: "A sturdy rune axe, good for woodcutting."
-value: 5000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 38
- crush_attack: 29
- strength_bonus: 32
-speed: 5
-tool_type: axe
-tool_speed: 1.5
-```
-
-**Dragon Axe** (`data/items/dragon_axe.yaml`):
-```yaml
-id: dragon_axe
-name: dragon axe
-color: "196"
-description: "A powerful dragon axe."
-value: 50000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: slash
-stats:
- stab_attack: -2
- slash_attack: 43
- crush_attack: 32
- strength_bonus: 36
-speed: 5
-tool_type: axe
-tool_speed: 1
-```
-
-### 11.4 Melee Weapons — Pickaxes (attack_type: stab)
-
-Pickaxes stab/pierce. Tool items with moderate combat stats.
-
-**Bronze Pickaxe** (`data/items/bronze_pickaxe.yaml`):
-```yaml
-id: bronze_pickaxe
-name: bronze pickaxe
-color: "178"
-description: "A sturdy bronze pickaxe, good for mining."
-value: 10
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 4
- slash_attack: -2
- crush_attack: 2
- strength_bonus: 3
-speed: 5
-tool_type: pickaxe
-tool_speed: 2
-```
-
-**Iron Pickaxe** (`data/items/iron_pickaxe.yaml`):
-```yaml
-id: iron_pickaxe
-name: iron pickaxe
-color: "250"
-description: "A sturdy iron pickaxe, good for mining."
-value: 25
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 7
- slash_attack: -2
- crush_attack: 3
- strength_bonus: 5
-speed: 5
-tool_type: pickaxe
-tool_speed: 1.5
-```
-
-**Steel Pickaxe** (`data/items/steel_pickaxe.yaml`):
-```yaml
-id: steel_pickaxe
-name: steel pickaxe
-color: "253"
-description: "A sturdy steel pickaxe, good for mining."
-value: 75
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 10
- slash_attack: -2
- crush_attack: 4
- strength_bonus: 7
-speed: 5
-tool_type: pickaxe
-tool_speed: 1.25
-```
-
-**Black Pickaxe** (`data/items/black_pickaxe.yaml`):
-```yaml
-id: black_pickaxe
-name: black pickaxe
-color: "240"
-description: "A black pickaxe, good for mining."
-value: 150
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 14
- slash_attack: -2
- crush_attack: 6
- strength_bonus: 9
-speed: 5
-tool_type: pickaxe
-tool_speed: 1
-```
-
-**Mithril Pickaxe** (`data/items/mithril_pickaxe.yaml`):
-```yaml
-id: mithril_pickaxe
-name: mithril pickaxe
-color: "75"
-description: "A sturdy mithril pickaxe, good for mining."
-value: 200
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 17
- slash_attack: -2
- crush_attack: 8
- strength_bonus: 12
-speed: 5
-tool_type: pickaxe
-tool_speed: 1
-```
-
-**Adamant Pickaxe** (`data/items/adamant_pickaxe.yaml`):
-```yaml
-id: adamant_pickaxe
-name: adamant pickaxe
-color: "120"
-description: "A sturdy adamant pickaxe, good for mining."
-value: 500
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 23
- slash_attack: -2
- crush_attack: 10
- strength_bonus: 16
-speed: 5
-tool_type: pickaxe
-tool_speed: 0.75
-```
-
-**Rune Pickaxe** (`data/items/rune_pickaxe.yaml`):
-```yaml
-id: rune_pickaxe
-name: rune pickaxe
-color: "87"
-description: "A sturdy rune pickaxe, good for mining."
-value: 5000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 30
- slash_attack: -2
- crush_attack: 14
- strength_bonus: 22
-speed: 5
-tool_type: pickaxe
-tool_speed: 0.5
-```
-
-**Dragon Pickaxe** (`data/items/dragon_pickaxe.yaml`):
-```yaml
-id: dragon_pickaxe
-name: dragon pickaxe
-color: "196"
-description: "A powerful dragon pickaxe."
-value: 50000
-stackable: false
-equip_slot: main_hand
-weapon_type: melee
-attack_type: stab
-stats:
- stab_attack: 34
- slash_attack: -2
- crush_attack: 16
- strength_bonus: 26
-speed: 5
-tool_type: pickaxe
-tool_speed: 0.25
-```
-
-### 11.5 Armor — Med Helms
-
-Med helms have moderate defense, balanced across melee types. Slightly negative science defense (metal conducts energy), neutral ranged.
-
-**Bronze Med Helm** (`data/items/bronze_med_helm.yaml`):
-```yaml
-id: bronze_med_helm
-name: bronze med helm
-color: "178"
-description: "A bronze medium helmet."
-value: 8
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 3
- slash_defense: 4
- crush_defense: 2
- science_defense: -1
- ranged_defense: 3
-```
-
-**Iron Med Helm** (`data/items/iron_med_helm.yaml`):
-```yaml
-id: iron_med_helm
-name: iron med helm
-color: "250"
-description: "An iron medium helmet."
-value: 20
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 5
- slash_defense: 6
- crush_defense: 3
- science_defense: -1
- ranged_defense: 5
-```
-
-**Steel Med Helm** (`data/items/steel_med_helm.yaml`):
-```yaml
-id: steel_med_helm
-name: steel med helm
-color: "253"
-description: "A steel medium helmet."
-value: 60
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 7
- slash_defense: 9
- crush_defense: 5
- science_defense: -2
- ranged_defense: 7
-```
-
-**Mithril Med Helm** (`data/items/mithril_med_helm.yaml`):
-```yaml
-id: mithril_med_helm
-name: mithril med helm
-color: "75"
-description: "A mithril medium helmet."
-value: 150
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 10
- slash_defense: 12
- crush_defense: 7
- science_defense: -3
- ranged_defense: 10
-```
-
-**Adamant Med Helm** (`data/items/adamant_med_helm.yaml`):
-```yaml
-id: adamant_med_helm
-name: adamant med helm
-color: "120"
-description: "An adamant medium helmet."
-value: 400
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 14
- slash_defense: 16
- crush_defense: 10
- science_defense: -3
- ranged_defense: 14
-```
-
-**Rune Med Helm** (`data/items/rune_med_helm.yaml`):
-```yaml
-id: rune_med_helm
-name: rune med helm
-color: "87"
-description: "A rune medium helmet."
-value: 5000
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 19
- slash_defense: 22
- crush_defense: 13
- science_defense: -4
- ranged_defense: 19
-```
-
-### 11.6 Armor — Full Helms
-
-Full helms have higher defense than med helms. Larger science penalty.
-
-**Bronze Full Helm** (`data/items/bronze_full_helm.yaml`):
-```yaml
-id: bronze_full_helm
-name: bronze full helm
-color: "178"
-description: "A bronze full helmet."
-value: 20
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 5
- slash_defense: 6
- crush_defense: 4
- science_defense: -3
- ranged_defense: 5
-```
-
-**Iron Full Helm** (`data/items/iron_full_helm.yaml`):
-```yaml
-id: iron_full_helm
-name: iron full helm
-color: "250"
-description: "An iron full helmet."
-value: 50
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 8
- slash_defense: 9
- crush_defense: 6
- science_defense: -3
- ranged_defense: 8
-```
-
-**Steel Full Helm** (`data/items/steel_full_helm.yaml`):
-```yaml
-id: steel_full_helm
-name: steel full helm
-color: "253"
-description: "A steel full helmet."
-value: 150
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 12
- slash_defense: 13
- crush_defense: 9
- science_defense: -5
- ranged_defense: 12
-```
-
-**Mithril Full Helm** (`data/items/mithril_full_helm.yaml`):
-```yaml
-id: mithril_full_helm
-name: mithril full helm
-color: "75"
-description: "A mithril full helmet."
-value: 400
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 16
- slash_defense: 18
- crush_defense: 12
- science_defense: -6
- ranged_defense: 16
-```
-
-**Adamant Full Helm** (`data/items/adamant_full_helm.yaml`):
-```yaml
-id: adamant_full_helm
-name: adamant full helm
-color: "120"
-description: "An adamant full helmet."
-value: 1000
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 22
- slash_defense: 24
- crush_defense: 16
- science_defense: -8
- ranged_defense: 22
-```
-
-**Rune Full Helm** (`data/items/rune_full_helm.yaml`):
-```yaml
-id: rune_full_helm
-name: rune full helm
-color: "87"
-description: "A rune full helmet."
-value: 12000
-stackable: false
-equip_slot: head
-stats:
- stab_defense: 30
- slash_defense: 33
- crush_defense: 22
- science_defense: -11
- ranged_defense: 30
-```
-
-### 11.7 Armor — Platebodies
-
-Platebodies are the highest-defense torso armor. Heavy science penalty, strong melee defense.
-
-**Bronze Platebody** (`data/items/bronze_platebody.yaml`):
-```yaml
-id: bronze_platebody
-name: bronze platebody
-color: "178"
-description: "A bronze platebody."
-value: 60
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 12
- slash_defense: 15
- crush_defense: 10
- science_defense: -10
- ranged_defense: 12
-```
-
-**Iron Platebody** (`data/items/iron_platebody.yaml`):
-```yaml
-id: iron_platebody
-name: iron platebody
-color: "250"
-description: "An iron platebody."
-value: 150
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 18
- slash_defense: 22
- crush_defense: 14
- science_defense: -10
- ranged_defense: 18
-```
-
-**Steel Platebody** (`data/items/steel_platebody.yaml`):
-```yaml
-id: steel_platebody
-name: steel platebody
-color: "253"
-description: "A steel platebody."
-value: 500
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 27
- slash_defense: 32
- crush_defense: 22
- science_defense: -15
- ranged_defense: 27
-```
-
-**Mithril Platebody** (`data/items/mithril_platebody.yaml`):
-```yaml
-id: mithril_platebody
-name: mithril platebody
-color: "75"
-description: "A mithril platebody."
-value: 1500
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 36
- slash_defense: 42
- crush_defense: 30
- science_defense: -20
- ranged_defense: 36
-```
-
-**Adamant Platebody** (`data/items/adamant_platebody.yaml`):
-```yaml
-id: adamant_platebody
-name: adamant platebody
-color: "120"
-description: "An adamant platebody."
-value: 5000
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 49
- slash_defense: 55
- crush_defense: 40
- science_defense: -25
- ranged_defense: 49
-```
-
-**Rune Platebody** (`data/items/rune_platebody.yaml`):
-```yaml
-id: rune_platebody
-name: rune platebody
-color: "87"
-description: "A rune platebody."
-value: 40000
-stackable: false
-equip_slot: torso
-stats:
- stab_defense: 65
- slash_defense: 72
- crush_defense: 52
- science_defense: -30
- ranged_defense: 65
-```
-
-### 11.8 Ranged Weapons — Bows
-
-Bows use the `ranged_attack` field. The bug where they used `attack:` (which was silently ignored since ItemStats has no `attack` YAML tag) is fixed. Longbows have higher accuracy than shortbows but are slower.
-
-**Shortbow** (`data/items/shortbow.yaml`):
-```yaml
-id: shortbow
-name: shortbow
-color: "137"
-description: "A shortbow."
-value: 10
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 8
-```
-
-**Oak Shortbow** (`data/items/oak_shortbow.yaml`):
-```yaml
-id: oak_shortbow
-name: oak shortbow
-color: "136"
-description: "An oak shortbow."
-value: 40
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 14
-```
-
-**Willow Shortbow** (`data/items/willow_shortbow.yaml`):
-```yaml
-id: willow_shortbow
-name: willow shortbow
-color: "107"
-description: "A willow shortbow."
-value: 160
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 20
-```
-
-**Maple Shortbow** (`data/items/maple_shortbow.yaml`):
-```yaml
-id: maple_shortbow
-name: maple shortbow
-color: "172"
-description: "A maple shortbow."
-value: 640
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 29
-```
-
-**Yew Shortbow** (`data/items/yew_shortbow.yaml`):
-```yaml
-id: yew_shortbow
-name: yew shortbow
-color: "94"
-description: "A yew shortbow."
-value: 1600
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 47
-```
-
-**Magic Shortbow** (`data/items/magic_shortbow.yaml`):
-```yaml
-id: magic_shortbow
-name: magic shortbow
-color: "63"
-description: "A magic shortbow."
-value: 4000
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 4
-stats:
- ranged_attack: 69
-```
-
-**Longbow** (`data/items/longbow.yaml`):
-```yaml
-id: longbow
-name: longbow
-color: "137"
-description: "A longbow."
-value: 20
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 8
-```
-
-**Oak Longbow** (`data/items/oak_longbow.yaml`):
-```yaml
-id: oak_longbow
-name: oak longbow
-color: "136"
-description: "An oak longbow."
-value: 80
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 14
-```
-
-**Willow Longbow** (`data/items/willow_longbow.yaml`):
-```yaml
-id: willow_longbow
-name: willow longbow
-color: "107"
-description: "A willow longbow."
-value: 320
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 20
-```
-
-**Maple Longbow** (`data/items/maple_longbow.yaml`):
-```yaml
-id: maple_longbow
-name: maple longbow
-color: "172"
-description: "A maple longbow."
-value: 1280
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 29
-```
-
-**Yew Longbow** (`data/items/yew_longbow.yaml`):
-```yaml
-id: yew_longbow
-name: yew longbow
-color: "94"
-description: "A yew longbow."
-value: 2000
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 47
-```
-
-**Magic Longbow** (`data/items/magic_longbow.yaml`):
-```yaml
-id: magic_longbow
-name: magic longbow
-color: "63"
-description: "A magic longbow."
-value: 6000
-equip_slot: main_hand
-weapon_type: ranged
-attack_type: ranged
-speed: 6
-stats:
- ranged_attack: 69
-```
-
-### 11.9 Ranged Ammo — Arrows
-
-Arrows now use `ranged_strength` instead of the broken `attack:` field. Arrow accuracy (ranged_attack) is 0 — all accuracy comes from the bow. Damage comes from the arrow's `ranged_strength`.
-
-**Bronze Arrow** (`data/items/bronze_arrow.yaml`):
-```yaml
-id: bronze_arrow
-name: bronze arrow
-color: "178"
-description: "A bronze-tipped arrow."
-value: 1
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 7
-```
-
-**Iron Arrow** (`data/items/iron_arrow.yaml`):
-```yaml
-id: iron_arrow
-name: iron arrow
-color: "250"
-description: "An iron-tipped arrow."
-value: 2
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 10
-```
-
-**Steel Arrow** (`data/items/steel_arrow.yaml`):
-```yaml
-id: steel_arrow
-name: steel arrow
-color: "253"
-description: "A steel-tipped arrow."
-value: 5
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 16
-```
-
-**Mithril Arrow** (`data/items/mithril_arrow.yaml`):
-```yaml
-id: mithril_arrow
-name: mithril arrow
-color: "75"
-description: "A mithril-tipped arrow."
-value: 10
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 22
-```
-
-**Adamant Arrow** (`data/items/adamant_arrow.yaml`):
-```yaml
-id: adamant_arrow
-name: adamant arrow
-color: "120"
-description: "An adamant-tipped arrow."
-value: 20
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 31
-```
-
-**Rune Arrow** (`data/items/rune_arrow.yaml`):
-```yaml
-id: rune_arrow
-name: rune arrow
-color: "87"
-description: "A rune-tipped arrow."
-value: 40
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 49
-```
-
-### 11.10 Ranged Ammo — Bolts
-
-Same pattern as arrows. `ranged_strength` for damage.
-
-**Bronze Bolts** (`data/items/bronze_bolts.yaml`):
-```yaml
-id: bronze_bolts
-name: bronze bolts
-color: "178"
-description: "Bronze crossbow bolts."
-value: 1
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 10
-```
-
-**Iron Bolts** (`data/items/iron_bolts.yaml`):
-```yaml
-id: iron_bolts
-name: iron bolts
-color: "250"
-description: "Iron crossbow bolts."
-value: 3
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 17
-```
-
-**Steel Bolts** (`data/items/steel_bolts.yaml`):
-```yaml
-id: steel_bolts
-name: steel bolts
-color: "253"
-description: "Steel crossbow bolts."
-value: 6
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 25
-```
-
-**Mithril Bolts** (`data/items/mithril_bolts.yaml`):
-```yaml
-id: mithril_bolts
-name: mithril bolts
-color: "75"
-description: "Mithril crossbow bolts."
-value: 12
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 32
-```
-
-**Adamant Bolts** (`data/items/adamant_bolts.yaml`):
-```yaml
-id: adamant_bolts
-name: adamant bolts
-color: "120"
-description: "Adamant crossbow bolts."
-value: 24
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 43
-```
-
-**Rune Bolts** (`data/items/rune_bolts.yaml`):
-```yaml
-id: rune_bolts
-name: rune bolts
-color: "87"
-description: "Rune crossbow bolts."
-value: 48
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 55
-```
-
-**Sapphire Bolts** (`data/items/sapphire_bolts.yaml`):
-```yaml
-id: sapphire_bolts
-name: sapphire bolts
-color: "69"
-description: "Mithril bolts tipped with sapphire."
-value: 40
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 36
-```
-
-**Emerald Bolts** (`data/items/emerald_bolts.yaml`):
-```yaml
-id: emerald_bolts
-name: emerald bolts
-color: "83"
-description: "Mithril bolts tipped with emerald."
-value: 65
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 39
-```
-
-**Ruby Bolts** (`data/items/ruby_bolts.yaml`):
-```yaml
-id: ruby_bolts
-name: ruby bolts
-color: "196"
-description: "Adamant bolts tipped with ruby."
-value: 130
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 49
-```
-
-**Diamond Bolts** (`data/items/diamond_bolts.yaml`):
-```yaml
-id: diamond_bolts
-name: diamond bolts
-color: "255"
-description: "Adamant bolts tipped with diamond."
-value: 230
-stackable: true
-equip_slot: ammo
-stats:
- ranged_strength: 52
-```
-
-### 11.11 Ranged Armor — Leather
-
-Leather armor provides ranged defense and slight melee defense. Positive ranged defense, negative science defense.
-
-**Leather Cowl** (`data/items/leather_cowl.yaml`):
-```yaml
-id: leather_cowl
-name: leather cowl
-color: "130"
-description: "A leather cowl."
-value: 15
-equip_slot: head
-stats:
- stab_defense: 1
- slash_defense: 2
- crush_defense: 1
- science_defense: 2
- ranged_defense: 4
-```
-
-**Leather Body** (`data/items/leather_body.yaml`):
-```yaml
-id: leather_body
-name: leather body
-color: "130"
-description: "A leather body armour."
-value: 25
-equip_slot: torso
-stats:
- stab_defense: 4
- slash_defense: 7
- crush_defense: 5
- science_defense: 4
- ranged_defense: 10
-```
-
-**Leather Chaps** (`data/items/leather_chaps.yaml`):
-```yaml
-id: leather_chaps
-name: leather chaps
-color: "130"
-description: "A pair of leather chaps."
-value: 20
-equip_slot: legs
-stats:
- stab_defense: 2
- slash_defense: 4
- crush_defense: 3
- science_defense: 3
- ranged_defense: 6
-```
-
-**Leather Gloves** (`data/items/leather_gloves.yaml`):
-```yaml
-id: leather_gloves
-name: leather gloves
-color: "130"
-description: "A pair of leather gloves."
-value: 10
-equip_slot: hands
-stats:
- stab_defense: 1
- slash_defense: 1
- crush_defense: 1
- science_defense: 1
- ranged_defense: 2
-```
-
-**Leather Vambraces** (`data/items/leather_vambraces.yaml`):
-```yaml
-id: leather_vambraces
-name: leather vambraces
-color: "130"
-description: "A pair of leather vambraces."
-value: 18
-equip_slot: hands
-stats:
- stab_defense: 2
- slash_defense: 2
- crush_defense: 2
- science_defense: 2
- ranged_defense: 4
- ranged_attack: 4
-```
-
-**Leather Boots** (`data/items/leather_boots.yaml`):
-```yaml
-id: leather_boots
-name: leather boots
-color: "130"
-description: "A pair of leather boots."
-value: 12
-equip_slot: feet
-stats:
- stab_defense: 1
- slash_defense: 1
- crush_defense: 1
- science_defense: 1
- ranged_defense: 2
-```
-
-**Coif** (`data/items/coif.yaml`):
-```yaml
-id: coif
-name: coif
-color: "94"
-description: "A hard leather coif."
-value: 45
-equip_slot: head
-stats:
- stab_defense: 2
- slash_defense: 4
- crush_defense: 2
- science_defense: 4
- ranged_defense: 6
- ranged_attack: 2
-```
-
-**Hard Leather Body** (`data/items/hard_leather_body.yaml`):
-```yaml
-id: hard_leather_body
-name: hard leather body
-color: "94"
-description: "A hardened leather body armour."
-value: 60
-equip_slot: torso
-stats:
- stab_defense: 8
- slash_defense: 12
- crush_defense: 8
- science_defense: 6
- ranged_defense: 14
-```
-
-**Hard Leather Shield** (`data/items/hard_leather_shield.yaml`):
-```yaml
-id: hard_leather_shield
-name: hard leather shield
-color: "94"
-description: "A shield made of hardened leather."
-value: 55
-equip_slot: off_hand
-stats:
- stab_defense: 5
- slash_defense: 7
- crush_defense: 5
- science_defense: 4
- ranged_defense: 8
-```
-
-### 11.12 Non-Combat Equipment (no changes needed)
-
-These items have no combat stats and need no changes:
-- Graceful set (hat, torso, legs, gloves, boots, cape) — movement only
-- Cape of Agility — movement only
-- Jewelry (rings, necklaces, amulets, bracelets) — currently no stats
-- Tools without equip (needle, chisel, etc.)
-
-### 11.13 Unfinished Bow Items (no combat stats)
-
-These are fletching intermediates with no combat purpose:
-- `shortbow_u`, `longbow_u`, `oak_shortbow_u`, `oak_longbow_u`, `willow_shortbow_u`, `willow_longbow_u`, `maple_shortbow_u`, `maple_longbow_u`, `yew_shortbow_u`, `yew_longbow_u`, `magic_shortbow_u`, `magic_longbow_u`
-- `arrow_shaft`, `headless_arrow`, all `*_arrowtips`, all `*_bolts_unf`, all `*_bolt_tips`
-
-These have no equip slot or stats and need no changes.
-
----
-
-## 12. Mob YAML Updates
-
-### 12.1 Cow (`data/mobs/cow.yaml`)
-
-```yaml
-id: cow
-name: cow
-description: "A placid dairy cow, chewing cud."
-attack: 1
-strength: 1
-defense: 1
-hp: 8
-speed: 5
-aggressive: false
-respawn_ticks: 30
-attack_type: crush
-stab_defense: 1
-slash_defense: 1
-crush_defense: 1
-science_defense: 0
-ranged_defense: 0
-idle_descriptions:
- - "moos softly"
- - "chews on some grass"
- - "swishes its tail lazily"
- - "stares at you with big brown eyes"
-drops:
- remains: bones
- loot:
- - item_id: cowhide
- weight: 100
- - item_id: raw_beef
- weight: 100
-```
-
-### 12.2 Man (`data/mobs/man.yaml`)
-
-```yaml
-id: man
-name: man
-description: "A shabby-looking man loitering in the town square."
-combat_descriptions:
- - "is engaged in a fight to the death with %s"
- - "is getting pummelled by %s"
- - "is locked in combat with %s"
- - "trades blows with %s"
- - "circles warily around %s"
-idle_descriptions:
- - "scribbles something in a small notebook"
- - "gazes skyward at the clouds"
- - "leans against a wall, looking bored"
- - "scratches his head thoughtfully"
- - "stares off into the distance"
- - "adjusts his tunic and stretches"
-attack: 1
-strength: 1
-defense: 1
-hp: 7
-speed: 5
-aggressive: false
-respawn_ticks: 30
-attack_type: crush
-stab_defense: 0
-slash_defense: 0
-crush_defense: 0
-science_defense: 0
-ranged_defense: 0
-drops:
- remains: "bones"
- loot:
- - item_id: "credits"
- weight: 98
- quantity: 10
- - item_id: "credits"
- weight: 2
- quantity: 150
-```
-
-### 12.3 Guard (`data/mobs/guard.yaml`)
-
-```yaml
-id: guard
-name: Guard
-description: "A stern-looking guard in weathered armor."
-behavior: guard_talk
-unique: true
-protected: true
-attack: 5
-strength: 5
-defense: 5
-hp: 30
-speed: 5
-aggressive: false
-respawn_ticks: 60
-attack_type: slash
-attack_bonus: 8
-strength_bonus: 6
-stab_defense: 12
-slash_defense: 15
-crush_defense: 10
-science_defense: -5
-ranged_defense: 12
-idle_descriptions:
- - "scans the area with a watchful eye"
- - "adjusts the grip on his weapon"
- - "nods curtly at passersby"
- - "leans against the gate, arms folded"
-```
-
-### 12.4 Newbie Trainer (`data/mobs/newbie_trainer.yaml`)
-
-```yaml
-id: newbie_trainer
-name: Newbie Trainer
-description: "A friendly-looking instructor ready to help new adventurers."
-unique: true
-protected: true
-attack: 1
-strength: 1
-defense: 1
-hp: 50
-speed: 5
-aggressive: false
-respawn_ticks: 30
-attack_type: crush
-stab_defense: 0
-slash_defense: 0
-crush_defense: 0
-science_defense: 0
-ranged_defense: 0
-idle_descriptions:
- - "reviews a training manual"
- - "adjusts a practice dummy"
- - "demonstrates a sword stance to no one in particular"
- - "offers a friendly nod to passersby"
- - "polishes a wooden shield"
- - "checks a stopwatch and jots something down"
-```
-
-### 12.5 Tanner (`data/mobs/tanner.yaml`)
-
-```yaml
-id: tanner
-name: Tanner
-description: "A weathered craftsman with stained hands and a leather apron."
-behavior: tanner_talk
-unique: true
-protected: true
-hp: 50
-attack: 1
-strength: 1
-defense: 1
-speed: 5
-aggressive: false
-respawn_ticks: 30
-attack_type: crush
-stab_defense: 0
-slash_defense: 0
-crush_defense: 0
-science_defense: 0
-ranged_defense: 0
-idle_descriptions:
- - "scrapes at a piece of hide"
- - "examines a stack of leathers"
- - "sharpens a tanning knife"
- - "wipes sweat from his brow"
-```
-
----
-
-## 13. Example New Mobs
-
-These new mob YAMLs demonstrate the full stat spectrum. Place in `data/mobs/`.
-
-### 13.1 Rat (Level 1)
-
-```yaml
-id: rat
-name: rat
-description: "A large, mangy rat with beady red eyes."
-attack: 1
-strength: 1
-defense: 1
-hp: 2
-speed: 4
-aggressive: false
-respawn_ticks: 20
-attack_type: crush
-stab_defense: 0
-slash_defense: 0
-crush_defense: 0
-science_defense: 0
-ranged_defense: 0
-idle_descriptions:
- - "sniffs around the ground"
- - "nibbles on something"
- - "scurries in circles"
-drops:
- remains: bones
-```
-
-### 13.2 Goblin (Level 5)
-
-Weak to crush — goblins wear thin leather that resists slashing poorly and does nothing against blunt force.
-
-```yaml
-id: goblin
-name: goblin
-description: "A small green-skinned goblin with a rusty blade."
-attack: 1
-strength: 3
-defense: 3
-hp: 12
-speed: 5
-aggressive: true
-respawn_ticks: 25
-attack_type: slash
-attack_bonus: 3
-strength_bonus: 2
-stab_defense: 4
-slash_defense: 3
-crush_defense: -2
-science_defense: 0
-ranged_defense: 3
-idle_descriptions:
- - "mutters something unintelligible"
- - "waves a rusty blade around"
- - "picks its nose"
- - "kicks a pebble"
-drops:
- remains: bones
- loot:
- - item_id: credits
- weight: 80
- quantity: 5
- - item_id: credits
- weight: 20
- quantity: 25
-```
-
-### 13.3 Skeleton (Level 22)
-
-Skeletons have high slash defense (blade passes through gaps in bones), weak to crush (bones shatter).
-
-```yaml
-id: skeleton
-name: skeleton
-description: "An animated pile of bones, held together by dark energy."
-attack: 8
-strength: 6
-defense: 8
-hp: 22
-speed: 5
-aggressive: true
-respawn_ticks: 30
-attack_type: stab
-attack_bonus: 6
-strength_bonus: 4
-stab_defense: 10
-slash_defense: 18
-crush_defense: -5
-science_defense: -8
-ranged_defense: 10
-idle_descriptions:
- - "rattles ominously"
- - "clicks its jaw open and shut"
- - "stares with empty eye sockets"
-drops:
- remains: bones
- loot:
- - item_id: credits
- weight: 70
- quantity: 30
- - item_id: credits
- weight: 30
- quantity: 100
-```
-
-### 13.4 Zombie (Level 28)
-
-High slash defense (rotting flesh gives way), weak to crush and solar (sci-fi fire equivalent).
-
-```yaml
-id: zombie
-name: zombie
-description: "A shambling corpse reanimated by residual asteroid radiation."
-attack: 10
-strength: 8
-defense: 8
-hp: 30
-speed: 6
-aggressive: true
-respawn_ticks: 35
-attack_type: crush
-attack_bonus: 4
-strength_bonus: 6
-stab_defense: 8
-slash_defense: 20
-crush_defense: -8
-science_defense: -15
-ranged_defense: 8
-weakness: solar
-idle_descriptions:
- - "groans and shuffles forward"
- - "reaches out with decaying arms"
- - "stumbles and catches itself"
- - "stares blankly ahead"
-drops:
- remains: bones
- loot:
- - item_id: credits
- weight: 60
- quantity: 50
- - item_id: credits
- weight: 40
- quantity: 200
-```
-
-### 13.5 Moss Giant (Level 42)
-
-High HP, moderate defense across the board. Big and slow.
-
-```yaml
-id: moss_giant
-name: moss giant
-description: "A towering creature covered in moss and vines, its rocky skin dripping with moisture."
-attack: 12
-strength: 14
-defense: 10
-hp: 60
-speed: 7
-aggressive: true
-respawn_ticks: 45
-attack_type: crush
-attack_bonus: 10
-strength_bonus: 12
-stab_defense: 15
-slash_defense: 12
-crush_defense: 18
-science_defense: -5
-ranged_defense: 15
-idle_descriptions:
- - "surveys its territory with dull eyes"
- - "pulls a vine from its shoulder"
- - "stamps the ground, sending tremors"
- - "scratches its mossy hide"
-drops:
- remains: bones
- loot:
- - item_id: credits
- weight: 50
- quantity: 120
- - item_id: credits
- weight: 50
- quantity: 500
-```
-
-### 13.6 Lesser Drone (Level 53)
-
-A science-based mob. High melee defense, weak to ranged. Uses science attacks.
-
-```yaml
-id: lesser_drone
-name: lesser drone
-description: "A floating metallic drone with a pulsing energy core. Its surface crackles with electric discharge."
-attack: 1
-strength: 1
-defense: 18
-hp: 55
-ranged: 0
-science: 20
-speed: 4
-aggressive: true
-respawn_ticks: 50
-attack_type: science
-attack_bonus: 0
-science_bonus: 22
-strength_bonus: 0
-stab_defense: 35
-slash_defense: 35
-crush_defense: 30
-science_defense: 20
-ranged_defense: -10
-idle_descriptions:
- - "hovers silently, scanning the area"
- - "emits a low electronic hum"
- - "projects a thin beam of light across the floor"
- - "rotates slowly in place"
-drops:
- loot:
- - item_id: scrap_metal
- weight: 60
- - item_id: credits
- weight: 40
- quantity: 300
-```
-
-### 13.7 Greater Drone (Level 83)
-
-Stronger version of the lesser drone.
-
-```yaml
-id: greater_drone
-name: greater drone
-description: "A large, heavily armored drone with multiple energy emitters. Red warning lights pulse along its hull."
-attack: 1
-strength: 1
-defense: 30
-hp: 120
-ranged: 0
-science: 40
-speed: 4
-aggressive: true
-respawn_ticks: 80
-attack_type: science
-attack_bonus: 0
-science_bonus: 45
-strength_bonus: 0
-stab_defense: 55
-slash_defense: 55
-crush_defense: 50
-science_defense: 40
-ranged_defense: -20
-idle_descriptions:
- - "projects a defensive energy shield"
- - "recalibrates its targeting array"
- - "emits a warning klaxon"
- - "deploys sensor probes"
-drops:
- loot:
- - item_id: scrap_metal
- weight: 40
- quantity: 3
- - item_id: credits
- weight: 60
- quantity: 1500
-```
-
-### 13.8 Iron Sentinel (Level 92)
-
-A heavily armored mechanical guardian. Very high melee defense, weak to science attacks.
-
-```yaml
-id: iron_sentinel
-name: iron sentinel
-description: "A massive bipedal automaton forged from pre-regression alloys. Its joints grind with each deliberate step."
-attack: 25
-strength: 30
-defense: 45
-hp: 180
-speed: 6
-aggressive: true
-respawn_ticks: 100
-attack_type: crush
-attack_bonus: 40
-strength_bonus: 35
-stab_defense: 80
-slash_defense: 80
-crush_defense: 70
-science_defense: -30
-ranged_defense: 50
-idle_descriptions:
- - "stands motionless, servos whining softly"
- - "turns its armored head with a mechanical grind"
- - "vents steam from exhaust ports"
- - "scans the area with a red optical sensor"
-drops:
- loot:
- - item_id: scrap_metal
- weight: 30
- quantity: 5
- - item_id: runite_bar
- weight: 10
- - item_id: credits
- weight: 60
- quantity: 5000
-```
-
----
-
-## 14. Ranged Combat Integration
-
-### 14.1 Attack Flow
-
-When `weapon_type == "ranged"`:
-
-1. **Ammo check** (in `doAttack`, before `startCombat`):
- ```go
- if weaponDef.WeaponType == object.WeaponRanged {
- if _, hasAmmo := p.Equipment[object.SlotAmmo]; !hasAmmo || p.AmmoQty <= 0 {
- sess.WriteLine("You don't have any ammo equipped.")
- return
- }
- }
- ```
-
-2. **Attack roll**: Uses `Ranged` level + `RangedStyleBonus` + total `ranged_attack` from all equipment (bow + ammo + any ranged-boosting armor).
-
-3. **Max hit**: Uses `Ranged` level + `RangedStyleBonus` (strength portion) + total `ranged_strength` from equipment (primarily the ammo).
-
-4. **Mob defense**: Uses mob's `ranged_defense` value.
-
-5. **Ammo consumption**: After each attack (hit or miss), decrement `p.AmmoQty` by 1. If ammo reaches 0, unequip it and end combat.
-
-6. **XP**: 75% Ranged, 25% Hitpoints (all ranged styles).
-
-### 14.2 RangedStyleBonus
-
-| Style | Ranged Level Bonus | Defense Level Bonus | XP |
-|-------|-------------------|--------------------|----|
-| Accurate | +3 | +0 | 75% rng, 25% hp |
-| Aggressive | +3 | +0 | 75% rng, 25% hp |
-| Defensive | +0 | +3 | 75% rng, 25% hp |
-| Balanced | +1 | +1 | 75% rng, 25% hp |
-
-### 14.3 Ranged Max Hit Formula
-
-```
-effective_ranged = Ranged_level + style_bonus + 8
-max_hit = (effective_ranged * (total_ranged_strength + 64)) / 512
-```
-
-The `ranged_strength` comes primarily from ammo (arrows/bolts). Some equipment might also add ranged_strength in the future.
-
----
-
-## 15. Science Combat Integration
-
-Science (magic) combat is covered in a separate plan (`science.md`). Key integration points:
-
-- `attack_type: "science"` — uses Science level + `science_attack` bonus for accuracy
-- Max hit is determined by the equipped "mod" (spell/ability), NOT by equipment
-- Mob defense against science uses `science_defense`
-- XP: 75% Science, 25% Hitpoints
-- Science-based mobs (like drones) use `science` level + `science_bonus` for their attacks
-
-For this combat overhaul, the struct fields (`ScienceAttack`, `ScienceDefense`, `ScienceDamage`) are added but the science combat flow implementation is deferred.
-
----
-
-## 16. Backward Compatibility
-
-### Approach: Full YAML rewrite (recommended)
-
-All item YAML files are updated to use the new field names. The old `attack_bonus`, `defense_bonus`, and `attack` YAML tags are simply removed from the Go struct. Since `gopkg.in/yaml.v3` silently ignores unknown fields during unmarshalling, any YAML files accidentally left with old tags will simply have those values ignored (zeroed).
-
-**No custom `UnmarshalYAML`** is needed because:
-1. We control all the YAML files — they're in the repo under `data/`
-2. Player data (accounts/characters) doesn't store `ItemStats` — equipment is stored as item IDs
-3. There's no external API consuming these fields
-
-**Migration checklist:**
-- [x] All `attack_bonus: N` → replaced by `stab_attack`/`slash_attack`/`crush_attack` per weapon type
-- [x] All `defense_bonus: N` → replaced by per-type defense fields
-- [x] All `attack: N` (bows/arrows/bolts) → replaced by `ranged_attack` or `ranged_strength`
-- [x] All `strength_bonus: N` → kept as-is (same YAML tag)
-
-### What if old-format items are loaded?
-
-If someone has a custom item YAML with the old format, all combat stats will be 0. The item will have no combat effectiveness. This is acceptable — the items were already broken (bows used `attack:` which was silently ignored).
-
----
-
-## 17. Help Files
-
-### 17.1 Combat Help (`data/help/combat.yaml`)
-
-```yaml
-id: combat
-name: Combat
-aliases:
- - fighting
- - fight
-content: |
- COMBAT
-
- Attack a mob with: attack <mob>
-
- Your accuracy depends on your attack type (stab, slash, crush, ranged,
- or science) and your total equipment bonus for that type. Your weapon
- determines the attack type.
-
- Mobs have per-type defense bonuses. A skeleton with high slash defense
- but weak crush defense takes more hits from a mace than a sword.
-
- Use the 'style' command to change your combat style:
- accurate - +3 attack level, XP to attack
- aggressive - +3 strength level, XP to strength
- defensive - +3 defense level, XP to defense
- balanced - +1 to all, XP split evenly
-
- Use 'stats' to see your total equipment bonuses and current attack roll.
- Use 'look <item>' to see an item's combat stats.
-```
-
-### 17.2 Stats Help (`data/help/stats.yaml`)
-
-```yaml
-id: stats
-name: Stats
-aliases:
- - bonuses
- - equipment stats
-content: |
- STATS
-
- The 'stats' command shows your total equipment bonuses from all worn
- items, your current weapon and attack type, and your effective attack
- roll and max hit.
-
- Attack bonuses affect accuracy. The relevant bonus is determined by
- your weapon's attack type (stab, slash, crush, ranged, or science).
-
- Defense bonuses reduce incoming damage. When a mob attacks you, the
- game uses your defense bonus matching the mob's attack type.
-
- Strength bonus increases your max melee hit.
- Ranged strength increases your max ranged hit.
- Science damage increases your max science hit.
- Technology bonus will affect technology abilities (future).
-```
-
-### 17.3 Style Help (`data/help/style.yaml`)
-
-If this file already exists, update it. If not, create it:
-
-```yaml
-id: style
-name: Style
-aliases:
- - combat style
- - attack style
-content: |
- STYLE
-
- Usage: style <name>
-
- Changes your combat style. Prefix matching is supported.
-
- Styles:
- accurate - +3 to attack level, XP goes to Attack
- aggressive - +3 to strength level, XP goes to Strength
- defensive - +3 to defense level, XP goes to Defense
- balanced - +1 to all three, XP split evenly
-
- For ranged weapons:
- accurate - +3 to ranged level
- aggressive - +3 to ranged level (same as accurate for ranged)
- defensive - +3 to defense level
- balanced - +1 to ranged and defense
-
- Your style does NOT change your attack type. The attack type is
- determined by your weapon (e.g., swords use slash, daggers use stab).
-
- Use 'stats' to see how your current style affects your attack roll.
-```
-
----
-
-## 18. Implementation Order
-
-Step-by-step checklist. Each step should compile and pass `make vet` before moving to the next.
-
-### Phase 1: Struct Changes (no behavior change yet)
-
-- [ ] **Step 1:** Update `ItemStats` struct in `internal/object/item.go:67-73`
- - Replace the 5 fields with the 14 new fields
- - Add `AttackType string` field to `ItemDef` after line 41
-
-- [ ] **Step 2:** Update `MobDef` struct in `internal/world/mob.go:20-37`
- - Add: `Ranged`, `Science`, `AttackBonus`, `StrengthBonus`, `RangedBonus`, `ScienceBonus`, `AttackType`
- - Add: `StabDefense`, `SlashDefense`, `CrushDefense`, `ScienceDefense`, `RangedDefense`
- - Add: `Weakness`
-
-- [ ] **Step 3:** Update `MobInstance` struct in `internal/world/mob.go:39-62`
- - Mirror all new fields from MobDef
-
-- [ ] **Step 4:** Update `SeedMobs` in `internal/world/mob.go:213-259`
- - Copy all new fields from def to instance
-
-- [ ] **Step 5:** Fix all compilation errors from removed `AttackBonus`/`DefenseBonus`/`ScienceBonus` fields
- - `cmd_attack.go:187` — references `def.Stats.AttackBonus` → temporarily use `def.Stats.SlashAttack`
- - `cmd_attack.go:188` — references `def.Stats.StrengthBonus` → OK, field still exists
- - `cmd_attack.go:245` — references `def.Stats.DefenseBonus` → temporarily use sum of all defense types
- - Any other references to removed fields
-
-- [ ] **Step 6:** Run `make vet` — should pass with no errors
-
-### Phase 2: Item YAML Updates
-
-- [ ] **Step 7:** Update all 6 sword YAMLs (Section 11.1)
-- [ ] **Step 8:** Update all 6 dagger YAMLs (Section 11.2)
-- [ ] **Step 9:** Update all 8 axe YAMLs (Section 11.3)
-- [ ] **Step 10:** Update all 8 pickaxe YAMLs (Section 11.4)
-- [ ] **Step 11:** Update all 6 med helm YAMLs (Section 11.5)
-- [ ] **Step 12:** Update all 6 full helm YAMLs (Section 11.6)
-- [ ] **Step 13:** Update all 6 platebody YAMLs (Section 11.7)
-- [ ] **Step 14:** Update all 12 bow YAMLs (Section 11.8)
-- [ ] **Step 15:** Update all 6 arrow YAMLs (Section 11.9)
-- [ ] **Step 16:** Update all 10 bolt YAMLs (Section 11.10)
-- [ ] **Step 17:** Update all 9 leather/ranged armor YAMLs (Section 11.11)
-
-### Phase 3: Mob YAML Updates
-
-- [ ] **Step 18:** Update all 5 existing mob YAMLs (Section 12)
-- [ ] **Step 19:** Create 8 new mob YAMLs (Section 13)
-
-### Phase 4: Formula & Combat Logic
-
-- [ ] **Step 20:** Rewrite `internal/combat/formulas.go` (Section 5)
- - New `HitChance` function
- - Updated `HitCheck` to use probability curve
- - Add `RangedStyleBonus`
- - Add `SelectAttackBonus` and `SelectDefenseBonus`
-
-- [ ] **Step 21:** Add `playerEquipBonuses` helper (Section 8)
- - New file `internal/game/equip_stats.go` or add to `cmd_attack.go`
-
-- [ ] **Step 22:** Update `playerAttack` in `cmd_attack.go` (Section 6.1)
- - Determine attack type from weapon
- - Use `playerEquipBonuses` for totals
- - Use `SelectAttackBonus` for the right attack bonus
- - Use `SelectDefenseBonus` for mob's matching defense
- - Melee path: use Attack level + melee style bonus
- - Ranged path: use Ranged level + ranged style bonus + ammo consumption
-
-- [ ] **Step 23:** Update `mobAttack` in `cmd_attack.go` (Section 6.2)
- - Use mob's `AttackType` (default "crush")
- - Use mob's `AttackBonus` in attack roll
- - Use mob's `StrengthBonus` in max hit
- - Use player's matching defense bonus for the mob's attack type
-
-- [ ] **Step 24:** Update `awardCombatXP` (Section 6.3)
- - Add `isRanged` parameter
- - Ranged path: 75% Ranged, 25% Hitpoints
-
-- [ ] **Step 25:** Update `mobCombatLevel` in `utils.go` (Section 7.4)
- - New formula accounting for Ranged/Science levels
-
-- [ ] **Step 26:** Add ammo check in `doAttack` (Section 6.4)
-
-- [ ] **Step 27:** Run `make vet` and `make test`
-
-### Phase 5: Display Changes
-
-- [ ] **Step 28:** Add `showItemStats` helper to `cmd_look.go` (Section 9.1)
-- [ ] **Step 29:** Call `showItemStats` in item examination paths in `doLookTarget`
-- [ ] **Step 30:** Update mob examination in `doLookTarget` (Section 9.2)
-- [ ] **Step 31:** Create `cmd_stats.go` with `doStats` command (Section 10)
-- [ ] **Step 32:** Register `stats` command in `classifyCommand` and `executeCommand` in `game.go`
-- [ ] **Step 33:** Create help YAML files (Section 17)
-
-### Phase 6: Final Verification
-
-- [ ] **Step 34:** Run `make build`
-- [ ] **Step 35:** Run `make test`
-- [ ] **Step 36:** Run `make vet`
-- [ ] **Step 37:** Manual testing: equip weapons, check `stats`, `look <item>`, attack mobs, verify damage/accuracy feel reasonable
-- [ ] **Step 38:** Verify ranged combat works with ammo consumption
-- [ ] **Step 39:** Verify `look <mob>` shows defense bonuses
-
----
-
-## Appendix A: Complete Item Stats Reference Table
-
-### Melee Weapons
-
-| Item | Type | Stab | Slash | Crush | Str | Speed |
-|------|------|------|-------|-------|-----|-------|
-| bronze_dagger | stab | +5 | +3 | -1 | +4 | 3 |
-| iron_dagger | stab | +8 | +5 | -1 | +7 | 3 |
-| steel_dagger | stab | +12 | +7 | -1 | +10 | 3 |
-| mithril_dagger | stab | +17 | +10 | -1 | +14 | 3 |
-| adamant_dagger | stab | +23 | +14 | -1 | +19 | 3 |
-| rune_dagger | stab | +30 | +18 | -1 | +24 | 3 |
-| bronze_sword | slash | +4 | +10 | -2 | +7 | 4 |
-| iron_sword | slash | +7 | +15 | -2 | +10 | 4 |
-| steel_sword | slash | +10 | +22 | -2 | +14 | 4 |
-| mithril_sword | slash | +14 | +30 | -2 | +20 | 4 |
-| adamant_sword | slash | +20 | +40 | -2 | +27 | 4 |
-| rune_sword | slash | +27 | +52 | -2 | +36 | 4 |
-| bronze_axe | slash | -2 | +7 | +5 | +5 | 5 |
-| iron_axe | slash | -2 | +10 | +7 | +8 | 5 |
-| steel_axe | slash | -2 | +15 | +11 | +12 | 5 |
-| black_axe | slash | -2 | +18 | +14 | +14 | 5 |
-| mithril_axe | slash | -2 | +21 | +16 | +17 | 5 |
-| adamant_axe | slash | -2 | +29 | +22 | +24 | 5 |
-| rune_axe | slash | -2 | +38 | +29 | +32 | 5 |
-| dragon_axe | slash | -2 | +43 | +32 | +36 | 5 |
-| bronze_pickaxe | stab | +4 | -2 | +2 | +3 | 5 |
-| iron_pickaxe | stab | +7 | -2 | +3 | +5 | 5 |
-| steel_pickaxe | stab | +10 | -2 | +4 | +7 | 5 |
-| black_pickaxe | stab | +14 | -2 | +6 | +9 | 5 |
-| mithril_pickaxe | stab | +17 | -2 | +8 | +12 | 5 |
-| adamant_pickaxe | stab | +23 | -2 | +10 | +16 | 5 |
-| rune_pickaxe | stab | +30 | -2 | +14 | +22 | 5 |
-| dragon_pickaxe | stab | +34 | -2 | +16 | +26 | 5 |
-
-### Metal Armor (Defense Bonuses)
-
-| Item | Slot | Stab | Slash | Crush | Science | Ranged |
-|------|------|------|-------|-------|---------|--------|
-| bronze_med_helm | head | +3 | +4 | +2 | -1 | +3 |
-| iron_med_helm | head | +5 | +6 | +3 | -1 | +5 |
-| steel_med_helm | head | +7 | +9 | +5 | -2 | +7 |
-| mithril_med_helm | head | +10 | +12 | +7 | -3 | +10 |
-| adamant_med_helm | head | +14 | +16 | +10 | -3 | +14 |
-| rune_med_helm | head | +19 | +22 | +13 | -4 | +19 |
-| bronze_full_helm | head | +5 | +6 | +4 | -3 | +5 |
-| iron_full_helm | head | +8 | +9 | +6 | -3 | +8 |
-| steel_full_helm | head | +12 | +13 | +9 | -5 | +12 |
-| mithril_full_helm | head | +16 | +18 | +12 | -6 | +16 |
-| adamant_full_helm | head | +22 | +24 | +16 | -8 | +22 |
-| rune_full_helm | head | +30 | +33 | +22 | -11 | +30 |
-| bronze_platebody | torso | +12 | +15 | +10 | -10 | +12 |
-| iron_platebody | torso | +18 | +22 | +14 | -10 | +18 |
-| steel_platebody | torso | +27 | +32 | +22 | -15 | +27 |
-| mithril_platebody | torso | +36 | +42 | +30 | -20 | +36 |
-| adamant_platebody | torso | +49 | +55 | +40 | -25 | +49 |
-| rune_platebody | torso | +65 | +72 | +52 | -30 | +65 |
-
-### Leather/Ranged Armor (Defense Bonuses)
-
-| Item | Slot | Stab | Slash | Crush | Science | Ranged | Other |
-|------|------|------|-------|-------|---------|--------|-------|
-| leather_cowl | head | +1 | +2 | +1 | +2 | +4 | |
-| leather_body | torso | +4 | +7 | +5 | +4 | +10 | |
-| leather_chaps | legs | +2 | +4 | +3 | +3 | +6 | |
-| leather_gloves | hands | +1 | +1 | +1 | +1 | +2 | |
-| leather_vambraces | hands | +2 | +2 | +2 | +2 | +4 | ranged_attack: +4 |
-| leather_boots | feet | +1 | +1 | +1 | +1 | +2 | |
-| coif | head | +2 | +4 | +2 | +4 | +6 | ranged_attack: +2 |
-| hard_leather_body | torso | +8 | +12 | +8 | +6 | +14 | |
-| hard_leather_shield | off_hand | +5 | +7 | +5 | +4 | +8 | |
-
-### Ranged Weapons (Attack Bonuses)
-
-| Item | Ranged Attack | Ranged Str | Speed |
-|------|--------------|------------|-------|
-| shortbow | +8 | 0 | 4 |
-| oak_shortbow | +14 | 0 | 4 |
-| willow_shortbow | +20 | 0 | 4 |
-| maple_shortbow | +29 | 0 | 4 |
-| yew_shortbow | +47 | 0 | 4 |
-| magic_shortbow | +69 | 0 | 4 |
-| longbow | +8 | 0 | 6 |
-| oak_longbow | +14 | 0 | 6 |
-| willow_longbow | +20 | 0 | 6 |
-| maple_longbow | +29 | 0 | 6 |
-| yew_longbow | +47 | 0 | 6 |
-| magic_longbow | +69 | 0 | 6 |
-
-### Ranged Ammo (Ranged Strength)
-
-| Item | Ranged Str |
-|------|-----------|
-| bronze_arrow | +7 |
-| iron_arrow | +10 |
-| steel_arrow | +16 |
-| mithril_arrow | +22 |
-| adamant_arrow | +31 |
-| rune_arrow | +49 |
-| bronze_bolts | +10 |
-| iron_bolts | +17 |
-| steel_bolts | +25 |
-| mithril_bolts | +32 |
-| adamant_bolts | +43 |
-| rune_bolts | +55 |
-| sapphire_bolts | +36 |
-| emerald_bolts | +39 |
-| ruby_bolts | +49 |
-| diamond_bolts | +52 |
-
----
-
-## Appendix B: OSRS Accuracy Formula Explanation
-
-The true OSRS accuracy formula is:
-
-```
-if attack_roll > defense_roll:
- hit_chance = 1 - (defense_roll + 2) / (2 * (attack_roll + 1))
-else:
- hit_chance = attack_roll / (2 * (defense_roll + 1))
-```
-
-This produces a smooth curve rather than the current binary hit/miss:
-- Equal rolls → ~50% hit chance
-- 2x attack vs defense → ~75% hit chance
-- 0.5x attack vs defense → ~25% hit chance
-- Very high attack → approaches ~100% but never reaches it
-- Very low attack → approaches ~0% but never reaches it
-
-This is more balanced than the current system where `attackRoll > defenseRoll` is always a hit and `<` is always a miss.
-
----
-
-## Appendix C: Files Modified (Summary)
-
-| File | Changes |
-|------|---------|
-| `internal/object/item.go` | `ItemStats` struct rewrite (14 fields), `AttackType` field on `ItemDef` |
-| `internal/world/mob.go` | New fields on `MobDef`, `MobInstance`, `SeedMobs` propagation |
-| `internal/combat/formulas.go` | Full rewrite: `HitChance`, `HitCheck`, `RangedStyleBonus`, `SelectAttackBonus`, `SelectDefenseBonus` |
-| `internal/game/cmd_attack.go` | `playerAttack` rewrite, `mobAttack` rewrite, `awardCombatXP` ranged support, ammo consumption |
-| `internal/game/equip_stats.go` | New file: `playerEquipBonuses` helper |
-| `internal/game/cmd_stats.go` | New file: `doStats` command |
-| `internal/game/cmd_look.go` | `showItemStats` helper, item/mob examination updates |
-| `internal/game/game.go` | Register `stats` in `classifyCommand` and `executeCommand` |
-| `internal/game/utils.go` | Updated `mobCombatLevel` formula |
-| `data/items/*.yaml` | ~66 item files updated with per-type stats |
-| `data/mobs/*.yaml` | 5 existing mobs updated, 8 new mobs added |
-| `data/help/combat.yaml` | New help file |
-| `data/help/stats.yaml` | New help file |
-| `data/help/style.yaml` | New/updated help file |