Extract MessageLine, Player pack ops, and Level list management

Refactor step 6. MessageLine (was MsgLine) owns the msg/addmsg/endmsg
machinery, wired to its screen, pre---More-- redraw, and input via
attach(); RogueGame keeps one-line msg/addmsgf/endmsg shorthands so
the ~400 call sites are unchanged. Player gains nextPackChar and
removeFromPack (the state half of pack.c leave_pack); leavePack keeps
only the LastPick repeat-command tracking. Level gains ObjectAt
(misc.c find_obj) and AddObject/RemoveObject/AddMonster/RemoveMonster,
replacing direct attach/detach calls on the level lists. Inventory and
pickup UI flows stay on RogueGame: display and orchestration, not
state surgery. Behavior and RNG order unchanged; suite green.
This commit is contained in:
2026-07-07 02:42:18 +02:00
parent a094f7c6c3
commit 0b56ac8019
17 changed files with 177 additions and 109 deletions

41
TODO.md
View File

@@ -29,14 +29,25 @@ Refactor ground rules:
# Next Step # Next Step
Refactor step 6: extract types from the god object — MessageLine Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap
owns the msg/addmsg/endmsg machinery; pack/inventory operations move switches become per-kind handler tables of small named methods,
onto *Player; monster/object list management and map queries keeping effect order and RNG call sequence identical. This step also
consolidate onto *Level; RogueGame keeps turn orchestration and clears the outstanding cyclop/gocognit/nestif lint findings.
cross-system effects only.
# Completed Steps # Completed Steps
- 2026-07-07 Refactor step 6 (refactor/god-object-extraction):
MessageLine (was MsgLine) owns the msg/addmsg/endmsg machinery,
wired to its screen/look/input needs via attach(); RogueGame keeps
one-line msg/addmsgf/endmsg shorthands so call sites are unchanged.
Player owns pack bookkeeping (nextPackChar, removeFromPack — the
state half of leave_pack; leavePack keeps only LastPick tracking).
Level owns object/monster list management and lookup (ObjectAt
replaces findObj; AddObject/RemoveObject/AddMonster/RemoveMonster
replace direct attachObj/detachObj/attachMon/detachMon on level
lists). Inventory/pickup UI flows stay on RogueGame deliberately:
they are display and turn orchestration, not state surgery.
- 2026-07-07 Refactor step 5 (refactor/item-combat-ui-renames, three - 2026-07-07 Refactor step 5 (refactor/item-combat-ui-renames, three
commits, one subsystem each): items — getItem→promptPackItem now commits, one subsystem each): items — getItem→promptPackItem now
returning (obj, ok), invName→inventoryName, doPot→applyPotionFuse; returning (obj, ok), invName→inventoryName, doPot→applyPotionFuse;
@@ -117,31 +128,27 @@ cross-system effects only.
# Future Steps # Future Steps
1. Refactor step 7: effects dispatch — the giant quaff/readScroll/doZap 1. Refactor step 8: constructor and style pass per the house
switches become per-kind handler tables of small named methods,
keeping effect order and RNG call sequence identical. This step also
clears the outstanding cyclop/gocognit/nestif lint findings.
2. Refactor step 8: constructor and style pass per the house
styleguide — game.New(game.Params{...}) replacing NewGame(Config); styleguide — game.New(game.Params{...}) replacing NewGame(Config);
replace the gameEnd panic unwind with error-based turn results where replace the gameEnd panic unwind with error-based turn results where
feasible; 77-column wrap sweep. feasible; 77-column wrap sweep.
3. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the 2. Docs refresh: update ARCHITECTURE.md Part 2 and README.md for the
post-refactor names; add the C name → Go name rename table. post-refactor names; add the C name → Go name rename table.
4. Playtest hardening pass: play several full games with the tcell 3. Playtest hardening pass: play several full games with the tcell
binary and extend run_test.go to script a deeper multi-level binary and extend run_test.go to script a deeper multi-level
playthrough (descend past level 5, use potions, scrolls, zapping, playthrough (descend past level 5, use potions, scrolls, zapping,
save/restore). Fix any panics, message mismatches, or divergences save/restore). Fix any panics, message mismatches, or divergences
from the C behavior that this uncovers, with regression tests. from the C behavior that this uncovers, with regression tests.
5. Verify the seed-compatibility claim against the C reference on 4. Verify the seed-compatibility claim against the C reference on
c-master: same seed, same dungeon, same item tables, for several c-master: same seed, same dungeon, same item tables, for several
seeds. seeds.
6. Broaden unit test coverage where playtesting finds thin spots 5. Broaden unit test coverage where playtesting finds thin spots
(rings, sticks, wizard commands). (rings, sticks, wizard commands).
7. Tag a release once a full game (Amulet retrieval and score entry) 6. Tag a release once a full game (Amulet retrieval and score entry)
completes without defects. completes without defects.
8. Full-terminal-size support (deferred by explicit decision 7. Full-terminal-size support (deferred by explicit decision
2026-07-06): per-game dungeon dimensions instead of the 80x24 2026-07-06): per-game dungeon dimensions instead of the 80x24
constants; open design questions are resize policy, gameplay constants; open design questions are resize policy, gameplay
tuning at larger sizes, and a --classic 80x24 mode. tuning at larger sizes, and a --classic 80x24 mode.
9. Note: this repo is exempt from the standard policy scaffold. Do not 8. Note: this repo is exempt from the standard policy scaffold. Do not
add Makefile, Dockerfile, or REPO_POLICIES.md. add Makefile, Dockerfile, or REPO_POLICIES.md.

View File

@@ -169,7 +169,7 @@ func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
} else if this == *th.Dest { } else if this == *th.Dest {
for _, obj := range g.Level.Objects { for _, obj := range g.Level.Objects {
if th.Dest == &obj.Pos { if th.Dest == &obj.Pos {
detachObj(&g.Level.Objects, obj) g.Level.RemoveObject(obj)
attachObj(&th.Pack, obj) attachObj(&th.Pack, obj)
if th.Room.Flags.Has(Gone) { if th.Room.Flags.Has(Gone) {

View File

@@ -50,6 +50,47 @@ func (p *Player) IsWearing(ring RingKind) bool {
return p.IsRing(Left, ring) || p.IsRing(Right, ring) return p.IsRing(Left, ring) || p.IsRing(Right, ring)
} }
// nextPackChar claims and returns the next unused pack character (pack.c
// pack_char).
func (p *Player) nextPackChar() byte {
for i := range p.PackUsed {
if !p.PackUsed[i] {
p.PackUsed[i] = true
return byte(i) + 'a'
}
}
return byte(len(p.PackUsed)) + 'a' // C would walk off the array here
}
// removeFromPack takes an item out of the pack: the whole entry, or one
// of a stack when all is false (the bookkeeping half of pack.c
// leave_pack). It returns the object that left the pack — a copy when
// newobj asks for a split.
func (p *Player) removeFromPack(obj *Object, newobj, all bool) *Object {
p.Inpack--
nobj := obj
if obj.Count > 1 && !all {
obj.Count--
if obj.Group != 0 {
p.Inpack++
}
if newobj {
copied := *obj
nobj = &copied
nobj.Count = 1
}
} else {
p.PackUsed[obj.PackCh-'a'] = false
detachObj(&p.Pack, obj)
}
return nobj
}
// attachMon pushes a monster onto the front of a list (list.c attach). // attachMon pushes a monster onto the front of a list (list.c attach).
func attachMon(list *[]*Monster, item *Monster) { func attachMon(list *[]*Monster, item *Monster) {
*list = append([]*Monster{item}, *list...) *list = append([]*Monster{item}, *list...)

View File

@@ -549,7 +549,7 @@ func (g *RogueGame) removeMon(mp Coord, tp *Monster, waskill bool) {
g.Level.SetMonsterAt(mp.Y, mp.X, nil) g.Level.SetMonsterAt(mp.Y, mp.X, nil)
g.mvaddch(mp.Y, mp.X, tp.OldCh) g.mvaddch(mp.Y, mp.X, tp.OldCh)
detachMon(&g.Level.Monsters, tp) g.Level.RemoveMonster(tp)
if tp.On(Targeted) { if tp.On(Targeted) {
g.Kamikaze = false g.Kamikaze = false

View File

@@ -110,7 +110,7 @@ type RogueGame struct {
// screen / messages // screen / messages
scr *Screen scr *Screen
Msgs MsgLine Msgs MessageLine
statusCache statusCache statusCache statusCache
invPage invPage // things.c discovery-list pagination statics invPage invPage // things.c discovery-list pagination statics
@@ -169,6 +169,7 @@ func NewGame(cfg Config) *RogueGame {
g.InvDescribe = true g.InvDescribe = true
g.Msgs.SaveMsg = true g.Msgs.SaveMsg = true
g.scr = NewScreen(cfg.Term) g.scr = NewScreen(cfg.Term)
g.Msgs.attach(g.scr, g.look, g.readchar)
g.FileName = cfg.Home + "/rogue.save" g.FileName = cfg.Home + "/rogue.save"
g.rogueOpts = cfg.RogueOpts g.rogueOpts = cfg.RogueOpts

View File

@@ -10,9 +10,11 @@ import (
// maxMsg is io.c MAXMSG: how much message fits before --More--. // maxMsg is io.c MAXMSG: how much message fits before --More--.
const maxMsg = NumCols - len("--More--") - 1 const maxMsg = NumCols - len("--More--") - 1
// MsgLine is the io.c message machinery: the static msgbuf/newpos pair plus // MessageLine is the io.c message machinery: the static msgbuf/newpos
// the related globals (mpos, huh, and the message-behavior flags). // pair plus the related globals (mpos, huh, and the message-behavior
type MsgLine struct { // flags). It owns the top line of the screen; attach wires in the
// display and input it needs.
type MessageLine struct {
buf strings.Builder // msgbuf buf strings.Builder // msgbuf
newpos int newpos int
Mpos int // where cursor is on top line Mpos int // where cursor is on top line
@@ -20,49 +22,52 @@ type MsgLine struct {
SaveMsg bool // remember last msg SaveMsg bool // remember last msg
LowerMsg bool // messages should start w/lower case LowerMsg bool // messages should start w/lower case
MsgEsc bool // check for ESC from msg's --More-- MsgEsc bool // check for ESC from msg's --More--
scr *Screen // the top line lives on scr.Std
look func(wakeup bool) // redraw before a --More-- (misc.c look)
readChar func() byte // input for --More-- prompts
} }
// Msg displays a message at the top of the screen (io.c msg). It returns // Msg displays a message at the top of the screen (io.c msg). It returns
// Escape if the player escaped out of a --More--, ^Escape otherwise (the C // Escape if the player escaped out of a --More--, ^Escape otherwise (the
// convention: callers compare against ESCAPE). // C convention: callers compare against ESCAPE).
func (g *RogueGame) msg(format string, a ...any) int { func (m *MessageLine) Msg(format string, a ...any) int {
// if the string is "", just clear the line // if the string is "", just clear the line
if format == "" { if format == "" {
g.move(0, 0) m.scr.Std.Move(0, 0)
g.clrtoeol() m.scr.Std.Clrtoeol()
g.Msgs.Mpos = 0 m.Mpos = 0
return ^Escape return ^Escape
} }
// otherwise add to the message and flush it out // otherwise add to the message and flush it out
g.doaddf(format, a...) m.doaddf(format, a...)
return g.endmsg() return m.End()
} }
// addmsgf adds things to the current message (io.c addmsg). // Addf adds things to the current message (io.c addmsg).
func (g *RogueGame) addmsgf(format string, a ...any) { func (m *MessageLine) Addf(format string, a ...any) {
g.doaddf(format, a...) m.doaddf(format, a...)
} }
// endmsg displays a new msg, giving the player a chance to see the previous // End displays a new msg, giving the player a chance to see the previous
// one if it is up there with the --More-- (io.c endmsg). // one if it is up there with the --More-- (io.c endmsg).
func (g *RogueGame) endmsg() int { func (m *MessageLine) End() int {
m := &g.Msgs
if m.SaveMsg { if m.SaveMsg {
m.Huh = m.buf.String() m.Huh = m.buf.String()
} }
if m.Mpos != 0 { if m.Mpos != 0 {
g.look(false) m.look(false)
g.mvaddstr(0, m.Mpos, "--More--") m.scr.Std.MvAddStr(0, m.Mpos, "--More--")
g.refresh() m.scr.Refresh()
if !m.MsgEsc { if !m.MsgEsc {
g.waitFor(' ') m.waitForSpace()
} else { } else {
for { for {
ch := g.readchar() ch := m.readChar()
if ch == ' ' { if ch == ' ' {
break break
} }
@@ -85,30 +90,60 @@ func (g *RogueGame) endmsg() int {
out = string(toUpper(out[0])) + out[1:] out = string(toUpper(out[0])) + out[1:]
} }
g.mvaddstr(0, 0, out) m.scr.Std.MvAddStr(0, 0, out)
g.clrtoeol() m.scr.Std.Clrtoeol()
m.Mpos = m.newpos m.Mpos = m.newpos
m.newpos = 0 m.newpos = 0
m.buf.Reset() m.buf.Reset()
g.refresh() m.scr.Refresh()
return ^Escape return ^Escape
} }
// doaddf performs an add onto the message buffer (io.c doadd). // attach wires the message line to its display and input; NewGame and
func (g *RogueGame) doaddf(format string, a ...any) { // Restore call it once the screen and game exist.
m := &g.Msgs func (m *MessageLine) attach(scr *Screen, look func(bool), readChar func() byte) {
m.scr = scr
m.look = look
m.readChar = readChar
}
// waitForSpace absorbs input until the player types a space: the
// --More-- acknowledgement (io.c wait_for).
func (m *MessageLine) waitForSpace() {
for {
if m.readChar() == ' ' {
return
}
}
}
// doaddf performs an add onto the message buffer (io.c doadd).
func (m *MessageLine) doaddf(format string, a ...any) {
s := fmt.Sprintf(format, a...) s := fmt.Sprintf(format, a...)
if len(s)+m.newpos >= maxMsg { if len(s)+m.newpos >= maxMsg {
g.endmsg() m.End()
} }
m.buf.WriteString(s) m.buf.WriteString(s)
m.newpos = m.buf.Len() m.newpos = m.buf.Len()
} }
// msg, addmsgf, and endmsg are the game-side shorthands for the message
// line; the machinery lives on MessageLine.
func (g *RogueGame) msg(format string, a ...any) int {
return g.Msgs.Msg(format, a...)
}
func (g *RogueGame) addmsgf(format string, a ...any) {
g.Msgs.Addf(format, a...)
}
func (g *RogueGame) endmsg() {
g.Msgs.End()
}
// stepOk returns true if it is ok to step on ch (io.c step_ok). // stepOk returns true if it is ok to step on ch (io.c step_ok).
func stepOk(ch byte) bool { func stepOk(ch byte) bool {
switch ch { switch ch {

View File

@@ -51,6 +51,29 @@ func (l *Level) VisibleChar(y, x int) byte {
return l.Char(y, x) return l.Char(y, x)
} }
// ObjectAt finds the unclaimed object at (y, x) (misc.c find_obj).
func (l *Level) ObjectAt(y, x int) *Object {
for _, obj := range l.Objects {
if obj.Pos.Y == y && obj.Pos.X == x {
return obj
}
}
return nil
}
// AddObject puts an object on the level (list.c attach on lvl_obj).
func (l *Level) AddObject(obj *Object) { attachObj(&l.Objects, obj) }
// RemoveObject takes an object off the level (list.c detach on lvl_obj).
func (l *Level) RemoveObject(obj *Object) { detachObj(&l.Objects, obj) }
// AddMonster puts a monster on the level (list.c attach on mlist).
func (l *Level) AddMonster(m *Monster) { attachMon(&l.Monsters, m) }
// RemoveMonster takes a monster off the level (list.c detach on mlist).
func (l *Level) RemoveMonster(m *Monster) { detachMon(&l.Monsters, m) }
// goldCalc is the GOLDCALC macro: how much a gold pile is worth at depth. // goldCalc is the GOLDCALC macro: how much a gold pile is worth at depth.
func (g *RogueGame) goldCalc() int { func (g *RogueGame) goldCalc() int {
return g.rnd(50+10*g.Depth) + 2 return g.rnd(50+10*g.Depth) + 2

View File

@@ -223,17 +223,6 @@ func (g *RogueGame) showFloor() bool {
return true return true
} }
// findObj finds the unclaimed object at (y, x) (misc.c find_obj).
func (g *RogueGame) findObj(y, x int) *Object {
for _, obj := range g.Level.Objects {
if obj.Pos.Y == y && obj.Pos.X == x {
return obj
}
}
return nil
}
// eat lets her try to eat something (misc.c eat). // eat lets her try to eat something (misc.c eat).
func (g *RogueGame) eat() { func (g *RogueGame) eat() {
obj, ok := g.promptPackItem("eat", KindFood) obj, ok := g.promptPackItem("eat", KindFood)

View File

@@ -31,7 +31,7 @@ func (g *RogueGame) randMonster(wander bool) byte {
func (g *RogueGame) newMonster(tp *Monster, typ byte, cp Coord) { func (g *RogueGame) newMonster(tp *Monster, typ byte, cp Coord) {
levAdd := max(g.Depth-AmuletLevel, 0) levAdd := max(g.Depth-AmuletLevel, 0)
attachMon(&g.Level.Monsters, tp) g.Level.AddMonster(tp)
tp.Type = typ tp.Type = typ
tp.Disguise = typ tp.Disguise = typ
tp.Pos = cp tp.Pos = cp

View File

@@ -105,7 +105,7 @@ func (g *RogueGame) putThings() {
if g.rnd(100) < 36 { if g.rnd(100) < 36 {
// Pick a new object and link it in the list // Pick a new object and link it in the list
obj := g.newThing() obj := g.newThing()
attachObj(&g.Level.Objects, obj) g.Level.AddObject(obj)
// Put it somewhere // Put it somewhere
obj.Pos, _ = g.findFloor(false) obj.Pos, _ = g.findFloor(false)
g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph()) g.Level.SetChar(obj.Pos.Y, obj.Pos.X, obj.Kind.Glyph())
@@ -115,7 +115,7 @@ func (g *RogueGame) putThings() {
// yet, put it somewhere on the ground // yet, put it somewhere on the ground
if g.Depth >= AmuletLevel && !g.HasAmulet { if g.Depth >= AmuletLevel && !g.HasAmulet {
obj := newObject() obj := newObject()
attachObj(&g.Level.Objects, obj) g.Level.AddObject(obj)
obj.Damage = dice("0x0") obj.Damage = dice("0x0")
obj.HurlDmg = dice("0x0") obj.HurlDmg = dice("0x0")
obj.ArmorClass = 11 obj.ArmorClass = 11
@@ -137,7 +137,7 @@ func (g *RogueGame) treasureRoom() {
mp, _ := g.findFloorIn(rp, 2*maxTries, false) mp, _ := g.findFloorIn(rp, 2*maxTries, false)
tp := g.newThing() tp := g.newThing()
tp.Pos = mp tp.Pos = mp
attachObj(&g.Level.Objects, tp) g.Level.AddObject(tp)
g.Level.SetChar(mp.Y, mp.X, tp.Kind.Glyph()) g.Level.SetChar(mp.Y, mp.X, tp.Kind.Glyph())
} }

View File

@@ -9,7 +9,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
fromFloor := false fromFloor := false
if obj == nil { if obj == nil {
if obj = g.findObj(p.Pos.Y, p.Pos.X); obj == nil { if obj = g.Level.ObjectAt(p.Pos.Y, p.Pos.X); obj == nil {
return return
} }
@@ -18,7 +18,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
// Check for and deal with scare monster scrolls // Check for and deal with scare monster scrolls
if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster && obj.Flags.Has(WasFound) { if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster && obj.Flags.Has(WasFound) {
detachObj(&g.Level.Objects, obj) g.Level.RemoveObject(obj)
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh())
if p.Room.Flags.Has(Gone) { if p.Room.Flags.Has(Gone) {
@@ -34,7 +34,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
if len(p.Pack) == 0 { if len(p.Pack) == 0 {
p.Pack = append(p.Pack, obj) p.Pack = append(p.Pack, obj)
obj.PackCh = g.packChar() obj.PackCh = p.nextPackChar()
p.Inpack++ p.Inpack++
} else { } else {
// Walk the pack looking for the insertion point, keeping items of // Walk the pack looking for the insertion point, keeping items of
@@ -112,7 +112,7 @@ func (g *RogueGame) addPack(obj *Object, silent bool) {
return return
} }
obj.PackCh = g.packChar() obj.PackCh = p.nextPackChar()
p.Pack = append(p.Pack[:lp+1], p.Pack = append(p.Pack[:lp+1],
append([]*Object{obj}, p.Pack[lp+1:]...)...) append([]*Object{obj}, p.Pack[lp+1:]...)...)
} }
@@ -168,7 +168,7 @@ func (g *RogueGame) packRoom(fromFloor bool, obj *Object) bool {
} }
if fromFloor { if fromFloor {
detachObj(&g.Level.Objects, obj) g.Level.RemoveObject(obj)
g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh())
if p.Room.Flags.Has(Gone) { if p.Room.Flags.Has(Gone) {
@@ -181,46 +181,17 @@ func (g *RogueGame) packRoom(fromFloor bool, obj *Object) bool {
return true return true
} }
// leavePack takes an item out of the pack (pack.c leave_pack). // leavePack takes an item out of the pack (pack.c leave_pack), keeping
// the repeat-command bookkeeping; the pack surgery is
// Player.removeFromPack.
func (g *RogueGame) leavePack(obj *Object, newobj, all bool) *Object { func (g *RogueGame) leavePack(obj *Object, newobj, all bool) *Object {
p := &g.Player
p.Inpack--
nobj := obj
if obj.Count > 1 && !all { if obj.Count > 1 && !all {
g.LastPick = obj g.LastPick = obj
obj.Count--
if obj.Group != 0 {
p.Inpack++
}
if newobj {
copied := *obj
nobj = &copied
nobj.Count = 1
}
} else { } else {
g.LastPick = nil g.LastPick = nil
p.PackUsed[obj.PackCh-'a'] = false
detachObj(&p.Pack, obj)
} }
return nobj return g.Player.removeFromPack(obj, newobj, all)
}
// packChar returns the next unused pack character (pack.c pack_char).
func (g *RogueGame) packChar() byte {
p := &g.Player
for i := range p.PackUsed {
if !p.PackUsed[i] {
p.PackUsed[i] = true
return byte(i) + 'a'
}
}
return byte(len(p.PackUsed)) + 'a' // C would walk off the array here
} }
// inventory lists what is in the pack; returns true if there is something // inventory lists what is in the pack; returns true if there is something
@@ -277,7 +248,7 @@ func (g *RogueGame) pickUp(ch byte) {
return return
} }
obj := g.findObj(p.Pos.Y, p.Pos.X) obj := g.Level.ObjectAt(p.Pos.Y, p.Pos.X)
if g.MoveOn { if g.MoveOn {
g.moveMsg(obj) g.moveMsg(obj)
@@ -291,7 +262,7 @@ func (g *RogueGame) pickUp(ch byte) {
} }
g.money(obj.GoldValue) g.money(obj.GoldValue)
detachObj(&g.Level.Objects, obj) g.Level.RemoveObject(obj)
p.Room.GoldVal = 0 p.Room.GoldVal = 0
default: default:

View File

@@ -105,7 +105,7 @@ func (g *RogueGame) digRooms() {
gold.Flags = Stackable gold.Flags = Stackable
gold.Group = goldGrp gold.Group = goldGrp
gold.Kind = KindGold gold.Kind = KindGold
attachObj(&g.Level.Objects, gold) g.Level.AddObject(gold)
} }
// Put the monster in // Put the monster in
prob := 25 prob := 25

View File

@@ -618,6 +618,7 @@ func Restore(path string, cfg Config) (*RogueGame, error) {
restored: true, restored: true,
} }
g.scr = NewScreen(cfg.Term) g.scr = NewScreen(cfg.Term)
g.Msgs.attach(g.scr, g.look, g.readchar)
g.applySnapshot(&st) g.applySnapshot(&st)
// defeat multiple restarting from the same place // defeat multiple restarting from the same place

View File

@@ -104,7 +104,7 @@ func (g *RogueGame) readScroll() {
// Or anything else nasty // Or anything else nasty
if ch := g.Level.VisibleChar(y, x); stepOk(ch) { if ch := g.Level.VisibleChar(y, x); stepOk(ch) {
if ch == Scroll { if ch == Scroll {
if fo := g.findObj(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster { if fo := g.Level.ObjectAt(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster {
continue continue
} }
} }

View File

@@ -85,7 +85,7 @@ func (g *RogueGame) doZap() {
} }
case WandPolymorph: case WandPolymorph:
pp := tp.Pack pp := tp.Pack
detachMon(&g.Level.Monsters, tp) g.Level.RemoveMonster(tp)
if g.seeMonst(tp) { if g.seeMonst(tp) {
g.mvaddch(y, x, g.Level.Char(y, x)) g.mvaddch(y, x, g.Level.Char(y, x))

View File

@@ -153,7 +153,7 @@ func (g *RogueGame) dropIt() {
obj = g.leavePack(obj, true, !obj.Kind.MergesInPack()) obj = g.leavePack(obj, true, !obj.Kind.MergesInPack())
// Link it into the level object list // Link it into the level object list
attachObj(&g.Level.Objects, obj) g.Level.AddObject(obj)
g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Kind.Glyph()) g.Level.SetChar(p.Pos.Y, p.Pos.X, obj.Kind.Glyph())
g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Set(FDropped) g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Set(FDropped)

View File

@@ -78,7 +78,7 @@ func (g *RogueGame) fall(obj *Object, pr bool) {
} }
} }
attachObj(&g.Level.Objects, obj) g.Level.AddObject(obj)
return return
} }