6 from plomrogue.game import GameBase
7 from plomrogue.parser import Parser
8 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
9 from plomrogue.things import ThingBase
10 from plomrogue.misc import quote
11 from plomrogue.errors import BrokenSocketConnection
13 from ws4py.client import WebSocketBaseClient
14 class WebSocketClient(WebSocketBaseClient):
16 def __init__(self, recv_handler, *args, **kwargs):
17 super().__init__(*args, **kwargs)
18 self.recv_handler = recv_handler
21 def received_message(self, message):
23 message = str(message)
24 self.recv_handler(message)
27 def plom_closed(self):
28 return self.client_terminated
30 from plomrogue.io_tcp import PlomSocket
31 class PlomSocketClient(PlomSocket):
33 def __init__(self, recv_handler, url):
35 self.recv_handler = recv_handler
36 host, port = url.split(':')
37 super().__init__(socket.create_connection((host, port)))
45 for msg in self.recv():
47 self.socket = ssl.wrap_socket(self.socket)
49 self.recv_handler(msg)
50 except BrokenSocketConnection:
51 pass # we assume socket will be known as dead by now
53 def cmd_TURN(game, n):
57 game.turn_complete = False
58 cmd_TURN.argtypes = 'int:nonneg'
60 def cmd_LOGIN_OK(game):
61 game.tui.switch_mode('post_login_wait')
62 game.tui.send('GET_GAMESTATE')
63 game.tui.log_msg('@ welcome')
64 cmd_LOGIN_OK.argtypes = ''
66 def cmd_CHAT(game, msg):
67 game.tui.log_msg('# ' + msg)
68 game.tui.do_refresh = True
69 cmd_CHAT.argtypes = 'string'
71 def cmd_PLAYER_ID(game, player_id):
72 game.player_id = player_id
73 cmd_PLAYER_ID.argtypes = 'int:nonneg'
75 def cmd_THING(game, yx, thing_type, thing_id):
76 t = game.get_thing(thing_id)
78 t = ThingBase(game, thing_id)
82 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
84 def cmd_THING_NAME(game, thing_id, name):
85 t = game.get_thing(thing_id)
88 cmd_THING_NAME.argtypes = 'int:nonneg string'
90 def cmd_THING_CHAR(game, thing_id, c):
91 t = game.get_thing(thing_id)
94 cmd_THING_CHAR.argtypes = 'int:nonneg char'
96 def cmd_MAP(game, geometry, size, content):
97 map_geometry_class = globals()['MapGeometry' + geometry]
98 game.map_geometry = map_geometry_class(size)
99 game.map_content = content
100 if type(game.map_geometry) == MapGeometrySquare:
101 game.tui.movement_keys = {
102 game.tui.keys['square_move_up']: 'UP',
103 game.tui.keys['square_move_left']: 'LEFT',
104 game.tui.keys['square_move_down']: 'DOWN',
105 game.tui.keys['square_move_right']: 'RIGHT',
107 elif type(game.map_geometry) == MapGeometryHex:
108 game.tui.movement_keys = {
109 game.tui.keys['hex_move_upleft']: 'UPLEFT',
110 game.tui.keys['hex_move_upright']: 'UPRIGHT',
111 game.tui.keys['hex_move_right']: 'RIGHT',
112 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
113 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
114 game.tui.keys['hex_move_left']: 'LEFT',
116 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
118 def cmd_FOV(game, content):
120 cmd_FOV.argtypes = 'string'
122 def cmd_MAP_CONTROL(game, content):
123 game.map_control_content = content
124 cmd_MAP_CONTROL.argtypes = 'string'
126 def cmd_GAME_STATE_COMPLETE(game):
128 if game.tui.mode.name == 'post_login_wait':
129 game.tui.switch_mode('play')
130 if game.tui.mode.shows_info:
131 game.tui.query_info()
132 game.turn_complete = True
133 game.tui.do_refresh = True
134 cmd_GAME_STATE_COMPLETE.argtypes = ''
136 def cmd_PORTAL(game, position, msg):
137 game.portals[position] = msg
138 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
140 def cmd_PLAY_ERROR(game, msg):
142 game.tui.do_refresh = True
143 cmd_PLAY_ERROR.argtypes = 'string'
145 def cmd_GAME_ERROR(game, msg):
146 game.tui.log_msg('? game error: ' + msg)
147 game.tui.do_refresh = True
148 cmd_GAME_ERROR.argtypes = 'string'
150 def cmd_ARGUMENT_ERROR(game, msg):
151 game.tui.log_msg('? syntax error: ' + msg)
152 game.tui.do_refresh = True
153 cmd_ARGUMENT_ERROR.argtypes = 'string'
155 def cmd_ANNOTATION(game, position, msg):
156 game.info_db[position] = msg
157 if game.tui.mode.shows_info:
158 game.tui.do_refresh = True
159 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
161 def cmd_TASKS(game, tasks_comma_separated):
162 game.tasks = tasks_comma_separated.split(',')
163 cmd_TASKS.argtypes = 'string'
165 def cmd_THING_TYPE(game, thing_type, symbol_hint):
166 game.thing_types[thing_type] = symbol_hint
167 cmd_THING_TYPE.argtypes = 'string char'
169 def cmd_TERRAIN(game, terrain_char, terrain_desc):
170 game.terrains[terrain_char] = terrain_desc
171 cmd_TERRAIN.argtypes = 'char string'
175 cmd_PONG.argtypes = ''
177 class Game(GameBase):
178 turn_complete = False
182 def __init__(self, *args, **kwargs):
183 super().__init__(*args, **kwargs)
184 self.register_command(cmd_LOGIN_OK)
185 self.register_command(cmd_PONG)
186 self.register_command(cmd_CHAT)
187 self.register_command(cmd_PLAYER_ID)
188 self.register_command(cmd_TURN)
189 self.register_command(cmd_THING)
190 self.register_command(cmd_THING_TYPE)
191 self.register_command(cmd_THING_NAME)
192 self.register_command(cmd_THING_CHAR)
193 self.register_command(cmd_TERRAIN)
194 self.register_command(cmd_MAP)
195 self.register_command(cmd_MAP_CONTROL)
196 self.register_command(cmd_PORTAL)
197 self.register_command(cmd_ANNOTATION)
198 self.register_command(cmd_GAME_STATE_COMPLETE)
199 self.register_command(cmd_ARGUMENT_ERROR)
200 self.register_command(cmd_GAME_ERROR)
201 self.register_command(cmd_PLAY_ERROR)
202 self.register_command(cmd_TASKS)
203 self.register_command(cmd_FOV)
204 self.map_content = ''
210 def get_string_options(self, string_option_type):
211 if string_option_type == 'map_geometry':
212 return ['Hex', 'Square']
213 elif string_option_type == 'thing_type':
214 return self.thing_types.keys()
217 def get_command(self, command_name):
218 from functools import partial
219 f = partial(self.commands[command_name], self)
220 f.argtypes = self.commands[command_name].argtypes
227 def __init__(self, name, help_intro, has_input_prompt=False,
228 shows_info=False, is_intro = False):
230 self.has_input_prompt = has_input_prompt
231 self.shows_info = shows_info
232 self.is_intro = is_intro
233 self.help_intro = help_intro
235 def __init__(self, host):
239 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
240 self.mode_study = self.Mode('study', 'This mode allows you to study the map and its tiles in detail. Move the question mark over a tile, and the right half of the screen will show detailed information on it.', shows_info=True)
241 self.mode_edit = self.Mode('edit', 'This mode allows you to change the map tile you currently stand on (if your map editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.')
242 self.mode_annotate = self.Mode('annotate', 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your map editing password authorizes you so). Hit Return to leave.', has_input_prompt=True, shows_info=True)
243 self.mode_portal = self.Mode('portal', 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your map editing password authorizes you so). Enter or edit a URL to imprint a teleportation target; enter emptiness to remove a pre-existing teleportation target. Hit Return to leave.', has_input_prompt=True, shows_info=True)
244 self.mode_chat = self.Mode('chat', 'This mode allows you to engage in chit-chat with other users. Any line you enter into the input prompt that does not start with a "/" will be sent out to nearby players – but barriers and distance will reduce what they can read, so stand close to them to ensure they get your message. Lines that start with a "/" are used for commands like:', has_input_prompt=True)
245 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
246 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
247 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
248 self.mode_password = self.Mode('password', 'This mode allows you to change the password that you send to authorize yourself for editing password-protected map tiles. Hit return to confirm and leave.', has_input_prompt=True)
251 self.parser = Parser(self.game)
253 self.do_refresh = True
254 self.queue = queue.Queue()
255 self.login_name = None
256 self.map_mode = 'terrain'
257 self.password = 'foo'
258 self.switch_mode('waiting_for_server')
260 'switch_to_chat': 't',
261 'switch_to_play': 'p',
262 'switch_to_password': 'P',
263 'switch_to_annotate': 'M',
264 'switch_to_portal': 'T',
265 'switch_to_study': '?',
266 'switch_to_edit': 'm',
271 'toggle_map_mode': 'M',
272 'hex_move_upleft': 'w',
273 'hex_move_upright': 'e',
274 'hex_move_right': 'd',
275 'hex_move_downright': 'x',
276 'hex_move_downleft': 'y',
277 'hex_move_left': 'a',
278 'square_move_up': 'w',
279 'square_move_left': 'a',
280 'square_move_down': 's',
281 'square_move_right': 'd',
283 if os.path.isfile('config.json'):
284 with open('config.json', 'r') as f:
285 keys_conf = json.loads(f.read())
287 self.keys[k] = keys_conf[k]
288 self.show_help = False
289 self.disconnected = True
290 self.force_instant_connect = True
291 self.input_lines = []
293 curses.wrapper(self.loop)
300 def handle_recv(msg):
306 self.log_msg('@ attempting connect')
307 socket_client_class = PlomSocketClient
308 if self.host.startswith('ws://') or self.host.startswith('wss://'):
309 socket_client_class = WebSocketClient
311 self.socket = socket_client_class(handle_recv, self.host)
312 self.socket_thread = threading.Thread(target=self.socket.run)
313 self.socket_thread.start()
314 self.disconnected = False
315 self.game.thing_types = {}
316 self.game.terrains = {}
317 self.socket.send('TASKS')
318 self.socket.send('TERRAINS')
319 self.socket.send('THING_TYPES')
320 self.switch_mode('login')
321 except ConnectionRefusedError:
322 self.log_msg('@ server connect failure')
323 self.disconnected = True
324 self.switch_mode('waiting_for_server')
325 self.do_refresh = True
328 self.log_msg('@ attempting reconnect')
330 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
331 # conditions with ws4py, find out what exactly
332 self.switch_mode('waiting_for_server')
337 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
338 raise BrokenSocketConnection
339 self.socket.send(msg)
340 except (BrokenPipeError, BrokenSocketConnection):
341 self.log_msg('@ server disconnected :(')
342 self.disconnected = True
343 self.force_instant_connect = True
344 self.do_refresh = True
346 def log_msg(self, msg):
348 if len(self.log) > 100:
349 self.log = self.log[-100:]
351 def query_info(self):
352 self.send('GET_ANNOTATION ' + str(self.explorer))
354 def restore_input_values(self):
355 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
356 info = self.game.info_db[self.explorer]
359 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
360 self.input_ = self.game.portals[self.explorer]
361 elif self.mode.name == 'password':
362 self.input_ = self.password
364 def switch_mode(self, mode_name):
365 self.map_mode = 'terrain'
366 self.mode = getattr(self, 'mode_' + mode_name)
367 if self.mode.shows_info:
368 player = self.game.get_thing(self.game.player_id)
369 self.explorer = YX(player.position.y, player.position.x)
370 if self.mode.name == 'waiting_for_server':
371 self.log_msg('@ waiting for server …')
372 if self.mode.name == 'edit':
373 self.show_help = True
374 elif self.mode.name == 'login':
376 self.send('LOGIN ' + quote(self.login_name))
378 self.log_msg('@ enter username')
379 self.restore_input_values()
381 def loop(self, stdscr):
384 def safe_addstr(y, x, line):
385 if y < self.size.y - 1 or x + len(line) < self.size.x:
386 stdscr.addstr(y, x, line)
387 else: # workaround to <https://stackoverflow.com/q/7063128>
388 cut_i = self.size.x - x - 1
390 last_char = line[cut_i]
391 stdscr.addstr(y, self.size.x - 2, last_char)
392 stdscr.insstr(y, self.size.x - 2, ' ')
393 stdscr.addstr(y, x, cut)
395 def handle_input(msg):
396 command, args = self.parser.parse(msg)
399 def msg_into_lines_of_width(msg, width):
403 for i in range(len(msg)):
404 if x >= width or msg[i] == "\n":
414 def reset_screen_size():
415 self.size = YX(*stdscr.getmaxyx())
416 self.size = self.size - YX(self.size.y % 4, 0)
417 self.size = self.size - YX(0, self.size.x % 4)
418 self.window_width = int(self.size.x / 2)
420 def recalc_input_lines():
421 if not self.mode.has_input_prompt:
422 self.input_lines = []
424 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
427 def move_explorer(direction):
428 target = self.game.map_geometry.move(self.explorer, direction)
430 self.explorer = target
437 for line in self.log:
438 lines += msg_into_lines_of_width(line, self.window_width)
441 max_y = self.size.y - len(self.input_lines)
442 for i in range(len(lines)):
443 if (i >= max_y - height_header):
445 safe_addstr(max_y - i - 1, self.window_width, lines[i])
448 if not self.game.turn_complete:
450 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
451 info = 'outside field of view'
452 if self.game.fov[pos_i] == '.':
453 terrain_char = self.game.map_content[pos_i]
455 if terrain_char in self.game.terrains:
456 terrain_desc = self.game.terrains[terrain_char]
457 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
458 for t in self.game.things:
459 if t.position == self.explorer:
460 info += 'THING: %s / %s' % (t.type_,
461 self.game.thing_types[t.type_])
462 if hasattr(t, 'player_char'):
463 info += t.player_char
464 if hasattr(t, 'name'):
465 info += ' (%s)' % t.name
467 if self.explorer in self.game.portals:
468 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
470 info += 'PORTAL: (none)\n'
471 if self.explorer in self.game.info_db:
472 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
474 info += 'ANNOTATION: waiting …'
475 lines = msg_into_lines_of_width(info, self.window_width)
477 for i in range(len(lines)):
478 y = height_header + i
479 if y >= self.size.y - len(self.input_lines):
481 safe_addstr(y, self.window_width, lines[i])
484 y = self.size.y - len(self.input_lines)
485 for i in range(len(self.input_lines)):
486 safe_addstr(y, self.window_width, self.input_lines[i])
490 if not self.game.turn_complete:
492 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
495 help = "hit [%s] for help" % self.keys['help']
496 if self.mode.has_input_prompt:
497 help = "enter /help for help"
498 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
501 if not self.game.turn_complete:
504 map_content = self.game.map_content
505 if self.map_mode == 'control':
506 map_content = self.game.map_control_content
507 for y in range(self.game.map_geometry.size.y):
508 start = self.game.map_geometry.size.x * y
509 end = start + self.game.map_geometry.size.x
510 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
511 if self.map_mode == 'terrain':
512 for p in self.game.portals.keys():
513 map_lines_split[p.y][p.x] = 'P '
515 for t in self.game.things:
516 symbol = self.game.thing_types[t.type_]
518 if hasattr(t, 'player_char'):
519 meta_char = t.player_char
520 if t.position in used_positions:
522 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
523 used_positions += [t.position]
524 if self.mode.shows_info:
525 map_lines_split[self.explorer.y][self.explorer.x] = '??'
527 if type(self.game.map_geometry) == MapGeometryHex:
529 for line in map_lines_split:
530 map_lines += [indent*' ' + ''.join(line)]
531 indent = 0 if indent else 1
533 for line in map_lines_split:
534 map_lines += [''.join(line)]
535 window_center = YX(int(self.size.y / 2),
536 int(self.window_width / 2))
537 player = self.game.get_thing(self.game.player_id)
538 center = player.position
539 if self.mode.shows_info:
540 center = self.explorer
541 center = YX(center.y, center.x * 2)
542 offset = center - window_center
543 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
545 term_y = max(0, -offset.y)
546 term_x = max(0, -offset.x)
547 map_y = max(0, offset.y)
548 map_x = max(0, offset.x)
549 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
550 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
551 safe_addstr(term_y, term_x, to_draw)
556 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
557 self.mode.help_intro)
558 if self.mode == self.mode_play:
559 content += "Available actions:\n"
560 if 'MOVE' in self.game.tasks:
561 content += "[%s] – move player\n" % ','.join(self.movement_keys)
562 if 'PICK_UP' in self.game.tasks:
563 content += "[%s] – take thing under player\n" % self.keys['take_thing']
564 if 'DROP' in self.game.tasks:
565 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
566 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
567 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
568 content += '[%s] – teleport to other space\n' % self.keys['teleport']
569 content += 'Other modes available from here:\n'
570 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
571 content += '[%s] – study mode\n' % self.keys['switch_to_study']
572 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
573 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
574 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
575 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
576 elif self.mode == self.mode_study:
577 content += 'Available actions:\n'
578 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
579 content += '[%s] – toggle view between terrain, and password protection areas\n' % self.keys['toggle_map_mode']
580 content += '\n\nOther modes available from here:'
581 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
582 content += '[%s] – play mode\n' % self.keys['switch_to_play']
583 elif self.mode == self.mode_chat:
584 content += '/nick NAME – re-name yourself to NAME\n'
585 #content += '/msg USER TEXT – send TEXT to USER\n'
586 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
587 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
588 for i in range(self.size.y):
590 self.window_width * (not self.mode.has_input_prompt),
591 ' '*self.window_width)
593 for line in content.split('\n'):
594 lines += msg_into_lines_of_width(line, self.window_width)
595 for i in range(len(lines)):
599 self.window_width * (not self.mode.has_input_prompt),
604 if self.mode.has_input_prompt:
607 if self.mode.shows_info:
612 if not self.mode.is_intro:
618 curses.curs_set(False) # hide cursor
619 curses.use_default_colors();
622 self.explorer = YX(0, 0)
625 interval = datetime.timedelta(seconds=5)
626 last_ping = datetime.datetime.now() - interval
628 if self.disconnected and self.force_instant_connect:
629 self.force_instant_connect = False
631 now = datetime.datetime.now()
632 if now - last_ping > interval:
633 if self.disconnected:
640 self.do_refresh = False
643 msg = self.queue.get(block=False)
648 key = stdscr.getkey()
649 self.do_refresh = True
652 self.show_help = False
653 if key == 'KEY_RESIZE':
655 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
656 self.input_ = self.input_[:-1]
657 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
658 self.show_help = True
660 self.restore_input_values()
661 elif self.mode.has_input_prompt and key != '\n': # Return key
663 max_length = self.window_width * self.size.y - len(input_prompt) - 1
664 if len(self.input_) > max_length:
665 self.input_ = self.input_[:max_length]
666 elif key == self.keys['help'] and self.mode != self.mode_edit:
667 self.show_help = True
668 elif self.mode == self.mode_login and key == '\n':
669 self.login_name = self.input_
670 self.send('LOGIN ' + quote(self.input_))
672 elif self.mode == self.mode_password and key == '\n':
673 if self.input_ == '':
675 self.password = self.input_
677 self.switch_mode('play')
678 elif self.mode == self.mode_chat and key == '\n':
679 if self.input_ == '':
681 if self.input_[0] == '/': # FIXME fails on empty input
682 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
683 self.switch_mode('play')
684 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
685 self.switch_mode('study')
686 elif self.input_.startswith('/nick'):
687 tokens = self.input_.split(maxsplit=1)
689 self.send('NICK ' + quote(tokens[1]))
691 self.log_msg('? need login name')
692 #elif self.input_.startswith('/msg'):
693 # tokens = self.input_.split(maxsplit=2)
694 # if len(tokens) == 3:
695 # self.send('QUERY %s %s' % (quote(tokens[1]),
698 # self.log_msg('? need message target and message')
700 self.log_msg('? unknown command')
702 self.send('ALL ' + quote(self.input_))
704 elif self.mode == self.mode_annotate and key == '\n':
705 if self.input_ == '':
707 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
708 quote(self.password)))
710 self.switch_mode('play')
711 elif self.mode == self.mode_portal and key == '\n':
712 if self.input_ == '':
714 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
715 quote(self.password)))
717 self.switch_mode('play')
718 elif self.mode == self.mode_study:
719 if key == self.keys['switch_to_chat']:
720 self.switch_mode('chat')
721 elif key == self.keys['switch_to_play']:
722 self.switch_mode('play')
723 elif key == self.keys['toggle_map_mode']:
724 if self.map_mode == 'terrain':
725 self.map_mode = 'control'
727 self.map_mode = 'terrain'
728 elif key in self.movement_keys:
729 move_explorer(self.movement_keys[key])
730 elif self.mode == self.mode_play:
731 if key == self.keys['switch_to_chat']:
732 self.switch_mode('chat')
733 elif key == self.keys['switch_to_study']:
734 self.switch_mode('study')
735 elif key == self.keys['switch_to_annotate']:
736 self.switch_mode('annotate')
737 elif key == self.keys['switch_to_portal']:
738 self.switch_mode('portal')
739 elif key == self.keys['switch_to_password']:
740 self.switch_mode('password')
741 if key == self.keys['switch_to_edit'] and\
742 'WRITE' in self.game.tasks:
743 self.switch_mode('edit')
744 elif key == self.keys['flatten'] and\
745 'FLATTEN_SURROUNDINGS' in self.game.tasks:
746 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
747 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
748 self.send('TASK:PICK_UP')
749 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
750 self.send('TASK:DROP')
751 elif key == self.keys['teleport']:
752 player = self.game.get_thing(self.game.player_id)
753 if player.position in self.game.portals:
754 self.host = self.game.portals[player.position]
758 self.log_msg('? not standing on portal')
759 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
760 self.send('TASK:MOVE ' + self.movement_keys[key])
761 elif self.mode == self.mode_edit:
762 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
763 self.switch_mode('play')
765 #TUI('localhost:5000')
766 TUI('wss://plomlompom.com/rogue_chat/')