diff --git a/game/command.go b/game/command.go index f5dbf1b..4e82a07 100644 --- a/game/command.go +++ b/game/command.go @@ -16,119 +16,7 @@ func (g *RogueGame) command() { g.DoFuses(Before) for ; ntimes > 0; ntimes-- { - g.Again = false - if g.HasHit { - g.endmsg() - g.HasHit = false - } - // these are illegal things for the player to be, so if any are - // set, someone's been poking in memory - if p.On(Slowed | Greedy | Invisible | Regenerates | Targeted) { - panic("player flags corrupted") - } - - g.look(true) - - if !g.Running { - g.DoorStop = false - } - - g.status() - g.LastScore = p.Purse - g.move(p.Pos.Y, p.Pos.X) - - if (!g.Running && g.Count == 0) || !g.Options.Jump { - g.refresh() // draw screen - } - - g.Take = 0 - g.After = true - // Read command or continue run - if g.Wizard { - g.NoScore = true - } - - var ch byte - - if g.NoCommand == 0 { - switch { - case g.Running || g.ToDeath: - ch = g.RunCh - case g.Count != 0: - ch = g.countCh - default: - ch = g.readchar() - - g.MoveOn = false - if g.Msgs.Mpos != 0 { // erase message if its there - g.msg("") - } - } - } else { - ch = '.' - } - - if g.NoCommand != 0 { - if g.NoCommand--; g.NoCommand == 0 { - p.Flags.Set(Awake) - g.msg("you can move again") - } - } else { - // check for prefixes - g.newCount = false - if isDigit(ch) { - g.Count = 0 - - g.newCount = true - for isDigit(ch) { - g.Count = min(g.Count*10+int(ch-'0'), 255) - - ch = g.readchar() - } - - g.countCh = ch - // turn off count for commands which don't make sense - // to repeat - switch ch { - case CTRL('B'), CTRL('H'), CTRL('J'), CTRL('K'), - CTRL('L'), CTRL('N'), CTRL('U'), CTRL('Y'), - '.', 'a', 'b', 'h', 'j', 'k', 'l', 'm', 'n', 'q', - 'r', 's', 't', 'u', 'y', 'z', 'B', 'C', 'H', 'I', - 'J', 'K', 'L', 'N', 'U', 'Y', - CTRL('D'), CTRL('A'): - default: - g.Count = 0 - } - } - // execute a command - if g.Count != 0 && !g.Running { - g.Count-- - } - - if ch != 'a' && ch != Escape && - !g.Running && g.Count == 0 && !g.ToDeath { - g.LLastComm = g.LastComm - g.LLastDir = g.LastDir - g.LLastPick = g.LastPick - g.LastComm = ch - g.LastDir = 0 - g.LastPick = nil - } - - g.dispatch(ch) - // turn off flags if no longer needed - if !g.Running { - g.DoorStop = false - } - } - // If he ran into something to take, let him pick it up. - if g.Take != 0 { - g.pickUp(g.Take) - } - - if !g.Running { - g.DoorStop = false - } + g.playTurn() if !g.After { ntimes++ @@ -138,287 +26,368 @@ func (g *RogueGame) command() { g.DoDaemons(After) g.DoFuses(After) - if p.IsRing(Left, RingSearching) { - g.search() - } else if p.IsRing(Left, RingTeleportation) && g.rnd(50) == 0 { - g.teleport() - } + g.ringTurnEffects(Left) + g.ringTurnEffects(Right) +} - if p.IsRing(Right, RingSearching) { +// ringTurnEffects fires the per-turn ring powers on one hand: searching +// searches, teleportation teleports on 1 turn in 50 (the tail of +// command.c command). +func (g *RogueGame) ringTurnEffects(hand int) { + p := &g.Player + if p.IsRing(hand, RingSearching) { g.search() - } else if p.IsRing(Right, RingTeleportation) && g.rnd(50) == 0 { + } else if p.IsRing(hand, RingTeleportation) && g.rnd(50) == 0 { g.teleport() } } -// dispatch runs one command character (the big switch in command.c; the -// loop stands in for its `goto over` re-dispatch). -func (g *RogueGame) dispatch(ch byte) { +// playTurn runs one full player turn: upkeep, one command, and pickup +// (the body of the command.c command loop). +func (g *RogueGame) playTurn() { p := &g.Player + g.turnUpkeep() + + var ch byte + + if g.NoCommand == 0 { + ch = g.readCommand() + } else { + ch = '.' + } + + if g.NoCommand != 0 { + if g.NoCommand--; g.NoCommand == 0 { + p.Flags.Set(Awake) + g.msg("you can move again") + } + } else { + g.executeCommand(ch) + } + // If he ran into something to take, let him pick it up. + if g.Take != 0 { + g.pickUp(g.Take) + } + + if !g.Running { + g.DoorStop = false + } +} + +// turnUpkeep redraws and resets per-turn state before a command is read +// (the top of the command.c command loop). +func (g *RogueGame) turnUpkeep() { + p := &g.Player + + g.Again = false + if g.HasHit { + g.endmsg() + g.HasHit = false + } + // these are illegal things for the player to be, so if any are + // set, someone's been poking in memory + if p.On(Slowed | Greedy | Invisible | Regenerates | Targeted) { + panic("player flags corrupted") + } + + g.look(true) + + if !g.Running { + g.DoorStop = false + } + + g.status() + g.LastScore = p.Purse + g.move(p.Pos.Y, p.Pos.X) + + if (!g.Running && g.Count == 0) || !g.Options.Jump { + g.refresh() // draw screen + } + + g.Take = 0 + g.After = true + // Read command or continue run + if g.Wizard { + g.NoScore = true + } +} + +// readCommand picks the next command character: run/count repeats or +// fresh input (the read block of command.c command). +func (g *RogueGame) readCommand() byte { + switch { + case g.Running || g.ToDeath: + return g.RunCh + case g.Count != 0: + return g.countCh + default: + ch := g.readchar() + + g.MoveOn = false + if g.Msgs.Mpos != 0 { // erase message if its there + g.msg("") + } + + return ch + } +} + +// executeCommand runs one read command with the repeat-count prefix and +// the repeat-history bookkeeping (the execute block of command.c +// command). +func (g *RogueGame) executeCommand(ch byte) { + // check for prefixes + g.newCount = false + ch = g.countPrefix(ch) + // execute a command + if g.Count != 0 && !g.Running { + g.Count-- + } + + if ch != 'a' && ch != Escape && + !g.Running && g.Count == 0 && !g.ToDeath { + g.LLastComm = g.LastComm + g.LLastDir = g.LastDir + g.LLastPick = g.LastPick + g.LastComm = ch + g.LastDir = 0 + g.LastPick = nil + } + + g.dispatch(ch) + // turn off flags if no longer needed + if !g.Running { + g.DoorStop = false + } +} + +// countPrefix consumes a numeric repeat-count prefix and returns the +// command character that follows it (command.c). +func (g *RogueGame) countPrefix(ch byte) byte { + if !isDigit(ch) { + return ch + } + + g.Count = 0 + + g.newCount = true + for isDigit(ch) { + g.Count = min(g.Count*10+int(ch-'0'), 255) + + ch = g.readchar() + } + + g.countCh = ch + // turn off count for commands which don't make sense to repeat + switch ch { + case CTRL('B'), CTRL('H'), CTRL('J'), CTRL('K'), + CTRL('L'), CTRL('N'), CTRL('U'), CTRL('Y'), + '.', 'a', 'b', 'h', 'j', 'k', 'l', 'm', 'n', 'q', + 'r', 's', 't', 'u', 'y', 'z', 'B', 'C', 'H', 'I', + 'J', 'K', 'L', 'N', 'U', 'Y', + CTRL('D'), CTRL('A'): + default: + g.Count = 0 + } + + return ch +} + +// dispatch runs one command character, re-running dispatchKey when it +// asks (the C `goto over` re-dispatch in command.c). +func (g *RogueGame) dispatch(ch byte) { for { - switch ch { - case ',': - var found *Object + newCh, again := g.dispatchKey(ch) + if !again { + return + } - for _, obj := range g.Level.Objects { - if obj.Pos.Y == p.Pos.Y && obj.Pos.X == p.Pos.X { - found = obj + ch = newCh + } +} - break - } - } +// dispatchKey runs one command character (the big switch of command.c). +// A true second result asks dispatch to run once more with the returned +// key. +func (g *RogueGame) dispatchKey(ch byte) (byte, bool) { + if h, ok := g.data.commandHandlers[ch]; ok { + h(g) - if found != nil { - if !g.levitCheck() { - g.pickUp(found.Kind.Glyph()) - } - } else { - if !g.Options.Terse { - g.addmsgf("there is ") - } + return 0, false + } - g.addmsgf("nothing here") + switch ch { + case CTRL('H'), CTRL('J'), CTRL('K'), CTRL('L'), + CTRL('Y'), CTRL('U'), CTRL('B'), CTRL('N'): + return g.runCommand(ch) + case 'F', 'f': + return g.fightCommand(ch) + case 'a': + return g.repeatCommand() + case 'm': + return g.moveOnCommand() + default: + g.After = false + if g.Wizard { + g.wizardCommand(ch) + } else { + g.illcom(ch) + } + } - if !g.Options.Terse { - g.addmsgf(" to pick up") - } + return 0, false +} - g.endmsg() - } - case '!': - g.shell() - case 'h': - g.moveHero(0, -1) - case 'j': - g.moveHero(1, 0) - case 'k': - g.moveHero(-1, 0) - case 'l': - g.moveHero(0, 1) - case 'y': - g.moveHero(-1, -1) - case 'u': - g.moveHero(-1, 1) - case 'b': - g.moveHero(1, -1) - case 'n': - g.moveHero(1, 1) - case 'H': - g.startRun('h') - case 'J': - g.startRun('j') - case 'K': - g.startRun('k') - case 'L': - g.startRun('l') - case 'Y': - g.startRun('y') - case 'U': - g.startRun('u') - case 'B': - g.startRun('b') - case 'N': - g.startRun('n') - case CTRL('H'), CTRL('J'), CTRL('K'), CTRL('L'), - CTRL('Y'), CTRL('U'), CTRL('B'), CTRL('N'): - if !p.On(Blind) { - g.DoorStop = true - g.Firstmove = true - } +// runCommand handles the ctrl-direction run-until-adjacent prefix; the +// returned key re-dispatches as the matching run command (command.c). +func (g *RogueGame) runCommand(ch byte) (byte, bool) { + if !g.Player.On(Blind) { + g.DoorStop = true + g.Firstmove = true + } - if g.Count != 0 && !g.newCount { - ch = g.direction - } else { - ch += 'A' - CTRL('A') - g.direction = ch - } + if g.Count != 0 && !g.newCount { + ch = g.direction + } else { + ch += 'A' - CTRL('A') + g.direction = ch + } - continue // the C goto over: re-dispatch as the run command - case 'F', 'f': - if ch == 'F' { - g.Kamikaze = true - } + return ch, true // the C goto over: re-dispatch as the run command +} - if !g.promptDirection() { - g.After = false +// repeatCommand handles 'a': replay the last command by re-dispatching +// it (command.c). +func (g *RogueGame) repeatCommand() (byte, bool) { + if g.LastComm == 0 { + g.msg("you haven't typed a command yet") + g.After = false - break - } + return 0, false + } - g.Delta.Y += p.Pos.Y - g.Delta.X += p.Pos.X + g.Again = true - mp := g.Level.MonsterAt(g.Delta.Y, g.Delta.X) - if mp == nil || (!g.seeMonst(mp) && !p.On(SenseMonsters)) { - if !g.Options.Terse { - g.addmsgf("I see ") - } + return g.LastComm, true // the C goto over: replay the last command +} - g.msg("no monster there") - g.After = false - } else if g.diagOk(p.Pos, g.Delta) { - g.ToDeath = true - g.MaxHit = 0 +// moveOnCommand handles 'm': move without picking up; the direction key +// re-dispatches (command.c). +func (g *RogueGame) moveOnCommand() (byte, bool) { + g.MoveOn = true + if !g.promptDirection() { + g.After = false - mp.Flags.Set(Targeted) + return 0, false + } - g.RunCh = g.DirCh - ch = g.DirCh + g.countCh = g.DirCh - continue // the C goto over: fight by running at it - } - case 't': - if !g.promptDirection() { - g.After = false - } else { - g.missile(g.Delta.Y, g.Delta.X) - } - case 'a': - if g.LastComm == 0 { - g.msg("you haven't typed a command yet") - g.After = false - } else { - ch = g.LastComm - g.Again = true + return g.DirCh, true // the C goto over: move onto it without pickup +} - continue // the C goto over: replay the last command - } - case 'q': - g.quaff() - case 'Q': - g.After = false - g.QComm = true - g.quit(0) - g.QComm = false - case 'i': - g.After = false - g.inventory(p.Pack, 0) - case 'I': - g.After = false - g.pickyInven() - case 'd': - g.dropIt() - case 'r': - g.readScroll() - case 'e': - g.eat() - case 'w': - g.wield() - case 'W': - g.wear() - case 'T': - g.takeOff() - case 'P': - g.ringOn() - case 'R': - g.ringOff() - case 'o': - g.option() - g.After = false - case 'c': - g.call() - g.After = false - case '>': - g.After = false - g.dLevel() - case '<': - g.After = false - g.uLevel() - case '?': - g.After = false - g.help() - case '/': - g.After = false - g.identify() - case 's': - g.search() - case 'z': - if g.promptDirection() { - g.doZap() - } else { - g.After = false - } - case 'D': - g.After = false - g.discovered() - case CTRL('P'): - g.After = false - g.msg("%s", g.Msgs.Huh) - case CTRL('R'): - g.After = false - g.refresh() - case 'v': - g.After = false - g.msg("version %s. (mctesq was here)", Release) - case 'S': - g.After = false - g.saveGame() - case '.': - // rest command - case ' ': - g.After = false // "legal" illegal command - case '^': - g.After = false - if g.promptDirection() { - g.Delta.Y += p.Pos.Y - g.Delta.X += p.Pos.X +// pickupCommand handles ',': pick up whatever is here (command.c). +func (g *RogueGame) pickupCommand() { + p := &g.Player - fp := g.Level.FlagsAt(g.Delta.Y, g.Delta.X) - if !g.Options.Terse { - g.addmsgf("You have found ") - } + var found *Object - switch { - case g.Level.Char(g.Delta.Y, g.Delta.X) != Trap: - g.msg("no trap there") - case p.On(Hallucinating): - g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)]) - default: - g.msg("%s", g.data.trName[*fp&FTrapMask]) - fp.Set(FSeen) - } - } - case Escape: // escape - g.DoorStop = false - g.Count = 0 - g.After = false - g.Again = false - case 'm': - g.MoveOn = true - if !g.promptDirection() { - g.After = false - } else { - ch = g.DirCh - g.countCh = g.DirCh + for _, obj := range g.Level.Objects { + if obj.Pos.Y == p.Pos.Y && obj.Pos.X == p.Pos.X { + found = obj - continue // the C goto over: move onto it without pickup - } - case ')': - g.current(p.CurWeapon, "wielding", "") - case ']': - g.current(p.CurArmor, "wearing", "") - case '=': - g.current(p.CurRing[Left], "wearing", - g.chooseTerse("(L)", "on left hand")) - g.current(p.CurRing[Right], "wearing", - g.chooseTerse("(R)", "on right hand")) - case '@': - g.StatMsg = true - g.status() - g.StatMsg = false - g.After = false - default: - g.After = false - if g.Wizard { - g.wizardCommand(ch) - } else { - g.illcom(ch) - } + break + } + } + + if found != nil { + if !g.levitCheck() { + g.pickUp(found.Kind.Glyph()) } return } + + if !g.Options.Terse { + g.addmsgf("there is ") + } + + g.addmsgf("nothing here") + + if !g.Options.Terse { + g.addmsgf(" to pick up") + } + + g.endmsg() +} + +// fightCommand handles the f/F fight prefix; a true again asks dispatch +// to rerun with the direction key (command.c). +func (g *RogueGame) fightCommand(ch byte) (byte, bool) { + p := &g.Player + if ch == 'F' { + g.Kamikaze = true + } + + if !g.promptDirection() { + g.After = false + + return 0, false + } + + g.Delta.Y += p.Pos.Y + g.Delta.X += p.Pos.X + + mp := g.Level.MonsterAt(g.Delta.Y, g.Delta.X) + if mp == nil || (!g.seeMonst(mp) && !p.On(SenseMonsters)) { + if !g.Options.Terse { + g.addmsgf("I see ") + } + + g.msg("no monster there") + g.After = false + } else if g.diagOk(p.Pos, g.Delta) { + g.ToDeath = true + g.MaxHit = 0 + + mp.Flags.Set(Targeted) + + g.RunCh = g.DirCh + + return g.DirCh, true // the C goto over: fight by running at it + } + + return 0, false +} + +// identifyTrapCommand handles '^': name the trap in a direction +// (command.c). +func (g *RogueGame) identifyTrapCommand() { + p := &g.Player + + g.After = false + if !g.promptDirection() { + return + } + + g.Delta.Y += p.Pos.Y + g.Delta.X += p.Pos.X + + fp := g.Level.FlagsAt(g.Delta.Y, g.Delta.X) + if !g.Options.Terse { + g.addmsgf("You have found ") + } + + switch { + case g.Level.Char(g.Delta.Y, g.Delta.X) != Trap: + g.msg("no trap there") + case p.On(Hallucinating): + g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)]) + default: + g.msg("%s", g.data.trName[*fp&FTrapMask]) + fp.Set(FSeen) + } } // wizardCommand handles the MASTER debug commands (command.c). @@ -444,6 +413,17 @@ func (g *RogueGame) wizardCommand(ch byte) { g.NewLevel() case CTRL('F'): g.showMap() + default: + g.wizardDebugCommand(ch) + } +} + +// wizardDebugCommand handles the rest of the MASTER debug commands +// (command.c). +func (g *RogueGame) wizardDebugCommand(ch byte) { + p := &g.Player + + switch ch { case CTRL('T'): g.teleport() case CTRL('E'): @@ -457,25 +437,7 @@ func (g *RogueGame) wizardCommand(ch byte) { item.Charges = 10000 } case CTRL('I'): - for range 9 { - g.raiseLevel() - } - // Give him a sword (+1,+1) - obj := newObject() - g.initWeapon(obj, WeaponTwoHandedSword) - obj.HPlus = 1 - obj.DPlus = 1 - g.addPack(obj, true) - p.CurWeapon = obj - // And his suit of armor - obj = newObject() - obj.Kind = KindArmor - obj.Which = int(ArmorPlateMail) - obj.ArmorClass = -5 - obj.Flags.Set(Known) - obj.Count = 1 - p.CurArmor = obj - g.addPack(obj, true) + g.wizardKit() case '*': g.prList() default: @@ -483,6 +445,32 @@ func (g *RogueGame) wizardCommand(ch byte) { } } +// wizardKit levels the wizard up and equips him (the ^I case of the +// command.c wizard switch). +func (g *RogueGame) wizardKit() { + p := &g.Player + + for range 9 { + g.raiseLevel() + } + // Give him a sword (+1,+1) + obj := newObject() + g.initWeapon(obj, WeaponTwoHandedSword) + obj.HPlus = 1 + obj.DPlus = 1 + g.addPack(obj, true) + p.CurWeapon = obj + // And his suit of armor + obj = newObject() + obj.Kind = KindArmor + obj.Which = int(ArmorPlateMail) + obj.ArmorClass = -5 + obj.Flags.Set(Known) + obj.Count = 1 + p.CurArmor = obj + g.addPack(obj, true) +} + // illcom reports an illegal command (command.c illcom). func (g *RogueGame) illcom(ch byte) { g.Msgs.SaveMsg = false @@ -514,59 +502,8 @@ func (g *RogueGame) search() { continue } - fp := g.Level.FlagsAt(y, x) - if fp.Has(FReal) { - continue - } - - foundone := false - - switch g.Level.Char(y, x) { - case '|', '-': - if g.rnd(5+probinc) != 0 { - break - } - - g.Level.SetChar(y, x, Door) - g.msg("a secret door") - - foundone = true - case Floor: - if g.rnd(2+probinc) != 0 { - break - } - - g.Level.SetChar(y, x, Trap) - - if !g.Options.Terse { - g.addmsgf("you found ") - } - - if p.On(Hallucinating) { - g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)]) - } else { - g.msg("%s", g.data.trName[*fp&FTrapMask]) - fp.Set(FSeen) - } - - foundone = true - case ' ': - if g.rnd(3+probinc) != 0 { - break - } - - g.Level.SetChar(y, x, Passage) - - foundone = true - } - - if foundone { + if g.searchSpot(y, x, probinc) { found = true - - fp.Set(FReal) - - g.Count = 0 - g.Running = false } } } @@ -576,6 +513,72 @@ func (g *RogueGame) search() { } } +// searchSpot tries to reveal one hidden thing next to the hero: a +// secret door, a hidden trap, or a secret passage (the loop body of +// command.c search). It reports whether something was found. +func (g *RogueGame) searchSpot(y, x, probinc int) bool { + fp := g.Level.FlagsAt(y, x) + if fp.Has(FReal) { + return false + } + + foundone := false + + switch g.Level.Char(y, x) { + case '|', '-': + if g.rnd(5+probinc) != 0 { + break + } + + g.Level.SetChar(y, x, Door) + g.msg("a secret door") + + foundone = true + case Floor: + foundone = g.searchFloor(fp, y, x, probinc) + case ' ': + if g.rnd(3+probinc) != 0 { + break + } + + g.Level.SetChar(y, x, Passage) + + foundone = true + } + + if foundone { + fp.Set(FReal) + + g.Count = 0 + g.Running = false + } + + return foundone +} + +// searchFloor reveals a hidden trap underfoot and names it (the FLOOR +// arm of command.c search). +func (g *RogueGame) searchFloor(fp *PlaceFlags, y, x, probinc int) bool { + if g.rnd(2+probinc) != 0 { + return false + } + + g.Level.SetChar(y, x, Trap) + + if !g.Options.Terse { + g.addmsgf("you found ") + } + + if g.Player.On(Hallucinating) { + g.msg("%s", g.data.trName[g.rnd(NumTrapTypes)]) + } else { + g.msg("%s", g.data.trName[*fp&FTrapMask]) + fp.Set(FSeen) + } + + return true +} + // help gives single character help, or the whole mess if he wants it // (command.c help). func (g *RogueGame) help() { @@ -585,40 +588,36 @@ func (g *RogueGame) help() { // If it's not a *, print the right help string or an error if he // typed a funny character. if helpch != '*' { - g.move(0, 0) - - for _, strp := range g.data.helpStr { - if strp.Ch == helpch { - g.Msgs.LowerMsg = true - g.msg("%s%s", unctrl(strp.Ch), strp.Desc) - g.Msgs.LowerMsg = false - - return - } - } - - g.msg("unknown character '%s'", unctrl(helpch)) + g.helpOne(helpch) return } - // Here we print help for everything, then wait before we return to - // command mode. - numprint := 0 + + g.helpAll() +} + +// helpOne prints the help line for a single key, or an error for a +// funny character (command.c help). +func (g *RogueGame) helpOne(helpch byte) { + g.move(0, 0) for _, strp := range g.data.helpStr { - if strp.Print { - numprint++ + if strp.Ch == helpch { + g.Msgs.LowerMsg = true + g.msg("%s%s", unctrl(strp.Ch), strp.Desc) + g.Msgs.LowerMsg = false + + return } } - if numprint&1 == 1 { // round odd numbers up - numprint++ - } + g.msg("unknown character '%s'", unctrl(helpch)) +} - numprint /= 2 - if numprint > NumLines-1 { - numprint = NumLines - 1 - } +// helpAll prints help for everything in two columns, then waits before +// returning to command mode (command.c help). +func (g *RogueGame) helpAll() { + numprint := g.helpLines() hw := g.scr.Hw hw.Clear() @@ -655,6 +654,29 @@ func (g *RogueGame) help() { g.refresh() } +// helpLines counts how many rows the two-column help listing needs +// (command.c help). +func (g *RogueGame) helpLines() int { + numprint := 0 + + for _, strp := range g.data.helpStr { + if strp.Print { + numprint++ + } + } + + if numprint&1 == 1 { // round odd numbers up + numprint++ + } + + numprint /= 2 + if numprint > NumLines-1 { + numprint = NumLines - 1 + } + + return numprint +} + // identify tells the player what a certain thing is (command.c identify). func (g *RogueGame) identify() { g.msg("what do you want identified? ") @@ -706,21 +728,25 @@ func (g *RogueGame) uLevel() { return } - if g.Level.Char(g.Player.Pos.Y, g.Player.Pos.X) == Stairs { - if g.HasAmulet { - g.Depth-- - if g.Depth == 0 { - g.totalWinner() - } - - g.NewLevel() - g.msg("you feel a wrenching sensation in your gut") - } else { - g.msg("your way is magically blocked") - } - } else { + if g.Level.Char(g.Player.Pos.Y, g.Player.Pos.X) != Stairs { g.msg("I see no way up") + + return } + + if !g.HasAmulet { + g.msg("your way is magically blocked") + + return + } + + g.Depth-- + if g.Depth == 0 { + g.totalWinner() + } + + g.NewLevel() + g.msg("you feel a wrenching sensation in your gut") } // levitCheck checks whether she's levitating, and if she is, prints an @@ -744,63 +770,14 @@ func (g *RogueGame) call() { return } - var ( - op *ObjInfo - elsewise string - know *bool - guess *string - ) - - it := &g.Items - - switch obj.Kind { - case KindRing: - op = &it.Rings[obj.Which] - elsewise = it.RingStones[obj.Which] - case KindPotion: - op = &it.Potions[obj.Which] - elsewise = it.PotColors[obj.Which] - case KindScroll: - op = &it.Scrolls[obj.Which] - elsewise = it.ScrNames[obj.Which] - case KindWand: - op = &it.Sticks[obj.Which] - elsewise = it.WandMade[obj.Which] - case KindFood: - g.msg("you can't call that anything") - - return - default: - guess = &obj.Label - elsewise = obj.Label - } - - fromGuess := false - - if op != nil { - know = &op.Know - - guess = &op.Guess - if *guess != "" { - elsewise = *guess - fromGuess = true - } - } else { - fromGuess = elsewise != "" && elsewise == *guess - } - - if know != nil && *know { - g.msg("that has already been identified") - + op, elsewise, guess, ok := g.callTarget(obj) + if !ok { return } - if fromGuess { - if !g.Options.Terse { - g.addmsgf("Was ") - } - - g.msg("called \"%s\"", elsewise) + elsewise, guess, ok = g.callPrelude(op, elsewise, guess) + if !ok { + return } g.msg("%s", g.chooseTerse("call it: ", "what do you want to call it? ")) @@ -811,24 +788,67 @@ func (g *RogueGame) call() { } } +// callTarget picks what a call names: the lore record for identifiable +// kinds, the object's own label otherwise; ok is false for food (the +// switch of command.c call). +func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) { + it := &g.Items + + switch obj.Kind { + case KindRing: + return &it.Rings[obj.Which], it.RingStones[obj.Which], nil, true + case KindPotion: + return &it.Potions[obj.Which], it.PotColors[obj.Which], nil, true + case KindScroll: + return &it.Scrolls[obj.Which], it.ScrNames[obj.Which], nil, true + case KindWand: + return &it.Sticks[obj.Which], it.WandMade[obj.Which], nil, true + case KindFood: + g.msg("you can't call that anything") + + return nil, "", nil, false + default: + return nil, obj.Label, &obj.Label, true + } +} + +// callPrelude resolves the item's current name, reporting a previous +// guess; ok is false when the item is already identified (command.c +// call). +func (g *RogueGame) callPrelude(op *ObjInfo, elsewise string, guess *string) (string, *string, bool) { + fromGuess := false + + if op != nil { + if op.Know { + g.msg("that has already been identified") + + return "", nil, false + } + + guess = &op.Guess + if *guess != "" { + elsewise = *guess + fromGuess = true + } + } else { + fromGuess = elsewise != "" && elsewise == *guess + } + + if fromGuess { + if !g.Options.Terse { + g.addmsgf("Was ") + } + + g.msg("called \"%s\"", elsewise) + } + + return elsewise, guess, true +} + // current prints the current weapon/armor (command.c current). func (g *RogueGame) current(cur *Object, how, where string) { g.After = false - if cur != nil { - if !g.Options.Terse { - g.addmsgf("you are %s (", how) - } - - g.InvDescribe = false - g.addmsgf("%c) %s", cur.PackCh, g.inventoryName(cur, true)) - - g.InvDescribe = true - if where != "" { - g.addmsgf(" %s", where) - } - - g.endmsg() - } else { + if cur == nil { if !g.Options.Terse { g.addmsgf("you are ") } @@ -840,7 +860,23 @@ func (g *RogueGame) current(cur *Object, how, where string) { } g.endmsg() + + return } + + if !g.Options.Terse { + g.addmsgf("you are %s (", how) + } + + g.InvDescribe = false + g.addmsgf("%c) %s", cur.PackCh, g.inventoryName(cur, true)) + + g.InvDescribe = true + if where != "" { + g.addmsgf(" %s", where) + } + + g.endmsg() } // shell lets them escape for a while (main.c shell). The terminal decides diff --git a/game/tables.go b/game/tables.go index 2d66273..05044f4 100644 --- a/game/tables.go +++ b/game/tables.go @@ -142,6 +142,11 @@ type gameData struct { // lands, indexed by monster letter - 'A': the cases of the fight.c // attack switch. A true return means the monster removed itself. hitHandlers [26]func(g *RogueGame, mp *Monster, mname string) bool + + // commandHandlers dispatches the ordinary command keys: the simple + // cases of the big command.c switch. Keys that re-dispatch (runs, + // fight, repeat, move-on) and wizard keys stay in dispatchKey. + commandHandlers map[byte]func(g *RogueGame) } // ripWall is the repeated blank wall line of the tombstone art. @@ -672,6 +677,140 @@ func newGameData() *gameData { 'V' - 'A': (*RogueGame).hitLifeDrainer, 'W' - 'A': (*RogueGame).hitLifeDrainer, }, + + commandHandlers: map[byte]func(*RogueGame){ + ',': (*RogueGame).pickupCommand, + '!': (*RogueGame).shell, + 'h': func(g *RogueGame) { g.moveHero(0, -1) }, + 'j': func(g *RogueGame) { g.moveHero(1, 0) }, + 'k': func(g *RogueGame) { g.moveHero(-1, 0) }, + 'l': func(g *RogueGame) { g.moveHero(0, 1) }, + 'y': func(g *RogueGame) { g.moveHero(-1, -1) }, + 'u': func(g *RogueGame) { g.moveHero(-1, 1) }, + 'b': func(g *RogueGame) { g.moveHero(1, -1) }, + 'n': func(g *RogueGame) { g.moveHero(1, 1) }, + 'H': func(g *RogueGame) { g.startRun('h') }, + 'J': func(g *RogueGame) { g.startRun('j') }, + 'K': func(g *RogueGame) { g.startRun('k') }, + 'L': func(g *RogueGame) { g.startRun('l') }, + 'Y': func(g *RogueGame) { g.startRun('y') }, + 'U': func(g *RogueGame) { g.startRun('u') }, + 'B': func(g *RogueGame) { g.startRun('b') }, + 'N': func(g *RogueGame) { g.startRun('n') }, + 't': func(g *RogueGame) { + if !g.promptDirection() { + g.After = false + } else { + g.missile(g.Delta.Y, g.Delta.X) + } + }, + 'q': (*RogueGame).quaff, + 'Q': func(g *RogueGame) { + g.After = false + g.QComm = true + g.quit(0) + g.QComm = false + }, + 'i': func(g *RogueGame) { + g.After = false + g.inventory(g.Player.Pack, 0) + }, + 'I': func(g *RogueGame) { + g.After = false + g.pickyInven() + }, + 'd': (*RogueGame).dropIt, + 'r': (*RogueGame).readScroll, + 'e': (*RogueGame).eat, + 'w': (*RogueGame).wield, + 'W': (*RogueGame).wear, + 'T': (*RogueGame).takeOff, + 'P': (*RogueGame).ringOn, + 'R': (*RogueGame).ringOff, + 'o': func(g *RogueGame) { + g.option() + g.After = false + }, + 'c': func(g *RogueGame) { + g.call() + g.After = false + }, + '>': func(g *RogueGame) { + g.After = false + g.dLevel() + }, + '<': func(g *RogueGame) { + g.After = false + g.uLevel() + }, + '?': func(g *RogueGame) { + g.After = false + g.help() + }, + '/': func(g *RogueGame) { + g.After = false + g.identify() + }, + 's': (*RogueGame).search, + 'z': func(g *RogueGame) { + if g.promptDirection() { + g.doZap() + } else { + g.After = false + } + }, + 'D': func(g *RogueGame) { + g.After = false + g.discovered() + }, + CTRL('P'): func(g *RogueGame) { + g.After = false + g.msg("%s", g.Msgs.Huh) + }, + CTRL('R'): func(g *RogueGame) { + g.After = false + g.refresh() + }, + 'v': func(g *RogueGame) { + g.After = false + g.msg("version %s. (mctesq was here)", Release) + }, + 'S': func(g *RogueGame) { + g.After = false + g.saveGame() + }, + '.': func(*RogueGame) { + // rest command + }, + ' ': func(g *RogueGame) { + g.After = false // "legal" illegal command + }, + '^': (*RogueGame).identifyTrapCommand, + Escape: func(g *RogueGame) { + g.DoorStop = false + g.Count = 0 + g.After = false + g.Again = false + }, + ')': func(g *RogueGame) { + g.current(g.Player.CurWeapon, "wielding", "") + }, + ']': func(g *RogueGame) { + g.current(g.Player.CurArmor, "wearing", "") + }, + '=': func(g *RogueGame) { + g.current(g.Player.CurRing[Left], "wearing", + g.chooseTerse("(L)", "on left hand")) + g.current(g.Player.CurRing[Right], "wearing", + g.chooseTerse("(R)", "on right hand")) + }, + '@': func(g *RogueGame) { + g.StatMsg = true + g.status() + g.StatMsg = false + g.After = false + }, + }, } }