Files
rgoue/game/command.go

861 lines
15 KiB
Go

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")
}
}