class Client(): MOVE_NORMAL = 0 MOVE_FAST = 1 MOVE_MAP = 2 def __init__(self, address, port=None, logfile=None): self.stopping = False self.movement = self.MOVE_NORMAL self.map_ = None self.chunkmap = None self.chunkmap_active = False #self.address = f"ws://{address}:{port}/" self.address = "ws://{}:{}/".format(address, port) self._drawevent = threading.Event() self.pool = ClientChunkPool(self) self.logfile = logfile self.log_messages = [] def launch(self, stdscr): # connect to server try: self._ws = websocket.create_connection( self.address, enable_multithread=True ) except: #sys.stderr.write(f"Could not connect to server: {self.address!r}\n") sys.stderr.write("Could not connect to server: {!r}\n".format(self.address)) return # create map etc. sizey, sizex = stdscr.getmaxyx() self.map_ = Map(sizex, sizey, self.pool, self) self.chunkmap = ChunkMap(self.map_) # start connection thread self.connectionthread = threading.Thread( target=self.connection_thread, name="connectionthread" ) self.connectionthread.start() # start input thread self.inputthread = threading.Thread( target=self.input_thread, name="inputthread", args=(stdscr,), daemon=True ) self.inputthread.start() while not self.stopping: self.update_screen() if self.logfile: self.save_log(self.logfile) def update_screen(self): self._drawevent.wait() self._drawevent.clear() with self.map_ as m: m.draw() if self.chunkmap_active: self.chunkmap.draw() curses.curs_set(False) else: curses.curs_set(True) #m.update_cursor() #m.noutrefresh() curses.doupdate() def redraw(self): self._drawevent.set() def log(self, message): if self.logfile: self.log_messages.append(message) def save_log(self, filename): with open(filename, "a") as f: #f.write(f"[[[ {int(time.time())} ]]]\n") f.write("[[[ {} ]]]\n".format(int(time.time()))) for msg in self.log_messages: f.write(msg + "\n") def move(self, x, y, mode): if mode == self.MOVE_NORMAL: self.map_.move_cursor(x, y) elif mode == self.MOVE_FAST: self.map_.move_cursor(x*10, y*5) elif mode == self.MOVE_MAP: self.map_.scroll(x*20, y*10) def handle_input(self, i): if i == "\x1b": self.stop() elif i == curses.KEY_F1: self.movement = self.MOVE_NORMAL elif i == curses.KEY_F2: self.movement = self.MOVE_FAST elif i == curses.KEY_F3: self.movement = self.MOVE_MAP elif i == curses.KEY_F5: self.map_.redraw() elif i == curses.KEY_F6 or i == curses.KEY_F4: # real map will later toggle on F4 self.chunkmap_active = not self.chunkmap_active self.redraw() elif i == curses.KEY_F7: self.map_.alternating_colors = not self.map_.alternating_colors self.redraw() elif i == curses.KEY_F10: self.map_.set_cursor(0, 0) # scrolling the map (10 vertical, 20 horizontal) elif i in [569,566]: self.move( 0, -1, self.MOVE_MAP) # ctrl + up elif i in [528,525]: self.move( 0, 1, self.MOVE_MAP) # ctrl + down elif i in [548,545]: self.move(-1, 0, self.MOVE_MAP) # ctrl + left elif i in [563,560]: self.move( 1, 0, self.MOVE_MAP) # ctrl + right # break here if chunkmap is shown: Don't allow for cursor movement or input elif self.chunkmap_active and self.movement != self.MOVE_MAP: pass # quick cursor movement (5 vertical, 10 horizontal) elif i == curses.KEY_SR: self.move( 0, -1, self.MOVE_FAST) # shift + up, 337 elif i == curses.KEY_SF: self.move( 0, 1, self.MOVE_FAST) # shift + down, 336 elif i == curses.KEY_SLEFT: self.move(-1, 0, self.MOVE_FAST) # shift + left, 393 elif i == curses.KEY_SRIGHT: self.move( 1, 0, self.MOVE_FAST) # shift + right, 402 # normal cursor movement elif i == curses.KEY_UP: self.move( 0, -1, self.movement) elif i == curses.KEY_DOWN: self.move( 0, 1, self.movement) elif i == curses.KEY_LEFT: self.move(-1, 0, self.movement) elif i == curses.KEY_RIGHT: self.move( 1, 0, self.movement) # edit world elif i == "\x7f": self.map_.delete() elif i == "\n": self.map_.newline() #elif i in string.digits + string.ascii_letters + string.punctuation + " ": #self.map_.write(i) elif isinstance(i, str) and len(i) == 1 and ord(i) > 31 and (i not in string.whitespace or i == " "): self.map_.write(i) def input_thread(self, scr): while True: i = scr.get_wch() self.handle_input(i) #self.log(f"K: {i!r}") self.log("K: {!r}".format(i)) def connection_thread(self): try: while True: j = self._ws.recv() if j: self.handle_json(json.loads(j)) except (WSException, ConnectionResetError, OSError): self._ws = None self.stop() return def handle_json(self, message): if message["type"] == "apply-changes": diffs = dejsonify_diffs(message["data"]) self.map_.commit_diffs(diffs) def stop(self): self.stopping = True if self._ws: self._ws.close() self._ws = None self.redraw() def request_chunks(self, coords): message = {"type": "request-chunks", "data": coords} self._ws.send(json.dumps(message)) def unload_chunks(self, coords): message = {"type": "unload-chunks", "data": coords} self._ws.send(json.dumps(message)) def send_changes(self, diffs): diffs = jsonify_diffs(diffs) message = {"type": "save-changes", "data": diffs} self._ws.send(json.dumps(message))