Merge pull request #21 from jmdejong/ratuil
Use ratuil instead of my old hardcoded ncurses setup
This commit is contained in:
commit
10bdb33255
|
@ -0,0 +1,6 @@
|
|||
[*.{py,json,hy,yaml}]
|
||||
charset = utf-8
|
||||
indent_style = spaces
|
||||
insert_final_newline = true
|
||||
indent_size = 4
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
kate: indent-pasted-text false; indent-width 4; space-indent true;
|
|
@ -95,40 +95,40 @@ class CommandHandler:
|
|||
self.client.inputHandler.startTyping(startText)
|
||||
|
||||
def selectWidget(self, value, relative=False, modular=False):
|
||||
self.client.display.getWidget("switch").select(value, relative, modular)
|
||||
self.client.display.selectMenu(value, relative, modular)
|
||||
|
||||
def selectItem(self, value, relative=False, modular=False):
|
||||
self.client.display.getWidget("switch").getSelectedItem().getImpl().select(value, relative, modular)
|
||||
self.client.display.selectItem(None, value, relative, modular)
|
||||
|
||||
def actWithSelected(self, action, widget):
|
||||
self.input([action, self.client.display.getWidget(widget).getSelected()])
|
||||
def actWithSelected(self, action, menu):
|
||||
self.input([action, self.client.display.getSelectedItem(menu).getSelected()])
|
||||
|
||||
def useSelected(self):
|
||||
widget = self.client.display.getWidget("switch").getSelectedItem()
|
||||
selected = widget.getImpl().getSelected()
|
||||
if widget.name in ("inventory", "equipment"):
|
||||
menu = self.client.display.getSelectedMenu()
|
||||
selected = self.client.display.getSelectedItem(menu)
|
||||
if menu in ("inventory", "equipment"):
|
||||
action = "use"
|
||||
elif widget.name == "ground":
|
||||
elif menu == "ground":
|
||||
action = "interact",
|
||||
else:
|
||||
return
|
||||
self.input([action, selected])
|
||||
|
||||
def unUseSelected(self):
|
||||
widget = self.client.display.getWidget("switch").getSelectedItem()
|
||||
selected = widget.getImpl().getSelected()
|
||||
if widget.name == "inventory":
|
||||
menu = self.client.display.getSelectedMenu()
|
||||
selected = self.client.display.getSelectedItem(menu)
|
||||
if menu == "inventory":
|
||||
action = "drop"
|
||||
elif widget.name == "equipment":
|
||||
elif menu == "equipment":
|
||||
action = "unequip"
|
||||
else:
|
||||
return
|
||||
self.input([action, selected])
|
||||
|
||||
def takeSelected(self):
|
||||
widget = self.client.display.getWidget("switch").getSelectedItem()
|
||||
selected = widget.getImpl().getSelected()
|
||||
if widget.name == "ground":
|
||||
menu = self.client.display.getSelectedMenu()
|
||||
selected = self.client.display.getSelectedItem(menu)
|
||||
if menu == "ground":
|
||||
action = "take"
|
||||
else:
|
||||
return
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
|
||||
|
||||
|
||||
import os
|
||||
from ratuil.layout import Layout
|
||||
from ratuil.bufferedscreen import BufferedScreen as Screen
|
||||
#from ratuil.screen import Screen
|
||||
from ratuil.textstyle import TextStyle
|
||||
from asciifarm.common.utils import get
|
||||
from .listselector import ListSelector
|
||||
|
||||
|
||||
SIDEWIDTH = 20
|
||||
|
||||
ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
||||
|
||||
class Display:
|
||||
|
||||
def __init__(self, charMap):
|
||||
|
||||
self.characters = {}
|
||||
|
||||
def parseSprite(sprite):
|
||||
if isinstance(sprite, str):
|
||||
return (sprite, None, None)
|
||||
char = get(sprite, 0, " ")
|
||||
fg = get(sprite, 1)
|
||||
bg = get(sprite, 2)
|
||||
return (char, fg, bg)
|
||||
for name, sprite in charMap["mapping"].items():
|
||||
vals = parseSprite(sprite)
|
||||
if vals:
|
||||
self.characters[name] = vals
|
||||
|
||||
for name, colours in charMap.get("writable", {}).items():
|
||||
fg = get(colours, 0)
|
||||
bg = get(colours, 1)
|
||||
for i in range(min(len(ALPHABET), len(charMap.get("alphabet", [])))):
|
||||
self.characters[name + '-' + ALPHABET[i]] = (charMap["alphabet"][i], fg, bg)
|
||||
|
||||
self.defaultChar = parseSprite(charMap.get("default", "?"))
|
||||
|
||||
self.messageColours = charMap.get("msgcolours", {})
|
||||
|
||||
fname = os.path.join(os.path.dirname(__file__), "layout.xml")
|
||||
self.layout = Layout.from_xml_file(fname)
|
||||
self.layout.get("field").set_char_size(charMap.get("charwidth", 1))
|
||||
|
||||
self.screen = Screen()
|
||||
self.screen.clear()
|
||||
|
||||
self.layout.set_target(self.screen)
|
||||
self.layout.update()
|
||||
|
||||
|
||||
|
||||
# temporary, until these have a better place
|
||||
self.inventory = ListSelector(self.getWidget("inventory"))
|
||||
self.inventory._debug_name = "inventory"
|
||||
self.equipment = ListSelector(self.getWidget("equipment"))
|
||||
self.equipment._debug_name = "equipment"
|
||||
self.ground = ListSelector(self.getWidget("ground"))
|
||||
self.ground._debug_name = "ground"
|
||||
self.switch = ListSelector(self.getWidget("switchtitles"))
|
||||
self.switch._debug_name = "switch"
|
||||
|
||||
self.switch.setItems(["inventory", "equipment", "ground"])
|
||||
self.menus = {
|
||||
"inventory": self.inventory,
|
||||
"equipment": self.equipment,
|
||||
"ground": self.ground
|
||||
}
|
||||
|
||||
self.layout.get("switch").select(0)
|
||||
|
||||
|
||||
def getWidget(self, name):
|
||||
return self.layout.get(name)
|
||||
|
||||
def resizeField(self, size):
|
||||
self.getWidget("field").set_size(*size)
|
||||
|
||||
def drawFieldCells(self, cells):
|
||||
field = self.getWidget("field")
|
||||
for cell in cells:
|
||||
(x, y), spriteNames = cell
|
||||
if not len(spriteNames):
|
||||
char, fg, bg = self.getChar(' ')
|
||||
else:
|
||||
char, fg, bg = self.getChar(spriteNames[0])
|
||||
for spriteName in spriteNames[1:]:
|
||||
if bg is not None:
|
||||
break
|
||||
_char, _fg, bg = self.getChar(spriteName)
|
||||
field.change_cell(x, y, char, TextStyle(fg, bg))
|
||||
|
||||
|
||||
def setFieldCenter(self, pos):
|
||||
self.getWidget("field").set_center(*pos)
|
||||
|
||||
def setHealth(self, health, maxHealth):
|
||||
if health is None:
|
||||
health = 0
|
||||
if maxHealth is None:
|
||||
maxHealth = 0
|
||||
self.getWidget("health").set_total(maxHealth)
|
||||
self.getWidget("health").set_filled(health)
|
||||
|
||||
|
||||
def showInfo(self, infostring):
|
||||
self.getWidget("info").set_text(infostring)
|
||||
|
||||
def selectMenu(self, *args, **kwargs):
|
||||
self.switch.select(*args, **kwargs)
|
||||
self.layout.get("switch").select(self.getSelectedMenu())
|
||||
|
||||
def getSelectedMenu(self):
|
||||
return self.switch.getSelectedItem()
|
||||
|
||||
def getSelectedItem(self, menu=None):
|
||||
return self._getMenu(menu).getSelected()
|
||||
|
||||
def selectItem(self, menu=None, *args, **kwargs):
|
||||
self._getMenu(menu).select(*args, **kwargs)
|
||||
|
||||
def _getMenu(self, name=None):
|
||||
if name is None:
|
||||
name = self.getSelectedMenu()
|
||||
name = name.casefold()
|
||||
return self.menus[name]
|
||||
|
||||
def setInventory(self, items):
|
||||
self.inventory.setItems(items)
|
||||
|
||||
|
||||
def setEquipment(self, slots):
|
||||
self.equipment.setItems(
|
||||
sorted([
|
||||
slot + ": " + (item if item else "")
|
||||
for slot, item in slots.items()
|
||||
])
|
||||
)
|
||||
|
||||
def setGround(self, items):
|
||||
self.ground.setItems(items)
|
||||
|
||||
|
||||
def addMessage(self, message, msgtype=None):
|
||||
if msgtype is not None:
|
||||
style = TextStyle(*self.messageColours.get(msgtype, (7,0)))
|
||||
else:
|
||||
style = None
|
||||
self.getWidget("msg").add_message(message, style)
|
||||
|
||||
def log(self, message):
|
||||
self.addMessage(str(message))
|
||||
|
||||
def scrollBack(self, amount, relative=True):
|
||||
self.getWidget("msg").scroll(amount, relative)
|
||||
|
||||
def setInputString(self, string, cursor):
|
||||
self.getWidget("textinput").set_text(string, cursor)
|
||||
|
||||
def update(self):
|
||||
self.layout.update()
|
||||
self.screen.update()
|
||||
|
||||
def getChar(self, sprite):
|
||||
"""This returns the character belonging to some spritename. This does not read a character"""
|
||||
return self.characters.get(sprite, self.defaultChar)
|
||||
|
||||
def update_size(self):
|
||||
self.screen.reset()
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
import curses
|
||||
|
||||
class Colours:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.colours = min(curses.COLORS, 16)
|
||||
self.pairs = self.colours*self.colours
|
||||
|
||||
curses.use_default_colors()
|
||||
for i in range(0, self.pairs):
|
||||
curses.init_pair(i, i%self.colours, i//self.colours)
|
||||
|
||||
def get(self, fg=0, bg=0):
|
||||
if self.colours == 16:
|
||||
return curses.color_pair(fg + bg*self.colours)
|
||||
elif self.colours == 8:
|
||||
dfg = fg % 8
|
||||
dbg = bg % 8
|
||||
if bg == 8:
|
||||
dbg = 7
|
||||
if fg == 8:
|
||||
dfg = 7
|
||||
colour = curses.color_pair(dfg + dbg*self.colours)
|
||||
if fg >= 8 and bg < 8:
|
||||
colour |= curses.A_BOLD
|
||||
elif fg < 8 and bg >= 8:
|
||||
colour |= curses.A_DIM
|
||||
return colour
|
||||
else:
|
||||
return curses.color_pair(0)
|
|
@ -1,154 +0,0 @@
|
|||
|
||||
import curses
|
||||
|
||||
from .field import Field
|
||||
from .info import Info
|
||||
from .health import Health
|
||||
from .inventory import Inventory
|
||||
from .screen import Screen
|
||||
from .colours import Colours
|
||||
from .messages import Messages
|
||||
from .switcher import Switcher
|
||||
from .textinput import TextInput
|
||||
from .widget import Widget
|
||||
|
||||
from asciifarm.common.utils import get
|
||||
|
||||
|
||||
SIDEWIDTH = 20
|
||||
|
||||
ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
||||
|
||||
class Display:
|
||||
|
||||
def __init__(self, stdscr, charMap, colours=False):
|
||||
|
||||
if colours and curses.has_colors and curses.COLORS > 1:
|
||||
self.colours = Colours()
|
||||
else:
|
||||
self.colours = None
|
||||
self.characters = {}
|
||||
|
||||
def parseSprite(sprite):
|
||||
if isinstance(sprite, str):
|
||||
return (sprite, None, None)
|
||||
char = get(sprite, 0, " ")
|
||||
fg = get(sprite, 1)
|
||||
bg = get(sprite, 2)
|
||||
return (char, fg, bg)
|
||||
for name, sprite in charMap["mapping"].items():
|
||||
vals = parseSprite(sprite)
|
||||
if vals:
|
||||
self.characters[name] = vals
|
||||
|
||||
for name, colours in charMap.get("writable", {}).items():
|
||||
fg = get(colours, 0)
|
||||
bg = get(colours, 1)
|
||||
for i in range(min(len(ALPHABET), len(charMap.get("alphabet", [])))):
|
||||
self.characters[name + '-' + ALPHABET[i]] = (charMap["alphabet"][i], fg, bg)
|
||||
|
||||
self.defaultChar = parseSprite(charMap.get("default", "?"))
|
||||
screen = Screen(self, stdscr, self.colours)
|
||||
self.screen = screen
|
||||
|
||||
self.widgets = {}
|
||||
|
||||
self.addWidget(Field((1, 1), charMap.get("charwidth", 1), self.colours), "field")
|
||||
self.addWidget(Info(), "info")
|
||||
self.addWidget(Health(
|
||||
charMap.get("healthfull", ("@",7, 2)),
|
||||
charMap.get("healthempty", ("-",7, 1))
|
||||
),
|
||||
"health")
|
||||
self.addWidget(Inventory("Inventory"), "inventory")
|
||||
self.addWidget(Inventory("Ground"), "ground")
|
||||
self.addWidget(Inventory("Equipment"), "equipment")
|
||||
|
||||
|
||||
switcher = Switcher([self.widgets["ground"], self.widgets["inventory"], self.widgets["equipment"]], 1)
|
||||
self.addWidget(switcher, "switch")
|
||||
self.addWidget(Messages(charMap.get("msgcolours", {})), "msg")
|
||||
self.addWidget(TextInput(), "textinput")
|
||||
|
||||
self.forced = False
|
||||
|
||||
def addWidget(self, w, name, winname=None):
|
||||
if not winname:
|
||||
winname = name
|
||||
widget = Widget(w, name)
|
||||
self.widgets[name] = widget
|
||||
widget.setWin(winname, self.screen)
|
||||
|
||||
def getWidget(self, name):
|
||||
if name in self.widgets:
|
||||
return self.widgets[name].getImpl()
|
||||
else:
|
||||
return None
|
||||
|
||||
def resizeField(self, size):
|
||||
self.getWidget("field").resize(*size)
|
||||
self.forced = True
|
||||
|
||||
def drawFieldCells(self, cells):
|
||||
field = self.getWidget("field")
|
||||
for cell in cells:
|
||||
(x, y), spriteNames = cell
|
||||
sprites = [self.getChar(spriteName) for spriteName in spriteNames]
|
||||
if not len(sprites):
|
||||
sprites = [self.getChar(" ")]
|
||||
field.changeCell(x, y, sprites)
|
||||
|
||||
|
||||
def setFieldCenter(self, pos):
|
||||
self.getWidget("field").setCenter(pos)
|
||||
|
||||
def setHealth(self, health, maxHealth):
|
||||
self.getWidget("health").setHealth(health, maxHealth)
|
||||
|
||||
|
||||
def showInfo(self, infostring):
|
||||
self.getWidget("info").showString(infostring)
|
||||
|
||||
|
||||
def setInventory(self, items):
|
||||
self.getWidget("inventory").setInventory(items)
|
||||
|
||||
|
||||
def setEquipment(self, slots):
|
||||
self.getWidget("equipment").setInventory(
|
||||
sorted([
|
||||
slot + ": " + (item if item else "")
|
||||
for slot, item in slots.items()
|
||||
])
|
||||
)
|
||||
|
||||
def setGround(self, items):
|
||||
self.getWidget("ground").setInventory(items)
|
||||
|
||||
|
||||
def addMessage(self, message, type):
|
||||
self.getWidget("msg").addMessage(message, type)
|
||||
|
||||
def scrollBack(self, amount, relative=True):
|
||||
self.getWidget("msg").scroll(amount, relative)
|
||||
|
||||
def getChar(self, sprite):
|
||||
"""This returns the character belonging to some spritename. This does not read a character"""
|
||||
return self.characters.get(sprite, self.defaultChar)
|
||||
|
||||
def setInputString(self, string, cursor):
|
||||
self.getWidget("textinput").setText(string, cursor)
|
||||
|
||||
def update(self):
|
||||
changed = False
|
||||
for widget in self.widgets.values():
|
||||
if self.forced or widget.isChanged():
|
||||
widget.update()
|
||||
changed = True
|
||||
if changed:
|
||||
self.screen.update()
|
||||
self.forced = False
|
||||
|
||||
def forceUpdate(self):
|
||||
self.forced = True
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
|
||||
import curses
|
||||
from .widimp import WidImp
|
||||
|
||||
class Field(WidImp):
|
||||
|
||||
|
||||
def __init__(self, size=(1,1), charSize=1, colours=False):
|
||||
self.pad = curses.newpad(size[1]+1, (size[0]+1)*charSize)
|
||||
self.size = size
|
||||
self.charSize = charSize
|
||||
self.center = (0, 0)
|
||||
self.colours = colours
|
||||
self.changed = False
|
||||
self.redraw = False
|
||||
|
||||
def resize(self, width, height):
|
||||
self.size = (width, height)
|
||||
self.pad.resize(height+1, width*self.charSize)
|
||||
self.redraw = True
|
||||
self.change()
|
||||
|
||||
def changeCell(self, x, y, sprites):
|
||||
""" sprites must always have at least one element """
|
||||
char, colour, bgcolour = sprites[0]
|
||||
if bgcolour is None:
|
||||
for (ch, co, bg) in sprites:
|
||||
if bg is not None:
|
||||
bgcolour = bg
|
||||
break
|
||||
else:
|
||||
bgcolour = 0
|
||||
if colour is not None and self.colours:
|
||||
self.pad.addstr(y, x*self.charSize, " "*self.charSize, self.colours.get(7, 0))
|
||||
self.pad.addstr(y, x*self.charSize, char, self.colours.get(colour, bgcolour))
|
||||
else:
|
||||
self.pad.addstr(y, x*self.charSize, char)
|
||||
self.change()
|
||||
|
||||
def setCenter(self, pos):
|
||||
self.center = pos
|
||||
self.change()
|
||||
|
||||
def getWidth(self):
|
||||
return self.size[0]*self.charSize
|
||||
|
||||
def getHeight(self):
|
||||
return self.size[1]
|
||||
|
||||
def _roundWidth(self, x):
|
||||
return x // self.charSize * self.charSize
|
||||
|
||||
def update(self, win):
|
||||
if self.redraw:
|
||||
win.erase()
|
||||
win.noutrefresh()
|
||||
self.redraw = False
|
||||
width, height = win.getSize()
|
||||
x, y = win.getPos()
|
||||
xmax = x + width
|
||||
ymax = y + height
|
||||
self.pad.noutrefresh(
|
||||
max(0, min(self.getHeight()-height, self.center[1] - int(height/2))),
|
||||
max(0, min(
|
||||
self._roundWidth(self.getWidth()-width),
|
||||
self._roundWidth(self.center[0]*self.charSize - int(width/2)))),
|
||||
y,
|
||||
x + max(0, (width - self.getWidth()) // 2),
|
||||
ymax,
|
||||
xmax)
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
|
||||
from .widimp import WidImp
|
||||
|
||||
class Health(WidImp):
|
||||
|
||||
def __init__(self, char=None, emptyChar=None):
|
||||
self.char = char or ('@',7,0)
|
||||
self.emptyChar = emptyChar or ('-',7,0)
|
||||
self.changed = False
|
||||
self.health = 0
|
||||
self.maxHealth = 0
|
||||
|
||||
def setHealth(self, health, maxHealth):
|
||||
self.health = health or 0
|
||||
self.maxHealth = maxHealth or 0
|
||||
self.change()
|
||||
|
||||
def update(self, win):
|
||||
width, height = win.getSize()
|
||||
width -= 1
|
||||
barEnd = round(self.health/self.maxHealth * width) if self.maxHealth > 0 else 0
|
||||
win.erase()
|
||||
win.addLine((0,0),"Health: {}/{}".format(self.health, self.maxHealth)[:width])
|
||||
win.addLine((0, 1), self.char[0]*barEnd, self.char[1:])
|
||||
win.addLine((barEnd, 1), self.emptyChar[0]*(width-barEnd), self.emptyChar[1:])
|
||||
win.noutrefresh()
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
from .widimp import WidImp
|
||||
|
||||
class Info(WidImp):
|
||||
|
||||
def __init__(self):
|
||||
self.changed = False
|
||||
self.lines = []
|
||||
self.lastString = None
|
||||
|
||||
def showString(self, string):
|
||||
if string == self.lastString:
|
||||
return
|
||||
self.lines = string.split('\n')
|
||||
self.change()
|
||||
self.lastString = string
|
||||
|
||||
def update(self, win):
|
||||
width, height = win.getSize()
|
||||
lines = [line[:width-1] for line in self.lines][:height]
|
||||
win.erase()
|
||||
for i, line in enumerate(lines):
|
||||
win.addLine((0, i), line)
|
||||
win.noutrefresh()
|
|
@ -1,73 +0,0 @@
|
|||
|
||||
from asciifarm.common import utils
|
||||
|
||||
from .widimp import WidImp
|
||||
|
||||
class Inventory(WidImp):
|
||||
|
||||
def __init__(self, title, titlebar="{}:", selectorChar="*"):
|
||||
self.title = title
|
||||
self.titlebar = titlebar
|
||||
self.selectorChar = selectorChar
|
||||
self.items = []
|
||||
self.selector = 0
|
||||
|
||||
def getSelected(self):
|
||||
return self.selector
|
||||
|
||||
def select(self, value, relative=False, modular=False):
|
||||
invLen = len(self.items)
|
||||
if relative:
|
||||
value += self.selector
|
||||
if modular and invLen:
|
||||
value %= invLen
|
||||
if value < 0:
|
||||
value = 0
|
||||
if value >= invLen:
|
||||
value = invLen-1
|
||||
if value in range(invLen):
|
||||
self.doSelect(value)
|
||||
|
||||
def doSelect(self, value):
|
||||
self.selector = value
|
||||
self.change()
|
||||
|
||||
def setInventory(self, items):
|
||||
self.items = items
|
||||
self.selector = utils.clamp(self.selector, 0, len(items)-1)
|
||||
self.change()
|
||||
|
||||
def getItem(self, num):
|
||||
return self.items[num]
|
||||
|
||||
def getSelectedItem(self):
|
||||
return self.getItem(self.getSelected())
|
||||
|
||||
def setTitle(self, title):
|
||||
self.title = title
|
||||
|
||||
def getNumItems(self):
|
||||
return len(self.items)
|
||||
|
||||
def itemName(self, item):
|
||||
return item
|
||||
|
||||
def update(self, win):
|
||||
|
||||
width, height = win.getSize()
|
||||
height -= 1
|
||||
selected = self.selector
|
||||
start = min(selected - height//2, len(self.items)-height)
|
||||
start = max(start, 0)
|
||||
end = start + height
|
||||
win.erase()
|
||||
win.addLine((0,0), (self.titlebar.format(self.title))[:width])
|
||||
for i, item in enumerate(self.items[start:end]):
|
||||
if i + start == selected:
|
||||
win.addLine((0, i+1), self.selectorChar)
|
||||
win.addLine((1, i+1), self.itemName(item))
|
||||
if end < len(self.items):
|
||||
win.addLine((width-1, height), "+")
|
||||
if start > 0:
|
||||
win.addLine((width-1, 1), "-")
|
||||
win.noutrefresh()
|
|
@ -1,56 +0,0 @@
|
|||
|
||||
import textwrap
|
||||
|
||||
from .widimp import WidImp
|
||||
|
||||
class Messages(WidImp):
|
||||
|
||||
def __init__(self, colours):
|
||||
self.changed = False
|
||||
self.messages = []
|
||||
self.scrolledBack = 0
|
||||
self.colours = colours
|
||||
|
||||
def addMessage(self, message, type=None):
|
||||
self.messages.append([message, type])
|
||||
if self.scrolledBack:
|
||||
self.scrolledBack += 1
|
||||
self.change()
|
||||
|
||||
def scroll(self, amount, relative=True):
|
||||
if relative:
|
||||
self.scrolledBack += amount
|
||||
else:
|
||||
self.scrolledBack = amount
|
||||
self.scrolledBack = max(self.scrolledBack, 0)
|
||||
self.change()
|
||||
|
||||
def update(self, win):
|
||||
width, height = win.getSize()
|
||||
if height < 1:
|
||||
return
|
||||
lines = []
|
||||
messages = self.messages
|
||||
for message, type in messages:
|
||||
colour = self.colours.get(type, (7,0))
|
||||
for line in textwrap.wrap(message, width):
|
||||
lines.append((line, colour))
|
||||
self.scrolledBack = max(min(self.scrolledBack, len(lines)-height), 0)
|
||||
moreDown = False
|
||||
if self.scrolledBack > 0:
|
||||
lines = lines[:-self.scrolledBack]
|
||||
moreDown = True
|
||||
moreUp = False
|
||||
if len(lines) > height:
|
||||
moreUp = True
|
||||
lines = lines[len(lines)-height:]
|
||||
elif len(lines) < height:
|
||||
lines = (height-len(lines)) * [("",)] + lines
|
||||
win.erase()
|
||||
for i, line in enumerate(lines):
|
||||
win.addLine((0,i), *line)
|
||||
if moreUp:
|
||||
win.addLine((width-1, 0), '-')
|
||||
if moreDown:
|
||||
win.addLine((width-1, height-1), '+')
|
||||
win.noutrefresh()
|
|
@ -1,95 +0,0 @@
|
|||
|
||||
import curses
|
||||
from asciifarm.common.utils import clamp
|
||||
from .window import Window
|
||||
|
||||
import signal
|
||||
|
||||
class Screen:
|
||||
|
||||
def __init__(self, display, stdscr, colours):
|
||||
self.display = display
|
||||
try:
|
||||
curses.curs_set(0)
|
||||
self.cursorSet = False
|
||||
except curses.error:
|
||||
# Not all terminals support this functionality.
|
||||
# When the error is ignored the screen will look a little uglier,
|
||||
# A cursor will move around, but that's not terrible
|
||||
# So in order to keep the game as accesible as possible to everyone, it should be safe to ignore the error.
|
||||
self.cursorSet = True
|
||||
# It is probably possible to make sure the cursor is only in a corner of the screen
|
||||
# but I can't figure out how.
|
||||
# it seems to ignore all my move commands unless I press a key
|
||||
# I give up
|
||||
self.stdscr = stdscr
|
||||
self.colours = colours
|
||||
self.setWins()
|
||||
signal.signal(signal.SIGWINCH, self.updateSize)
|
||||
|
||||
def _limitHeight(self, h, y):
|
||||
return min(h + y, self.height) - y
|
||||
|
||||
def setWins(self):
|
||||
height, width = self.height, self.width = self.stdscr.getmaxyx()
|
||||
|
||||
sideW = 20
|
||||
sideX = width-sideW
|
||||
|
||||
msgH = clamp(height // 5, 3, 5)
|
||||
msgY = height - msgH-1
|
||||
inputH = 1
|
||||
inputY = msgY + msgH
|
||||
|
||||
healthY = 0
|
||||
healthH = self._limitHeight(2, healthY)
|
||||
indexY = healthY + healthH
|
||||
indexH = self._limitHeight(4, indexY)
|
||||
listY = indexY + indexH + 1
|
||||
listH = self._limitHeight(12, listY)
|
||||
infoY = listY + listH
|
||||
infoH = self._limitHeight(20, infoY)
|
||||
|
||||
lists = self.makeWin(sideX, listY, sideW, listH)
|
||||
|
||||
self.windows = {
|
||||
"field": self.makeWin(0, 0, sideX - 1, msgY),
|
||||
"msg": self.makeWin(0, msgY, sideX - 1, msgH),
|
||||
"textinput": self.makeWin(0, inputY, sideX - 1, inputH),
|
||||
|
||||
"health": self.makeWin(sideX, healthY, sideW, healthH),
|
||||
"switch": self.makeWin(sideX, indexY, sideW, indexH),
|
||||
"ground": lists,
|
||||
"inventory": lists,
|
||||
"equipment": lists,
|
||||
"info": self.makeWin(sideX, infoY, sideW, infoH)
|
||||
}
|
||||
|
||||
|
||||
def makeWin(self, x, y, width, height):
|
||||
if width < 1 or height < 1:
|
||||
win = None
|
||||
else:
|
||||
win = curses.newwin(height, width, y, x)
|
||||
return Window(win, self.colours)
|
||||
|
||||
def getWin(self, name):
|
||||
return self.windows.get(name, None)
|
||||
|
||||
def updateSize(self, *args):
|
||||
curses.endwin()
|
||||
curses.initscr()
|
||||
self.setWins()
|
||||
self.stdscr.clear()
|
||||
self.display.forceUpdate()
|
||||
|
||||
def update(self):
|
||||
curses.doupdate()
|
||||
|
||||
def getWidth(self):
|
||||
return self.width
|
||||
|
||||
def getHeight(self):
|
||||
return self.height
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
|
||||
|
||||
from .inventory import Inventory
|
||||
|
||||
class Switcher(Inventory):
|
||||
"""An area that can contain multiple widgets but only shows one at a time.
|
||||
There is a function to switch between the displayed widgets.
|
||||
"""
|
||||
|
||||
def __init__(self, widgets, initial=0):
|
||||
Inventory.__init__(self, "", "", "=")
|
||||
self.setInventory(widgets)
|
||||
|
||||
for wid in widgets:
|
||||
wid.hidden = True
|
||||
|
||||
self.select(initial)
|
||||
|
||||
def doSelect(self, value):
|
||||
self.getSelectedItem().hidden = True
|
||||
self.selector = value
|
||||
self.change()
|
||||
newWid = self.getSelectedItem()
|
||||
newWid.hidden = False
|
||||
newWid.change()
|
||||
|
||||
def itemName(self, item):
|
||||
return item.getImpl().title
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
import curses
|
||||
from .widimp import WidImp
|
||||
|
||||
class TextInput(WidImp):
|
||||
|
||||
def __init__(self):
|
||||
self.text = ""
|
||||
self.cursor = -1
|
||||
|
||||
def setText(self, text, cursor):
|
||||
self.text = text
|
||||
self.cursor = cursor
|
||||
self.change()
|
||||
|
||||
def update(self, win):
|
||||
width, height = win.getSize()
|
||||
win.erase()
|
||||
win.addLine((0, 0), self.text[:width])
|
||||
if self.cursor >= 0 and self.cursor <= len(self.text):
|
||||
win.setAttr((min(self.cursor, width-1), 0), curses.A_REVERSE)
|
||||
win.noutrefresh()
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
class Widget:
|
||||
|
||||
|
||||
def __init__(self, impl, name=None):
|
||||
self.impl = impl
|
||||
|
||||
self.win = None
|
||||
self.screen = None
|
||||
self.changed = False
|
||||
self.hidden = False
|
||||
self.name = name
|
||||
self.impl.setWidget(self)
|
||||
|
||||
def setWin(self, win, screen):
|
||||
self.win = win
|
||||
self.screen = screen
|
||||
|
||||
def getWin(self):
|
||||
return self.win and self.screen and self.screen.getWin(self.win)
|
||||
|
||||
def getImpl(self):
|
||||
return self.impl
|
||||
|
||||
def change(self):
|
||||
self.changed = True
|
||||
|
||||
def isChanged(self):
|
||||
return self.changed
|
||||
|
||||
def update(self):
|
||||
if not self.getWin() or self.hidden:
|
||||
return
|
||||
self.impl.update(self.getWin())
|
||||
self.changed = False
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
|
||||
class WidImp:
|
||||
|
||||
"""widget implementation"""
|
||||
|
||||
_widget = None
|
||||
|
||||
def setWidget(self, widget):
|
||||
self._widget = widget
|
||||
self.change()
|
||||
|
||||
def change(self):
|
||||
if self._widget is not None:
|
||||
self._widget.change()
|
||||
|
||||
def update(self, win):
|
||||
pass
|
|
@ -1,74 +0,0 @@
|
|||
|
||||
import curses
|
||||
|
||||
class Window:
|
||||
""" Small wrapper around curses windows """
|
||||
|
||||
def __init__(self, win, colours=None):
|
||||
|
||||
self.win = win
|
||||
self.colours = colours
|
||||
|
||||
def getSize(self):
|
||||
if not self.win:
|
||||
return (0, 0)
|
||||
height, width = self.win.getmaxyx()
|
||||
return (width, height)
|
||||
|
||||
def getPos(self):
|
||||
if not self.win:
|
||||
return (0, 0)
|
||||
y, x = self.win.getparyx()
|
||||
return (x, y)
|
||||
|
||||
def addLine(self, pos, string, colour=(0,0)):
|
||||
"""Draw a string that does not contain newlines or characters with larger width
|
||||
|
||||
long lines are cropped to fit in the window"""
|
||||
|
||||
if not self.win:
|
||||
return
|
||||
x, y = pos
|
||||
width, height = self.getSize()
|
||||
string = string[:width-x]
|
||||
drawLast = None
|
||||
if self.colours:
|
||||
self._addstr(y, x, string, self.colours.get(*colour))
|
||||
else:
|
||||
self._addstr(y, x, string)
|
||||
|
||||
|
||||
def _addstr(self, y, x, string, *args):
|
||||
if not self.win:
|
||||
return
|
||||
width, height = self.getSize()
|
||||
if y == height-1 and x+len(string) == width:
|
||||
if len(string) > 1:
|
||||
self.win.addstr(y, x, string[:-1], *args)
|
||||
try:
|
||||
self.win.addstr(height-1, width-1, string[-1], *args)
|
||||
except curses.error:
|
||||
# ncurses has a weird problem:
|
||||
# it always raises an error when drawing to the last character in the window
|
||||
# it draws first and then raises the error
|
||||
# therefore to draw in the last place of the window the last character needs to be ingored
|
||||
# other solutions might be possible, but are more hacky
|
||||
pass
|
||||
else:
|
||||
self.win.addstr(y, x, string, *args)
|
||||
|
||||
def erase(self):
|
||||
if self.win:
|
||||
self.win.erase()
|
||||
|
||||
def noutrefresh(self):
|
||||
if self.win:
|
||||
self.win.noutrefresh()
|
||||
|
||||
def setAttr(self, pos, attr, num=1):
|
||||
if self.win:
|
||||
x, y = pos
|
||||
self.win.chgat(y, x, num, attr)
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#! /usr/bin/python3
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -10,13 +10,14 @@ import argparse
|
|||
import string
|
||||
from queue import Queue
|
||||
|
||||
import ratuil.inputs
|
||||
|
||||
from .inputhandler import InputHandler
|
||||
|
||||
class Client:
|
||||
|
||||
def __init__(self, stdscr, display, name, connection, keybindings, logFile=None):
|
||||
self.stdscr = stdscr
|
||||
def __init__(self, display, name, connection, keybindings, logFile=None):
|
||||
|
||||
self.display = display
|
||||
self.name = name
|
||||
self.keepalive = True
|
||||
|
@ -54,7 +55,7 @@ class Client:
|
|||
|
||||
def getInput(self):
|
||||
while True:
|
||||
key = self.stdscr.getch()
|
||||
key = ratuil.inputs.get_key()
|
||||
self.queue.put(("input", key))
|
||||
|
||||
def close(self, msg=None):
|
||||
|
@ -138,12 +139,19 @@ class Client:
|
|||
if action[0] == "message":
|
||||
self.update(action[1])
|
||||
elif action[0] == "input":
|
||||
if action[1] == "^C":
|
||||
raise KeyboardInterrupt
|
||||
self.inputHandler.onInput(action[1])
|
||||
elif action[0] == "error":
|
||||
raise action[1]
|
||||
elif action[0] == "sigwinch":
|
||||
self.display.update_size()
|
||||
else:
|
||||
raise Exception("invalid action in queue")
|
||||
|
||||
def onSigwinch(self, signum, frame):
|
||||
self.queue.put(("sigwinch", (signum, frame)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
import curses
|
||||
import curses.ascii
|
||||
import string
|
||||
|
||||
from .commandhandler import CommandHandler, InvalidCommandException
|
||||
from .keynames import nameFromKey
|
||||
|
||||
import ratuil.inputs as inp
|
||||
|
||||
|
||||
class InputHandler:
|
||||
|
@ -20,7 +20,7 @@ class InputHandler:
|
|||
|
||||
def onInput(self, key):
|
||||
if not self.typing:
|
||||
keyName = nameFromKey(key)
|
||||
keyName = key
|
||||
if keyName in self.keybindings:
|
||||
self.commandHandler.execute(self.keybindings[keyName])
|
||||
else:
|
||||
|
@ -58,36 +58,36 @@ class InputHandler:
|
|||
self.client.display.setInputString(self.string, self.cursor if self.typing else -1)
|
||||
|
||||
def addKey(self, key):
|
||||
if curses.ascii.isprint(key):
|
||||
self.string = self.string[:self.cursor] + chr(key) + self.string[self.cursor:]
|
||||
if key in string.printable:
|
||||
self.string = self.string[:self.cursor] + key + self.string[self.cursor:]
|
||||
self.cursor += 1
|
||||
elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS or key == curses.ascii.DEL:
|
||||
elif key == inp.BACKSPACE:
|
||||
self.string = self.string[:self.cursor-1] + self.string[self.cursor:]
|
||||
self.cursor = max(self.cursor - 1, 0)
|
||||
elif key == curses.KEY_RIGHT:
|
||||
elif key == inp.RIGHT:
|
||||
self.cursor = min(self.cursor + 1, len(self.string))
|
||||
elif key == curses.KEY_LEFT:
|
||||
elif key == inp.LEFT:
|
||||
self.cursor = max(self.cursor - 1, 0)
|
||||
elif key == curses.KEY_DC:
|
||||
elif key == inp.DELETE:
|
||||
self.string = self.string[:self.cursor] + self.string[self.cursor+1:]
|
||||
elif key == curses.KEY_HOME:
|
||||
elif key == inp.HOME:
|
||||
self.cursor = 0
|
||||
elif key == curses.KEY_END:
|
||||
elif key == inp.END:
|
||||
self.cursor = len(self.string)
|
||||
|
||||
elif key == curses.ascii.ESC or key == curses.KEY_DL:
|
||||
elif key == inp.ESCAPE:
|
||||
# throw away entered string and go back to game
|
||||
self.typing = False
|
||||
self.string = ""
|
||||
self.cursor = 0
|
||||
elif key == curses.ascii.LF or key == curses.ascii.CR:
|
||||
elif key == inp.ENTER:
|
||||
# process entered string and reset it
|
||||
message = self.string
|
||||
self.string = ""
|
||||
self.cursor = 0
|
||||
self.typing = False
|
||||
self.processString(message)
|
||||
elif key == curses.ascii.TAB:
|
||||
elif key == "^I": # tab
|
||||
# return to game but keep entered string
|
||||
self.typing = False
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
import curses
|
||||
|
||||
prenamed = {
|
||||
10: "NEWLINE"
|
||||
}
|
||||
|
||||
def nameFromKey(key):
|
||||
if key in prenamed:
|
||||
return prenamed[key]
|
||||
try:
|
||||
keyname = curses.keyname(key)
|
||||
except ValueError:
|
||||
return None
|
||||
return str(keyname, "utf-8")
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0"?>
|
||||
<hbox>
|
||||
<vbox width="20" align="right">
|
||||
<bar id="health" height="2" full-char="#" empty-char="_" full-style="fg:2; bg:2" empty-style="fg:1; bg: 1;"></bar>
|
||||
<listing id="switchtitles" height="0"></listing>
|
||||
<switchbox id="switch" height="50%">
|
||||
<vbox key="inventory">
|
||||
<textbox height="1">Inventory:</textbox>
|
||||
<listing id="inventory">
|
||||
milk
|
||||
eggs
|
||||
bread
|
||||
</listing>
|
||||
</vbox>
|
||||
<vbox key="equipment">
|
||||
<textbox height="1">Equipment:</textbox>
|
||||
<listing id="equipment">
|
||||
cotton underwear
|
||||
cotton shirt
|
||||
jeans
|
||||
friendship bracelet
|
||||
</listing>
|
||||
</vbox>
|
||||
<vbox key="ground">
|
||||
<textbox height="1">Ground:</textbox>
|
||||
<listing id="ground">
|
||||
concrete
|
||||
</listing>
|
||||
</vbox>
|
||||
</switchbox>
|
||||
<textbox id="info"></textbox>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<textinput id="textinput" align="bottom" height="1">hello</textinput>
|
||||
<log id="msg" align="bottom" height="20%%">
|
||||
Welcome to asciifarm
|
||||
</log>
|
||||
<field id="field" char-size="1"></field>
|
||||
</vbox>
|
||||
</hbox>
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
from asciifarm.common import utils
|
||||
|
||||
|
||||
class ListSelector:
|
||||
|
||||
def __init__(self, widget):
|
||||
self.widget = widget
|
||||
self.items = []
|
||||
self.selector = 0
|
||||
|
||||
def getSelected(self):
|
||||
return self.selector
|
||||
|
||||
def select(self, value, relative=False, modular=False):
|
||||
invLen = len(self.items)
|
||||
if relative:
|
||||
value += self.selector
|
||||
if modular and invLen:
|
||||
value %= invLen
|
||||
if value < 0:
|
||||
value = 0
|
||||
if value >= invLen:
|
||||
value = invLen-1
|
||||
if value in range(invLen):
|
||||
self.doSelect(value)
|
||||
|
||||
def doSelect(self, value):
|
||||
self.selector = value
|
||||
self.widget.select(value)
|
||||
|
||||
def setItems(self, items):
|
||||
self.items = items
|
||||
self.selector = utils.clamp(self.selector, 0, len(items)-1)
|
||||
self.widget.set_items([self.itemName(item) for item in self.items])
|
||||
self.widget.select(self.selector)
|
||||
|
||||
def getItem(self, num):
|
||||
return self.items[num]
|
||||
|
||||
def getSelectedItem(self):
|
||||
return self.getItem(self.getSelected())
|
||||
|
||||
def getNumItems(self):
|
||||
return len(self.items)
|
||||
|
||||
def itemName(self, item):
|
||||
return item
|
||||
|
|
@ -61,8 +61,6 @@ def loadCharmap(name):
|
|||
writable = {}
|
||||
default = None
|
||||
charwidth = 1
|
||||
healthfull = None
|
||||
healthempty = None
|
||||
alphabet = ""
|
||||
msgcolours = {}
|
||||
|
||||
|
@ -71,8 +69,6 @@ def loadCharmap(name):
|
|||
writable.update(template.get("writable", {}))
|
||||
default = template.get("default", default)
|
||||
charwidth = template.get("charwidth", charwidth)
|
||||
healthfull = template.get("healthfull", healthfull)
|
||||
healthempty = template.get("healthempty", healthempty)
|
||||
alphabet = template.get("alphabet", alphabet)
|
||||
msgcolours.update(template.get("msgcolours", {}))
|
||||
return {
|
||||
|
@ -80,8 +76,6 @@ def loadCharmap(name):
|
|||
"writable": writable,
|
||||
"default": default,
|
||||
"charwidth": charwidth,
|
||||
"healthfull": healthfull,
|
||||
"healthempty": healthempty,
|
||||
"alphabet": alphabet,
|
||||
"msgcolours": msgcolours
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
#! /usr/bin/python3
|
||||
import curses
|
||||
|
||||
import json
|
||||
import os
|
||||
import getpass
|
||||
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
import signal
|
||||
#import os
|
||||
|
||||
from .connection import Connection
|
||||
from .gameclient import Client
|
||||
from .display.display import Display
|
||||
from .display import Display
|
||||
from .parseargs import parse_args
|
||||
from ratuil.screen import Screen
|
||||
|
||||
def main(argv=None):
|
||||
|
||||
|
@ -24,33 +29,19 @@ def main(argv=None):
|
|||
error = None
|
||||
closeMessage = None
|
||||
|
||||
os.environ.setdefault("ESCDELAY", "25")
|
||||
#os.environ.setdefault("ESCDELAY", "25")
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
oldterm = termios.tcgetattr(fd)
|
||||
|
||||
try:
|
||||
# Initialize curses
|
||||
stdscr = curses.initscr()
|
||||
|
||||
tty.setraw(sys.stdin)
|
||||
Screen.default.hide_cursor()
|
||||
|
||||
# Turn off echoing of keys, and enter cbreak mode,
|
||||
# where no buffering is performed on keyboard input
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
|
||||
# In keypad mode, escape sequences for special keys
|
||||
# (like the cursor keys) will be interpreted and
|
||||
# a special value like curses.KEY_LEFT will be returned
|
||||
stdscr.keypad(1)
|
||||
|
||||
# Start color, too. Harmless if the terminal doesn't have
|
||||
# color; user can test with has_color() later on. The try/catch
|
||||
# works around a minor bit of over-conscientiousness in the curses
|
||||
# module -- the error return from C start_color() is ignorable.
|
||||
try:
|
||||
curses.start_color()
|
||||
except:
|
||||
pass
|
||||
|
||||
display = Display(stdscr, characters, colours)
|
||||
client = Client(stdscr, display, name, connection, keybindings, logfile)
|
||||
display = Display(characters)
|
||||
client = Client(display, name, connection, keybindings, logfile)
|
||||
signal.signal(signal.SIGWINCH, client.onSigwinch)
|
||||
try:
|
||||
client.start()
|
||||
except KeyboardInterrupt:
|
||||
|
@ -61,12 +52,9 @@ def main(argv=None):
|
|||
error = e
|
||||
closeMessage = client.closeMessage
|
||||
finally:
|
||||
# Set everything back to normal
|
||||
if 'stdscr' in locals():
|
||||
stdscr.keypad(0)
|
||||
curses.echo()
|
||||
curses.nocbreak()
|
||||
curses.endwin()
|
||||
## Set everything back to normal
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, oldterm)
|
||||
Screen.default.finalize()
|
||||
|
||||
|
||||
if error is not None:
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
"s": ["move", "south"],
|
||||
"d": ["move", "east"],
|
||||
"a": ["move", "west"],
|
||||
"KEY_UP": ["move", "north"],
|
||||
"KEY_DOWN": ["move", "south"],
|
||||
"KEY_RIGHT": ["move", "east"],
|
||||
"KEY_LEFT": ["move", "west"],
|
||||
"up": ["move", "north"],
|
||||
"down": ["move", "south"],
|
||||
"right": ["move", "east"],
|
||||
"left": ["move", "west"],
|
||||
"k": ["move", "north"],
|
||||
"j": ["move", "south"],
|
||||
"l": ["move", "east"],
|
||||
|
@ -33,9 +33,9 @@
|
|||
"D": ["input", ["attack", "east"]],
|
||||
"A": ["input", ["attack", "west"]],
|
||||
"t": ["runinput"],
|
||||
"NEWLINE": ["runinput"],
|
||||
"KEY_PPAGE": ["scrollchat", 1],
|
||||
"KEY_NPAGE": ["scrollchat", -1],
|
||||
"enter": ["runinput"],
|
||||
"pageup": ["scrollchat", 1],
|
||||
"pagedown": ["scrollchat", -1],
|
||||
"/": ["runinput", "/"]
|
||||
},
|
||||
"help": "Controls:\n wasd or arrows:\n Move around\n e: Grab\n q: Drop/unequip\n selected\n r: Interact\n f: Attack\n t: Chat\n E: Use selected\n Q: Take selected\n xc: select item\n vb: select menu\n ctrl-c: close client"
|
||||
|
|
Loading…
Reference in New Issue