#!/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
+
+
mode_helps = {
'play': {
}
}
-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'
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):
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'
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.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):
+ import time
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')
- 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('@ ' + msg)
+
def log_msg(self, msg):
self.log += [msg]
if len(self.log) > 100:
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):
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:
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'):
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))
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:
'dance': 'DANCE',
}
- curses.curs_set(False) # hide cursor
+ curses.curs_set(0) # hide cursor
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 = '> '
- 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
+ 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
- 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
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