From b940cfc41fbe08f396e20a0ac28da9adb3893549 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 6 Jul 2026 22:00:43 +0200 Subject: [PATCH] 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. --- game/armor.go | 4 +- game/chase.go | 4 +- game/command.go | 22 +++---- game/creature.go | 6 +- game/daemons.go | 2 +- game/effects_test.go | 24 ++++---- game/fight.go | 8 +-- game/init.go | 14 ++--- game/misc.go | 4 +- game/move.go | 8 +-- game/newlevel.go | 6 +- game/newlevel_test.go | 13 ++-- game/object.go | 135 ++++++++++++++++++++++++++++++++++++++---- game/pack.go | 34 ++++++----- game/potions.go | 24 ++++---- game/rings.go | 12 ++-- game/rip.go | 22 +++---- game/rooms.go | 2 +- game/save.go | 9 ++- game/save_test.go | 2 +- game/scrolls.go | 30 +++++----- game/sticks.go | 24 ++++---- game/tables_test.go | 2 +- game/things.go | 48 +++++++-------- game/types.go | 131 ++++++++++++++++++++++++++++++---------- game/weapons.go | 28 ++++----- game/wizard.go | 67 +++++++-------------- 27 files changed, 426 insertions(+), 259 deletions(-) diff --git a/game/armor.go b/game/armor.go index e17373d..bd01068 100644 --- a/game/armor.go +++ b/game/armor.go @@ -5,7 +5,7 @@ package game // wear lets the player put armor on (armor.c wear). func (g *RogueGame) wear() { p := &g.Player - obj := g.getItem("wear", int(Armor)) + obj := g.getItem("wear", KindArmor) if obj == nil { return } @@ -18,7 +18,7 @@ func (g *RogueGame) wear() { g.After = false return } - if obj.Type != Armor { + if obj.Kind != KindArmor { g.msg("you can't wear that") return } diff --git a/game/chase.go b/game/chase.go index 37b158f..b52eb9a 100644 --- a/game/chase.go +++ b/game/chase.go @@ -233,7 +233,7 @@ func (g *RogueGame) chase(tp *Monster, ee Coord) bool { break } } - if found != nil && found.Which == ScrollScareMonster { + if found != nil && found.ScrollKind() == ScrollScareMonster { continue } } @@ -375,7 +375,7 @@ func (g *RogueGame) findDest(tp *Monster) *Coord { return &g.Player.Pos } for _, obj := range g.Level.Objects { - if obj.Type == Scroll && obj.Which == ScrollScareMonster { + if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster { continue } if g.roomin(obj.Pos) == tp.Room && g.rnd(100) < prob { diff --git a/game/command.go b/game/command.go index 298b3e9..061418a 100644 --- a/game/command.go +++ b/game/command.go @@ -149,7 +149,7 @@ over: } if found != nil { if !g.levitCheck() { - g.pickUp(found.Type) + g.pickUp(found.Kind.Glyph()) } } else { if !g.Options.Terse { @@ -409,7 +409,7 @@ func (g *RogueGame) wizardCommand(ch byte) { case CTRL('X'): g.turnSee(p.On(SenseMonsters)) case CTRL('~'): - if item := g.getItem("charge", int(Stick)); item != nil { + if item := g.getItem("charge", KindWand); item != nil { item.SetCharges(10000) } case CTRL('I'): @@ -425,8 +425,8 @@ func (g *RogueGame) wizardCommand(ch byte) { p.CurWeapon = obj // And his suit of armor obj = newObject() - obj.Type = Armor - obj.Which = ArmorPlateMail + obj.Kind = KindArmor + obj.Which = int(ArmorPlateMail) obj.Arm = -5 obj.Flags.Set(Known) obj.Count = 1 @@ -671,7 +671,7 @@ func (g *RogueGame) levitCheck() bool { // call allows a user to call a potion, scroll, or ring something // (command.c call). func (g *RogueGame) call() { - obj := g.getItem("call", Callable) + obj := g.getItem("call", KindCallable) // Make certain that it is something that we want to name if obj == nil { return @@ -681,20 +681,20 @@ func (g *RogueGame) call() { var know *bool var guess *string it := &g.Items - switch obj.Type { - case Ring: + switch obj.Kind { + case KindRing: op = &it.Rings[obj.Which] elsewise = it.RingStones[obj.Which] - case Potion: + case KindPotion: op = &it.Potions[obj.Which] elsewise = it.PotColors[obj.Which] - case Scroll: + case KindScroll: op = &it.Scrolls[obj.Which] elsewise = it.ScrNames[obj.Which] - case Stick: + case KindWand: op = &it.Sticks[obj.Which] elsewise = it.WandMade[obj.Which] - case Food: + case KindFood: g.msg("you can't call that anything") return default: diff --git a/game/creature.go b/game/creature.go index 2dd0cf0..f112f05 100644 --- a/game/creature.go +++ b/game/creature.go @@ -40,12 +40,12 @@ type Player struct { func (c *Creature) On(f CreatureFlags) bool { return c.Flags.Has(f) } // IsRing is the C ISRING(hand, ring) macro. -func (p *Player) IsRing(hand, ring int) bool { - return p.CurRing[hand] != nil && p.CurRing[hand].Which == ring +func (p *Player) IsRing(hand int, ring RingKind) bool { + return p.CurRing[hand] != nil && p.CurRing[hand].RingKind() == ring } // IsWearing is the C ISWEARING(ring) macro: is the ring on either hand. -func (p *Player) IsWearing(ring int) bool { +func (p *Player) IsWearing(ring RingKind) bool { return p.IsRing(Left, ring) || p.IsRing(Right, ring) } diff --git a/game/daemons.go b/game/daemons.go index 10f3086..b38abe3 100644 --- a/game/daemons.go +++ b/game/daemons.go @@ -194,7 +194,7 @@ func (g *RogueGame) comeDown(int) { // undo the things for _, tp := range g.Level.Objects { if g.cansee(tp.Pos.Y, tp.Pos.X) { - g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Type) + g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Kind.Glyph()) } } diff --git a/game/effects_test.go b/game/effects_test.go index 9dd0e3e..faecadd 100644 --- a/game/effects_test.go +++ b/game/effects_test.go @@ -22,8 +22,8 @@ func give(g *RogueGame, obj *Object) byte { func TestQuaffHealingPotion(t *testing.T) { g := mkGameInput(t, 5, "") pot := newObject() - pot.Type = Potion - pot.Which = PotionHealing + pot.Kind = KindPotion + pot.Which = int(PotionHealing) ch := give(g, pot) g.scr.term.(*testTerm).input = []byte{ch} @@ -43,8 +43,8 @@ func TestQuaffHealingPotion(t *testing.T) { func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) { g := mkGameInput(t, 5, "") pot := newObject() - pot.Type = Potion - pot.Which = PotionConfusion + pot.Kind = KindPotion + pot.Which = int(PotionConfusion) ch := give(g, pot) g.scr.term.(*testTerm).input = []byte{ch} @@ -67,8 +67,8 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) { func TestReadEnchantArmor(t *testing.T) { g := mkGameInput(t, 5, "") scr := newObject() - scr.Type = Scroll - scr.Which = ScrollEnchantArmor + scr.Kind = KindScroll + scr.Which = int(ScrollEnchantArmor) ch := give(g, scr) g.scr.term.(*testTerm).input = []byte{ch} @@ -90,8 +90,8 @@ func TestReadHoldMonsterFreezesAdjacent(t *testing.T) { tp := spawnAdjacent(g, 'Z') tp.Flags.Set(Awake) scr := newObject() - scr.Type = Scroll - scr.Which = ScrollHoldMonster + scr.Kind = KindScroll + scr.Which = int(ScrollHoldMonster) ch := give(g, scr) g.scr.term.(*testTerm).input = []byte{ch} @@ -111,8 +111,8 @@ func TestHoldScrollGreedyMonsterQuirk(t *testing.T) { tp := spawnAdjacent(g, 'O') tp.Flags.Set(Awake) scr := newObject() - scr.Type = Scroll - scr.Which = ScrollHoldMonster + scr.Kind = KindScroll + scr.Which = int(ScrollHoldMonster) ch := give(g, scr) g.scr.term.(*testTerm).input = []byte{ch} @@ -132,8 +132,8 @@ func TestZapSlowMonster(t *testing.T) { g := mkGameInput(t, 5, "") tp := spawnAdjacent(g, 'Z') stick := newObject() - stick.Type = Stick - stick.Which = WandSlowMonster + stick.Kind = KindWand + stick.Which = int(WandSlowMonster) g.fixStick(stick) ch := give(g, stick) g.scr.term.(*testTerm).input = []byte{ch} diff --git a/game/fight.go b/game/fight.go index bbf2704..96263c7 100644 --- a/game/fight.go +++ b/game/fight.go @@ -344,7 +344,7 @@ func (g *RogueGame) rollEm(thatt, thdef *Creature, weap *Object, hurl bool) bool cp = weap.Damage if hurl { if weap.Flags.Has(Missile) && p.CurWeapon != nil && - p.CurWeapon.Which == weap.Launch { + WeaponKind(p.CurWeapon.Which) == weap.Launch { cp = weap.HurlDmg hplus += p.CurWeapon.HPlus dplus += p.CurWeapon.DPlus @@ -425,7 +425,7 @@ func (g *RogueGame) thunk(weap *Object, mname string, noend bool) { if g.ToDeath { return } - if weap.Type == Weapon { + if weap.Kind == KindWeapon { g.addmsg("the %s hits ", g.Items.Weapons[weap.Which].Name) } else { g.addmsg("you hit ") @@ -488,7 +488,7 @@ func (g *RogueGame) bounce(weap *Object, mname string, noend bool) { if g.ToDeath { return } - if weap.Type == Weapon { + if weap.Kind == KindWeapon { g.addmsg("the %s misses ", g.Items.Weapons[weap.Which].Name) } else { g.addmsg("you missed ") @@ -539,7 +539,7 @@ func (g *RogueGame) killed(tp *Monster, pr bool) { } if ok && g.Depth >= g.MaxDepth { gold := newObject() - gold.Type = Gold + gold.Kind = KindGold gold.SetGoldVal(g.goldCalc()) if g.save(VsMagic) { gold.Arm += g.goldCalc() + g.goldCalc() + g.goldCalc() + g.goldCalc() diff --git a/game/init.go b/game/init.go index 9ada940..8830f56 100644 --- a/game/init.go +++ b/game/init.go @@ -13,13 +13,13 @@ func (g *RogueGame) initPlayer() { p.FoodLeft = HungerTime // Give him some food obj := newObject() - obj.Type = Food + obj.Kind = KindFood obj.Count = 1 g.addPack(obj, true) // And his suit of armor obj = newObject() - obj.Type = Armor - obj.Which = ArmorRingMail + obj.Kind = KindArmor + obj.Which = int(ArmorRingMail) obj.Arm = aClass[ArmorRingMail] - 1 obj.Flags.Set(Known) obj.Count = 1 @@ -51,7 +51,7 @@ func (g *RogueGame) initPlayer() { // (init.c init_colors). func (g *RogueGame) initColors() { used := make([]bool, len(rainbow)) - for i := 0; i < NumPotionTypes; i++ { + for i := PotionKind(0); i < NumPotionTypes; i++ { var j int for { j = g.rnd(len(rainbow)) @@ -66,7 +66,7 @@ func (g *RogueGame) initColors() { // initNames generates the names of the various scrolls (init.c init_names). func (g *RogueGame) initNames() { - for i := 0; i < NumScrollTypes; i++ { + for i := ScrollKind(0); i < NumScrollTypes; i++ { var cp strings.Builder nwords := g.rnd(3) + 2 for ; nwords > 0; nwords-- { @@ -88,7 +88,7 @@ func (g *RogueGame) initNames() { // (init.c init_stones). func (g *RogueGame) initStones() { used := make([]bool, len(stoneTable)) - for i := 0; i < NumRingTypes; i++ { + for i := RingKind(0); i < NumRingTypes; i++ { var j int for { j = g.rnd(len(stoneTable)) @@ -107,7 +107,7 @@ func (g *RogueGame) initStones() { func (g *RogueGame) initMaterials() { used := make([]bool, len(woods)) metused := make([]bool, len(metals)) - for i := 0; i < NumWandTypes; i++ { + for i := WandKind(0); i < NumWandTypes; i++ { var str string for { if g.rnd(2) == 0 { diff --git a/game/misc.go b/game/misc.go index eb135dd..b4706c5 100644 --- a/game/misc.go +++ b/game/misc.go @@ -213,11 +213,11 @@ func (g *RogueGame) findObj(y, x int) *Object { // eat lets her try to eat something (misc.c eat). func (g *RogueGame) eat() { - obj := g.getItem("eat", int(Food)) + obj := g.getItem("eat", KindFood) if obj == nil { return } - if obj.Type != Food { + if obj.Kind != KindFood { if !g.Options.Terse { g.msg("ugh, you would get ill if you ate that") } else { diff --git a/game/move.go b/game/move.go index 0dd328e..c7a60dd 100644 --- a/game/move.go +++ b/game/move.go @@ -192,7 +192,7 @@ func (g *RogueGame) doorOpen(rp *Room) { } // beTrapped makes him pay for stepping on a trap (move.c be_trapped). -func (g *RogueGame) beTrapped(tc Coord) int { +func (g *RogueGame) beTrapped(tc Coord) TrapKind { p := &g.Player if p.On(Levitating) { return TrapRust // anything that's not a door or teleport @@ -201,7 +201,7 @@ func (g *RogueGame) beTrapped(tc Coord) int { g.Count = 0 pp := g.Level.At(tc.Y, tc.X) pp.Ch = Trap - tr := int(pp.Flags & FTrapMask) + tr := TrapKind(pp.Flags & FTrapMask) pp.Flags.Set(FSeen) switch tr { case TrapDoor: @@ -311,7 +311,7 @@ func (g *RogueGame) rndmove(who *Creature) Coord { break } } - if found != nil && found.Which == ScrollScareMonster { + if found != nil && found.ScrollKind() == ScrollScareMonster { return who.Pos } } @@ -321,7 +321,7 @@ func (g *RogueGame) rndmove(who *Creature) Coord { // rustArmor rusts the given armor, if it is a legal kind to rust, and we // aren't wearing a magic ring (move.c rust_armor). func (g *RogueGame) rustArmor(arm *Object) { - if arm == nil || arm.Type != Armor || arm.Which == ArmorLeather || + if arm == nil || arm.Kind != KindArmor || arm.ArmorKind() == ArmorLeather || arm.Arm >= 9 { return } diff --git a/game/newlevel.go b/game/newlevel.go index 1ee51b4..54a2592 100644 --- a/game/newlevel.go +++ b/game/newlevel.go @@ -103,7 +103,7 @@ func (g *RogueGame) putThings() { attachObj(&g.Level.Objects, obj) // Put it somewhere obj.Pos, _ = g.findFloor(nil, 0, false) - g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Type) + g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph()) } } // If he is really deep in the dungeon and he hasn't found the amulet @@ -114,7 +114,7 @@ func (g *RogueGame) putThings() { obj.Damage = "0x0" obj.HurlDmg = "0x0" obj.Arm = 11 - obj.Type = Amulet + obj.Kind = KindAmulet // Put it somewhere obj.Pos, _ = g.findFloor(nil, 0, false) g.Level.SetChar(obj.Pos.Y, obj.Pos.X, Amulet) @@ -134,7 +134,7 @@ func (g *RogueGame) treasRoom() { tp := g.newThing() tp.Pos = mp attachObj(&g.Level.Objects, tp) - g.Level.SetChar(mp.Y, mp.X, tp.Type) + g.Level.SetChar(mp.Y, mp.X, tp.Kind.Glyph()) } // fill up room with monsters from the next level down diff --git a/game/newlevel_test.go b/game/newlevel_test.go index 350dccb..134b8b6 100644 --- a/game/newlevel_test.go +++ b/game/newlevel_test.go @@ -75,9 +75,10 @@ func TestNewLevelInvariants(t *testing.T) { // share cells only with monsters standing on them). for _, obj := range g.Level.Objects { ch := g.Level.Char(obj.Pos.Y, obj.Pos.X) - if ch != obj.Type && g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil { - t.Errorf("seed %d: object %q at (%d,%d) but map shows %q", - seed, obj.Type, obj.Pos.Y, obj.Pos.X, ch) + if ch != obj.Kind.Glyph() && + g.Level.MonsterAt(obj.Pos.Y, obj.Pos.X) == nil { + t.Errorf("seed %d: object %v at (%d,%d) but map shows %q", + seed, obj.Kind, obj.Pos.Y, obj.Pos.X, ch) } } @@ -86,10 +87,12 @@ func TestNewLevelInvariants(t *testing.T) { t.Errorf("seed %d: starting pack has %d items, want 5", seed, len(g.Player.Pack)) } - if g.Player.CurWeapon == nil || g.Player.CurWeapon.Which != WeaponMace { + if g.Player.CurWeapon == nil || + g.Player.CurWeapon.WeaponKind() != WeaponMace { t.Errorf("seed %d: not wielding the starting mace", seed) } - if g.Player.CurArmor == nil || g.Player.CurArmor.Which != ArmorRingMail { + if g.Player.CurArmor == nil || + g.Player.CurArmor.ArmorKind() != ArmorRingMail { t.Errorf("seed %d: not wearing the starting ring mail", seed) } } diff --git a/game/object.go b/game/object.go index 31b963f..8e4d2b6 100644 --- a/game/object.go +++ b/game/object.go @@ -1,20 +1,111 @@ package game +// ObjectKind is the category of an item. In C this was the o_type byte, +// which doubled as the character drawn on the map; the port separates the +// category (ObjectKind) from its display character (Glyph). +type ObjectKind int + +const ( + KindNone ObjectKind = iota + KindPotion + KindScroll + KindFood + KindWeapon + KindArmor + KindRing + KindWand // a "stick": wand or staff + KindAmulet + KindGold +) + +// Prompt-filter pseudo-kinds for item selection (rogue.h CALLABLE and +// R_OR_S): they never appear on an Object, only as getItem filters. +const ( + KindCallable ObjectKind = -1 + KindRingOrStick ObjectKind = -2 +) + +// kindGlyphs maps each kind to the character Rogue draws for it. +var kindGlyphs = [...]byte{ + KindNone: ' ', + KindPotion: Potion, + KindScroll: Scroll, + KindFood: Food, + KindWeapon: Weapon, + KindArmor: Armor, + KindRing: Ring, + KindWand: Stick, + KindAmulet: Amulet, + KindGold: Gold, +} + +// Glyph returns the map/display character for this kind of object. +func (k ObjectKind) Glyph() byte { + if k < 0 || int(k) >= len(kindGlyphs) { + return ' ' + } + return kindGlyphs[k] +} + +// String names the category the way the C type_name() did. +func (k ObjectKind) String() string { + switch k { + case KindPotion: + return "potion" + case KindScroll: + return "scroll" + case KindFood: + return "food" + case KindWeapon: + return "weapon" + case KindArmor: + return "suit of armor" + case KindRing: + return "ring" + case KindWand: + return "wand or staff" + case KindAmulet: + return "amulet" + case KindGold: + return "gold" + case KindRingOrStick: + return "ring, wand or staff" + } + return "bizarre thing" +} + +// objectKindForGlyph is the reverse of Glyph: what category of item does a +// map character denote. Returns KindNone for non-item characters. +func objectKindForGlyph(ch byte) ObjectKind { + for k, g := range kindGlyphs { + if g == ch && ObjectKind(k) != KindNone { + return ObjectKind(k) + } + } + return KindNone +} + +// MergesInPack reports whether picking up another of this kind merges into +// an existing pack entry's count (rogue.h ISMULT). +func (k ObjectKind) MergesInPack() bool { + return k == KindPotion || k == KindScroll || k == KindFood +} + // Object is the _o arm of the C THING union: anything that can lie on the // floor or ride in a pack. type Object struct { - Type byte // what kind of object it is (Potion, Scroll, Weapon, ...) - Pos Coord // where it lives on the screen - Text string // what it says if you read it - Launch int // what you need to launch it (weapon index, -1 none) - PackCh byte // what character it is in the pack - Damage string // damage if used like sword - HurlDmg string // damage if thrown - Count int // count for plural objects - Which int // which object of a type it is - HPlus int // plusses to hit - DPlus int // plusses to damage - Arm int // armor protection — overloaded as in C (see Charges/GoldVal) + Kind ObjectKind // what kind of object it is (o_type) + Pos Coord // where it lives on the screen + Text string // what it says if you read it + Launch WeaponKind // what you need to launch it (noWeapon if none) + PackCh byte // what character it is in the pack + Damage string // damage if used like sword + HurlDmg string // damage if thrown + Count int // count for plural objects + Which int // which object of a type it is (index for the Kind's table) + HPlus int // plusses to hit + DPlus int // plusses to damage + Arm int // armor protection — overloaded as in C (see Charges/GoldVal) Flags ObjFlags Group int // group number for this object Label string // label for object @@ -23,7 +114,7 @@ type Object struct { // newObject is list.c new_item() for objects: a zeroed Object with the // same non-zero defaults the C code relies on. func newObject() *Object { - return &Object{Launch: -1} + return &Object{Launch: noWeapon} } // Charges is the o_charges alias for Arm on wands and staffs. @@ -38,6 +129,24 @@ func (o *Object) GoldVal() int { return o.Arm } // SetGoldVal stores a gold value in the overloaded Arm field. func (o *Object) SetGoldVal(n int) { o.Arm = n } +// PotionKind returns Which as a potion kind; valid only for KindPotion. +func (o *Object) PotionKind() PotionKind { return PotionKind(o.Which) } + +// ScrollKind returns Which as a scroll kind; valid only for KindScroll. +func (o *Object) ScrollKind() ScrollKind { return ScrollKind(o.Which) } + +// RingKind returns Which as a ring kind; valid only for KindRing. +func (o *Object) RingKind() RingKind { return RingKind(o.Which) } + +// WandKind returns Which as a wand kind; valid only for KindWand. +func (o *Object) WandKind() WandKind { return WandKind(o.Which) } + +// WeaponKind returns Which as a weapon kind; valid only for KindWeapon. +func (o *Object) WeaponKind() WeaponKind { return WeaponKind(o.Which) } + +// ArmorKind returns Which as an armor kind; valid only for KindArmor. +func (o *Object) ArmorKind() ArmorKind { return ArmorKind(o.Which) } + // attachObj is list.c attach(): push item onto the front of a list. func attachObj(list *[]*Object, item *Object) { *list = append([]*Object{item}, *list...) diff --git a/game/pack.go b/game/pack.go index a4846b9..85e3178 100644 --- a/game/pack.go +++ b/game/pack.go @@ -15,7 +15,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { } // Check for and deal with scare monster scrolls - if obj.Type == Scroll && obj.Which == ScrollScareMonster && obj.Flags.Has(WasFound) { + if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster && obj.Flags.Has(WasFound) { detachObj(&g.Level.Objects, obj) g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) if p.Room.Flags.Has(Gone) { @@ -39,12 +39,12 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { lp := -1 merged := false for i := 0; i < len(p.Pack); i++ { - if p.Pack[i].Type != obj.Type { + if p.Pack[i].Kind != obj.Kind { lp = i continue } // found the group of our type: scan for matching subtype - for p.Pack[i].Type == obj.Type && p.Pack[i].Which != obj.Which { + for p.Pack[i].Kind == obj.Kind && p.Pack[i].Which != obj.Which { lp = i if i+1 >= len(p.Pack) { break @@ -52,8 +52,8 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { i++ } op := p.Pack[i] - if op.Type == obj.Type && op.Which == obj.Which { - if IsMult(op.Type) { + if op.Kind == obj.Kind && op.Which == obj.Which { + if op.Kind.MergesInPack() { if !g.packRoom(fromFloor, obj) { return } @@ -63,7 +63,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { merged = true } else if obj.Group != 0 { lp = i - for p.Pack[i].Type == obj.Type && + for p.Pack[i].Kind == obj.Kind && p.Pack[i].Which == obj.Which && p.Pack[i].Group != obj.Group { lp = i @@ -73,7 +73,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { i++ } op = p.Pack[i] - if op.Type == obj.Type && op.Which == obj.Which && + if op.Kind == obj.Kind && op.Which == obj.Which && op.Group == obj.Group { op.Count += obj.Count p.Inpack-- @@ -111,7 +111,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) { } } - if obj.Type == Amulet { + if obj.Kind == KindAmulet { g.HasAmulet = true } // Notify the user @@ -193,12 +193,14 @@ func (g *RogueGame) packChar() byte { // inventory lists what is in the pack; returns true if there is something // of the given type (pack.c inventory). -func (g *RogueGame) inventory(list []*Object, typ int) bool { +func (g *RogueGame) inventory(list []*Object, kind ObjectKind) bool { g.NObjs = 0 for _, item := range list { - if typ != 0 && typ != int(item.Type) && - !(typ == Callable && item.Type != Food && item.Type != Amulet) && - !(typ == RorS && (item.Type == Ring || item.Type == Stick)) { + if kind != KindNone && kind != item.Kind && + !(kind == KindCallable && item.Kind != KindFood && + item.Kind != KindAmulet) && + !(kind == KindRingOrStick && + (item.Kind == KindRing || item.Kind == KindWand)) { continue } g.NObjs++ @@ -213,13 +215,13 @@ func (g *RogueGame) inventory(list []*Object, typ int) bool { } if g.NObjs == 0 { if g.Options.Terse { - if typ == 0 { + if kind == KindNone { g.msg("empty handed") } else { g.msg("nothing appropriate") } } else { - if typ == 0 { + if kind == KindNone { g.msg("you are empty handed") } else { g.msg("you don't have anything appropriate") @@ -294,7 +296,7 @@ func (g *RogueGame) pickyInven() { } // getItem picks something out of a pack for a purpose (pack.c get_item). -func (g *RogueGame) getItem(purpose string, typ int) *Object { +func (g *RogueGame) getItem(purpose string, kind ObjectKind) *Object { p := &g.Player if len(p.Pack) == 0 { g.msg("you aren't carrying anything") @@ -328,7 +330,7 @@ func (g *RogueGame) getItem(purpose string, typ int) *Object { g.NObjs = 1 // normal case: person types one char if ch == '*' { g.Msgs.Mpos = 0 - if !g.inventory(p.Pack, typ) { + if !g.inventory(p.Pack, kind) { g.After = false return nil } diff --git a/game/potions.go b/game/potions.go index 8a83402..d949870 100644 --- a/game/potions.go +++ b/game/potions.go @@ -34,12 +34,12 @@ var pActions = [NumPotionTypes]pact{ // quaff drinks a potion from the pack (potions.c quaff). func (g *RogueGame) quaff() { p := &g.Player - obj := g.getItem("quaff", int(Potion)) + obj := g.getItem("quaff", KindPotion) // Make certain that it is something that we want to drink if obj == nil { return } - if obj.Type != Potion { + if obj.Kind != KindPotion { if !g.Options.Terse { g.msg("yuk! Why would you want to drink that?") } else { @@ -54,7 +54,7 @@ func (g *RogueGame) quaff() { // Calculate the effect it has on the poor guy. trip := p.On(Hallucinating) g.leavePack(obj, false, false) - switch obj.Which { + switch obj.PotionKind() { case PotionConfusion: g.doPot(PotionConfusion, !trip) case PotionPoison: @@ -187,10 +187,10 @@ func (g *RogueGame) raiseLevel() { // doPot does a potion with standard setup: it uses a fuse and turns on a // flag (potions.c do_pot). -func (g *RogueGame) doPot(typ int, knowit bool) { - pp := &pActions[typ] - if !g.Items.Potions[typ].Know { - g.Items.Potions[typ].Know = knowit +func (g *RogueGame) doPot(kind PotionKind, knowit bool) { + pp := &pActions[kind] + if !g.Items.Potions[kind].Know { + g.Items.Potions[kind].Know = knowit } t := g.spread(pp.time) if !g.Player.On(pp.flags) { @@ -201,7 +201,7 @@ func (g *RogueGame) doPot(typ int, knowit bool) { g.Lengthen(pp.daemon, t) } high, straight := pp.high, pp.straight - if typ == PotionSeeInvisible { + if kind == PotionSeeInvisible { s := fmt.Sprintf("this potion tastes like %s juice", g.Fruit) high, straight = s, s } @@ -210,12 +210,12 @@ func (g *RogueGame) doPot(typ int, knowit bool) { // isMagic reports whether an object radiates magic (potions.c is_magic). func (o *Object) isMagic() bool { - switch o.Type { - case Armor: + switch o.Kind { + case KindArmor: return o.Flags.Has(Protected) || o.Arm != aClass[o.Which] - case Weapon: + case KindWeapon: return o.HPlus != 0 || o.DPlus != 0 - case Potion, Scroll, Stick, Ring, Amulet: + case KindPotion, KindScroll, KindWand, KindRing, KindAmulet: return true } return false diff --git a/game/rings.go b/game/rings.go index 6f957f4..a151a83 100644 --- a/game/rings.go +++ b/game/rings.go @@ -7,12 +7,12 @@ import "fmt" // ringOn puts a ring on a hand (rings.c ring_on). func (g *RogueGame) ringOn() { p := &g.Player - obj := g.getItem("put on", int(Ring)) + obj := g.getItem("put on", KindRing) // Make certain that it is something that we want to wear if obj == nil { return } - if obj.Type != Ring { + if obj.Kind != KindRing { if !g.Options.Terse { g.msg("it would be difficult to wrap that around a finger") } else { @@ -47,7 +47,7 @@ func (g *RogueGame) ringOn() { p.CurRing[ring] = obj // Calculate the effect it has on the poor guy. - switch obj.Which { + switch obj.RingKind() { case RingAddStrength: g.chgStr(obj.Arm) case RingSeeInvisible: @@ -147,7 +147,7 @@ func (g *RogueGame) ringEat(hand int) int { if ring == nil { return 0 } - eat := ringUses[ring.Which] + eat := ringUses[ring.RingKind()] if eat < 0 { if g.rnd(-eat) == 0 { eat = 1 @@ -155,7 +155,7 @@ func (g *RogueGame) ringEat(hand int) int { eat = 0 } } - if ring.Which == RingSlowDigestion { + if ring.RingKind() == RingSlowDigestion { eat = -eat } return eat @@ -166,7 +166,7 @@ func ringNum(g *RogueGame, obj *Object) string { if !obj.Flags.Has(Known) { return "" } - switch obj.Which { + switch obj.RingKind() { case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity: return fmt.Sprintf(" [%s]", num(obj.Arm, 0, Ring)) } diff --git a/game/rip.go b/game/rip.go index 50b9cd5..8a1cd95 100644 --- a/game/rip.go +++ b/game/rip.go @@ -115,37 +115,37 @@ func (g *RogueGame) totalWinner() { for _, obj := range p.Pack { worth := 0 it := &g.Items - switch obj.Type { - case Food: + switch obj.Kind { + case KindFood: worth = 2 * obj.Count - case Weapon: + case KindWeapon: worth = it.Weapons[obj.Which].Worth worth *= 3*(obj.HPlus+obj.DPlus) + obj.Count obj.Flags.Set(Known) - case Armor: + case KindArmor: worth = it.Armors[obj.Which].Worth worth += (9 - obj.Arm) * 100 worth += 10 * (aClass[obj.Which] - obj.Arm) obj.Flags.Set(Known) - case Scroll: + case KindScroll: op := &it.Scrolls[obj.Which] worth = op.Worth * obj.Count if !op.Know { worth /= 2 } op.Know = true - case Potion: + case KindPotion: op := &it.Potions[obj.Which] worth = op.Worth * obj.Count if !op.Know { worth /= 2 } op.Know = true - case Ring: + case KindRing: op := &it.Rings[obj.Which] worth = op.Worth - if obj.Which == RingAddStrength || obj.Which == RingIncreaseDamage || - obj.Which == RingProtection || obj.Which == RingDexterity { + if obj.RingKind() == RingAddStrength || obj.RingKind() == RingIncreaseDamage || + obj.RingKind() == RingProtection || obj.RingKind() == RingDexterity { if obj.Arm > 0 { worth += obj.Arm * 100 } else { @@ -157,7 +157,7 @@ func (g *RogueGame) totalWinner() { } obj.Flags.Set(Known) op.Know = true - case Stick: + case KindWand: op := &it.Sticks[obj.Which] worth = op.Worth worth += 20 * obj.Charges() @@ -166,7 +166,7 @@ func (g *RogueGame) totalWinner() { } obj.Flags.Set(Known) op.Know = true - case Amulet: + case KindAmulet: worth = 1000 } if worth < 0 { diff --git a/game/rooms.go b/game/rooms.go index f6b04c3..51f391d 100644 --- a/game/rooms.go +++ b/game/rooms.go @@ -94,7 +94,7 @@ func (g *RogueGame) doRooms() { g.Level.SetChar(rp.Gold.Y, rp.Gold.X, Gold) gold.Flags = Stackable gold.Group = goldGrp - gold.Type = Gold + gold.Kind = KindGold attachObj(&g.Level.Objects, gold) } // Put the monster in diff --git a/game/save.go b/game/save.go index 78e5433..8d555bf 100644 --- a/game/save.go +++ b/game/save.go @@ -11,6 +11,11 @@ import ( // what state.c's rs_fix_thing did: pointers that alias other structures // (equipment, chase targets, room membership) are encoded as indices. +// saveFormatVersion identifies the snapshot layout; it changes whenever a +// field rename or retype would make gob silently mis-decode an older +// save ("go2": Object.Type became Kind ObjectKind). +const saveFormatVersion = Release + "-go2" + // destRef encodes a monster's chase target (state.c rs_write_thing's // (kind, index) pairs). type destRef struct { @@ -165,7 +170,7 @@ func (g *RogueGame) packIdx(obj *Object) int { func (g *RogueGame) snapshot() *SaveState { p := &g.Player st := &SaveState{ - Version: Release, + Version: saveFormatVersion, Seed: g.Rng.Seed, Dnum: g.Dnum, Whoami: g.Whoami, @@ -530,7 +535,7 @@ func Restore(path string, cfg Config) (*RogueGame, error) { if err := gob.NewDecoder(f).Decode(&st); err != nil { return nil, fmt.Errorf("%s: corrupt or incompatible save file: %w", path, err) } - if st.Version != Release { + if st.Version != saveFormatVersion { return nil, fmt.Errorf("sorry, saved game is out of date") } diff --git a/game/save_test.go b/game/save_test.go index a35f6ab..8e39c05 100644 --- a/game/save_test.go +++ b/game/save_test.go @@ -83,7 +83,7 @@ func TestSaveRestoreRoundTrip(t *testing.T) { len(h.Player.Pack)) found := false for i, o := range h.Player.Pack { - t.Logf(" pack[%d]=%p type=%c which=%d", i, o, o.Type, o.Which) + t.Logf(" pack[%d]=%p type=%v which=%d", i, o, o.Kind, o.Which) if o == h.Player.CurWeapon { found = true } diff --git a/game/scrolls.go b/game/scrolls.go index 9c15f0d..a643212 100644 --- a/game/scrolls.go +++ b/game/scrolls.go @@ -2,25 +2,25 @@ package game // scrolls.c — read a scroll and let it happen. -// idType maps identify scrolls to the type they identify (scrolls.c -// static id_type). -var idType = [ScrollIdentifyRingOrStick + 1]int{ - ScrollIdentifyPotion: int(Potion), - ScrollIdentifyScroll: int(Scroll), - ScrollIdentifyWeapon: int(Weapon), - ScrollIdentifyArmor: int(Armor), - ScrollIdentifyRingOrStick: RorS, +// idType maps identify scrolls to the kind of item they identify +// (scrolls.c static id_type). +var idType = [ScrollIdentifyRingOrStick + 1]ObjectKind{ + ScrollIdentifyPotion: KindPotion, + ScrollIdentifyScroll: KindScroll, + ScrollIdentifyWeapon: KindWeapon, + ScrollIdentifyArmor: KindArmor, + ScrollIdentifyRingOrStick: KindRingOrStick, } // readScroll reads a scroll from the pack and does the appropriate thing // (scrolls.c read_scroll). func (g *RogueGame) readScroll() { p := &g.Player - obj := g.getItem("read", int(Scroll)) + obj := g.getItem("read", KindScroll) if obj == nil { return } - if obj.Type != Scroll { + if obj.Kind != KindScroll { if !g.Options.Terse { g.msg("there is nothing on it to read") } else { @@ -35,7 +35,7 @@ func (g *RogueGame) readScroll() { // Get rid of the thing g.leavePack(obj, false, false) - switch obj.Which { + switch obj.ScrollKind() { case ScrollMonsterConfusion: // Scroll of monster confusion. Give him that power. p.Flags.Set(CanConfuse) @@ -99,7 +99,7 @@ func (g *RogueGame) readScroll() { // Or anything else nasty if ch := g.Level.VisibleChar(y, x); stepOk(ch) { if ch == Scroll { - if fo := g.findObj(y, x); fo != nil && fo.Which == ScrollScareMonster { + if fo := g.findObj(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster { continue } } @@ -119,7 +119,7 @@ func (g *RogueGame) readScroll() { // Identify, let him figure something out g.Items.Scrolls[obj.Which].Know = true g.msg("this scroll is an %s scroll", g.Items.Scrolls[obj.Which].Name) - g.whatis(true, idType[obj.Which]) + g.whatis(true, idType[obj.ScrollKind()]) case ScrollMagicMapping: // Scroll of magic mapping. g.Items.Scrolls[ScrollMagicMapping].Know = true @@ -192,7 +192,7 @@ func (g *RogueGame) readScroll() { found := false g.scr.Hw.Clear() for _, fo := range g.Level.Objects { - if fo.Type == Food { + if fo.Kind == KindFood { found = true g.scr.Hw.MvAddCh(fo.Pos.Y, fo.Pos.X, Food) } @@ -211,7 +211,7 @@ func (g *RogueGame) readScroll() { g.Items.Scrolls[ScrollTeleportation].Know = true } case ScrollEnchantWeapon: - if p.CurWeapon == nil || p.CurWeapon.Type != Weapon { + if p.CurWeapon == nil || p.CurWeapon.Kind != KindWeapon { g.msg("you feel a strange sense of loss") } else { p.CurWeapon.Flags.Clear(Cursed) diff --git a/game/sticks.go b/game/sticks.go index 822d1ab..55bdd5e 100644 --- a/game/sticks.go +++ b/game/sticks.go @@ -7,11 +7,11 @@ import "fmt" // doZap performs a zap with a wand (sticks.c do_zap). func (g *RogueGame) doZap() { p := &g.Player - obj := g.getItem("zap with", int(Stick)) + obj := g.getItem("zap with", KindWand) if obj == nil { return } - if obj.Type != Stick { + if obj.Kind != KindWand { g.After = false g.msg("you can't zap with that!") return @@ -20,7 +20,7 @@ func (g *RogueGame) doZap() { g.msg("nothing happens") return } - switch obj.Which { + switch obj.WandKind() { case WandLight: // Reddy Kilowatt wand. Light up the room g.Items.Sticks[WandLight].Know = true @@ -57,7 +57,7 @@ func (g *RogueGame) doZap() { if monster == 'F' { p.Flags.Clear(Held) } - switch obj.Which { + switch obj.WandKind() { case WandInvisibility: tp.Flags.Set(Invisible) if g.cansee(y, x) { @@ -91,7 +91,7 @@ func (g *RogueGame) doZap() { } case WandTeleportAway, WandTeleportTo: var newPos Coord - if obj.Which == WandTeleportAway { + if obj.WandKind() == WandTeleportAway { for { newPos, _ = g.findFloor(nil, 0, true) if newPos != p.Pos { @@ -110,13 +110,13 @@ func (g *RogueGame) doZap() { case WandMagicMissile: g.Items.Sticks[WandMagicMissile].Know = true bolt := newObject() - bolt.Type = '*' + bolt.Kind = KindGold // C set o_type='*': draws a '*' and is not a weapon bolt.HurlDmg = "1x4" bolt.HPlus = 100 bolt.DPlus = 1 bolt.Flags = Missile if p.CurWeapon != nil { - bolt.Launch = p.CurWeapon.Which + bolt.Launch = WeaponKind(p.CurWeapon.Which) } g.doMotion(bolt, g.Delta.Y, g.Delta.X) if tp := g.Level.MonsterAt(bolt.Pos.Y, bolt.Pos.X); tp != nil && @@ -135,7 +135,7 @@ func (g *RogueGame) doZap() { x += g.Delta.X } if tp := g.Level.MonsterAt(y, x); tp != nil { - if obj.Which == WandHasteMonster { + if obj.WandKind() == WandHasteMonster { if tp.On(Slowed) { tp.Flags.Clear(Slowed) } else { @@ -155,7 +155,7 @@ func (g *RogueGame) doZap() { } case WandLightning, WandFire, WandCold: var name string - switch obj.Which { + switch obj.WandKind() { case WandLightning: name = "bolt" case WandFire: @@ -211,8 +211,8 @@ func (g *RogueGame) fireBolt(start Coord, dir *Coord, name string) { fromHero := start == p.Pos bolt := newObject() - bolt.Type = Weapon - bolt.Which = WeaponFlame + bolt.Kind = KindWeapon + bolt.Which = int(WeaponFlame) bolt.HurlDmg = "6x6" bolt.HPlus = 100 bolt.DPlus = 0 @@ -328,7 +328,7 @@ func (g *RogueGame) fixStick(cur *Object) { } cur.HurlDmg = "1x1" - switch cur.Which { + switch cur.WandKind() { case WandLight: cur.SetCharges(g.rnd(10) + 10) default: diff --git a/game/tables_test.go b/game/tables_test.go index 0504282..2844016 100644 --- a/game/tables_test.go +++ b/game/tables_test.go @@ -33,7 +33,7 @@ func TestInitProbsCumulative(t *testing.T) { if last != 100 { t.Errorf("cumulative potion probability ends at %d, want 100", last) } - for i := 1; i < NumPotionTypes; i++ { + for i := PotionKind(1); i < NumPotionTypes; i++ { if g.Items.Potions[i].Prob < g.Items.Potions[i-1].Prob { t.Errorf("potion probs not nondecreasing at %d", i) } diff --git a/game/things.go b/game/things.go index 3fde819..fc90159 100644 --- a/game/things.go +++ b/game/things.go @@ -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++ diff --git a/game/types.go b/game/types.go index cd5170b..47c3bcc 100644 --- a/game/types.go +++ b/game/types.go @@ -62,12 +62,6 @@ const ( Stick byte = '/' ) -// Pseudo-types for item-selection prompts (rogue.h) -const ( - Callable = -1 - RorS = -2 -) - // Various constants (rogue.h) const ( HealTime = 30 @@ -173,22 +167,37 @@ func (f PlaceFlags) Has(b PlaceFlags) bool { return f&b != 0 } func (f *PlaceFlags) Set(b PlaceFlags) { *f |= b } func (f *PlaceFlags) Clear(b PlaceFlags) { *f &^= b } -// Trap types (rogue.h) +// TrapKind identifies a trap (rogue.h trap types). The kind is stored in +// the low bits of a map cell's PlaceFlags (FTrapMask). +type TrapKind int + const ( - TrapDoor = 0 - TrapArrow = 1 - TrapSleep = 2 - TrapBear = 3 - TrapTeleport = 4 - TrapDart = 5 - TrapRust = 6 - TrapMystery = 7 - NumTrapTypes = 8 + TrapDoor TrapKind = 0 + TrapArrow TrapKind = 1 + TrapSleep TrapKind = 2 + TrapBear TrapKind = 3 + TrapTeleport TrapKind = 4 + TrapDart TrapKind = 5 + TrapRust TrapKind = 6 + TrapMystery TrapKind = 7 + NumTrapTypes = 8 ) -// Potion types (rogue.h) +// String returns the trap's display name, article included, as the C +// tr_name table had it. +func (t TrapKind) String() string { + if t < 0 || t >= NumTrapTypes { + return "a bizarre trap" + } + return trName[t] +} + +// PotionKind identifies a potion (rogue.h potion types). +type PotionKind int + +// Potion kinds (rogue.h) const ( - PotionConfusion = iota + PotionConfusion PotionKind = iota PotionLSD PotionPoison PotionGainStrength @@ -205,9 +214,20 @@ const ( NumPotionTypes ) -// Scroll types (rogue.h) +// String returns the potion's true name ("healing", "haste self", ...). +func (p PotionKind) String() string { + if p < 0 || p >= NumPotionTypes { + return "strange potion" + } + return basePotInfo[p].Name +} + +// ScrollKind identifies a scroll (rogue.h scroll types). +type ScrollKind int + +// Scroll kinds (rogue.h) const ( - ScrollMonsterConfusion = iota + ScrollMonsterConfusion ScrollKind = iota ScrollMagicMapping ScrollHoldMonster ScrollSleep @@ -228,9 +248,20 @@ const ( NumScrollTypes ) -// Weapon types (rogue.h) +// String returns the scroll's true name ("magic mapping", ...). +func (s ScrollKind) String() string { + if s < 0 || s >= NumScrollTypes { + return "strange scroll" + } + return baseScrInfo[s].Name +} + +// WeaponKind identifies a weapon (rogue.h weapon types). +type WeaponKind int + +// Weapon kinds (rogue.h) const ( - WeaponMace = iota + WeaponMace WeaponKind = iota WeaponLongSword WeaponBow WeaponArrow @@ -243,9 +274,20 @@ const ( NumWeaponTypes = WeaponFlame ) -// Armor types (rogue.h) +// String returns the weapon's name ("mace", "two handed sword", ...). +func (w WeaponKind) String() string { + if w < 0 || w > WeaponFlame { + return "strange weapon" + } + return baseWeapInfo[w].Name +} + +// ArmorKind identifies a suit of armor (rogue.h armor types). +type ArmorKind int + +// Armor kinds (rogue.h) const ( - ArmorLeather = iota + ArmorLeather ArmorKind = iota ArmorRingMail ArmorStuddedLeather ArmorScaleMail @@ -256,9 +298,20 @@ const ( NumArmorTypes ) -// Ring types (rogue.h) +// String returns the armor's name ("ring mail", "plate mail", ...). +func (a ArmorKind) String() string { + if a < 0 || a >= NumArmorTypes { + return "strange armor" + } + return baseArmInfo[a].Name +} + +// RingKind identifies a ring (rogue.h ring types). +type RingKind int + +// Ring kinds (rogue.h) const ( - RingProtection = iota + RingProtection RingKind = iota RingAddStrength RingSustainStrength RingSearching @@ -275,9 +328,20 @@ const ( NumRingTypes ) -// Rod/Wand/Staff types (rogue.h) +// String returns the ring's true name ("add strength", "stealth", ...). +func (r RingKind) String() string { + if r < 0 || r >= NumRingTypes { + return "strange ring" + } + return baseRingInfo[r].Name +} + +// WandKind identifies a wand or staff (rogue.h rod/wand/staff types). +type WandKind int + +// Wand kinds (rogue.h) const ( - WandLight = iota + WandLight WandKind = iota WandInvisibility WandLightning WandFire @@ -294,6 +358,14 @@ const ( NumWandTypes ) +// String returns the wand/staff's true name ("lightning", ...). +func (w WandKind) String() string { + if w < 0 || w >= NumWandTypes { + return "strange stick" + } + return baseWsInfo[w].Name +} + // Coord is a position on the level (rogue.h coord). A value type: the C // ce(a,b) macro is plain == here. type Coord struct { @@ -349,9 +421,6 @@ type Stone struct { // CTRL maps a letter to its control character, as the C CTRL() macro. func CTRL(c byte) byte { return c & 0o37 } -// IsMult reports whether an object type stacks in the pack (rogue.h ISMULT). -func IsMult(typ byte) bool { return typ == Potion || typ == Scroll || typ == Food } - // distance returns the squared distance between two points (chase.c dist). func distance(y1, x1, y2, x2 int) int { dx := x2 - x1 diff --git a/game/weapons.go b/game/weapons.go index b9f7be8..f539107 100644 --- a/game/weapons.go +++ b/game/weapons.go @@ -4,12 +4,12 @@ import "fmt" // weapons.c — functions for dealing with problems brought about by weapons. -const noWeapon = -1 +const noWeapon WeaponKind = -1 // missile fires a missile in a given direction (weapons.c missile). func (g *RogueGame) missile(ydelta, xdelta int) { // Get which thing we are hurling - obj := g.getItem("throw", int(Weapon)) + obj := g.getItem("throw", KindWeapon) if obj == nil { return } @@ -48,7 +48,7 @@ func (g *RogueGame) doMotion(obj *Object, ydelta, xdelta int) { if stepOk(ch) && ch != Door { // It hasn't hit anything yet, so display it if it's alright. if g.cansee(obj.Pos.Y, obj.Pos.X) && !g.Options.Terse { - g.mvaddch(obj.Pos.Y, obj.Pos.X, obj.Type) + g.mvaddch(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph()) g.refresh() } continue @@ -61,13 +61,13 @@ func (g *RogueGame) doMotion(obj *Object, ydelta, xdelta int) { func (g *RogueGame) fall(obj *Object, pr bool) { if fpos, ok := g.fallpos(obj.Pos); ok { pp := g.Level.At(fpos.Y, fpos.X) - pp.Ch = obj.Type + pp.Ch = obj.Kind.Glyph() obj.Pos = fpos if g.cansee(fpos.Y, fpos.X) { if pp.Monst != nil { - pp.Monst.OldCh = obj.Type + pp.Monst.OldCh = obj.Kind.Glyph() } else { - g.mvaddch(fpos.Y, fpos.X, obj.Type) + g.mvaddch(fpos.Y, fpos.X, obj.Kind.Glyph()) } } attachObj(&g.Level.Objects, obj) @@ -98,12 +98,12 @@ func (g *RogueGame) wield() { return } p.CurWeapon = oweapon - obj := g.getItem("wield", int(Weapon)) + obj := g.getItem("wield", KindWeapon) if obj == nil { g.After = false return } - if obj.Type == Armor { + if obj.Kind == KindArmor { g.msg("you can't wield armor") g.After = false return @@ -123,9 +123,9 @@ func (g *RogueGame) wield() { // initWeaps is the weapons.c init_dam[] table. var initWeaps = [NumWeaponTypes]struct { - dam string // damage when wielded - hrl string // damage when thrown - launch int // launching weapon + dam string // damage when wielded + hrl string // damage when thrown + launch WeaponKind // launching weapon flags ObjFlags }{ {"2x4", "1x3", noWeapon, 0}, // WeaponMace @@ -140,10 +140,10 @@ var initWeaps = [NumWeaponTypes]struct { } // initWeapon sets up a new weapon (weapons.c init_weapon). -func (g *RogueGame) initWeapon(weap *Object, which int) { +func (g *RogueGame) initWeapon(weap *Object, which WeaponKind) { iwp := &initWeaps[which] - weap.Type = Weapon - weap.Which = which + weap.Kind = KindWeapon + weap.Which = int(which) weap.Damage = iwp.dam weap.HurlDmg = iwp.hrl weap.Launch = iwp.launch diff --git a/game/wizard.go b/game/wizard.go index 7bc6287..b2079b6 100644 --- a/game/wizard.go +++ b/game/wizard.go @@ -9,9 +9,9 @@ package game func (g *RogueGame) createObj() { obj := newObject() g.msg("type of item: ") - obj.Type = g.readchar() + obj.Kind = objectKindForGlyph(g.readchar()) g.Msgs.Mpos = 0 - g.msg("which %c do you want? (0-f)", obj.Type) + g.msg("which %c do you want? (0-f)", obj.Kind.Glyph()) ch := g.readchar() if isDigit(ch) { obj.Which = int(ch - '0') @@ -22,15 +22,15 @@ func (g *RogueGame) createObj() { obj.Count = 1 g.Msgs.Mpos = 0 switch { - case obj.Type == Weapon || obj.Type == Armor: + case obj.Kind == KindWeapon || obj.Kind == KindArmor: g.msg("blessing? (+,-,n)") bless := g.readchar() g.Msgs.Mpos = 0 if bless == '-' { obj.Flags.Set(Cursed) } - if obj.Type == Weapon { - g.initWeapon(obj, obj.Which) + if obj.Kind == KindWeapon { + g.initWeapon(obj, WeaponKind(obj.Which)) if bless == '-' { obj.HPlus -= g.rnd(3) + 1 } @@ -46,8 +46,8 @@ func (g *RogueGame) createObj() { obj.Arm -= g.rnd(3) + 1 } } - case obj.Type == Ring: - switch obj.Which { + case obj.Kind == KindRing: + switch obj.RingKind() { case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage: g.msg("blessing? (+,-,n)") bless := g.readchar() @@ -61,9 +61,9 @@ func (g *RogueGame) createObj() { case RingAggravateMonsters, RingTeleportation: obj.Flags.Set(Cursed) } - case obj.Type == Stick: + case obj.Kind == KindWand: g.fixStick(obj) - case obj.Type == Gold: + case obj.Kind == KindGold: g.msg("how much?") buf := "" if g.getStr(&buf, g.scr.Std) == Norm { @@ -93,7 +93,7 @@ func (g *RogueGame) showMap() { } // whatis identifies what a certain object is (wizard.c whatis). -func (g *RogueGame) whatis(insist bool, typ int) { +func (g *RogueGame) whatis(insist bool, kind ObjectKind) { p := &g.Player if len(p.Pack) == 0 { g.msg("you don't have anything in your pack to identify") @@ -102,7 +102,7 @@ func (g *RogueGame) whatis(insist bool, typ int) { var obj *Object for { - obj = g.getItem("identify", typ) + obj = g.getItem("identify", kind) if !insist { break } @@ -111,9 +111,10 @@ func (g *RogueGame) whatis(insist bool, typ int) { } if obj == nil { g.msg("you must identify something") - } else if typ != 0 && int(obj.Type) != typ && - !(typ == RorS && (obj.Type == Ring || obj.Type == Stick)) { - g.msg("you must identify a %s", typeName(typ)) + } else if kind != KindNone && obj.Kind != kind && + !(kind == KindRingOrStick && + (obj.Kind == KindRing || obj.Kind == KindWand)) { + g.msg("you must identify a %s", kind) } else { break } @@ -123,16 +124,16 @@ func (g *RogueGame) whatis(insist bool, typ int) { return } - switch obj.Type { - case Scroll: + switch obj.Kind { + case KindScroll: setKnow(obj, g.Items.Scrolls[:]) - case Potion: + case KindPotion: setKnow(obj, g.Items.Potions[:]) - case Stick: + case KindWand: setKnow(obj, g.Items.Sticks[:]) - case Weapon, Armor: + case KindWeapon, KindArmor: obj.Flags.Set(Known) - case Ring: + case KindRing: setKnow(obj, g.Items.Rings[:]) } g.msg("%s", g.invName(obj, false)) @@ -146,30 +147,8 @@ func setKnow(obj *Object, info []ObjInfo) { info[obj.Which].Guess = "" } -// typeNameTable is the wizard.c type_name static tlist. -var typeNameTable = []struct { - ch int - desc string -}{ - {int(Potion), "potion"}, - {int(Scroll), "scroll"}, - {int(Food), "food"}, - {RorS, "ring, wand or staff"}, - {int(Ring), "ring"}, - {int(Stick), "wand or staff"}, - {int(Weapon), "weapon"}, - {int(Armor), "suit of armor"}, -} - -// typeName returns the name of an object type (wizard.c type_name). -func typeName(typ int) string { - for _, hp := range typeNameTable { - if typ == hp.ch { - return hp.desc - } - } - return "" -} +// The C type_name()/tlist table is gone: ObjectKind.String() carries the +// same vocabulary. // teleport bamfs the hero someplace else (wizard.c teleport). func (g *RogueGame) teleport() {