package game // command.c — read and execute the user commands. // command processes one player command, with its BEFORE/AFTER daemon // bracketing (command.c command). func (g *RogueGame) command() { p := &g.Player ntimes := 1 // number of player moves if p.On(IsHaste) { ntimes++ } // Let the daemons start up g.DoDaemons(Before) 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(IsSlow | IsGreed | IsInvis | IsRegen | IsTarget) { 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 { if g.Running || g.ToDeath { ch = g.RunCh } else if g.Count != 0 { ch = g.countCh } else { 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(IsRun) 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 = g.Count*10 + int(ch-'0') if g.Count > 255 { g.Count = 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 } if !g.After { ntimes++ } } g.DoDaemons(After) g.DoFuses(After) if p.IsRing(Left, RSearch) { g.search() } else if p.IsRing(Left, RTeleport) && g.rnd(50) == 0 { g.teleport() } if p.IsRing(Right, RSearch) { g.search() } else if p.IsRing(Right, RTeleport) && g.rnd(50) == 0 { g.teleport() } } // dispatch runs one command character (the big switch in command.c, // including its `goto over` re-dispatch). func (g *RogueGame) dispatch(ch byte) { p := &g.Player over: switch ch { case ',': var found *Object for _, obj := range g.Level.Objects { if obj.Pos.Y == p.Pos.Y && obj.Pos.X == p.Pos.X { found = obj break } } if found != nil { if !g.levitCheck() { g.pickUp(found.Type) } } else { if !g.Options.Terse { g.addmsg("there is ") } g.addmsg("nothing here") if !g.Options.Terse { g.addmsg(" to pick up") } g.endmsg() } case '!': g.shell() case 'h': g.doMove(0, -1) case 'j': g.doMove(1, 0) case 'k': g.doMove(-1, 0) case 'l': g.doMove(0, 1) case 'y': g.doMove(-1, -1) case 'u': g.doMove(-1, 1) case 'b': g.doMove(1, -1) case 'n': g.doMove(1, 1) case 'H': g.doRun('h') case 'J': g.doRun('j') case 'K': g.doRun('k') case 'L': g.doRun('l') case 'Y': g.doRun('y') case 'U': g.doRun('u') case 'B': g.doRun('b') case 'N': g.doRun('n') case CTRL('H'), CTRL('J'), CTRL('K'), CTRL('L'), CTRL('Y'), CTRL('U'), CTRL('B'), CTRL('N'): if !p.On(IsBlind) { g.DoorStop = true g.Firstmove = true } if g.Count != 0 && !g.newCount { ch = g.direction } else { ch += 'A' - CTRL('A') g.direction = ch } goto over case 'F', 'f': if ch == 'F' { g.Kamikaze = true } if !g.getDir() { g.After = false break } 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(SeeMonst)) { if !g.Options.Terse { g.addmsg("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(IsTarget) g.RunCh = g.DirCh ch = g.DirCh goto over } case 't': if !g.getDir() { 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 goto over } 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.getDir() { 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.getDir() { 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.addmsg("You have found ") } if g.Level.Char(g.Delta.Y, g.Delta.X) != Trap { g.msg("no trap there") } else if p.On(IsHalu) { g.msg("%s", trName[g.rnd(NTraps)]) } else { g.msg("%s", trName[*fp&FTMask]) 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.getDir() { g.After = false } else { ch = g.DirCh g.countCh = g.DirCh goto over } 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) } } } // wizardCommand handles the MASTER debug commands (command.c). func (g *RogueGame) wizardCommand(ch byte) { p := &g.Player switch ch { case '|': g.msg("@ %d,%d", p.Pos.Y, p.Pos.X) case 'C': g.createObj() case '$': g.msg("inpack = %d", p.Inpack) case CTRL('G'): g.inventory(g.Level.Objects, 0) case CTRL('W'): g.whatis(false, 0) case CTRL('D'): g.Depth++ g.NewLevel() case CTRL('A'): g.Depth-- g.NewLevel() case CTRL('F'): g.showMap() case CTRL('T'): g.teleport() case CTRL('E'): g.msg("food left: %d", p.FoodLeft) case CTRL('C'): g.addPass() case CTRL('X'): g.turnSee(p.On(SeeMonst)) case CTRL('~'): if item := g.getItem("charge", int(Stick)); item != nil { item.SetCharges(10000) } case CTRL('I'): for range 9 { g.raiseLevel() } // Give him a sword (+1,+1) obj := newObject() g.initWeapon(obj, TwoSword) obj.HPlus = 1 obj.DPlus = 1 g.addPack(obj, true) p.CurWeapon = obj // And his suit of armor obj = newObject() obj.Type = Armor obj.Which = PlateMail obj.Arm = -5 obj.Flags.Set(IsKnow) obj.Count = 1 p.CurArmor = obj g.addPack(obj, true) case '*': g.prList() default: g.illcom(ch) } } // illcom reports an illegal command (command.c illcom). func (g *RogueGame) illcom(ch byte) { g.Msgs.SaveMsg = false g.Count = 0 g.msg("illegal command '%s'", unctrl(ch)) g.Msgs.SaveMsg = true } // search gropes about to find hidden things (command.c search). func (g *RogueGame) search() { p := &g.Player ey := p.Pos.Y + 1 ex := p.Pos.X + 1 probinc := 0 if p.On(IsHalu) { probinc = 3 } if p.On(IsBlind) { probinc += 2 } found := false for y := p.Pos.Y - 1; y <= ey; y++ { for x := p.Pos.X - 1; x <= ex; x++ { if y == p.Pos.Y && x == p.Pos.X { 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.addmsg("you found ") } if p.On(IsHalu) { g.msg("%s", trName[g.rnd(NTraps)]) } else { g.msg("%s", trName[*fp&FTMask]) fp.Set(FSeen) } foundone = true case ' ': if g.rnd(3+probinc) != 0 { break } g.Level.SetChar(y, x, Passage) foundone = true } if foundone { found = true fp.Set(FReal) g.Count = 0 g.Running = false } } } if found { g.look(false) } } // help gives single character help, or the whole mess if he wants it // (command.c help). func (g *RogueGame) help() { g.msg("character you want help for (* for all): ") helpch := g.readchar() g.Msgs.Mpos = 0 // 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 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)) return } // Here we print help for everything, then wait before we return to // command mode. numprint := 0 for _, strp := range helpStr { if strp.Print { numprint++ } } if numprint&1 == 1 { // round odd numbers up numprint++ } numprint /= 2 if numprint > NumLines-1 { numprint = NumLines - 1 } hw := g.scr.Hw hw.Clear() cnt := 0 for _, strp := range helpStr { if !strp.Print { continue } x := 0 if cnt >= numprint { x = NumCols / 2 } hw.Move(cnt%numprint, x) if strp.Ch != 0 { hw.AddStr(unctrl(strp.Ch)) } hw.AddStr(strp.Desc) if cnt++; cnt >= numprint*2 { break } } hw.MvAddStr(NumLines-1, 0, "--Press space to continue--") g.scr.RefreshWin(hw) g.waitFor(' ') g.msg("") g.refresh() } // identList is command.c's static ident_list. var identList = []helpEntry{ {'|', "wall of a room", false}, {'-', "wall of a room", false}, {Gold, "gold", false}, {Stairs, "a staircase", false}, {Door, "door", false}, {Floor, "room floor", false}, {PlayerCh, "you", false}, {Passage, "passage", false}, {Trap, "trap", false}, {Potion, "potion", false}, {Scroll, "scroll", false}, {Food, "food", false}, {Weapon, "weapon", false}, {' ', "solid rock", false}, {Armor, "armor", false}, {Amulet, "the Amulet of Yendor", false}, {Ring, "ring", false}, {Stick, "wand or staff", false}, } // identify tells the player what a certain thing is (command.c identify). func (g *RogueGame) identify() { g.msg("what do you want identified? ") ch := g.readchar() g.Msgs.Mpos = 0 if ch == Escape { g.msg("") return } var str string if isUpper(ch) { str = g.Monsters[ch-'A'].Name } else { str = "unknown character" for _, hp := range identList { if hp.Ch == ch { str = hp.Desc break } } } g.msg("'%s': %s", unctrl(ch), str) } // dLevel: he wants to go down a level (command.c d_level). func (g *RogueGame) dLevel() { if g.levitCheck() { return } if g.Level.Char(g.Player.Pos.Y, g.Player.Pos.X) != Stairs { g.msg("I see no way down") } else { g.Depth++ g.SeenStairs = false g.NewLevel() } } // uLevel: he wants to go up a level (command.c u_level). func (g *RogueGame) uLevel() { if g.levitCheck() { 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 { g.msg("I see no way up") } } // levitCheck checks whether she's levitating, and if she is, prints an // appropriate message (command.c levit_check). func (g *RogueGame) levitCheck() bool { if !g.Player.On(IsLevit) { return false } g.msg("You can't. You're floating off the ground!") return true } // call allows a user to call a potion, scroll, or ring something // (command.c call). func (g *RogueGame) call() { obj := g.getItem("call", Callable) // Make certain that it is something that we want to name if obj == nil { return } var op *ObjInfo var elsewise string var know *bool var guess *string it := &g.Items switch obj.Type { case Ring: op = &it.Rings[obj.Which] elsewise = it.RingStones[obj.Which] case Potion: op = &it.Potions[obj.Which] elsewise = it.PotColors[obj.Which] case Scroll: op = &it.Scrolls[obj.Which] elsewise = it.ScrNames[obj.Which] case Stick: op = &it.Sticks[obj.Which] elsewise = it.WandMade[obj.Which] case Food: 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") return } if fromGuess { if !g.Options.Terse { g.addmsg("Was ") } g.msg("called \"%s\"", elsewise) } g.msg("%s", g.chooseTerse("call it: ", "what do you want to call it? ")) buf := elsewise if g.getStr(&buf, g.scr.Std) == Norm { *guess = buf } } // 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.addmsg("you are %s (", how) } g.InvDescribe = false g.addmsg("%c) %s", cur.PackCh, g.invName(cur, true)) g.InvDescribe = true if where != "" { g.addmsg(" %s", where) } g.endmsg() } else { if !g.Options.Terse { g.addmsg("you are ") } g.addmsg("%s nothing", how) if where != "" { g.addmsg(" %s", where) } g.endmsg() } } // shell lets them escape for a while (main.c shell). The terminal decides // how to actually suspend; headless terminals just decline. func (g *RogueGame) shell() { g.After = false if se, ok := g.scr.term.(interface{ ShellEscape() }); ok { g.InShell = true se.ShellEscape() g.InShell = false g.refresh() } else { g.msg("shell escape is not available") } }