home · contact · privacy
Actually commit the previously forgotten rogue_chat curses client refactor.
[plomrogue2] / rogue_chat_curses.py
index a6f152cc9483254e5a5952df9025fcab2017e3ef..b4ac8e50302dbabc4ff94bbd676a5d635790740a 100755 (executable)
@@ -1,15 +1,15 @@
 #!/usr/bin/env python3
 import curses
 #!/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
 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
+
+
 
 mode_helps = {
     'play': {
 
 mode_helps = {
     'play': {
@@ -133,46 +133,6 @@ mode_helps = {
     }
 }
 
     }
 }
 
-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.turn_complete = False
 cmd_TURN.argtypes = 'int:nonneg'
 def cmd_TURN(game, n):
     game.turn_complete = False
 cmd_TURN.argtypes = 'int:nonneg'
@@ -187,8 +147,6 @@ def cmd_LOGIN_OK(game):
     game.tui.switch_mode('post_login_wait')
     game.tui.send('GET_GAMESTATE')
     game.tui.log_msg('@ welcome!')
     game.tui.switch_mode('post_login_wait')
     game.tui.send('GET_GAMESTATE')
     game.tui.log_msg('@ welcome!')
-    game.tui.log_msg('@ hint: see top of terminal for how to get help.')
-    game.tui.log_msg('@ hint: enter study mode to understand your environment.')
 cmd_LOGIN_OK.argtypes = ''
 
 def cmd_ADMIN_OK(game):
 cmd_LOGIN_OK.argtypes = ''
 
 def cmd_ADMIN_OK(game):
@@ -535,13 +493,12 @@ class TUI:
         self.mode_edit.available_actions = ["move", "flatten", "install",
                                             "toggle_map_mode"]
         self.mode = None
         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.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.login_name = None
         self.map_mode = 'terrain + things'
         self.password = 'foo'
@@ -593,8 +550,6 @@ class TUI:
             for k in keys_conf:
                 self.keys[k] = keys_conf[k]
         self.show_help = False
             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.input_lines = []
         self.fov = ''
         self.flash = False
@@ -604,55 +559,30 @@ class TUI:
         self.offset = YX(0,0)
         curses.wrapper(self.loop)
 
         self.offset = YX(0,0)
         curses.wrapper(self.loop)
 
-    def connect(self):
-
-        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):
 
     def reconnect(self):
+        import time
         self.log_msg('@ attempting reconnect')
         self.log_msg('@ attempting reconnect')
-        self.send('QUIT')
+        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')
         # 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):
 
     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
 
             self.do_refresh = True
 
+    def socket_log(self, msg):
+        self.log_msg('@ ' + msg)
+
     def log_msg(self, msg):
         self.log += [msg]
         if len(self.log) > 100:
     def log_msg(self, msg):
         self.log += [msg]
         if len(self.log) > 100:
@@ -706,6 +636,8 @@ class TUI:
             self.log_msg('@ finished tile protection drawing.')
         self.draw_face = False
         self.tile_draw = False
             self.log_msg('@ 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):
         if mode_name == 'command_thing' and\
            (not self.game.player.carrying or
             not self.game.player.carrying.commandable):
@@ -786,13 +718,16 @@ class TUI:
             for i in range(len(self.selectables)):
                 self.log_msg(str(i) + ': ' + self.selectables[i])
         elif self.mode.name == 'enter_design':
             for i in range(len(self.selectables)):
                 self.log_msg(str(i) + ': ' + self.selectables[i])
         elif self.mode.name == 'enter_design':
-            self.log_msg('@ 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))
             if self.game.player.carrying.type_ == 'Hat':
             if self.game.player.carrying.type_ == 'Hat':
+                self.log_msg('@ 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_msg('@ Legal characters: ' + self.game.players_hat_chars)
                 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
                 self.log_msg('@ Legal characters: ' + self.game.players_hat_chars)
                 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
+            else:
+                self.log_msg('@ Width of first line determines maximum width for remaining design')
+                self.log_msg('@ 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':
         elif self.mode.name == 'command_thing':
             self.send('TASK:COMMAND ' + quote('HELP'))
         elif self.mode.name == 'control_pw_pw':
@@ -803,8 +738,9 @@ class TUI:
         self.restore_input_values()
 
     def set_default_colors(self):
         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):
         self.do_refresh = True
 
     def set_random_colors(self):
@@ -813,8 +749,9 @@ class TUI:
             import random
             return int(offset + random.random()*375)
 
             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):
         self.do_refresh = True
 
     def get_info(self):
@@ -896,7 +833,6 @@ class TUI:
         return info
 
     def loop(self, stdscr):
         return info
 
     def loop(self, stdscr):
-        import datetime
 
         def safe_addstr(y, x, line):
             if y < self.size.y - 1 or x + len(line) < self.size.x:
 
         def safe_addstr(y, x, line):
             if y < self.size.y - 1 or x + len(line) < self.size.x:
@@ -937,7 +873,8 @@ class TUI:
             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.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)
+            self.left_window_width = min(52, int(self.size.x / 2))
+            self.right_window_width = self.size.x - self.left_window_width
 
         def recalc_input_lines():
             if not self.mode.has_input_prompt:
 
         def recalc_input_lines():
             if not self.mode.has_input_prompt:
@@ -945,7 +882,7 @@ class TUI:
             else:
                 self.input_lines = msg_into_lines_of_width(input_prompt
                                                            + self.input_ + '█',
             else:
                 self.input_lines = msg_into_lines_of_width(input_prompt
                                                            + self.input_ + '█',
-                                                           self.window_width)
+                                                           self.right_window_width)
 
         def move_explorer(direction):
             target = self.game.map_geometry.move_yx(self.explorer, direction)
 
         def move_explorer(direction):
             target = self.game.map_geometry.move_yx(self.explorer, direction)
@@ -960,41 +897,41 @@ class TUI:
         def draw_history():
             lines = []
             for line in self.log:
         def draw_history():
             lines = []
             for line in self.log:
-                lines += msg_into_lines_of_width(line, self.window_width)
+                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
             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])
+                safe_addstr(max_y - i - 1, self.left_window_width, lines[i])
 
         def draw_info():
             info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
 
         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)
+            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
             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])
+                safe_addstr(y, self.left_window_width, lines[i])
 
         def draw_input():
             y = self.size.y - len(self.input_lines)
             for i in range(len(self.input_lines)):
 
         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])
+                safe_addstr(y, self.left_window_width, self.input_lines[i])
                 y += 1
 
         def draw_stats():
             stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
                                                 self.game.bladder_pressure)
                 y += 1
 
         def draw_stats():
             stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
                                                 self.game.bladder_pressure)
-            safe_addstr(0, self.window_width, stats)
+            safe_addstr(0, self.left_window_width, stats)
 
         def draw_mode():
             help = "hit [%s] for help" % self.keys['help']
             if self.mode.has_input_prompt:
                 help = "enter /help for help"
 
         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,
+            safe_addstr(1, self.left_window_width,
                         'MODE: %s – %s' % (self.mode.short_desc, help))
 
         def draw_map():
                         'MODE: %s – %s' % (self.mode.short_desc, help))
 
         def draw_map():
@@ -1051,7 +988,7 @@ class TUI:
                     for line in map_lines_split:
                         self.map_lines += [''.join(line)]
                 window_center = YX(int(self.size.y / 2),
                     for line in map_lines_split:
                         self.map_lines += [''.join(line)]
                 window_center = YX(int(self.size.y / 2),
-                                   int(self.window_width / 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 = self.game.player.position
                 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
                     center = self.explorer
@@ -1064,18 +1001,35 @@ class TUI:
             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):
             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]
+                to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
                 safe_addstr(term_y, term_x, to_draw)
                 term_y += 1
                 map_y += 1
 
                 safe_addstr(term_y, term_x, to_draw)
                 term_y += 1
                 map_y += 1
 
+        def draw_names():
+            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] + '…'
+                safe_addstr(y, 0, '@%s:%s' % (t.thing_char, name))
+                y += 1
+                if y >= self.size.y:
+                    break
+
         def draw_face_popup():
             t = self.game.get_thing(self.draw_face)
             if not t or not hasattr(t, 'face'):
                 self.draw_face = False
                 return
 
         def draw_face_popup():
             t = self.game.get_thing(self.draw_face)
             if not t or not hasattr(t, 'face'):
                 self.draw_face = False
                 return
 
-            start_x = self.window_width - 10
+            start_x = self.left_window_width - 10
             def draw_body_part(body_part, end_y):
                 safe_addstr(end_y - 3, start_x, '----------')
                 safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
             def draw_body_part(body_part, end_y):
                 safe_addstr(end_y - 3, start_x, '----------')
                 safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
@@ -1088,8 +1042,8 @@ class TUI:
                 draw_body_part(t.hat, self.size.y - 6)
             safe_addstr(self.size.y - 2, start_x, '----------')
             name = t.name[:]
                 draw_body_part(t.hat, self.size.y - 6)
             safe_addstr(self.size.y - 2, start_x, '----------')
             name = t.name[:]
-            if len(name) > 6:
-                name = name[:6] + '…'
+            if len(name) > 7:
+                name = name[:6 - 1] + '…'
             safe_addstr(self.size.y - 1, start_x,
                         '@%s:%s' % (t.thing_char, name))
 
             safe_addstr(self.size.y - 1, start_x,
                         '@%s:%s' % (t.thing_char, name))
 
@@ -1113,16 +1067,16 @@ class TUI:
             content += self.mode.list_available_modes(self)
             for i in range(self.size.y):
                 safe_addstr(i,
             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)
+                            self.left_window_width * (not self.mode.has_input_prompt),
+                            ' ' * self.left_window_width)
             lines = []
             for line in content.split('\n'):
             lines = []
             for line in content.split('\n'):
-                lines += msg_into_lines_of_width(line, self.window_width)
+                lines += msg_into_lines_of_width(line, self.right_window_width)
             for i in range(len(lines)):
                 if i >= self.size.y:
                     break
                 safe_addstr(i,
             for i in range(len(lines)):
                 if i >= self.size.y:
                     break
                 safe_addstr(i,
-                            self.window_width * (not self.mode.has_input_prompt),
+                            self.left_window_width * (not self.mode.has_input_prompt),
                             lines[i])
 
         def draw_screen():
                             lines[i])
 
         def draw_screen():
@@ -1141,8 +1095,10 @@ class TUI:
                 draw_map()
             if self.show_help:
                 draw_help()
                 draw_map()
             if self.show_help:
                 draw_help()
-            if self.draw_face and self.mode.name in {'chat', 'play'}:
-                draw_face_popup()
+            if self.mode.name in {'chat', 'play'}:
+                draw_names()
+                if self.draw_face:
+                    draw_face_popup()
 
         def pick_selectable(task_name):
             try:
 
         def pick_selectable(task_name):
             try:
@@ -1156,19 +1112,43 @@ class TUI:
             self.input_ = ''
             self.switch_mode('play')
 
             self.input_ = ''
             self.switch_mode('play')
 
-        def enter_ascii_art(command, height, width, with_pw=False):
-            if len(self.input_) > width:
-                self.log_msg('? wrong input length, '
+        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_msg('? 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_msg('? input too long, '
                              'must be max %s; try again' % width)
                 return
                              'must be max %s; try again' % width)
                 return
-            if len(self.input_) < width:
-                self.input_ += ' ' * (width - len(self.input_))
             self.log_msg('  ' + self.input_)
             self.log_msg('  ' + self.input_)
-            self.full_ascii_draw += 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()
             else:
             self.ascii_draw_stage += 1
             if self.ascii_draw_stage < height:
                 self.restore_input_values()
             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)))
                 if with_pw:
                     self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
                                             quote(self.password)))
@@ -1209,41 +1189,33 @@ class TUI:
             'dance': 'DANCE',
         }
 
             'dance': 'DANCE',
         }
 
-        curses.curs_set(False)  # hide cursor
+        curses.curs_set(0)  # hide cursor
         curses.start_color()
         self.set_default_colors()
         curses.start_color()
         self.set_default_colors()
-        curses.init_pair(1, 1, 2)
+        curses.init_pair(1, 7, 0)
+        if not curses.can_change_color():
+            self.log_msg('@ unfortunately, your terminal does not seem to '
+                         'support re-definition of colors; you might miss out '
+                         'on some color effects')
         stdscr.timeout(10)
         reset_screen_size()
         self.explorer = YX(0, 0)
         self.input_ = ''
         store_widechar = False
         input_prompt = '> '
         stdscr.timeout(10)
         reset_screen_size()
         self.explorer = YX(0, 0)
         self.input_ = ''
         store_widechar = False
         input_prompt = '> '
-        interval = datetime.timedelta(seconds=5)
-        last_ping = datetime.datetime.now() - interval
         while True:
         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
+            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
             if self.do_refresh:
                 draw_screen()
                 self.do_refresh = False
             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
+            for msg in self.socket.get_message():
+                handle_input(msg)
             try:
                 key = stdscr.getkey()
                 self.do_refresh = True
             try:
                 key = stdscr.getkey()
                 self.do_refresh = True
@@ -1280,7 +1252,7 @@ class TUI:
                 self.restore_input_values()
             elif self.mode.has_input_prompt and key != '\n':  # Return key
                 self.input_ += key
                 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
+                max_length = self.right_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:
                 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:
@@ -1292,9 +1264,15 @@ class TUI:
             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':
             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':
-                enter_ascii_art('THING_DESIGN',
-                                self.game.player.carrying.design[0].y,
-                                self.game.player.carrying.design[0].x, True)
+                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':
             elif self.mode.name == 'take_thing' and key == '\n':
                 pick_selectable('PICK_UP')
             elif self.mode.name == 'drop_thing' and key == '\n':
@@ -1393,7 +1371,7 @@ class TUI:
                     self.send('TASK:DANCE')
                 elif key == self.keys['teleport']:
                     if self.game.player.position in self.game.portals:
                     self.send('TASK:DANCE')
                 elif key == self.keys['teleport']:
                     if self.game.player.position in self.game.portals:
-                        self.host = self.game.portals[self.game.player.position]
+                        self.socket.host = self.game.portals[self.game.player.position]
                         self.reconnect()
                     else:
                         self.flash = True
                         self.reconnect()
                     else:
                         self.flash = True