diff options
Diffstat (limited to 'skill_plans/combat.md')
| -rw-r--r-- | skill_plans/combat.md | 3402 |
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 | |
