Give item categories and subtypes real types (refactor step 2)

ObjectKind separates what an item is from how it draws: Object.Type
(a byte that doubled as the map character) becomes Kind ObjectKind,
with Glyph() producing the display character and objectKindForGlyph
converting back at the map boundary. KindWand covers the C 'stick'
category. The magic-missile bolt uses KindGold, matching C's literal
o_type='*' trick, with a comment.

PotionKind, ScrollKind, RingKind, WandKind, WeaponKind, ArmorKind and
TrapKind are now iota enums with Stringer (names come from the base
info tables); Object gains typed accessors (obj.RingKind(), ...) and
Launch is typed WeaponKind. beTrapped returns TrapKind. The
getItem/inventory/whatis prompt filters take ObjectKind, with
KindCallable/KindRingOrStick replacing the C CALLABLE/R_OR_S
sentinels; wizard.c's type_name table is subsumed by
ObjectKind.String(). IsRing/IsWearing/initWeapon/doPot signatures are
typed accordingly.

The save format version becomes 5.4.4-go2: the gob field rename would
otherwise silently zero Kind when reading old saves.

No behavior change; full suite green.
This commit is contained in:
2026-07-06 22:00:43 +02:00
parent 626f8c5a7d
commit b940cfc41f
27 changed files with 426 additions and 259 deletions

View File

@@ -13,14 +13,14 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
var pb strings.Builder
which := obj.Which
it := &g.Items
switch obj.Type {
case Potion:
switch obj.Kind {
case KindPotion:
g.nameit(&pb, obj, "potion", it.PotColors[which], &it.Potions[which], nullstr)
case Ring:
case KindRing:
g.nameit(&pb, obj, "ring", it.RingStones[which], &it.Rings[which], ringNum)
case Stick:
case KindWand:
g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr)
case Scroll:
case KindScroll:
if obj.Count == 1 {
pb.WriteString("A scroll ")
} else {
@@ -34,7 +34,7 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
} else {
fmt.Fprintf(&pb, "titled '%s'", it.ScrNames[which])
}
case Food:
case KindFood:
if which == 1 {
if obj.Count == 1 {
fmt.Fprintf(&pb, "A%s %s", vowelstr(g.Fruit), g.Fruit)
@@ -48,7 +48,7 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
fmt.Fprintf(&pb, "%d rations of food", obj.Count)
}
}
case Weapon:
case KindWeapon:
sp := it.Weapons[which].Name
if obj.Count > 1 {
fmt.Fprintf(&pb, "%d ", obj.Count)
@@ -66,7 +66,7 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label)
}
case Armor:
case KindArmor:
sp := it.Armors[which].Name
if obj.Flags.Has(Known) {
fmt.Fprintf(&pb, "%s %s [", num(aClass[which]-obj.Arm, 0, Armor), sp)
@@ -80,9 +80,9 @@ func (g *RogueGame) invName(obj *Object, drop bool) string {
if obj.Label != "" {
fmt.Fprintf(&pb, " called %s", obj.Label)
}
case Amulet:
case KindAmulet:
pb.WriteString("The Amulet of Yendor")
case Gold:
case KindGold:
fmt.Fprintf(&pb, "%d Gold pieces", obj.GoldVal())
}
@@ -121,20 +121,20 @@ func (g *RogueGame) dropIt() {
g.msg("there is something there already")
return
}
obj := g.getItem("drop", 0)
obj := g.getItem("drop", KindNone)
if obj == nil {
return
}
if !g.dropCheck(obj) {
return
}
obj = g.leavePack(obj, true, !IsMult(obj.Type))
obj = g.leavePack(obj, true, !obj.Kind.MergesInPack())
// Link it into the level object list
attachObj(&g.Level.Objects, obj)
g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Type)
g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Kind.Glyph())
g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Set(FDropped)
obj.Pos = p.Pos
if obj.Type == Amulet {
if obj.Kind == KindAmulet {
g.HasAmulet = false
}
g.msg("dropped %s", g.invName(obj, true))
@@ -166,7 +166,7 @@ func (g *RogueGame) dropCheck(obj *Object) bool {
hand = Left
}
p.CurRing[hand] = nil
switch obj.Which {
switch obj.RingKind() {
case RingAddStrength:
g.chgStr(-obj.Arm)
case RingSeeInvisible:
@@ -195,13 +195,13 @@ func (g *RogueGame) newThing() *Object {
}
switch kind {
case 0:
cur.Type = Potion
cur.Kind = KindPotion
cur.Which = pickOne(g, g.Items.Potions[:])
case 1:
cur.Type = Scroll
cur.Kind = KindScroll
cur.Which = pickOne(g, g.Items.Scrolls[:])
case 2:
cur.Type = Food
cur.Kind = KindFood
g.Player.NoFood = 0
if g.rnd(10) != 0 {
cur.Which = 0
@@ -209,7 +209,7 @@ func (g *RogueGame) newThing() *Object {
cur.Which = 1
}
case 3:
g.initWeapon(cur, pickOne(g, g.Items.Weapons[:NumWeaponTypes]))
g.initWeapon(cur, WeaponKind(pickOne(g, g.Items.Weapons[:NumWeaponTypes])))
if r := g.rnd(100); r < 10 {
cur.Flags.Set(Cursed)
cur.HPlus -= g.rnd(3) + 1
@@ -217,7 +217,7 @@ func (g *RogueGame) newThing() *Object {
cur.HPlus += g.rnd(3) + 1
}
case 4:
cur.Type = Armor
cur.Kind = KindArmor
cur.Which = pickOne(g, g.Items.Armors[:])
cur.Arm = aClass[cur.Which]
if r := g.rnd(100); r < 20 {
@@ -227,9 +227,9 @@ func (g *RogueGame) newThing() *Object {
cur.Arm -= g.rnd(3) + 1
}
case 5:
cur.Type = Ring
cur.Kind = KindRing
cur.Which = pickOne(g, g.Items.Rings[:])
switch cur.Which {
switch cur.RingKind() {
case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage:
if cur.Arm = g.rnd(3); cur.Arm == 0 {
cur.Arm = -1
@@ -239,7 +239,7 @@ func (g *RogueGame) newThing() *Object {
cur.Flags.Set(Cursed)
}
case 6:
cur.Type = Stick
cur.Kind = KindWand
cur.Which = pickOne(g, g.Items.Sticks[:])
g.fixStick(cur)
}
@@ -336,7 +336,7 @@ func (g *RogueGame) printDisc(typ byte) {
numFound := 0
for i := range info {
if info[order[i]].Know || info[order[i]].Guess != "" {
obj.Type = typ
obj.Kind = objectKindForGlyph(typ)
obj.Which = order[i]
g.addLine("%s", g.invName(&obj, false))
numFound++