#!/usr/bin/env python3
import curses
-import queue
-import threading
-import time
import sys
from plomrogue.game import GameBase
from plomrogue.parser import Parser
from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
from plomrogue.things import ThingBase
from plomrogue.misc import quote
-from plomrogue.errors import BrokenSocketConnection, ArgError
+from plomrogue.errors import ArgError
+from plomrogue_client.socket import ClientSocket
+from plomrogue_client.tui import msg_into_lines_of_width, TUI
+
+
mode_helps = {
'play': {
'name_thing': {
'short': 'name thing',
'intro': '',
- 'long': 'Give name to/change name of thing here.'
+ 'long': 'Give name to/change name of carried thing.'
},
'command_thing': {
- 'short': 'command thing',
+ 'short': 'command',
'intro': '',
'long': 'Enter a command to the thing you carry. Enter nothing to return to play mode.'
},
'take_thing': {
- 'short': 'take thing',
+ 'short': 'take',
'intro': 'Pick up a thing in reach by entering its index number. Enter nothing to abort.',
'long': 'You see a list of things which you could pick up. Enter the target thing\'s index, or, to leave, nothing.'
},
'drop_thing': {
- 'short': 'drop thing',
+ 'short': 'drop',
'intro': 'Enter number of direction to which you want to drop thing.',
'long': 'Drop currently carried thing by entering the target direction index. Enter nothing to return to play mode..'
},
'admin_thing_protect': {
'short': 'change thing protection',
'intro': '@ enter thing protection character:',
- 'long': 'Change protection character for thing here.'
+ 'long': 'Change protection character for carried thing.'
},
'enter_face': {
- 'short': 'enter your face',
- 'intro': '@ enter face line (enter nothing to abort):',
+ 'short': 'edit face',
+ 'intro': '@ enter face line:',
'long': 'Draw your face as ASCII art. The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
},
+ 'enter_design': {
+ 'short': 'edit design',
+ 'intro': '@ enter design:',
+ 'long': 'Enter design for carried thing as ASCII art.'
+ },
'write': {
- 'short': 'change terrain',
+ 'short': 'edit tile',
'intro': '',
'long': 'This mode allows you to change the map tile you currently stand on (if your world editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.'
},
}
}
-from ws4py.client import WebSocketBaseClient
-class WebSocketClient(WebSocketBaseClient):
-
- def __init__(self, recv_handler, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.recv_handler = recv_handler
- self.connect()
-
- def received_message(self, message):
- if message.is_text:
- message = str(message)
- self.recv_handler(message)
-
- @property
- def plom_closed(self):
- return self.client_terminated
-
-from plomrogue.io_tcp import PlomSocket
-class PlomSocketClient(PlomSocket):
-
- def __init__(self, recv_handler, url):
- import socket
- self.recv_handler = recv_handler
- host, port = url.split(':')
- super().__init__(socket.create_connection((host, port)))
-
- def close(self):
- self.socket.close()
-
- def run(self):
- import ssl
- try:
- for msg in self.recv():
- if msg == 'NEED_SSL':
- self.socket = ssl.wrap_socket(self.socket)
- continue
- self.recv_handler(msg)
- except BrokenSocketConnection:
- pass # we assume socket will be known as dead by now
-
def cmd_TURN(game, n):
- game.annotations = {}
- game.turn = n
- game.things = []
- game.portals = {}
game.turn_complete = False
- game.fov = ''
cmd_TURN.argtypes = 'int:nonneg'
+def cmd_OTHER_WIPE(game):
+ game.portals_new = {}
+ game.annotations_new = {}
+ game.things_new = []
+cmd_OTHER_WIPE.argtypes = ''
+
def cmd_LOGIN_OK(game):
game.tui.switch_mode('post_login_wait')
game.tui.send('GET_GAMESTATE')
- game.tui.log_msg('@ welcome')
+ game.tui.log_msg('@ welcome!')
cmd_LOGIN_OK.argtypes = ''
def cmd_ADMIN_OK(game):
def cmd_CHATFACE(game, thing_id):
game.tui.draw_face = thing_id
+ game.tui.do_refresh = True
cmd_CHATFACE.argtypes = 'int:pos'
def cmd_PLAYER_ID(game, player_id):
game.player_id = player_id
cmd_PLAYER_ID.argtypes = 'int:nonneg'
+def cmd_PLAYERS_HAT_CHARS(game, hat_chars):
+ game.players_hat_chars_new = hat_chars
+cmd_PLAYERS_HAT_CHARS.argtypes = 'string'
+
def cmd_THING(game, yx, thing_type, protection, thing_id, portable, commandable):
- t = game.get_thing(thing_id)
+ t = game.get_thing_temp(thing_id)
if not t:
t = ThingBase(game, thing_id)
- game.things += [t]
+ game.things_new += [t]
t.position = yx
t.type_ = thing_type
t.protection = protection
cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type char int:nonneg bool bool'
def cmd_THING_NAME(game, thing_id, name):
- t = game.get_thing(thing_id)
+ t = game.get_thing_temp(thing_id)
t.name = name
cmd_THING_NAME.argtypes = 'int:pos string'
def cmd_THING_FACE(game, thing_id, face):
- t = game.get_thing(thing_id)
+ t = game.get_thing_temp(thing_id)
t.face = face
cmd_THING_FACE.argtypes = 'int:pos string'
def cmd_THING_HAT(game, thing_id, hat):
- t = game.get_thing(thing_id)
+ t = game.get_thing_temp(thing_id)
t.hat = hat
cmd_THING_HAT.argtypes = 'int:pos string'
+def cmd_THING_DESIGN(game, thing_id, size, design):
+ t = game.get_thing_temp(thing_id)
+ t.design = [size, design]
+cmd_THING_DESIGN.argtypes = 'int:pos yx_tuple string'
+
def cmd_THING_CHAR(game, thing_id, c):
- t = game.get_thing(thing_id)
+ t = game.get_thing_temp(thing_id)
t.thing_char = c
cmd_THING_CHAR.argtypes = 'int:pos char'
def cmd_MAP(game, geometry, size, content):
map_geometry_class = globals()['MapGeometry' + geometry]
- game.map_geometry = map_geometry_class(size)
- game.map_content = content
- if type(game.map_geometry) == MapGeometrySquare:
+ game.map_geometry_new = map_geometry_class(size)
+ game.map_content_new = content
+ if type(game.map_geometry_new) == MapGeometrySquare:
game.tui.movement_keys = {
game.tui.keys['square_move_up']: 'UP',
game.tui.keys['square_move_left']: 'LEFT',
game.tui.keys['square_move_down']: 'DOWN',
game.tui.keys['square_move_right']: 'RIGHT',
}
- elif type(game.map_geometry) == MapGeometryHex:
+ elif type(game.map_geometry_new) == MapGeometryHex:
game.tui.movement_keys = {
game.tui.keys['hex_move_upleft']: 'UPLEFT',
game.tui.keys['hex_move_upright']: 'UPRIGHT',
cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
def cmd_FOV(game, content):
- game.fov = content
+ game.fov_new = content
cmd_FOV.argtypes = 'string'
def cmd_MAP_CONTROL(game, content):
- game.map_control_content = content
+ game.map_control_content_new = content
cmd_MAP_CONTROL.argtypes = 'string'
def cmd_GAME_STATE_COMPLETE(game):
- game.turn_complete = True
game.tui.do_refresh = True
game.tui.info_cached = None
+ game.things = game.things_new
+ game.portals = game.portals_new
+ game.annotations = game.annotations_new
+ game.fov = game.fov_new
+ game.map_geometry = game.map_geometry_new
+ game.map_content = game.map_content_new
+ game.map_control_content = game.map_control_content_new
game.player = game.get_thing(game.player_id)
+ game.players_hat_chars = game.players_hat_chars_new
+ game.bladder_pressure = game.bladder_pressure_new
+ game.energy = game.energy_new
+ game.turn_complete = True
if game.tui.mode.name == 'post_login_wait':
game.tui.switch_mode('play')
cmd_GAME_STATE_COMPLETE.argtypes = ''
def cmd_PORTAL(game, position, msg):
- game.portals[position] = msg
+ game.portals_new[position] = msg
cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
def cmd_PLAY_ERROR(game, msg):
cmd_ARGUMENT_ERROR.argtypes = 'string'
def cmd_ANNOTATION(game, position, msg):
- game.annotations[position] = msg
+ game.annotations_new[position] = msg
if game.tui.mode.shows_info:
game.tui.do_refresh = True
cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
cmd_THING_TYPE.argtypes = 'string char'
def cmd_THING_INSTALLED(game, thing_id):
- game.get_thing(thing_id).installed = True
+ game.get_thing_temp(thing_id).installed = True
cmd_THING_INSTALLED.argtypes = 'int:pos'
def cmd_THING_CARRYING(game, thing_id, carried_id):
- game.get_thing(thing_id).carrying = game.get_thing(carried_id)
+ game.get_thing_temp(thing_id).carrying = game.get_thing_temp(carried_id)
cmd_THING_CARRYING.argtypes = 'int:pos int:pos'
def cmd_TERRAIN(game, terrain_char, terrain_desc):
game.tui.set_random_colors()
cmd_RANDOM_COLORS.argtypes = ''
+def cmd_STATS(game, bladder_pressure, energy):
+ game.bladder_pressure_new = bladder_pressure
+ game.energy_new = energy
+cmd_STATS.argtypes = 'int:nonneg int'
+
class Game(GameBase):
turn_complete = False
tasks = {}
thing_types = {}
+ things_new = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register_command(cmd_REPLY)
self.register_command(cmd_PLAYER_ID)
self.register_command(cmd_TURN)
+ self.register_command(cmd_OTHER_WIPE)
self.register_command(cmd_THING)
self.register_command(cmd_THING_TYPE)
self.register_command(cmd_THING_NAME)
self.register_command(cmd_THING_CHAR)
self.register_command(cmd_THING_FACE)
self.register_command(cmd_THING_HAT)
+ self.register_command(cmd_THING_DESIGN)
self.register_command(cmd_THING_CARRYING)
self.register_command(cmd_THING_INSTALLED)
self.register_command(cmd_TERRAIN)
self.register_command(cmd_PORTAL)
self.register_command(cmd_ANNOTATION)
self.register_command(cmd_GAME_STATE_COMPLETE)
+ self.register_command(cmd_PLAYERS_HAT_CHARS)
self.register_command(cmd_ARGUMENT_ERROR)
self.register_command(cmd_GAME_ERROR)
self.register_command(cmd_PLAY_ERROR)
self.register_command(cmd_FOV)
self.register_command(cmd_DEFAULT_COLORS)
self.register_command(cmd_RANDOM_COLORS)
+ self.register_command(cmd_STATS)
self.map_content = ''
+ self.players_hat_chars = ''
self.player_id = -1
self.annotations = {}
+ self.annotations_new = {}
self.portals = {}
+ self.portals_new = {}
self.terrains = {}
self.player = None
f.argtypes = self.commands[command_name].argtypes
return f
+ def get_thing_temp(self, id_):
+ for thing in self.things_new:
+ if id_ == thing.id_:
+ return thing
+ return None
+
class Mode:
def __init__(self, name, has_input_prompt=False, shows_info=False,
return True
return False
-class TUI:
+class RogueChatTUI(TUI):
mode_admin_enter = Mode('admin_enter', has_input_prompt=True)
mode_admin = Mode('admin')
mode_play = Mode('play')
mode_take_thing = Mode('take_thing', has_input_prompt=True)
mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
mode_enter_face = Mode('enter_face', has_input_prompt=True)
+ mode_enter_design = Mode('enter_design', has_input_prompt=True)
is_admin = False
tile_draw = False
- def __init__(self, host):
+ def __init__(self, host, *args, **kwargs):
import os
import json
self.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
"command_thing", "take_thing",
"drop_thing"]
self.mode_play.available_actions = ["move", "teleport", "door", "consume",
- "install", "wear", "spin"]
+ "install", "wear", "spin", "dance"]
self.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
self.mode_study.available_actions = ["toggle_map_mode", "move_explorer"]
self.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
"control_tile_type", "chat",
"study", "play", "edit"]
- self.mode_admin.available_actions = ["move"]
+ self.mode_admin.available_actions = ["move", "toggle_map_mode"]
self.mode_control_tile_draw.available_modes = ["admin_enter"]
self.mode_control_tile_draw.available_actions = ["move_explorer",
"toggle_tile_draw"]
self.mode_edit.available_modes = ["write", "annotate", "portal",
- "name_thing", "enter_face", "password",
+ "name_thing", "enter_face", "enter_design",
+ "password",
"chat", "study", "play", "admin_enter"]
self.mode_edit.available_actions = ["move", "flatten", "install",
"toggle_map_mode"]
self.mode = None
- self.host = host
+ self.socket = ClientSocket(host, self.socket_log)
self.game = Game()
self.game.tui = self
self.parser = Parser(self.game)
- self.log = []
- self.do_refresh = True
- self.queue = queue.Queue()
self.login_name = None
self.map_mode = 'terrain + things'
self.password = 'foo'
- self.switch_mode('waiting_for_server')
self.keys = {
'switch_to_chat': 't',
'switch_to_play': 'p',
'switch_to_admin_thing_protect': 'T',
'flatten': 'F',
'switch_to_enter_face': 'f',
+ 'switch_to_enter_design': 'D',
'switch_to_take_thing': 'z',
'switch_to_drop_thing': 'u',
'teleport': 'p',
'install': 'I',
'wear': 'W',
'spin': 'S',
+ 'dance': 'T',
'help': 'h',
'toggle_map_mode': 'L',
'toggle_tile_draw': 'm',
for k in keys_conf:
self.keys[k] = keys_conf[k]
self.show_help = False
- self.disconnected = True
- self.force_instant_connect = True
self.input_lines = []
self.fov = ''
self.flash = False
self.map_lines = []
- self.draw_face = False
+ self.ascii_draw_stage = 0
+ self.full_ascii_draw = ''
self.offset = YX(0,0)
- curses.wrapper(self.loop)
-
- def connect(self):
+ self.explorer = YX(0, 0)
+ self.input_ = ''
+ self.input_prompt = '> '
+ self.action_descriptions = {
+ 'move': 'move',
+ 'flatten': 'flatten surroundings',
+ 'teleport': 'teleport',
+ 'take_thing': 'pick up thing',
+ 'drop_thing': 'drop thing',
+ 'toggle_map_mode': 'toggle map view',
+ 'toggle_tile_draw': 'toggle protection character drawing',
+ 'install': '(un-)install',
+ 'wear': '(un-)wear',
+ 'door': 'open/close',
+ 'consume': 'consume',
+ 'spin': 'spin',
+ 'dance': 'dance',
+ }
+ self.action_tasks = {
+ 'flatten': 'FLATTEN_SURROUNDINGS',
+ 'take_thing': 'PICK_UP',
+ 'drop_thing': 'DROP',
+ 'door': 'DOOR',
+ 'install': 'INSTALL',
+ 'wear': 'WEAR',
+ 'move': 'MOVE',
+ 'command': 'COMMAND',
+ 'consume': 'INTOXICATE',
+ 'spin': 'SPIN',
+ 'dance': 'DANCE',
+ }
+ super().__init__(*args, **kwargs)
- def handle_recv(msg):
- if msg == 'BYE':
- self.socket.close()
- else:
- self.queue.put(msg)
-
- self.log_msg('@ attempting connect')
- socket_client_class = PlomSocketClient
- if self.host.startswith('ws://') or self.host.startswith('wss://'):
- socket_client_class = WebSocketClient
- try:
- self.socket = socket_client_class(handle_recv, self.host)
- self.socket_thread = threading.Thread(target=self.socket.run)
- self.socket_thread.start()
- self.disconnected = False
- self.game.thing_types = {}
- self.game.terrains = {}
- time.sleep(0.1) # give potential SSL negotation some time …
- self.socket.send('TASKS')
- self.socket.send('TERRAINS')
- self.socket.send('THING_TYPES')
- self.switch_mode('login')
- except ConnectionRefusedError:
- self.log_msg('@ server connect failure')
- self.disconnected = True
- self.switch_mode('waiting_for_server')
- self.do_refresh = True
+ def update_on_connect(self):
+ self.socket.send('TASKS')
+ self.socket.send('TERRAINS')
+ self.socket.send('THING_TYPES')
+ self.switch_mode('login')
def reconnect(self):
- self.log_msg('@ attempting reconnect')
- self.send('QUIT')
+ import time
+ self.log('@ attempting reconnect')
+ self.socket.send('QUIT')
# necessitated by some strange SSL race conditions with ws4py
time.sleep(0.1) # FIXME find out why exactly necessary
self.switch_mode('waiting_for_server')
- self.connect()
+ self.socket.connect()
+ self.update_on_connect()
def send(self, msg):
- try:
- if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
- raise BrokenSocketConnection
- self.socket.send(msg)
- except (BrokenPipeError, BrokenSocketConnection):
- self.log_msg('@ server disconnected :(')
- self.disconnected = True
- self.force_instant_connect = True
+ self.socket.send(msg)
+ if self.socket.disconnected:
self.do_refresh = True
+ def socket_log(self, msg):
+ self.log('@ ' + msg)
+
def log_msg(self, msg):
- self.log += [msg]
- if len(self.log) > 100:
- self.log = self.log[-100:]
+ super().log(msg)
+ #self.log += [msg]
+ if len(self._log) > 100:
+ self.log = self._log[-100:]
def restore_input_values(self):
if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
elif self.mode.name == 'password':
self.input_ = self.password
elif self.mode.name == 'name_thing':
- if hasattr(self.thing_selected, 'name'):
- self.input_ = self.thing_selected.name
+ if hasattr(self.game.player.carrying, 'name'):
+ self.input_ = self.game.player.carrying.name
elif self.mode.name == 'admin_thing_protect':
- if hasattr(self.thing_selected, 'protection'):
- self.input_ = self.thing_selected.protection
+ if hasattr(self.game.player.carrying, 'protection'):
+ self.input_ = self.game.player.carrying.protection
+ elif self.mode.name == 'enter_face':
+ start = self.ascii_draw_stage * 6
+ end = (self.ascii_draw_stage + 1) * 6
+ self.input_ = self.game.player.face[start:end]
+ elif self.mode.name == 'enter_design':
+ width = self.game.player.carrying.design[0].x
+ start = self.ascii_draw_stage * width
+ end = (self.ascii_draw_stage + 1) * width
+ self.input_ = self.game.player.carrying.design[1][start:end]
def send_tile_control_command(self):
self.send('SET_TILE_CONTROL %s %s' %
def switch_mode(self, mode_name):
def fail(msg, return_mode='play'):
- self.log_msg('? ' + msg)
+ self.log('? ' + msg)
self.flash = True
self.switch_mode(return_mode)
if self.mode and self.mode.name == 'control_tile_draw':
- self.log_msg('@ finished tile protection drawing.')
+ self.log('@ finished tile protection drawing.')
+ self.draw_face = False
self.tile_draw = False
+ self.ascii_draw_stage = 0
+ self.full_ascii_draw = ''
if mode_name == 'command_thing' and\
(not self.game.player.carrying or
not self.game.player.carrying.commandable):
return fail('not carrying anything commandable')
+ if mode_name == 'name_thing' and not self.game.player.carrying:
+ return fail('not carrying anything to re-name', 'edit')
+ if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
+ return fail('not carrying anything to protect')
if mode_name == 'take_thing' and self.game.player.carrying:
return fail('already carrying something')
if mode_name == 'drop_thing' and not self.game.player.carrying:
return fail('not carrying anything droppable')
+ if mode_name == 'enter_design' and\
+ (not self.game.player.carrying or
+ not hasattr(self.game.player.carrying, 'design')):
+ return fail('not carrying designable to edit', 'edit')
if mode_name == 'admin_enter' and self.is_admin:
mode_name = 'admin'
- elif mode_name in {'name_thing', 'admin_thing_protect'}:
- thing = None
- for t in [t for t in self.game.things
- if t.position == self.game.player.position
- and t.id_ != self.game.player.id_]:
- thing = t
- break
- if not thing:
- return fail('not standing over thing', 'edit')
- else:
- self.thing_selected = thing
self.mode = getattr(self, 'mode_' + mode_name)
if self.mode.name in {'control_tile_draw', 'control_tile_type',
'control_pw_type'}:
if self.mode.is_single_char_entry:
self.show_help = True
if len(self.mode.intro_msg) > 0:
- self.log_msg(self.mode.intro_msg)
+ self.log(self.mode.intro_msg)
if self.mode.name == 'login':
if self.login_name:
self.send('LOGIN ' + quote(self.login_name))
else:
- self.log_msg('@ enter username')
+ self.log('@ enter username')
elif self.mode.name == 'take_thing':
- self.log_msg('Portable things in reach for pick-up:')
- select_range = [self.game.player.position,
- self.game.player.position + YX(0,-1),
- self.game.player.position + YX(0, 1),
- self.game.player.position + YX(-1, 0),
- self.game.player.position + YX(1, 0)]
- if type(self.game.map_geometry) == MapGeometryHex:
+ self.log('Portable things in reach for pick-up:')
+ directed_moves = {
+ 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
+ }
+ if type(self.game.map_geometry) == MapGeometrySquare:
+ directed_moves['UP'] = YX(-1, 0)
+ directed_moves['DOWN'] = YX(1, 0)
+ elif type(self.game.map_geometry) == MapGeometryHex:
if self.game.player.position.y % 2:
- select_range += [self.game.player.position + YX(-1, 1),
- self.game.player.position + YX(1, 1)]
+ directed_moves['UPLEFT'] = YX(-1, 0)
+ directed_moves['UPRIGHT'] = YX(-1, 1)
+ directed_moves['DOWNLEFT'] = YX(1, 0)
+ directed_moves['DOWNRIGHT'] = YX(1, 1)
else:
- select_range += [self.game.player.position + YX(-1, -1),
- self.game.player.position + YX(1, -1)]
- self.selectables = [t.id_ for t in self.game.things
- if t.portable and t.position in select_range]
+ directed_moves['UPLEFT'] = YX(-1, -1)
+ directed_moves['UPRIGHT'] = YX(-1, 0)
+ directed_moves['DOWNLEFT'] = YX(1, -1)
+ directed_moves['DOWNRIGHT'] = YX(1, 0)
+ select_range = {}
+ for direction in directed_moves:
+ move = directed_moves[direction]
+ select_range[direction] = self.game.player.position + move
+ self.selectables = []
+ directions = []
+ for direction in select_range:
+ for t in [t for t in self.game.things
+ if t.portable and t.position == select_range[direction]]:
+ self.selectables += [t.id_]
+ directions += [direction]
if len(self.selectables) == 0:
return fail('nothing to pick-up')
else:
for i in range(len(self.selectables)):
t = self.game.get_thing(self.selectables[i])
- self.log_msg(str(i) + ': ' + self.get_thing_info(t))
+ self.log('%s %s: %s' % (i, directions[i],
+ self.get_thing_info(t)))
elif self.mode.name == 'drop_thing':
- self.log_msg('Direction to drop thing to:')
+ self.log('Direction to drop thing to:')
self.selectables =\
['HERE'] + list(self.game.tui.movement_keys.values())
for i in range(len(self.selectables)):
- self.log_msg(str(i) + ': ' + self.selectables[i])
+ self.log(str(i) + ': ' + self.selectables[i])
+ elif self.mode.name == 'enter_design':
+ if self.game.player.carrying.type_ == 'Hat':
+ self.log('@ The design you enter must be %s lines of max %s '
+ 'characters width each'
+ % (self.game.player.carrying.design[0].y,
+ self.game.player.carrying.design[0].x))
+ self.log('@ Legal characters: ' + self.game.players_hat_chars)
+ self.log('@ (Eat cookies to extend the ASCII characters available for drawing.)')
+ else:
+ self.log('@ Width of first line determines maximum width for remaining design')
+ self.log('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
elif self.mode.name == 'command_thing':
self.send('TASK:COMMAND ' + quote('HELP'))
elif self.mode.name == 'control_pw_pw':
- self.log_msg('@ enter protection password for "%s":' % self.tile_control_char)
+ self.log('@ enter protection password for "%s":' % self.tile_control_char)
elif self.mode.name == 'control_tile_draw':
- self.log_msg('@ can draw protection character "%s", turn drawing on/off with [%s], finish with [%s].' % (self.tile_control_char, self.keys['toggle_tile_draw'], self.keys['switch_to_admin_enter']))
+ self.log('@ can draw protection character "%s", turn drawing on/off with [%s], finish with [%s].' % (self.tile_control_char, self.keys['toggle_tile_draw'], self.keys['switch_to_admin_enter']))
self.input_ = ""
self.restore_input_values()
def set_default_colors(self):
- curses.init_color(1, 1000, 1000, 1000)
- curses.init_color(2, 0, 0, 0)
+ if curses.can_change_color():
+ curses.init_color(7, 1000, 1000, 1000)
+ curses.init_color(0, 0, 0, 0)
self.do_refresh = True
def set_random_colors(self):
import random
return int(offset + random.random()*375)
- curses.init_color(1, rand(625), rand(625), rand(625))
- curses.init_color(2, rand(0), rand(0), rand(0))
+ if curses.can_change_color():
+ curses.init_color(7, rand(625), rand(625), rand(625))
+ curses.init_color(0, rand(0), rand(0), rand(0))
self.do_refresh = True
def get_info(self):
else:
for t in self.game.things:
if t.position == self.explorer:
- info_to_cache += 'THING: %s' % self.get_thing_info(t)
- protection = t.protection
- if protection == '.':
- protection = 'none'
- info_to_cache += ' / protection: %s\n' % protection
- if hasattr(t, 'hat'):
- info_to_cache += t.hat[0:6] + '\n'
- info_to_cache += t.hat[6:12] + '\n'
- info_to_cache += t.hat[12:18] + '\n'
- if hasattr(t, 'face'):
- info_to_cache += t.face[0:6] + '\n'
- info_to_cache += t.face[6:12] + '\n'
- info_to_cache += t.face[12:18] + '\n'
+ info_to_cache += '%s' % self.get_thing_info(t, True)
terrain_char = self.game.map_content[pos_i]
terrain_desc = '?'
if terrain_char in self.game.terrains:
terrain_desc = self.game.terrains[terrain_char]
- info_to_cache += 'TERRAIN: "%s" / %s\n' % (terrain_char,
+ info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
terrain_desc)
protection = self.game.map_control_content[pos_i]
- if protection == '.':
- protection = 'unprotected'
- info_to_cache += 'PROTECTION: %s\n' % protection
+ if protection != '.':
+ info_to_cache += '/protection:%s' % protection
+ info_to_cache += ')\n'
if self.explorer in self.game.portals:
info_to_cache += 'PORTAL: ' +\
self.game.portals[self.explorer] + '\n'
- else:
- info_to_cache += 'PORTAL: (none)\n'
if self.explorer in self.game.annotations:
info_to_cache += 'ANNOTATION: ' +\
self.game.annotations[self.explorer]
self.info_cached = info_to_cache
return self.info_cached
- def get_thing_info(self, t):
- info = '%s / %s' %\
- (t.type_, self.game.thing_types[t.type_])
+ def get_thing_info(self, t, detailed=False):
+ info = ''
+ if detailed:
+ info += '- '
+ info += self.game.thing_types[t.type_]
if hasattr(t, 'thing_char'):
info += t.thing_char
if hasattr(t, 'name'):
- info += ' (%s)' % t.name
+ info += ': %s' % t.name
+ info += ' (%s' % t.type_
if hasattr(t, 'installed'):
- info += ' / installed'
+ info += '/installed'
+ if t.type_ == 'Bottle':
+ if t.thing_char == '_':
+ info += '/empty'
+ elif t.thing_char == '~':
+ info += '/full'
+ if detailed:
+ protection = t.protection
+ if protection != '.':
+ info += '/protection:%s' % protection
+ info += ')\n'
+ if hasattr(t, 'hat') or hasattr(t, 'face'):
+ info += '----------\n'
+ if hasattr(t, 'hat'):
+ info += '| %s |\n' % t.hat[0:6]
+ info += '| %s |\n' % t.hat[6:12]
+ info += '| %s |\n' % t.hat[12:18]
+ if hasattr(t, 'face'):
+ info += '| %s |\n' % t.face[0:6]
+ info += '| %s |\n' % t.face[6:12]
+ info += '| %s |\n' % t.face[12:18]
+ info += '----------\n'
+ if hasattr(t, 'design'):
+ line_length = t.design[0].x
+ lines = []
+ for i in range(t.design[0].y):
+ start = i * line_length
+ end = (i + 1) * line_length
+ lines += [t.design[1][start:end]]
+ info += '-' * (line_length + 4) + '\n'
+ for line in lines:
+ info += '| %s |\n' % line
+ info += '-' * (line_length + 4) + '\n'
+ else:
+ info += ')'
return info
- def loop(self, stdscr):
- import datetime
+ def reset_size(self):
+ super().reset_size()
+ self.left_window_width = min(52, int(self.size.x / 2))
+ self.right_window_width = self.size.x - self.left_window_width
- def safe_addstr(y, x, line):
- if y < self.size.y - 1 or x + len(line) < self.size.x:
- stdscr.addstr(y, x, line, curses.color_pair(1))
- else: # workaround to <https://stackoverflow.com/q/7063128>
- cut_i = self.size.x - x - 1
- cut = line[:cut_i]
- last_char = line[cut_i]
- stdscr.addstr(y, self.size.x - 2, last_char, curses.color_pair(1))
- stdscr.insstr(y, self.size.x - 2, ' ')
- stdscr.addstr(y, x, cut, curses.color_pair(1))
+ def addstr(self, y, x, line, ignore=None):
+ super().addstr(y, x, line, curses.color_pair(1))
- def handle_input(msg):
- command, args = self.parser.parse(msg)
- command(*args)
+ def init_loop(self):
+ self.switch_mode('waiting_for_server')
+ curses.start_color()
+ self.set_default_colors()
+ curses.init_pair(1, 7, 0)
+ if not curses.can_change_color():
+ self.log('@ unfortunately, your terminal does not seem to '
+ 'support re-definition of colors; you might miss out '
+ 'on some color effects')
+ super().init_loop()
+
+ def recalc_input_lines(self):
+ if not self.mode.has_input_prompt:
+ self.input_lines = []
+ else:
+ self.input_lines = msg_into_lines_of_width(self.input_prompt
+ + self.input_ + '█',
+ self.right_window_width)
+ def draw_history(self):
+ lines = []
+ for line in self._log:
+ lines += msg_into_lines_of_width(line, self.right_window_width)
+ lines.reverse()
+ height_header = 2
+ max_y = self.size.y - len(self.input_lines)
+ for i in range(len(lines)):
+ if (i >= max_y - height_header):
+ break
+ self.addstr(max_y - i - 1, self.left_window_width, lines[i])
+
+ def draw_info(self):
+ info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
+ lines = msg_into_lines_of_width(info, self.right_window_width)
+ height_header = 2
+ for i in range(len(lines)):
+ y = height_header + i
+ if y >= self.size.y - len(self.input_lines):
+ break
+ self.addstr(y, self.left_window_width, lines[i])
+
+ def draw_input(self):
+ y = self.size.y - len(self.input_lines)
+ for i in range(len(self.input_lines)):
+ self.addstr(y, self.left_window_width, self.input_lines[i])
+ y += 1
+
+ def draw_stats(self):
+ stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
+ self.game.bladder_pressure)
+ self.addstr(0, self.left_window_width, stats)
+
+ def draw_mode(self):
+ help = "hit [%s] for help" % self.keys['help']
+ if self.mode.has_input_prompt:
+ help = "enter /help for help"
+ self.addstr(1, self.left_window_width,
+ 'MODE: %s – %s' % (self.mode.short_desc, help))
+
+ def draw_map(self):
+ if (not self.game.turn_complete) and len(self.map_lines) == 0:
+ return
+ if self.game.turn_complete:
+ map_lines_split = []
+ for y in range(self.game.map_geometry.size.y):
+ start = self.game.map_geometry.size.x * y
+ end = start + self.game.map_geometry.size.x
+ if self.map_mode == 'protections':
+ map_lines_split += [[c + ' ' for c
+ in self.game.map_control_content[start:end]]]
+ else:
+ map_lines_split += [[c + ' ' for c
+ in self.game.map_content[start:end]]]
+ if self.map_mode == 'terrain + annotations':
+ for p in self.game.annotations:
+ map_lines_split[p.y][p.x] = 'A '
+ elif self.map_mode == 'terrain + things':
+ for p in self.game.portals.keys():
+ original = map_lines_split[p.y][p.x]
+ map_lines_split[p.y][p.x] = original[0] + 'P'
+ used_positions = []
+
+ def draw_thing(t, used_positions):
+ symbol = self.game.thing_types[t.type_]
+ meta_char = ' '
+ if hasattr(t, 'thing_char'):
+ meta_char = t.thing_char
+ if t.position in used_positions:
+ meta_char = '+'
+ if hasattr(t, 'carrying') and t.carrying:
+ meta_char = '$'
+ map_lines_split[t.position.y][t.position.x] = symbol + meta_char
+ used_positions += [t.position]
+
+ for t in [t for t in self.game.things if t.type_ != 'Player']:
+ draw_thing(t, used_positions)
+ for t in [t for t in self.game.things if t.type_ == 'Player']:
+ draw_thing(t, used_positions)
+ if self.mode.shows_info or self.mode.name == 'control_tile_draw':
+ map_lines_split[self.explorer.y][self.explorer.x] = '??'
+ elif self.map_mode != 'terrain + things':
+ map_lines_split[self.game.player.position.y]\
+ [self.game.player.position.x] = '??'
+ self.map_lines = []
+ if type(self.game.map_geometry) == MapGeometryHex:
+ indent = 0
+ for line in map_lines_split:
+ self.map_lines += [indent * ' ' + ''.join(line)]
+ indent = 0 if indent else 1
+ else:
+ for line in map_lines_split:
+ self.map_lines += [''.join(line)]
+ window_center = YX(int(self.size.y / 2),
+ int(self.left_window_width / 2))
+ center = self.game.player.position
+ if self.mode.shows_info or self.mode.name == 'control_tile_draw':
+ center = self.explorer
+ center = YX(center.y, center.x * 2)
+ self.offset = center - window_center
+ if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
+ self.offset += YX(0, 1)
+ term_y = max(0, -self.offset.y)
+ term_x = max(0, -self.offset.x)
+ map_y = max(0, self.offset.y)
+ map_x = max(0, self.offset.x)
+ while term_y < self.size.y and map_y < len(self.map_lines):
+ to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
+ self.addstr(term_y, term_x, to_draw)
+ term_y += 1
+ map_y += 1
+
+ def draw_names(self):
+ players = [t for t in self.game.things if t.type_ == 'Player']
+ players.sort(key=lambda t: len(t.name))
+ players.reverse()
+ shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2)
+ y = 0
+ for t in players:
+ offset_y = y - shrink_offset
+ max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
+ name = t.name[:]
+ if len(name) > max_len:
+ name = name[:max_len - 1] + '…'
+ self.addstr(y, 0, '@%s:%s' % (t.thing_char, name))
+ y += 1
+ if y >= self.size.y:
+ break
+
+ def draw_face_popup(self):
+ t = self.game.get_thing(self.draw_face)
+ if not t or not hasattr(t, 'face'):
+ self.draw_face = False
+ return
+
+ start_x = self.left_window_width - 10
+ def draw_body_part(body_part, end_y):
+ self.addstr(end_y - 3, start_x, '----------')
+ self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
+ self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
+ self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
+
+ if hasattr(t, 'face'):
+ draw_body_part(t.face, self.size.y - 3)
+ if hasattr(t, 'hat'):
+ draw_body_part(t.hat, self.size.y - 6)
+ self.addstr(self.size.y - 2, start_x, '----------')
+ name = t.name[:]
+ if len(name) > 7:
+ name = name[:6 - 1] + '…'
+ self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name))
+
+ def draw_help(self):
+ content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
+ self.mode.help_intro)
+ if len(self.mode.available_actions) > 0:
+ content += "Available actions:\n"
+ for action in self.mode.available_actions:
+ if action in self.action_tasks:
+ if self.action_tasks[action] not in self.game.tasks:
+ continue
+ if action == 'move_explorer':
+ action = 'move'
+ if action == 'move':
+ key = ','.join(self.movement_keys)
+ else:
+ key = self.keys[action]
+ content += '[%s] – %s\n' % (key, self.action_descriptions[action])
+ content += '\n'
+ content += self.mode.list_available_modes(self)
+ for i in range(self.size.y):
+ self.addstr(i,
+ self.left_window_width * (not self.mode.has_input_prompt),
+ ' ' * self.left_window_width)
+ lines = []
+ for line in content.split('\n'):
+ lines += msg_into_lines_of_width(line, self.right_window_width)
+ for i in range(len(lines)):
+ if i >= self.size.y:
+ break
+ self.addstr(i,
+ self.left_window_width * (not self.mode.has_input_prompt),
+ lines[i])
+
+ def draw_screen(self):
+ super().draw_screen()
+ self.stdscr.bkgd(' ', curses.color_pair(1))
+ self.recalc_input_lines()
+ if self.mode.has_input_prompt:
+ self.draw_input()
+ if self.mode.shows_info:
+ self.draw_info()
+ else:
+ self.draw_history()
+ self.draw_mode()
+ if not self.mode.is_intro:
+ self.draw_stats()
+ self.draw_map()
+ if self.show_help:
+ self.draw_help()
+ if self.mode.name in {'chat', 'play'}:
+ self.draw_names()
+ if self.draw_face:
+ self.draw_face_popup()
+
+ def handle_server_message(self, msg):
+ command, args = self.parser.parse(msg)
+ command(*args)
+
+ def on_each_loop_start(self):
+ prev_disconnected = self.socket.disconnected
+ self.socket.keep_connection_alive()
+ if prev_disconnected and not self.socket.disconnected:
+ self.update_on_connect()
+ if self.flash:
+ curses.flash()
+ self.flash = False
+
+ def on_key(self, key, keycode):
def task_action_on(action):
- return action_tasks[action] in self.game.tasks
-
- def msg_into_lines_of_width(msg, width):
- chunk = ''
- lines = []
- x = 0
- for i in range(len(msg)):
- if x >= width or msg[i] == "\n":
- lines += [chunk]
- chunk = ''
- x = 0
- if msg[i] == "\n":
- x -= 1
- if msg[i] != "\n":
- chunk += msg[i]
- x += 1
- lines += [chunk]
- return lines
-
- def reset_screen_size():
- self.size = YX(*stdscr.getmaxyx())
- self.size = self.size - YX(self.size.y % 4, 0)
- self.size = self.size - YX(0, self.size.x % 4)
- self.window_width = int(self.size.x / 2)
-
- def recalc_input_lines():
- if not self.mode.has_input_prompt:
- self.input_lines = []
- else:
- self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
- self.window_width)
+ return self.action_tasks[action] in self.game.tasks
def move_explorer(direction):
target = self.game.map_geometry.move_yx(self.explorer, direction)
self.send_tile_control_command()
else:
self.flash = True
-
- def draw_history():
- lines = []
- for line in self.log:
- lines += msg_into_lines_of_width(line, self.window_width)
- lines.reverse()
- height_header = 2
- max_y = self.size.y - len(self.input_lines)
- for i in range(len(lines)):
- if (i >= max_y - height_header):
- break
- safe_addstr(max_y - i - 1, self.window_width, lines[i])
-
- def draw_info():
- info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
- lines = msg_into_lines_of_width(info, self.window_width)
- height_header = 2
- for i in range(len(lines)):
- y = height_header + i
- if y >= self.size.y - len(self.input_lines):
- break
- safe_addstr(y, self.window_width, lines[i])
-
- def draw_input():
- y = self.size.y - len(self.input_lines)
- for i in range(len(self.input_lines)):
- safe_addstr(y, self.window_width, self.input_lines[i])
- y += 1
-
- def draw_turn():
- if not self.game.turn_complete:
- return
- safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
-
- def draw_mode():
- help = "hit [%s] for help" % self.keys['help']
- if self.mode.has_input_prompt:
- help = "enter /help for help"
- safe_addstr(1, self.window_width,
- 'MODE: %s – %s' % (self.mode.short_desc, help))
-
- def draw_map():
- if not self.game.turn_complete and len(self.map_lines) == 0:
- return
- if self.game.turn_complete:
- map_lines_split = []
- for y in range(self.game.map_geometry.size.y):
- start = self.game.map_geometry.size.x * y
- end = start + self.game.map_geometry.size.x
- if self.map_mode == 'protections':
- map_lines_split += [[c + ' ' for c
- in self.game.map_control_content[start:end]]]
- else:
- map_lines_split += [[c + ' ' for c
- in self.game.map_content[start:end]]]
- if self.map_mode == 'terrain + annotations':
- for p in self.game.annotations:
- map_lines_split[p.y][p.x] = 'A '
- elif self.map_mode == 'terrain + things':
- for p in self.game.portals.keys():
- original = map_lines_split[p.y][p.x]
- map_lines_split[p.y][p.x] = original[0] + 'P'
- used_positions = []
-
- def draw_thing(t, used_positions):
- symbol = self.game.thing_types[t.type_]
- meta_char = ' '
- if hasattr(t, 'thing_char'):
- meta_char = t.thing_char
- if t.position in used_positions:
- meta_char = '+'
- if hasattr(t, 'carrying') and t.carrying:
- meta_char = '$'
- map_lines_split[t.position.y][t.position.x] = symbol + meta_char
- used_positions += [t.position]
-
- for t in [t for t in self.game.things if t.type_ != 'Player']:
- draw_thing(t, used_positions)
- for t in [t for t in self.game.things if t.type_ == 'Player']:
- draw_thing(t, used_positions)
- if self.mode.shows_info or self.mode.name == 'control_tile_draw':
- map_lines_split[self.explorer.y][self.explorer.x] = '??'
- elif self.map_mode != 'terrain + things':
- map_lines_split[self.game.player.position.y]\
- [self.game.player.position.x] = '??'
- self.map_lines = []
- if type(self.game.map_geometry) == MapGeometryHex:
- indent = 0
- for line in map_lines_split:
- self.map_lines += [indent * ' ' + ''.join(line)]
- indent = 0 if indent else 1
- else:
- for line in map_lines_split:
- self.map_lines += [''.join(line)]
- window_center = YX(int(self.size.y / 2),
- int(self.window_width / 2))
- center = self.game.player.position
- if self.mode.shows_info or self.mode.name == 'control_tile_draw':
- center = self.explorer
- center = YX(center.y, center.x * 2)
- self.offset = center - window_center
- if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
- self.offset += YX(0, 1)
- term_y = max(0, -self.offset.y)
- term_x = max(0, -self.offset.x)
- map_y = max(0, self.offset.y)
- map_x = max(0, self.offset.x)
- while term_y < self.size.y and map_y < len(self.map_lines):
- to_draw = self.map_lines[map_y][map_x:self.window_width + self.offset.x]
- safe_addstr(term_y, term_x, to_draw)
- term_y += 1
- map_y += 1
-
- def draw_face_popup():
- t = self.game.get_thing(self.draw_face)
- if not t:
- self.draw_face = False
- return
-
- def draw_body_part(body_part, end_y):
- start_x = self.window_width - 10
- safe_addstr(end_y - 4, start_x, '+--------+')
- safe_addstr(end_y - 3, start_x, '| |')
- safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
- safe_addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
- safe_addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
-
- if hasattr(t, 'face'):
- draw_body_part(t.face, self.size.y - 1)
- if hasattr(t, 'hat'):
- draw_body_part(t.hat, self.size.y - 4)
-
- def draw_help():
- content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
- self.mode.help_intro)
- if len(self.mode.available_actions) > 0:
- content += "Available actions:\n"
- for action in self.mode.available_actions:
- if action in action_tasks:
- if action_tasks[action] not in self.game.tasks:
- continue
- if action == 'move_explorer':
- action = 'move'
- if action == 'move':
- key = ','.join(self.movement_keys)
- else:
- key = self.keys[action]
- content += '[%s] – %s\n' % (key, action_descriptions[action])
- content += '\n'
- content += self.mode.list_available_modes(self)
- for i in range(self.size.y):
- safe_addstr(i,
- self.window_width * (not self.mode.has_input_prompt),
- ' ' * self.window_width)
- lines = []
- for line in content.split('\n'):
- lines += msg_into_lines_of_width(line, self.window_width)
- for i in range(len(lines)):
- if i >= self.size.y:
- break
- safe_addstr(i,
- self.window_width * (not self.mode.has_input_prompt),
- lines[i])
-
- def draw_screen():
- stdscr.clear()
- stdscr.bkgd(' ', curses.color_pair(1))
- recalc_input_lines()
- if self.mode.has_input_prompt:
- draw_input()
- if self.mode.shows_info:
- draw_info()
- else:
- draw_history()
- draw_mode()
- if not self.mode.is_intro:
- draw_turn()
- draw_map()
- if self.show_help:
- draw_help()
- if self.draw_face and self.mode.name in {'chat', 'play'}:
- draw_face_popup()
-
def pick_selectable(task_name):
try:
i = int(self.input_)
if i < 0 or i >= len(self.selectables):
- self.log_msg('? invalid index, aborted')
+ self.log('? invalid index, aborted')
else:
self.send('TASK:%s %s' % (task_name, self.selectables[i]))
except ValueError:
- self.log_msg('? invalid index, aborted')
+ self.log('? invalid index, aborted')
self.input_ = ''
self.switch_mode('play')
- action_descriptions = {
- 'move': 'move',
- 'flatten': 'flatten surroundings',
- 'teleport': 'teleport',
- 'take_thing': 'pick up thing',
- 'drop_thing': 'drop thing',
- 'toggle_map_mode': 'toggle map view',
- 'toggle_tile_draw': 'toggle protection character drawing',
- 'install': '(un-)install',
- 'wear': '(un-)wear',
- 'door': 'open/close',
- 'consume': 'consume',
- 'spin': 'spin',
- }
-
- action_tasks = {
- 'flatten': 'FLATTEN_SURROUNDINGS',
- 'take_thing': 'PICK_UP',
- 'drop_thing': 'DROP',
- 'door': 'DOOR',
- 'install': 'INSTALL',
- 'wear': 'WEAR',
- 'move': 'MOVE',
- 'command': 'COMMAND',
- 'consume': 'INTOXICATE',
- 'spin': 'SPIN',
- }
-
- curses.curs_set(False) # hide cursor
- curses.start_color()
- self.set_default_colors()
- curses.init_pair(1, 1, 2)
- stdscr.timeout(10)
- reset_screen_size()
- self.explorer = YX(0, 0)
- self.input_ = ''
- input_prompt = '> '
- interval = datetime.timedelta(seconds=5)
- last_ping = datetime.datetime.now() - interval
- while True:
- if self.disconnected and self.force_instant_connect:
- self.force_instant_connect = False
- self.connect()
- now = datetime.datetime.now()
- if now - last_ping > interval:
- if self.disconnected:
- self.connect()
- else:
- self.send('PING')
- last_ping = now
- if self.flash:
- curses.flash()
- self.flash = False
- if self.do_refresh:
- draw_screen()
- self.do_refresh = False
- while True:
- try:
- msg = self.queue.get(block=False)
- handle_input(msg)
- except queue.Empty:
- break
- try:
- key = stdscr.getkey()
- self.do_refresh = True
- except curses.error:
- continue
- keycode = None
- if len(key) == 1:
- keycode = ord(key)
- self.show_help = False
- self.draw_face = False
- if key == 'KEY_RESIZE':
- reset_screen_size()
- elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
- self.input_ = self.input_[:-1]
- elif (((not self.mode.is_intro) and keycode == 27) # Escape
- or (self.mode.has_input_prompt and key == '\n'
- and self.input_ == ''\
- and self.mode.name in {'chat', 'command_thing',
- 'take_thing', 'drop_thing',
- 'admin_enter'})):
- if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
- self.log_msg('@ aborted')
- self.switch_mode('play')
- elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
- self.show_help = True
- self.input_ = ""
+ def enter_ascii_art(command, height, width,
+ with_pw=False, with_size=False):
+ if with_size and self.ascii_draw_stage == 0:
+ width = len(self.input_)
+ if width > 36:
+ self.log('? input too long, must be max 36; try again')
+ # TODO: move max width mechanism server-side
+ return
+ old_size = self.game.player.carrying.design[0]
+ if width != old_size.x:
+ # TODO: save remaining design?
+ self.game.player.carrying.design[1] = ''
+ self.game.player.carrying.design[0] = YX(old_size.y, width)
+ elif len(self.input_) > width:
+ self.log('? input too long, '
+ 'must be max %s; try again' % width)
+ return
+ self.log(' ' + self.input_)
+ if with_size and self.input_ in {'', ' '}\
+ and self.ascii_draw_stage > 0:
+ height = self.ascii_draw_stage
+ else:
+ if with_size:
+ height = self.ascii_draw_stage + 2
+ if len(self.input_) < width:
+ self.input_ += ' ' * (width - len(self.input_))
+ self.full_ascii_draw += self.input_
+ if with_size:
+ old_size = self.game.player.carrying.design[0]
+ self.game.player.carrying.design[0] = YX(height, old_size.x)
+ self.ascii_draw_stage += 1
+ if self.ascii_draw_stage < height:
self.restore_input_values()
- elif self.mode.has_input_prompt and key != '\n': # Return key
- self.input_ += key
- max_length = self.window_width * self.size.y - len(input_prompt) - 1
- if len(self.input_) > max_length:
- self.input_ = self.input_[:max_length]
- elif key == self.keys['help'] and not self.mode.is_single_char_entry:
- self.show_help = True
- elif self.mode.name == 'login' and key == '\n':
- self.login_name = self.input_
- self.send('LOGIN ' + quote(self.input_))
- self.input_ = ""
- elif self.mode.name == 'enter_face' and key == '\n':
- if len(self.input_) != 18:
- self.log_msg('? wrong input length, aborting')
+ else:
+ if with_pw and with_size:
+ self.send('%s_SIZE %s %s' % (command, YX(height, width),
+ quote(self.password)))
+ if with_pw:
+ self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
+ quote(self.password)))
else:
- self.send('PLAYER_FACE %s' % quote(self.input_))
+ self.send('%s %s' % (command, quote(self.full_ascii_draw)))
+ self.full_ascii_draw = ""
+ self.ascii_draw_stage = 0
self.input_ = ""
self.switch_mode('edit')
- elif self.mode.name == 'take_thing' and key == '\n':
- pick_selectable('PICK_UP')
- elif self.mode.name == 'drop_thing' and key == '\n':
- pick_selectable('DROP')
- elif self.mode.name == 'command_thing' and key == '\n':
- self.send('TASK:COMMAND ' + quote(self.input_))
- self.input_ = ""
- elif self.mode.name == 'control_pw_pw' and key == '\n':
- if self.input_ == '':
- self.log_msg('@ aborted')
- else:
- self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
- self.log_msg('@ sent new password for protection character "%s"' % self.tile_control_char)
+
+ self.show_help = False
+ self.draw_face = False
+ if key == 'KEY_RESIZE':
+ self.reset_size()
+ elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
+ self.input_ = self.input_[:-1]
+ elif (((not self.mode.is_intro) and keycode == 27) # Escape
+ or (self.mode.has_input_prompt and key == '\n'
+ and self.input_ == ''\
+ and self.mode.name in {'chat', 'command_thing',
+ 'take_thing', 'drop_thing',
+ 'admin_enter'})):
+ if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
+ self.log('@ aborted')
+ self.switch_mode('play')
+ elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
+ self.show_help = True
+ self.input_ = ""
+ self.restore_input_values()
+ elif self.mode.has_input_prompt and key != '\n': # Return key
+ self.input_ += key
+ max_length = self.right_window_width * self.size.y - len(self.input_prompt) - 1
+ if len(self.input_) > max_length:
+ self.input_ = self.input_[:max_length]
+ elif key == self.keys['help'] and not self.mode.is_single_char_entry:
+ self.show_help = True
+ elif self.mode.name == 'login' and key == '\n':
+ self.login_name = self.input_
+ self.send('LOGIN ' + quote(self.input_))
+ self.input_ = ""
+ elif self.mode.name == 'enter_face' and key == '\n':
+ enter_ascii_art('PLAYER_FACE', 3, 6)
+ elif self.mode.name == 'enter_design' and key == '\n':
+ if self.game.player.carrying.type_ == 'Hat':
+ enter_ascii_art('THING_DESIGN',
+ self.game.player.carrying.design[0].y,
+ self.game.player.carrying.design[0].x, True)
+ else:
+ enter_ascii_art('THING_DESIGN',
+ self.game.player.carrying.design[0].y,
+ self.game.player.carrying.design[0].x,
+ True, True)
+ elif self.mode.name == 'take_thing' and key == '\n':
+ pick_selectable('PICK_UP')
+ elif self.mode.name == 'drop_thing' and key == '\n':
+ pick_selectable('DROP')
+ elif self.mode.name == 'command_thing' and key == '\n':
+ self.send('TASK:COMMAND ' + quote(self.input_))
+ self.input_ = ""
+ elif self.mode.name == 'control_pw_pw' and key == '\n':
+ if self.input_ == '':
+ self.log('@ aborted')
+ else:
+ self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
+ self.log('@ sent new password for protection character "%s"' % self.tile_control_char)
+ self.switch_mode('admin')
+ elif self.mode.name == 'password' and key == '\n':
+ if self.input_ == '':
+ self.input_ = ' '
+ self.password = self.input_
+ self.switch_mode('edit')
+ elif self.mode.name == 'admin_enter' and key == '\n':
+ self.send('BECOME_ADMIN ' + quote(self.input_))
+ self.switch_mode('play')
+ elif self.mode.name == 'control_pw_type' and key == '\n':
+ if len(self.input_) != 1:
+ self.log('@ entered non-single-char, therefore aborted')
self.switch_mode('admin')
- elif self.mode.name == 'password' and key == '\n':
- if self.input_ == '':
- self.input_ = ' '
- self.password = self.input_
- self.switch_mode('edit')
- elif self.mode.name == 'admin_enter' and key == '\n':
- self.send('BECOME_ADMIN ' + quote(self.input_))
- self.switch_mode('play')
- elif self.mode.name == 'control_pw_type' and key == '\n':
- if len(self.input_) != 1:
- self.log_msg('@ entered non-single-char, therefore aborted')
- self.switch_mode('admin')
- else:
- self.tile_control_char = self.input_
- self.switch_mode('control_pw_pw')
- elif self.mode.name == 'admin_thing_protect' and key == '\n':
- if len(self.input_) != 1:
- self.log_msg('@ entered non-single-char, therefore aborted')
- else:
- self.send('THING_PROTECTION %s %s' % (self.thing_selected.id_,
- quote(self.input_)))
- self.log_msg('@ sent new protection character for thing')
+ else:
+ self.tile_control_char = self.input_
+ self.switch_mode('control_pw_pw')
+ elif self.mode.name == 'admin_thing_protect' and key == '\n':
+ if len(self.input_) != 1:
+ self.log('@ entered non-single-char, therefore aborted')
+ else:
+ self.send('THING_PROTECTION %s' % (quote(self.input_)))
+ self.log('@ sent new protection character for thing')
+ self.switch_mode('admin')
+ elif self.mode.name == 'control_tile_type' and key == '\n':
+ if len(self.input_) != 1:
+ self.log('@ entered non-single-char, therefore aborted')
self.switch_mode('admin')
- elif self.mode.name == 'control_tile_type' and key == '\n':
- if len(self.input_) != 1:
- self.log_msg('@ entered non-single-char, therefore aborted')
- self.switch_mode('admin')
- else:
- self.tile_control_char = self.input_
- self.switch_mode('control_tile_draw')
- elif self.mode.name == 'chat' and key == '\n':
- if self.input_ == '':
- continue
- if self.input_[0] == '/':
- if self.input_.startswith('/nick'):
- tokens = self.input_.split(maxsplit=1)
- if len(tokens) == 2:
- self.send('NICK ' + quote(tokens[1]))
- else:
- self.log_msg('? need login name')
+ else:
+ self.tile_control_char = self.input_
+ self.switch_mode('control_tile_draw')
+ elif self.mode.name == 'chat' and key == '\n':
+ if self.input_ == '':
+ return
+ if self.input_[0] == '/':
+ if self.input_.startswith('/nick'):
+ tokens = self.input_.split(maxsplit=1)
+ if len(tokens) == 2:
+ self.send('NICK ' + quote(tokens[1]))
else:
- self.log_msg('? unknown command')
+ self.log('? need login name')
else:
- self.send('ALL ' + quote(self.input_))
- self.input_ = ""
- elif self.mode.name == 'name_thing' and key == '\n':
- if self.input_ == '':
- self.input_ = ' '
- self.send('THING_NAME %s %s %s' % (self.thing_selected.id_,
- quote(self.input_),
- quote(self.password)))
- self.switch_mode('edit')
- elif self.mode.name == 'annotate' and key == '\n':
- if self.input_ == '':
- self.input_ = ' '
- self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
- quote(self.password)))
- self.switch_mode('edit')
- elif self.mode.name == 'portal' and key == '\n':
- if self.input_ == '':
- self.input_ = ' '
- self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
- quote(self.password)))
- self.switch_mode('edit')
- elif self.mode.name == 'study':
- if self.mode.mode_switch_on_key(self, key):
- continue
- elif key == self.keys['toggle_map_mode']:
- self.toggle_map_mode()
- elif key in self.movement_keys:
- move_explorer(self.movement_keys[key])
- elif self.mode.name == 'play':
- if self.mode.mode_switch_on_key(self, key):
- continue
- elif key == self.keys['door'] and task_action_on('door'):
- self.send('TASK:DOOR')
- elif key == self.keys['consume'] and task_action_on('consume'):
- self.send('TASK:INTOXICATE')
- elif key == self.keys['wear'] and task_action_on('wear'):
- self.send('TASK:WEAR')
- elif key == self.keys['spin'] and task_action_on('spin'):
- self.send('TASK:SPIN')
- elif key == self.keys['teleport']:
- if self.game.player.position in self.game.portals:
- self.host = self.game.portals[self.game.player.position]
- self.reconnect()
- else:
- self.flash = True
- self.log_msg('? not standing on portal')
- elif key in self.movement_keys and task_action_on('move'):
- self.send('TASK:MOVE ' + self.movement_keys[key])
- elif self.mode.name == 'write':
- self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
- self.switch_mode('edit')
- elif self.mode.name == 'control_tile_draw':
- if self.mode.mode_switch_on_key(self, key):
- continue
- elif key in self.movement_keys:
- move_explorer(self.movement_keys[key])
- elif key == self.keys['toggle_tile_draw']:
- self.tile_draw = False if self.tile_draw else True
- elif self.mode.name == 'admin':
- if self.mode.mode_switch_on_key(self, key):
- continue
- elif key in self.movement_keys and task_action_on('move'):
- self.send('TASK:MOVE ' + self.movement_keys[key])
- elif self.mode.name == 'edit':
- if self.mode.mode_switch_on_key(self, key):
- continue
- elif key == self.keys['flatten'] and task_action_on('flatten'):
- self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
- elif key == self.keys['install'] and task_action_on('install'):
- self.send('TASK:INSTALL %s' % quote(self.password))
- elif key == self.keys['toggle_map_mode']:
- self.toggle_map_mode()
- elif key in self.movement_keys and task_action_on('move'):
- self.send('TASK:MOVE ' + self.movement_keys[key])
+ self.log('? unknown command')
+ else:
+ self.send('ALL ' + quote(self.input_))
+ self.input_ = ""
+ elif self.mode.name == 'name_thing' and key == '\n':
+ if self.input_ == '':
+ self.input_ = ' '
+ self.send('THING_NAME %s %s' % (quote(self.input_),
+ quote(self.password)))
+ self.switch_mode('edit')
+ elif self.mode.name == 'annotate' and key == '\n':
+ if self.input_ == '':
+ self.input_ = ' '
+ self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
+ quote(self.password)))
+ self.switch_mode('edit')
+ elif self.mode.name == 'portal' and key == '\n':
+ if self.input_ == '':
+ self.input_ = ' '
+ self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
+ quote(self.password)))
+ self.switch_mode('edit')
+ elif self.mode.name == 'study':
+ if self.mode.mode_switch_on_key(self, key):
+ return
+ elif key == self.keys['toggle_map_mode']:
+ self.toggle_map_mode()
+ elif key in self.movement_keys:
+ move_explorer(self.movement_keys[key])
+ elif self.mode.name == 'play':
+ if self.mode.mode_switch_on_key(self, key):
+ return
+ elif key == self.keys['door'] and task_action_on('door'):
+ self.send('TASK:DOOR')
+ elif key == self.keys['consume'] and task_action_on('consume'):
+ self.send('TASK:INTOXICATE')
+ elif key == self.keys['wear'] and task_action_on('wear'):
+ self.send('TASK:WEAR')
+ elif key == self.keys['spin'] and task_action_on('spin'):
+ self.send('TASK:SPIN')
+ elif key == self.keys['dance'] and task_action_on('dance'):
+ self.send('TASK:DANCE')
+ elif key == self.keys['teleport']:
+ if self.game.player.position in self.game.portals:
+ self.socket.host = self.game.portals[self.game.player.position]
+ self.reconnect()
+ else:
+ self.flash = True
+ self.log('? not standing on portal')
+ elif key in self.movement_keys and task_action_on('move'):
+ self.send('TASK:MOVE ' + self.movement_keys[key])
+ elif self.mode.name == 'write':
+ self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
+ self.switch_mode('edit')
+ elif self.mode.name == 'control_tile_draw':
+ if self.mode.mode_switch_on_key(self, key):
+ return
+ elif key in self.movement_keys:
+ move_explorer(self.movement_keys[key])
+ elif key == self.keys['toggle_tile_draw']:
+ self.tile_draw = False if self.tile_draw else True
+ elif self.mode.name == 'admin':
+ if self.mode.mode_switch_on_key(self, key):
+ return
+ elif key == self.keys['toggle_map_mode']:
+ self.toggle_map_mode()
+ elif key in self.movement_keys and task_action_on('move'):
+ self.send('TASK:MOVE ' + self.movement_keys[key])
+ elif self.mode.name == 'edit':
+ if self.mode.mode_switch_on_key(self, key):
+ return
+ elif key == self.keys['flatten'] and task_action_on('flatten'):
+ self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
+ elif key == self.keys['install'] and task_action_on('install'):
+ self.send('TASK:INSTALL %s' % quote(self.password))
+ elif key == self.keys['toggle_map_mode']:
+ self.toggle_map_mode()
+ elif key in self.movement_keys and task_action_on('move'):
+ self.send('TASK:MOVE ' + self.movement_keys[key])
if len(sys.argv) != 2:
raise ArgError('wrong number of arguments, need game host')
host = sys.argv[1]
-TUI(host)
+RogueChatTUI(host)