Un-overload Object fields and pre-parse damage dice (refactor step 3)

Object.Arm carried four meanings in C (o_arm/o_charges/o_goldval plus
ring bonuses); it is now four fields: ArmorClass, Charges, GoldValue,
and Bonus. Stats.Arm becomes Stats.ArmorClass. The Charges()/GoldVal()
accessor pair is gone.

Damage dice strings ("1x4/1x2") are parsed once into DiceSpec — at
table definition for the bestiary and weapon tables, at creation for
items — instead of re-parsed with atoi on every swing as fight.c
roll_em did. ParseDice preserves the C parse semantics exactly,
including the junk-tolerant "%%%x0" bestiary placeholder and the
"000x0" flytrap reset (regression-tested in dice_test.go); the
flytrap's growing grip becomes DiceSpec{{VfHit, 1}}.

Save format bumps to 5.4.4-go3 (field renames and retypes would
silently zero under gob's match-by-name decoding).

No behavior change; full suite green.
This commit is contained in:
2026-07-06 23:07:57 +02:00
parent c75af2ec22
commit 2eff377a73
28 changed files with 268 additions and 186 deletions

View File

@@ -1,10 +1,6 @@
package game
import (
"fmt"
"strconv"
"strings"
)
import "strconv"
// fight.c — all the fighting gets done here.
@@ -245,7 +241,7 @@ func (g *RogueGame) attack(mp *Monster) int {
// Venus Flytrap stops the poor guy from moving
p.Flags.Set(Held)
p.VfHit++
g.Monsters['F'-'A'].Stats.Dmg = fmt.Sprintf("%dx1", p.VfHit)
g.Monsters['F'-'A'].Stats.Dmg = DiceSpec{{Count: p.VfHit, Sides: 1}}
if p.Stats.HP--; p.Stats.HP <= 0 {
g.death('F')
}
@@ -322,34 +318,34 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool
p := &g.Player
att := &thatt.Stats
def := &thdef.Stats
var cp string
var attacks DiceSpec
var hplus, dplus int
if weap == nil {
cp = att.Dmg
attacks = att.Dmg
} else {
hplus = weap.HPlus
dplus = weap.DPlus
if weap == p.CurWeapon {
if p.IsRing(Left, RingIncreaseDamage) {
dplus += p.CurRing[Left].Arm
dplus += p.CurRing[Left].Bonus
} else if p.IsRing(Left, RingDexterity) {
hplus += p.CurRing[Left].Arm
hplus += p.CurRing[Left].Bonus
}
if p.IsRing(Right, RingIncreaseDamage) {
dplus += p.CurRing[Right].Arm
dplus += p.CurRing[Right].Bonus
} else if p.IsRing(Right, RingDexterity) {
hplus += p.CurRing[Right].Arm
hplus += p.CurRing[Right].Bonus
}
}
cp = weap.Damage
attacks = weap.Damage
if hurl {
if weap.Flags.Has(Missile) && p.CurWeapon != nil &&
WeaponKind(p.CurWeapon.Which) == weap.Launch {
cp = weap.HurlDmg
attacks = weap.HurlDmg
hplus += p.CurWeapon.HPlus
dplus += p.CurWeapon.DPlus
} else if weap.Launch < 0 {
cp = weap.HurlDmg
attacks = weap.HurlDmg
}
}
}
@@ -358,40 +354,28 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool
if !thdef.Flags.Has(Awake) {
hplus += 4
}
defArm := def.Arm
defArm := def.ArmorClass
if def == &p.Stats {
if p.CurArmor != nil {
defArm = p.CurArmor.Arm
defArm = p.CurArmor.ArmorClass
}
if p.IsRing(Left, RingProtection) {
defArm -= p.CurRing[Left].Arm
defArm -= p.CurRing[Left].Bonus
}
if p.IsRing(Right, RingProtection) {
defArm -= p.CurRing[Right].Arm
defArm -= p.CurRing[Right].Bonus
}
}
didHit := false
for cp != "" {
ndice := cAtoi(cp)
xi := strings.IndexByte(cp, 'x')
if xi < 0 {
break
}
cp = cp[xi+1:]
nsides := cAtoi(cp)
for _, atk := range attacks {
if g.swing(att.Lvl, defArm, hplus+strPlus[att.Str]) {
proll := g.roll(ndice, nsides)
proll := g.roll(atk.Count, atk.Sides)
damage := dplus + proll + addDam[att.Str]
if damage > 0 {
def.HP -= damage
}
didHit = true
}
si := strings.IndexByte(cp, '/')
if si < 0 {
break
}
cp = cp[si+1:]
}
return didHit
}
@@ -531,7 +515,7 @@ func (g *RogueGame) killed(tp *Monster, pr bool) {
case 'F':
p.Flags.Clear(Held)
p.VfHit = 0
g.Monsters['F'-'A'].Stats.Dmg = "000x0"
g.Monsters['F'-'A'].Stats.Dmg = dice("000x0")
case 'L':
pos, ok := g.fallpos(tp.Pos)
if ok {
@@ -540,9 +524,9 @@ func (g *RogueGame) killed(tp *Monster, pr bool) {
if ok && g.Depth >= g.MaxDepth {
gold := newObject()
gold.Kind = KindGold
gold.SetGoldVal(g.goldCalc())
gold.GoldValue = g.goldCalc()
if g.save(VsMagic) {
gold.Arm += g.goldCalc() + g.goldCalc() + g.goldCalc() + g.goldCalc()
gold.GoldValue += g.goldCalc() + g.goldCalc() + g.goldCalc() + g.goldCalc()
}
attachObj(&tp.Pack, gold)
}