summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--asciifarm/client/commandhandler.py30
-rw-r--r--asciifarm/client/display.py174
-rw-r--r--asciifarm/client/display/__init__.py0
-rw-r--r--asciifarm/client/display/colours.py32
-rw-r--r--asciifarm/client/display/display.py154
-rw-r--r--asciifarm/client/display/field.py70
-rw-r--r--asciifarm/client/display/health.py27
-rw-r--r--asciifarm/client/display/info.py24
-rw-r--r--asciifarm/client/display/inventory.py73
-rw-r--r--asciifarm/client/display/messages.py56
-rw-r--r--asciifarm/client/display/screen.py95
-rw-r--r--asciifarm/client/display/switcher.py29
-rw-r--r--asciifarm/client/display/textinput.py22
-rw-r--r--asciifarm/client/display/widget.py35
-rw-r--r--asciifarm/client/display/widimp.py18
-rw-r--r--asciifarm/client/display/window.py74
-rw-r--r--asciifarm/client/gameclient.py16
-rw-r--r--asciifarm/client/inputhandler.py30
-rw-r--r--asciifarm/client/keynames.py15
-rw-r--r--asciifarm/client/layout.xml40
-rw-r--r--asciifarm/client/listselector.py49
-rw-r--r--asciifarm/client/loaders.py6
-rw-r--r--asciifarm/client/main.py56
-rw-r--r--asciifarm/keybindings/default.json14
24 files changed, 334 insertions, 805 deletions
diff --git a/asciifarm/client/commandhandler.py b/asciifarm/client/commandhandler.py
index ca72b68..f2e94d1 100644
--- a/asciifarm/client/commandhandler.py
+++ b/asciifarm/client/commandhandler.py
@@ -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
diff --git a/asciifarm/client/display.py b/asciifarm/client/display.py
new file mode 100644
index 0000000..f138382
--- /dev/null
+++ b/asciifarm/client/display.py
@@ -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()
+
diff --git a/asciifarm/client/display/__init__.py b/asciifarm/client/display/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/asciifarm/client/display/__init__.py
+++ /dev/null
diff --git a/asciifarm/client/display/colours.py b/asciifarm/client/display/colours.py
deleted file mode 100644
index 1403300..0000000
--- a/asciifarm/client/display/colours.py
+++ /dev/null
@@ -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)
diff --git a/asciifarm/client/display/display.py b/asciifarm/client/display/display.py
deleted file mode 100644
index edc3f95..0000000
--- a/asciifarm/client/display/display.py
+++ /dev/null
@@ -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
-
diff --git a/asciifarm/client/display/field.py b/asciifarm/client/display/field.py
deleted file mode 100644
index 64c4bfc..0000000
--- a/asciifarm/client/display/field.py
+++ /dev/null
@@ -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)
diff --git a/asciifarm/client/display/health.py b/asciifarm/client/display/health.py
deleted file mode 100644
index d42edbf..0000000
--- a/asciifarm/client/display/health.py
+++ /dev/null
@@ -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()
diff --git a/asciifarm/client/display/info.py b/asciifarm/client/display/info.py
deleted file mode 100644
index 599627c..0000000
--- a/asciifarm/client/display/info.py
+++ /dev/null
@@ -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()
diff --git a/asciifarm/client/display/inventory.py b/asciifarm/client/display/inventory.py
deleted file mode 100644
index 48bf41e..0000000
--- a/asciifarm/client/display/inventory.py
+++ /dev/null
@@ -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()
diff --git a/asciifarm/client/display/messages.py b/asciifarm/client/display/messages.py
deleted file mode 100644
index d551cc2..0000000
--- a/asciifarm/client/display/messages.py
+++ /dev/null
@@ -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()
diff --git a/asciifarm/client/display/screen.py b/asciifarm/client/display/screen.py
deleted file mode 100644
index 666d3bc..0000000
--- a/asciifarm/client/display/screen.py
+++ /dev/null
@@ -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
-
-
diff --git a/asciifarm/client/display/switcher.py b/asciifarm/client/display/switcher.py
deleted file mode 100644
index 035aea3..0000000
--- a/asciifarm/client/display/switcher.py
+++ /dev/null
@@ -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
-
diff --git a/asciifarm/client/display/textinput.py b/asciifarm/client/display/textinput.py
deleted file mode 100644
index 092386c..0000000
--- a/asciifarm/client/display/textinput.py
+++ /dev/null
@@ -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()
diff --git a/asciifarm/client/display/widget.py b/asciifarm/client/display/widget.py
deleted file mode 100644
index 31bb093..0000000
--- a/asciifarm/client/display/widget.py
+++ /dev/null
@@ -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
diff --git a/asciifarm/client/display/widimp.py b/asciifarm/client/display/widimp.py
deleted file mode 100644
index 145aa6b..0000000
--- a/asciifarm/client/display/widimp.py
+++ /dev/null
@@ -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
diff --git a/asciifarm/client/display/window.py b/asciifarm/client/display/window.py
deleted file mode 100644
index d5c3945..0000000
--- a/asciifarm/client/display/window.py
+++ /dev/null
@@ -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)
-
-
-
diff --git a/asciifarm/client/gameclient.py b/asciifarm/client/gameclient.py
index 2bd5420..d9d0c89 100644
--- a/asciifarm/client/gameclient.py
+++ b/asciifarm/client/gameclient.py
@@ -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)))
+
diff --git a/asciifarm/client/inputhandler.py b/asciifarm/client/inputhandler.py
index 2d038d2..b2e4ac9 100644
--- a/asciifarm/client/inputhandler.py
+++ b/asciifarm/client/inputhandler.py
@@ -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
diff --git a/asciifarm/client/keynames.py b/asciifarm/client/keynames.py
deleted file mode 100644
index bbb8009..0000000
--- a/asciifarm/client/keynames.py
+++ /dev/null
@@ -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")
diff --git a/asciifarm/client/layout.xml b/asciifarm/client/layout.xml
new file mode 100644
index 0000000..78337dc
--- /dev/null
+++ b/asciifarm/client/layout.xml
@@ -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>
diff --git a/asciifarm/client/listselector.py b/asciifarm/client/listselector.py
new file mode 100644
index 0000000..b88b967
--- /dev/null
+++ b/asciifarm/client/listselector.py
@@ -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
+
diff --git a/asciifarm/client/loaders.py b/asciifarm/client/loaders.py
index 488ccf9..efdd1c0 100644
--- a/asciifarm/client/loaders.py
+++ b/asciifarm/client/loaders.py
@@ -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
}
diff --git a/asciifarm/client/main.py b/asciifarm/client/main.py
index c00b592..d720477 100644
--- a/asciifarm/client/main.py
+++ b/asciifarm/client/main.py
@@ -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()
-
- # 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
+
+ tty.setraw(sys.stdin)
+ Screen.default.hide_cursor()
- 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:
diff --git a/asciifarm/keybindings/default.json b/asciifarm/keybindings/default.json
index a816527..f8a5b25 100644
--- a/asciifarm/keybindings/default.json
+++ b/asciifarm/keybindings/default.json
@@ -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"