import curses
from curses import wrapper
from curses import panel
import logging, pprint
from util import *
import pyro_items
import sys
# Check what OS we're running under; keycodes differ:
WINDOWS = False
try:
WINDOWS = 'WCurses' in curses.__version__
except AttributeError:
pass
ESC = 27
SPC = 32
DEL = 127
tiles = {
# Terrain/features:
"floor": ".",
"wall": "#",
"door_open": "/",
"door_closed": "+",
"trap": "^",
"stairs_up": "<",
"stairs_down": ">",
"fire": "#",
# Mobs:
"player": "@",
"mob": "x", # The most generic mob tile, shouldn't be seen
"ape": "a",
"ant": "a",
"bat": "b",
"centipede": "c",
"canine": "d",
"dragon": "D",
"eye": "e",
"feline": "f",
"goblin": "g",
"golem": "G",
"humanoid": "h",
"imp": "i",
"jelly": "j",
"kobold": "k",
"lizard": "l",
"mold": "m",
"ogre": "O",
"rodent": "r",
"spider": "s",
"snake": "s",
"troll": "T",
"undead": "z",
"lurker": ".",
"demon": "&",
# Items:
"wand": "/",
"ring": "=",
"amulet": '"',
"stone": "*",
"armor": "[",
"melee_weapon": "(",
"missile_weapon": "{",
"tool": "~",
"scroll": "?",
"book": "+",
"potion": "!",
"food": "%",
"corpse": "%",
"ammunition": "|",
"money": "$",
# Misc:
"unknown": ";",
"blank": " ",
}
[docs]class TargettingCommand:
mode = 'x' # x = cancel, t = mob, d = direction
direction = [0, 0]
target = None
path = []
blocked = []
[docs]class IOWrapper(object):
def __init__(self, stdscr):
self.more_prompt = " ^Y^%s^0^" % lang.prompt_more
self.any_key_prompt = "^Y^%s^0^" % lang.prompt_any_key
self.message_log = []
self.attack_ticker = []
self.message_wait = False
self.tab_targeting = False
self.stdscr = stdscr
self.stdscr.clear()
self.screen = stdscr # FIXME
self.screen.refresh()
curses.initscr()
curses.cbreak()
curses.noecho()
self.stdscr.keypad(1)
curses.curs_set(0)
self.messages_displayed = 0
# Colors
self.colors = [curses.color_pair(0)]
for i in range(1, 16):
curses.init_pair(i, i % 8, 0)
if i < 8:
self.colors.append(curses.color_pair(i))
else:
self.colors.append(curses.color_pair(i) | curses.A_BOLD)
# Layout
self.msg_height = 3
self.status_height = 3
self.stats_width = 1
self.height = None
self.width = None
self.game_height = None
self.game_width = None
self.game_start_y = None
self.game_start_x = None
# TODO: reset these on new level
self.game_ul_x = -1
self.game_ul_y = -1
self.calc_layout()
# Windows
self.msg_win = curses.newwin(self.msg_height, self.width, 0, 0)
self.msg_win.refresh()
self.game_win = curses.newwin(
self.game_height, self.game_width, self.msg_height, self.stats_width)
self.status_win = curses.newwin(
self.status_height, self.width, self.msg_height+self.game_height, 0)
self.status_win.refresh()
self.clear()
[docs] def calc_layout(self):
self.height, self.width = self.stdscr.getmaxyx()
self.game_height = self.height - self.msg_height - self.status_height
self.game_width = self.width - self.stats_width
self.game_start_y = self.msg_height
self.game_start_x = self.stats_width
self.pad_height = 80
self.pad_width = 180
self.pad = curses.newpad(self.pad_height, self.pad_width)
curses.doupdate()
self.pad.touchwin()
[docs] def clear(self):
self.dattr = self.colors[0]
self.chars = [[" "] * self.width for i in range(self.height)]
self.attrs = [[self.colors[0]] *
self.width for i in range(self.height)]
self.cursor = [0, 0]
self.pad.clear()
self.pad.refresh(0, 0,
self.game_start_y, self.game_start_x,
self.game_height, self.game_width)
for win in [self.msg_win, self.status_win]: # , self.stats_win ]:
win.refresh()
Global.FullDungeonRefresh = True
[docs] def clearline(self, lines, win):
"Clear one or more lines."
try:
for line in lines:
self.addstr(line, 0, ' ' * self.width, win, c_white)
except TypeError:
self.addstr(lines, 0, ' ' * self.width, win, c_white)
[docs] def getch(self):
return self.screen.getch()
[docs] def keypad(self, arg):
return self.screen.keypad(arg)
[docs] def move(self, y, x):
y, x = Global.pc.y, Global.pc.x
x_step = self.game_width // 2
y_step = self.game_height // 2
adj_x = max(x - self.game_width // 2, 0)
adj_y = max(y - self.game_height // 2, 0)
if self.game_ul_x == -1 or self.game_ul_y == -1:
# initial center
y, x = adj_y, adj_x
self.game_ul_x = x
self.game_ul_y = y
else:
if x - self.game_ul_x < 5:
self.game_ul_x = max(self.game_ul_x - x_step, 0)
elif (self.game_ul_x+self.game_width) - x < 5:
# try to move right
self.game_ul_x = min(
self.game_ul_x + x_step, self.pad_width-self.game_width)
if y - self.game_ul_y < 5:
# try to move up
self.game_ul_y = max(self.game_ul_y - y_step, 0)
elif (self.game_ul_y+self.game_height) - y < 5:
# try to move down
self.game_ul_y = min(
self.game_ul_y + y_step, self.pad_height-self.game_height)
self.pad.refresh(self.game_ul_y, self.game_ul_x,
self.game_start_y, self.game_start_x,
self.game_start_y + self.game_height-2,
self.game_start_x + self.game_width-2)
curses.doupdate()
self.pad.touchwin()
[docs] def PutChar(self, y, x, ch, attr):
if True or self.chars[y][x] != ch or self.attrs[y][x] != attr:
self.pad.addstr(y, x, ch, curses.color_pair(attr))
[docs] def refresh(self):
Global.FullDungeonRefresh = True
self.msg_win.touchwin()
self.msg_win.refresh()
self.status_win.touchwin()
self.status_win.refresh()
self.move(0, 0)
self.pad.touchwin()
self.stdscr.refresh()
[docs] def AnimateProjectile(self, path, char, color):
"Show a projectile traveling the specified path."
path, clear = path
for x, y in path:
ch = "*"
self.PutChar(y, x, ch, color)
animation_delay()
self.move(Global.pc.y, Global.pc.x)
self.refresh()
for px, py in path:
Global.pc.current_level.PaintSquare(px, py)
self.move(Global.pc.y, Global.pc.x)
self.refresh()
[docs] def AnimateAreaEffect(self, tx, ty, radius, char, color):
'''Show a 'Ball' effect, returning the affected points
Args
tx (int) : x position
ty (int) : y position
radius (int) radius in squares
char (char): character to draw
color (curses color): mapped in util
Returns
pts affected (As calculated by fov.Ball
'''
pts = Global.pc.current_level.fov.Ball(
tx, ty, radius, ignore_walls=False)
for pt in pts:
self.PutChar(pt[1], pt[0], "*", color)
self.PutChar(ty, tx, "*", c_Red)
animation_delay()
self.pad.refresh(0, 0,
self.game_start_y, self.game_start_x,
self.game_height, self.game_width)
for pt in pts:
Global.pc.current_level.PaintSquare(pt[0], pt[1])
self.move(Global.pc.y, Global.pc.x)
return pts
[docs] def Ask(self, question, opts, attr=c_yellow):
"Ask the player a question."
self.Message(question)
while True:
k = self.GetKey()
if chr(k) in opts:
answer = chr(k)
break
self.msg_win.addstr(0, 0, " " * self.width)
self.messages_displayed = 0
return answer
[docs] def BeginTurn(self):
"Called right after input is taken from the player."
self.msg_win.clear()
self.msg_win.touchwin()
self.msg_win.refresh()
self.messages_displayed = 0
[docs] def ClearScreen(self):
self.screen.clear()
[docs] def CreatureSymbol(self, mob):
"Return a message code for a mob's symbol."
tile = mob.tile
color = "^0^"
for k in msg_colors:
if msg_colors[k] == mob.color:
color = "^%s^" % k
return "%s%s^0^" % (color, tile)
[docs] def CommandList(self, pc, lattr=c_yellow, hattr=c_Yellow):
"Display the keyboard commands to the player."
L = []
L.append(lang.label_help_title.center(self.game_width-1))
L.append("")
L.append(lang.label_keyboard_commands)
L.append("^Y^" + lang.label_movement_keys)
L.append(" ^g^7 8 9 y k u")
L.append(" ^g^ \ | / \ | /")
L.append(
" ^g^4 ^y^- ^g^5 ^y^- ^g^6 ^g^%s ^G^h ^y^- ^G^. ^y^- ^G^l" % lang.word_or)
L.append(" ^y^ / | \ / | \ ")
L.append("^G^ 1 2 3 b j n")
L.append("^G^5 ^y^ %s" % (lang.label_rest_one_turn))
L.append(" ^G^.^y^ %s" % lang.label_run)
L.append("")
special = {9: lang.key_tab}
for c in pc.commands:
keys = []
for k in c.keys:
try:
keys.append(special[k])
except KeyError:
keys.append(chr(k))
ckeys = (" ^0^%s^G^ " % lang.word_or).join(keys)
cstr = "^G^%6s^y^ - ^y^%s" % (ckeys, c.desc[:32])
L.append(cstr)
self.MsgWindow(L)
[docs] def GetDetailedStats(self, pc):
"Show a detailed player stats screen."
# self.screen.clear()
L = []
L.append(("^Y^-= %s =-" % lang.label_char_title) %
(pc.name, pc.level, pc.archetype.cname))
L.append("")
b = pc.MeleeDamageBonus()
if b >= 0:
b = "+%s" % b
L.append("^Y^%s: %2s^0^ %s %s" % (lang.stat_abbr_str.upper(),
pc.stats("str"), b, lang.label_melee_damage))
L.append(" %.2fs carried, %s: %2s" %
(pc.inventory.TotalWeight(), lang.stat_abbr_estr, pc.eSTR()))
L.append("")
b = pc.MeleeHitBonus()
if b >= 0:
b = "+%s" % b
L.append("^Y^%s: %2s^0^ %s %s" % (lang.stat_abbr_dex.upper(),
pc.stats("dex"), b, lang.label_to_hit))
b = pc.EvasionBonus()
if b >= 0:
b = "+%s" % b
if pc.stats("dex") - 8 > pc.eSTR():
limit = "^R^ %s^0^" % lang.label_evasion_limited
else:
limit = ""
L.append(" %s %s%s" % (b, lang.word_evasion, limit))
L.append("")
L.append("^Y^%s: %2s^0^ %s%% %s" %
(lang.stat_abbr_int.upper(), pc.stats("int"),
max(0, min(100, 25*(10-pc.stats("int")))), lang.label_spell_failure))
L.append(" %s%% %s" % (
max(0, min(100, 25*(8-pc.stats("int")))), lang.label_item_use_failure))
L.append("")
return L
[docs] def DetailedStats(self, pc):
self.MsgWindow(self.GetDetailedStats(pc))
[docs] def DisplayInventory(self, mob, norefresh=False, equipped=False, types=""):
"Display inventory."
choices = []
if types == "":
display_types = pyro_items.types
else:
display_types = [t for t in pyro_items.types if t[1] in types]
while True:
choices = []
for type, symbol in display_types:
if equipped:
itemlist = [i for i in mob.inventory.ItemsOfType(type) if \
i[0] in mob.equipped ]
else:
itemlist = [i for i in mob.inventory.ItemsOfType(type) if not \
i[0] in mob.equipped ]
for x, letter in itemlist:
choices.append([letter, x.name, x.desc])
if equipped:
cmds = ['*', '[*] Inventory']
title = 'Equipment'
else:
cmds = ['*', '[*] Equipment']
title = 'Inventory'
choice = self.ChoiceWindow(title=title, msg=choices, cmds=cmds)
if choice is None:
return None
elif choice is '*':
logging.debug('caught *')
equipped = not equipped
else:
ret = choices[choice][0]
return ret
[docs] def DisplayText(self, text, attr=c_white):
"Display multiline text (\n separated) and wait for a keypress."
self.ClearScreen()
text = "\n".join(wrap(text, self.width-2))
y = self.addstr_color(0, 0, text, self.stdscr, attr)
self.addstr_color(y+1, 0, lang.prompt_any_key, self.stdscr, c_Yellow)
self.GetKey()
self.ClearScreen()
[docs] def DrawPathToTarget(self):
if self.path_clear:
color = c_green
else:
color = c_red
for x, y in self.path[1:-1]:
self.PutChar(y, x, "*", color)
Global.pc.current_level.Dirty(x, y)
[docs] def EndTurn(self):
"Called right before input is taken from the player."
# Put the cursor on the @:
if self.tab_targeting and Global.pc.target:
#self.pad.move(Global.pc.target.y, Global.pc.target.x)
self.move(Global.pc.target.y, Global.pc.target.x)
else:
self.move(Global.pc.y, Global.pc.x)
[docs] def GetChoice(self, item_list, prompt="Choose one: ", nohelp=False, nocancel=True):
"Allow the player to choose from a list of options with descriptions."
# item_list must be a list of objects with attributes 'name' and 'desc'.
if nohelp:
ex = ""
else:
ex = "?"
if not nocancel:
ex += " "
prompt += " (%s) " % lang.label_space_to_cancel
choice = None
while True:
r = self.Menu([r.name for r in item_list],
question=prompt, attr=c_yellow, key_attr=c_Yellow,
doublewide=True, extra_opts=ex)
if r == ' ':
choice = None
break
if r in letters:
# An item was chosen:
choice = item_list[letters.index(r)]
break
if r == "?":
# Show a description:
r = self.Menu([r.name for r in item_list],
question=lang.prompt_describe_which_item,
attr=c_yellow, key_attr=c_Yellow,
doublewide=True)
if r in letters:
# self.ClearScreen()
y = 0
for line in wrap(item_list[letters.index(r)].desc, self.width-1):
self.addstr(y, 0, line, c_yellow, self.stdscr)
y += 1
self.WaitPrompt(y, c_Yellow)
self.refresh()
return choice
[docs] def GetDirection(self):
"Ask the player for a direction."
self.addstr(0, 0, lang.prompt_which_direction, self.stdscr)
while True:
k = self.GetKey()
if k in range(49, 58):
dx, dy = offsets[k-49]
r = k, dx, dy
break
elif k in arrow_offsets:
dx, dy = arrow_offsets[k]
r = k, dx, dy
break
elif chr(k) in vi_offsets:
dx, dy = vi_offsets[chr(k)]
r = k, dx, dy
break
elif k in [SPC, ESC]:
r = None, None, None
break
self.addstr(0, 0, " " * self.width, self.stdscr)
return r
[docs] def GetDirectionOrTarget(self, caster, target_range=None):
'''Return a direction or a target for targetting.
Returns
class TargettingCommand:
mode = 'x' # x = cancel, t = mob, d = direction
direction = [0,0]
target = None
path = []
blocked = []
'''
self.screen.addstr(0, 0, lang.prompt_direction_or_target)
self.refresh()
cmd = TargettingCommand()
while True:
k = self.GetKey()
if k in range(49, 58):
dx, dy = offsets[k-49]
path, blocked = range_direction_path(caster.x, caster.y, target_range, [
dx, dy], Global.pc.current_level.BlocksPassage)
cmd.type = 'd'
cmd.direction = [dx, dy]
cmd.path = path
cmd.blocked = blocked
break
elif k in arrow_offsets:
dx, dy = arrow_offsets[k]
path, blocked = range_direction_path(caster.x, caster.y, target_range, [
dx, dy], Global.pc.current_level.BlocksPassage)
cmd.type = 'd'
cmd.direction = [dx, dy]
cmd.path = path
cmd.blocked = blocked
break
elif chr(k) in vi_offsets:
dx, dy = vi_offsets[chr(k)]
path, blocked = range_direction_path(caster.x, caster.y, target_range, [
dx, dy], Global.pc.current_level.BlocksPassage)
cmd.type = 'd'
cmd.direction = [dx, dy]
cmd.path = path
cmd.blocked = blocked
break
elif k in [SPC, ESC]:
cmd.type = 'x'
break
elif chr(k) == 't':
target, (path, blocked) = self.GetTarget(
target_range=target_range)
if target:
cmd.type = 't'
cmd.target = target
cmd.path = path
cmd.blocked = blocked
else:
cmd.type = 'x'
break
self.addstr(0, 0, " " * self.width, self.stdscr)
return cmd
[docs] def GetItemFromInventory(self, mob, prompt=None, equipped=False, types="", notoggle=False):
"Ask the player to choose an item from inventory."
# If notoggle is true, then the player can't move between equipped and backpack
hattr, lattr, sattr = c_Yellow, c_yellow, c_White
need_refresh = True
k = self.DisplayInventory(mob, types, equipped=equipped)
try:
item = mob.inventory.GetItemByLetter(k)
if item is None:
return None
except ValueError:
return None
return item
[docs] def GetKey(self):
"Return the next keystroke in queue, blocking if none."
self.screen.refresh()
if Global.KeyBuffer:
# Take the keystroke from the buffer if available
# (for automated testing and maybe macros)
k, Global.KeyBuffer = Global.KeyBuffer[0], Global.KeyBuffer[1:]
return ord(k)
k = 0
while not 0 < k < 512:
k = self.screen.getch()
return k
[docs] def GetLocation(self, prompt=lang.prompt_choose_location):
"Ask the player to specify a square in the current level."
x, y = Global.pc.x, Global.pc.y
prompt = "%s (%s)" % (prompt, lang.prompt_enter_select_space_cancel)
self.screen.addstr_color(0, 0, prompt, c_yellow)
while True:
self.move(y, x)
k = self.GetKey()
dx, dy = 0, 0
if k in range(49, 58):
dx, dy = offsets[k-49]
elif chr(k) in vi_offsets:
dx, dy = vi_offsets[chr(k)]
elif k in [SPC, ESC]:
r = None, None
break
elif k in (10, 13):
r = x, y
break
x = max(0, min(x+dx, Global.pc.current_level.width-1))
y = max(0, min(y+dy, Global.pc.current_level.height-1))
self.addstr(0, 0, " " * self.width, self.stdscr)
return r
[docs] def GetMonsterChoice(self, prompt):
monster = Global.IO.GetString(prompt, noblank=True,
pattr=c_yellow, iattr=c_Yellow)
self.clearline(0, self.stdscr)
return monster
[docs] def GetQuantity(self, max_qty=None, prompt="How many?"):
if max_qty:
range = "1-%s, " % max_qty
else:
range = ""
qstr = "[%s*=all, blank=cancel]" % range
prompt = "%s %s: " % (prompt, qstr)
qty = self.GetString(prompt, 0, 0, mask="0123456789*")
self.clearline(0, self.stdscr)
if qty == "*":
return max_qty
elif qty == "":
return None
else:
try:
qty = int(qty)
except ValueError:
self.Message(lang.error_bad_qty)
return None
if max_qty is not None:
qty = min(qty, max_qty)
return qty
[docs] def GetSpell(self):
"Ask the player to choose a spell."
spells = Global.pc.spells
if not spells:
self.Message(lang.error_no_spells_known)
return None
title = lang.prompt_choose_spell
choices = []
letters = "abcdefghijklmnopqrstuvwxyz0123456789"
for idx, spell in enumerate(spells):
choices.append([letters[idx],
spell.name,
spell.desc])
ret = self.ChoiceWindow(title=title,
msg=choices)
if ret is not None:
ret = spells[ret]
logging.debug("Spell: {0}".format(ret))
return ret
[docs] def GetString(self, prompt, x=0, y=0, pattr=c_yellow, iattr=c_yellow,
max_length=999, noblank=False, nostrip=False, mask=None):
"Prompt the user for a string and return it."
mask = mask or " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
str = ""
self.addstr(y, x, prompt, self.stdscr, pattr)
x += len(prompt)
while True:
self.screen.move(y, x + len(str))
k = self.GetKey()
if chr(k) in mask:
str += chr(k)
elif k == 8 or k == curses.KEY_BACKSPACE or k == DEL:
# Backspace
str = str[:-1]
elif k in (10, 13):
# Enter
if str.strip() or not noblank:
if nostrip:
return str
else:
return str.strip()
self.addstr(y, x, str+" ", self.stdscr, iattr)
[docs] def GetTarget(self, prompt=lang.prompt_choose_target, LOS=True, target=None, target_range=None):
"""Ask the player to target a mob.
Returns:
mob : a creature selected for targetting
(path, blocked) : list of Squares in the path, list of blocked squres
"""
path, clear = [], False
mobs = self.NearbyMobCycler(target_range=target_range)
if Global.pc.target:
# If a target is already selected, default to it:
x, y = Global.pc.target.x, Global.pc.target.y
if not Global.pc.current_level.fov.Lit(x, y):
x, y = Global.pc.x, Global.pc.y
# If target not visible or out of range it can't be our target
if target_range:
range_to_target = calc_distance(
Global.pc.x, Global.pc.y, Global.pc.target.x, Global.pc.target.y)
if range_to_target > target_range:
x, y = Global.pc.x, Global.pc.y
elif mobs:
# If mobs are nearby, default to the closest one:
mob, (path, clear) = mobs.next()
x, y = mob.x, mob.y
else:
# Otherwise start at the player's location:
x, y = Global.pc.x, Global.pc.y
while True:
mob = Global.pc.current_level.CreatureAt(x, y)
if LOS:
for i, j in path:
Global.pc.current_level.PaintSquare(i, j)
path, clear = linear_path(
Global.pc.x, Global.pc.y, x, y, Global.pc.current_level.BlocksPassage)
if clear:
color = c_green
else:
color = c_red
if not Global.pc.current_level.fov.Lit(x, y):
path = []
for i, j in path[1:]:
self.PutChar(j, i, "*", color)
if mob:
# Repaint the last square if a mob is there, so the target
# path doesn't obscure it:
Global.pc.current_level.PaintSquare(*path[-1])
if not path:
mob = None
if mob:
if mob is Global.pc:
name = "^R^yourself^0^"
else:
if clear:
blocked = ""
else:
blocked = " ^R^(%s)^0^" % lang.word_blocked
name = "^Y^%s^0^%s" % (mob.Name(), blocked)
else:
name = "nothing"
prompt = lang.prompt_choose_target2 % name
self.clearline(0, self.stdscr)
self.addstr_color(0, 0, prompt, self.stdscr)
self.move(y, x)
k = self.GetKey()
dx, dy = 0, 0
if k in range(49, 58):
dx, dy = offsets[k-49]
elif chr(k) in vi_offsets:
dx, dy = vi_offsets[chr(k)]
elif k in [SPC, ESC]:
# Space to cancel:
mob = None
for i, j in path:
Global.pc.current_level.PaintSquare(i, j)
path = []
break
elif k == 9:
# Tab to cycle targets:
if mobs:
mob, (self.path, self.path_clear) = mobs.next()
x, y = mob.x, mob.y
elif k in (10, 13):
# Enter to select:
# TODO: Allow targeting an empty square, maybe for certain spells only.
if mob:
break
x = max(0, min(x+dx, Global.pc.current_level.width-1))
y = max(0, min(y+dy, Global.pc.current_level.height-1))
self.addstr(0, 0, " " * self.width, self.stdscr)
self.addstr(1, 0, " " * self.width, self.stdscr)
for i, j in path:
Global.pc.current_level.PaintSquare(i, j)
return mob, (path, clear)
[docs] def Message(self, msg, attr=c_yellow, nowait=False, forcewait=False):
"Display a message to the player, wrapping and pausing if necessary."
if not msg.strip():
raise ValueError
for m in wrap(msg, self.width - 7):
self.ShowMessage(m, attr, nowait, forcewait)
self.message_log.append((m, attr))
self.message_log = self.message_log[-MESSAGE_LOG_SIZE:]
[docs] def MessageLog(self):
self.clear()
for i in range(1, min(len(self.message_log)+1, self.height)):
msg, attr = self.message_log[-i]
self.addstr_color(self.height - i - 1, 0, msg, self.stdscr, attr)
self.addstr_color(self.height-1, 0,
lang.prompt_any_key, self.stdscr, c_Yellow)
self.GetKey()
self.clear()
[docs] def MorePrompt(self):
if self.messages_displayed > 1:
self.addstr(1, self.width-7, lang.prompt_more,
self.msg_win, c_Yellow)
self.GetKey()
self.messages_displayed = 0
self.msg_win.clear()
self.msg_win.touchwin()
self.msg_win.refresh()
[docs] def MsgWindow(self, msg=["a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l",
"m", "n", "o", "p", "q", "r",
"s", "t", "u", "v", "w", "x",
"y", "z", "1", "2", "3", "4",
"5", "6", "7", "8", "9", "10",
"11", "12", "13", "14", "15"]):
'''Popup Panel with text
params:
msg: list of strings - display one per line
center: boolean - if true, center the message
todo:
scrollability, multi-column, "Press any key"
'''
win = self.game_win.derwin(0, 0)
win.keypad(1)
p = panel.new_panel(win)
panel.update_panels()
p.top()
p.show()
win.clear()
win.box()
x = 1
max_height = self.game_height - 4
num_lines = len(msg)
start_idx = 0
end_idx = min(start_idx + max_height, max_height)
end_idx = min(end_idx, len(msg))
while True:
paint_idx = 1
for idx in range(start_idx, end_idx):
line = msg[idx]
self.addstr_color(paint_idx, x, line, win, c_Yellow)
paint_idx += 1
self.addstr_color(self.game_height-2, 2,
" [ESC] close [SP] forward [B] back", win, c_Green)
curses.doupdate()
key = win.getch()
if key == 0x20:
start_idx += max_height
end_idx = start_idx + max_height
if end_idx > len(msg):
end_idx = len(msg)
start_idx = max(0, end_idx - max_height)
elif key == ord('b'):
start_idx = max(0, start_idx - max_height)
end_idx = min(start_idx + max_height, max_height)
end_idx = min(end_idx, len(msg))
elif key in [27,10]:
break
else:
pass
win.clear()
p.hide()
panel.update_panels()
curses.doupdate()
return key
[docs] def ChoiceWindow(self,
title="Menu",
msg=[["H", "Hello This is a very long option consisting of lots of words", "Description of Hello"],
["W", "World", "Description of World, which is quite a lot of text.\nIt should wrap around as a test."],
["A", "A", "A"],
["B", "B", "B"],
["C", "C", "C"],
["D", "D", "D"],
["E", "E", "E"],
["F", "F", "F"],
["G", "G", "G"],
["H", "H", "H"],
["I", "I", "I"],
["J", "J", "J"],
["K", "K", "K"],
["L", "L", "L"],
["M", "M", "M"],
["N", "N", "N"],
["O", "O", "O"],
["P", "P", "P"],
["Q", "Q", "Q"],
["R", "R", "R"],
["S", "S", "S"],
["T", "T", "T"],
["U", "U", "U"],
["V", "V", "V"],
["W", "W", "W"],
["X", "X", "X"],
["Y", "Y", "Y"],
["Z", "Z", "Z"],
["1", "1", "1"],
["2", "2", "2"],
["3", "3", "3"],
["4", "4", "4"],
["5", "5", "5"],
["6", "6", "6"],
["7", "7", "7"],
["9", "9", "9"],
["1", "10", "10"],
["1", "11", "11"],
["1", "12", "12"],
["1", "13", "13"],
["1", "14", "14"]],
cmds = None ):
''' Popup Panel with List of Choices
params:
msg: list of lists, each containing [ char/shortcut, string ]
todo:
handle msg list too long, multi-columns, etc.
win: list, up/down highlights, half-width
win2: description
'''
win = self.game_win.derwin(0, 0)
win.keypad(1)
p = panel.new_panel(win)
panel.update_panels()
p.top()
p.show()
win.clear()
win.box()
x = 1
choice = 0
valid_keys = [ord(msg_item[0]) for msg_item in msg]
if cmds:
cmd_keys = [ ord(k) for k in cmds[0] ]
items_to_show = 20
# Draw menu down the left and description on the selected item
# on the right half
while True:
# find the address that it in the window
first_idx = choice
last_idx = first_idx + items_to_show
if last_idx > len(msg) - 1:
last_idx = len(msg)
first_idx = max(0, last_idx - items_to_show)
win.clear()
win.addstr(1, 2, title, curses.A_STANDOUT)
screen_help = "[ESC] : Exit, [SP] : Pg Down, [UP/DOWN] Scroll, [RET] Select [LETTER] Select"
self.addstr_color(self.game_height-4, 2,
screen_help, win, c_Yellow)
if cmds:
self.addstr_color(self.game_height-3,2,
cmds[1], win, c_Yellow)
draw_idx = 3
for idx in range(first_idx, last_idx):
line = msg[idx]
color = c_Yellow
if idx == choice:
color = c_Green
# draw the description text if selected
descr_text = wrap(line[2], self.game_width//2 - 2)
desc_idx = 3
for d in descr_text:
self.addstr_color(
desc_idx, self.game_width//2, d, win, c_Green)
desc_idx += 1
text = wrap(line[1], self.game_width//2 - 6)
self.addstr_color(draw_idx, x, "[{0}] {1}".format(
line[0], text[0]), win, color)
draw_idx += 1
for lindex, l in enumerate(text[1:]):
self.addstr_color(
draw_idx, x, " {0}".format(l), win, color)
draw_idx += 1
retval = None
win.refresh()
key = win.getch()
# Test for space, esc
logging.debug("key={0}".format(key))
ret = None
if key in [0x1b]: # ESC exits
break
if key == 0x20:
choice = min(len(msg)-1, choice+items_to_show)
if key == curses.KEY_DOWN:
choice += 1
if choice > len(msg):
choice = 0
elif key == curses.KEY_UP:
choice -= 1
if choice < 0:
choice = len(msg) - 1
elif key in valid_keys:
ret = chr(key)
break
elif key in cmd_keys:
ret = chr(key)
break
elif key in [curses.KEY_ENTER, 10]:
ret = msg[choice][0]
break
else:
pass
# FInd the corresponding row index
if ret is not None:
for idx, line in enumerate(msg):
if line[0] == ret:
ret = idx
break
win.clear()
p.hide()
panel.update_panels()
curses.doupdate()
return ret
[docs] def NearbyMobCycler(self, target_range=None):
"Return a Cycler instance for mobs near the PC."
# Build a list of targetable mobs:
nearby_mobs = [
mob for mob in Global.pc.current_level.AllCreatures() if mob.pc_can_see]
if target_range:
nearby_mobs = [mob for mob in nearby_mobs if calc_distance(
Global.pc.x, Global.pc.y, mob.x, mob.y) <= target_range]
if not nearby_mobs:
return None
targets = [(mob, linear_path(Global.pc.x, Global.pc.y, mob.x, mob.y,
Global.pc.current_level.BlocksPassage))
for mob in nearby_mobs]
# Sort by distance:
targets.sort(key=lambda m: (
m[0].x-Global.pc.x)**2 + (m[0].y-Global.pc.y)**2)
return Cycler(targets)
[docs] def PutTile(self, x, y, tile, color):
self.PutChar(y, x, tile, color)
[docs] def ShowMessage(self, msg, attr, nowait, forcewait):
self.MorePrompt()
self.addstr_color(self.messages_displayed, 0, msg, self.msg_win , attr)
self.wait_x = clen(msg) + 1
if nowait:
self.msg_win.refresh()
elif forcewait:
self.messages_displayed = 2
self.MorePrompt()
else:
self.messages_displayed += 1
self.msg_win.touchwin()
self.msg_win.refresh()
[docs] def ShowStatus(self):
"Show key stats to the player."
p = Global.pc
exp_to_go = p.xp_for_next_level - p.xp
if float(p.hp)/p.hp_max < 0.15:
hp_col = "^R^"
elif float(p.hp)/p.hp_max < 0.25:
hp_col = "^Y^"
else:
hp_col = "^G^"
if p.mp_max == 0 or float(p.mp)/p.mp_max < 0.2:
mp_col = "^M^"
else:
mp_col = "^B^"
stats = ("%s:%s(%s) %s:%s %s:%s %s:%s" %
(lang.word_level_abbr.title(), p.level, exp_to_go,
lang.stat_abbr_str.title(), p.stats("str"),
lang.stat_abbr_dex.title(), p.stats("dex"),
lang.stat_abbr_int.title(), p.stats("int")))
hp = "%s%s:%s/%s^0^" % (hp_col,
lang.word_hitpoints_abbr.upper(), p.hp, p.hp_max)
if p.mp_max + p.mp > 0:
mp = " %s%s:%s/%s^0^" % (mp_col,
lang.word_mana_abbr.upper(), p.mp, p.mp_max)
else:
mp = ""
if p.EvasionBonus() < p.RawEvasionBonus():
ecolor = "^R^"
else:
ecolor = "^0^"
armor = "%s:%s %s%s:%s^0^" % (lang.word_protection_abbr.title(), p.ProtectionBonus(), ecolor,
lang.word_evasion_abbr.title(), p.EvasionBonus())
dlvl = "%s:%s" % (lang.word_dungeonlevel_abbr, p.current_level.depth)
line = "%s %s%s %s %s" % (stats, hp, mp, armor, dlvl)
status_lines = []
status_lines.append(line)
# Effects
is_confused = p.is_confused
speed = p.GetMoveSpeed()
if Global.pc.target:
target = Global.pc.target.Name().title()
target_color = "^Y^"
else:
target = lang.word_none.title()
target_color = "^0^"
if speed < 100:
speed = '^R^' + str(speed)
effects_string = 'Speed: {0}'.format(speed)
if is_confused:
effects_string += ' ^R^confused'
line = "%s: %s%s^0^ %s" % (lang.word_target.title(), target_color, target, effects_string)
status_lines.append(line)
self.set_status(status_lines)
[docs] def Shutdown(self):
"Shut down the IO system."
# Restore the terminal settings:
self.screen.keypad(0)
curses.echo()
curses.nocbreak()
curses.endwin()
[docs] def TabTarget(self):
"Allow the player to cycle through available targets."
if not self.tab_targeting:
self.target_mobs = self.NearbyMobCycler()
if not self.target_mobs:
return
# Enter tab target mode:
self.tab_targeting = True
mob, (self.path, self.path_clear) = self.target_mobs.next()
if self.path_clear:
blocked = ""
else:
blocked = " ^R^(%s)^0^" % lang.word_blocked
self.Message("%s: ^Y^%s%s" %
(lang.label_now_targeting, mob.Name().title(), blocked))
Global.pc.target = mob
[docs] def WaitPrompt(self, y, attr=c_white, prompt=lang.prompt_any_key):
self.addstr(y, 0, prompt, self.msg_win, attr)
self.GetKey()
[docs] def YesNo(self, question, attr=c_yellow):
"Ask the player a yes or no question."
self.MorePrompt()
#self.addstr(0, 0, " " * self.width, self.msg_win)
self.msg_win.clear()
self.addstr(0, 0, "%s [%s/%s]: " %
(question, lang.word_yes_key, lang.word_no_key), self.msg_win, attr)
logging.debug("yes no 3")
self.msg_win.touchwin()
self.msg_win.refresh()
yes_keys = lang.word_yes_key.upper() + lang.word_yes_key.lower()
no_keys = lang.word_no_key.upper() + lang.word_no_key.lower()
while True:
k = chr(self.GetKey())
if k in yes_keys:
answer = True
break
elif k in no_keys:
answer = False
break
#self.addstr(0, 0, " " * self.width, self.msg_win)
self.msg_win.clear()
self.msg_win.touchwin()
self.msg_win.refresh()
self.messages_displayed = 0
return answer
[docs] def set_status(self, lines):
for idx, line in enumerate(lines):
self.addstr_color(idx+1, 1, ' ' * len(line), self.status_win)
self.addstr_color(idx+1, 1, line, self.status_win)
self.status_win.refresh()
[docs] def addstr(self, y, x, s, win, attr=c_yellow):
strs = s.split("\n")
for s in strs:
win.addstr(y, x, s, self.colors[attr])
y += 1
return y - 1
[docs] def addstr_color(self, y, x, text, win, attr=c_yellow, inverse=False):
"addstr with embedded color code support."
# Color codes are ^color^, for instance,
# "This is ^R^red text^W^ and this is white."
buff = ""
base_attr = attr
while text:
ch, text = text[0], text[1:]
if ch == "^":
if text[:2] == "0^":
text = text[2:]
y = self.addstr(y, x, buff, win, attr)
x += len(buff)
buff, ch = "", ""
attr = base_attr
continue
for color in msg_colors.keys():
code = "%s^" % color
if text[:2] == code:
text = text[2:]
y = self.addstr(y, x, buff, win, attr)
x += len(buff)
buff, ch = "", ""
attr = msg_colors[color]
break
else:
buff += ch
else:
buff += ch
if buff:
y = self.addstr(y, x, buff, win, attr)
return y
[docs]def main(stdscr):
G = gui(stdscr)
G.handle_input()
if __name__ == '__main__':
wrapper(main)