wot/client.py

188 lines
4.8 KiB
Python

import curses
import json
import os
import string
import sys
import threading
import time
import websocket
from websocket import WebSocketException as WSException
from maps import Map, ChunkMap
from chunks import ChunkDiff, jsonify_diffs, dejsonify_diffs
from utils import Position
from clientchunkpool import ClientChunkPool
class Client():
def __init__(self, address, logfile=None):
self.stopping = False
self.map_ = None
self.chunkmap = None
self.chunkmap_active = False
self.address = f"ws://{address}/"
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")
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()
# update screen until stopped
while not self.stopping:
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()
if self.logfile:
self.save_log(self.logfile)
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, "w") as f:
f.write(f"[[[ {int(time.time())} ]]]\n")
for msg in self.log_messages:
f.write(msg + "\n")
def input_thread(self, scr):
while True:
i = scr.get_wch()
if i == "\x1b": self.stop()
elif i == 266: # F2
self.chunkmap_active = not self.chunkmap_active
self.redraw()
elif i == 267: # F3
self.map_.alternating_colors = not self.map_.alternating_colors
self.redraw()
elif i == 269: # F5
self.map_.redraw()
# scrolling the map (10 vertical, 20 horizontal)
elif i == 569: self.map_.scroll(0, -10) # ctrl + up
elif i == 528: self.map_.scroll(0, 10) # ctrl + down
elif i == 548: self.map_.scroll(-20, 0) # ctrl + left
elif i == 563: self.map_.scroll(20, 0) # ctrl + right
# break here if chunkmap is shown: Don't allow for cursor movement or input
elif self.chunkmap_active: pass
# quick cursor movement (5 vertical, 10 horizontal)
elif i == 337: self.map_.move_cursor(0, -5) # shift + up
elif i == 336: self.map_.move_cursor(0, 5) # shift + down
elif i == 393: self.map_.move_cursor(-10, 0) # shift + left
elif i == 402: self.map_.move_cursor(10, 0) # shift + right
# normal cursor movement
elif i == 259: self.map_.move_cursor(0, -1) # up
elif i == 258: self.map_.move_cursor(0, 1) # down
elif i == 260: self.map_.move_cursor(-1, 0) # left
elif i == 261: self.map_.move_cursor(1, 0) # right
# 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)
self.log(f"K: {i!r}")
def connection_thread(self):
while True:
try:
j = self._ws.recv()
if j:
self.handle_json(json.loads(j))
except (WSException, ConnectionResetError, OSError):
#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
self._ws.close()
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))
def main(argv):
if len(argv) == 2:
client = Client(argv[1])
elif len(argv) == 3:
client = Client(argv[1], argv[2])
else:
print("Usage:")
print(f" {argv[0]} address [logfile]")
return
os.environ.setdefault('ESCDELAY', '25') # only a 25 millisecond delay
curses.wrapper(client.launch)
if __name__ == "__main__":
main(sys.argv)