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(Hasted) { 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(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 } if !g.After { ntimes++ } } 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() } if p.IsRing(Right, RingSearching) { g.search() } else if p.IsRing(Right, 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) { p := &g.Player for { 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.Kind.Glyph()) } } else { if !g.Options.Terse { g.addmsgf("there is ") } g.addmsgf("nothing here") if !g.Options.Terse { g.addmsgf(" to pick up") } 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 } 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 } if !g.promptDirection() { 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(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 ch = 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 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 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) } } 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 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) } } return } } // 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(SenseMonsters)) case CTRL('~'): if item, ok := g.promptPackItem("charge", KindWand); ok { 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) 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(Hallucinating) { probinc = 3 } if p.On(Blind) { 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.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 { 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 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)) return } // Here we print help for everything, then wait before we return to // command mode. 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 } hw := g.scr.Hw hw.Clear() cnt := 0 for _, strp := range g.data.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() } // 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 g.data.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(Levitating) { 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, ok := g.promptPackItem("call", KindCallable) // Make certain that it is something that we want to name if !ok { 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") return } if fromGuess { if !g.Options.Terse { g.addmsgf("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.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 !g.Options.Terse { g.addmsgf("you are ") } g.addmsgf("%s nothing", how) if where != "" { g.addmsgf(" %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") } }