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):
141 game.tui.log_msg('? ' + msg)
142 game.tui.flash = True
143 game.tui.do_refresh = True
144 cmd_PLAY_ERROR.argtypes = 'string'
146 def cmd_GAME_ERROR(game, msg):
147 game.tui.log_msg('? game error: ' + msg)
148 game.tui.do_refresh = True
149 cmd_GAME_ERROR.argtypes = 'string'
151 def cmd_ARGUMENT_ERROR(game, msg):
152 game.tui.log_msg('? syntax error: ' + msg)
153 game.tui.do_refresh = True
154 cmd_ARGUMENT_ERROR.argtypes = 'string'
156 def cmd_ANNOTATION(game, position, msg):
157 game.info_db[position] = msg
158 if game.tui.mode.shows_info:
159 game.tui.do_refresh = True
160 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
162 def cmd_TASKS(game, tasks_comma_separated):
163 game.tasks = tasks_comma_separated.split(',')
164 cmd_TASKS.argtypes = 'string'
166 def cmd_THING_TYPE(game, thing_type, symbol_hint):
167 game.thing_types[thing_type] = symbol_hint
168 cmd_THING_TYPE.argtypes = 'string char'
170 def cmd_TERRAIN(game, terrain_char, terrain_desc):
171 game.terrains[terrain_char] = terrain_desc
172 cmd_TERRAIN.argtypes = 'char string'
176 cmd_PONG.argtypes = ''
178 class Game(GameBase):
179 turn_complete = False
183 def __init__(self, *args, **kwargs):
184 super().__init__(*args, **kwargs)
185 self.register_command(cmd_LOGIN_OK)
186 self.register_command(cmd_PONG)
187 self.register_command(cmd_CHAT)
188 self.register_command(cmd_PLAYER_ID)
189 self.register_command(cmd_TURN)
190 self.register_command(cmd_THING)
191 self.register_command(cmd_THING_TYPE)
192 self.register_command(cmd_THING_NAME)
193 self.register_command(cmd_THING_CHAR)
194 self.register_command(cmd_TERRAIN)
195 self.register_command(cmd_MAP)
196 self.register_command(cmd_MAP_CONTROL)
197 self.register_command(cmd_PORTAL)
198 self.register_command(cmd_ANNOTATION)
199 self.register_command(cmd_GAME_STATE_COMPLETE)
200 self.register_command(cmd_ARGUMENT_ERROR)
201 self.register_command(cmd_GAME_ERROR)
202 self.register_command(cmd_PLAY_ERROR)
203 self.register_command(cmd_TASKS)
204 self.register_command(cmd_FOV)
205 self.map_content = ''
211 def get_string_options(self, string_option_type):
212 if string_option_type == 'map_geometry':
213 return ['Hex', 'Square']
214 elif string_option_type == 'thing_type':
215 return self.thing_types.keys()
218 def get_command(self, command_name):
219 from functools import partial
220 f = partial(self.commands[command_name], self)
221 f.argtypes = self.commands[command_name].argtypes
228 def __init__(self, name, help_intro, has_input_prompt=False,
229 shows_info=False, is_intro = False):
231 self.has_input_prompt = has_input_prompt
232 self.shows_info = shows_info
233 self.is_intro = is_intro
234 self.help_intro = help_intro
236 def __init__(self, host):
240 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
241 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)
242 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.')
243 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)
244 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)
245 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)
246 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
247 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
248 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
249 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)
252 self.parser = Parser(self.game)
254 self.do_refresh = True
255 self.queue = queue.Queue()
256 self.login_name = None
257 self.map_mode = 'terrain'
258 self.password = 'foo'
259 self.switch_mode('waiting_for_server')
261 'switch_to_chat': 't',
262 'switch_to_play': 'p',
263 'switch_to_password': 'P',
264 'switch_to_annotate': 'M',
265 'switch_to_portal': 'T',
266 'switch_to_study': '?',
267 'switch_to_edit': 'm',
272 'toggle_map_mode': 'M',
273 'hex_move_upleft': 'w',
274 'hex_move_upright': 'e',
275 'hex_move_right': 'd',
276 'hex_move_downright': 'x',
277 'hex_move_downleft': 'y',
278 'hex_move_left': 'a',
279 'square_move_up': 'w',
280 'square_move_left': 'a',
281 'square_move_down': 's',
282 'square_move_right': 'd',
284 if os.path.isfile('config.json'):
285 with open('config.json', 'r') as f:
286 keys_conf = json.loads(f.read())
288 self.keys[k] = keys_conf[k]
289 self.show_help = False
290 self.disconnected = True
291 self.force_instant_connect = True
292 self.input_lines = []
295 curses.wrapper(self.loop)
299 def handle_recv(msg):
305 self.log_msg('@ attempting connect')
306 socket_client_class = PlomSocketClient
307 if self.host.startswith('ws://') or self.host.startswith('wss://'):
308 socket_client_class = WebSocketClient
310 self.socket = socket_client_class(handle_recv, self.host)
311 self.socket_thread = threading.Thread(target=self.socket.run)
312 self.socket_thread.start()
313 self.disconnected = False
314 self.game.thing_types = {}
315 self.game.terrains = {}
316 self.socket.send('TASKS')
317 self.socket.send('TERRAINS')
318 self.socket.send('THING_TYPES')
319 self.switch_mode('login')
320 except ConnectionRefusedError:
321 self.log_msg('@ server connect failure')
322 self.disconnected = True
323 self.switch_mode('waiting_for_server')
324 self.do_refresh = True
327 self.log_msg('@ attempting reconnect')
329 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
330 # conditions with ws4py, find out what exactly
331 self.switch_mode('waiting_for_server')
336 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
337 raise BrokenSocketConnection
338 self.socket.send(msg)
339 except (BrokenPipeError, BrokenSocketConnection):
340 self.log_msg('@ server disconnected :(')
341 self.disconnected = True
342 self.force_instant_connect = True
343 self.do_refresh = True
345 def log_msg(self, msg):
347 if len(self.log) > 100:
348 self.log = self.log[-100:]
350 def query_info(self):
351 self.send('GET_ANNOTATION ' + str(self.explorer))
353 def restore_input_values(self):
354 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
355 info = self.game.info_db[self.explorer]
358 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
359 self.input_ = self.game.portals[self.explorer]
360 elif self.mode.name == 'password':
361 self.input_ = self.password
363 def switch_mode(self, mode_name):
364 self.map_mode = 'terrain'
365 self.mode = getattr(self, 'mode_' + mode_name)
366 if self.mode.shows_info:
367 player = self.game.get_thing(self.game.player_id)
368 self.explorer = YX(player.position.y, player.position.x)
369 if self.mode.name == 'waiting_for_server':
370 self.log_msg('@ waiting for server …')
371 if self.mode.name == 'edit':
372 self.show_help = True
373 elif self.mode.name == 'login':
375 self.send('LOGIN ' + quote(self.login_name))
377 self.log_msg('@ enter username')
378 self.restore_input_values()
380 def loop(self, stdscr):
383 def safe_addstr(y, x, line):
384 if y < self.size.y - 1 or x + len(line) < self.size.x:
385 stdscr.addstr(y, x, line)
386 else: # workaround to <https://stackoverflow.com/q/7063128>
387 cut_i = self.size.x - x - 1
389 last_char = line[cut_i]
390 stdscr.addstr(y, self.size.x - 2, last_char)
391 stdscr.insstr(y, self.size.x - 2, ' ')
392 stdscr.addstr(y, x, cut)
394 def handle_input(msg):
395 command, args = self.parser.parse(msg)
398 def msg_into_lines_of_width(msg, width):
402 for i in range(len(msg)):
403 if x >= width or msg[i] == "\n":
413 def reset_screen_size():
414 self.size = YX(*stdscr.getmaxyx())
415 self.size = self.size - YX(self.size.y % 4, 0)
416 self.size = self.size - YX(0, self.size.x % 4)
417 self.window_width = int(self.size.x / 2)
419 def recalc_input_lines():
420 if not self.mode.has_input_prompt:
421 self.input_lines = []
423 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
426 def move_explorer(direction):
427 target = self.game.map_geometry.move(self.explorer, direction)
429 self.explorer = target
436 for line in self.log:
437 lines += msg_into_lines_of_width(line, self.window_width)
440 max_y = self.size.y - len(self.input_lines)
441 for i in range(len(lines)):
442 if (i >= max_y - height_header):
444 safe_addstr(max_y - i - 1, self.window_width, lines[i])
447 if not self.game.turn_complete:
449 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
450 info = 'outside field of view'
451 if self.game.fov[pos_i] == '.':
452 terrain_char = self.game.map_content[pos_i]
454 if terrain_char in self.game.terrains:
455 terrain_desc = self.game.terrains[terrain_char]
456 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
457 for t in self.game.things:
458 if t.position == self.explorer:
459 info += 'THING: %s / %s' % (t.type_,
460 self.game.thing_types[t.type_])
461 if hasattr(t, 'player_char'):
462 info += t.player_char
463 if hasattr(t, 'name'):
464 info += ' (%s)' % t.name
466 if self.explorer in self.game.portals:
467 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
469 info += 'PORTAL: (none)\n'
470 if self.explorer in self.game.info_db:
471 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
473 info += 'ANNOTATION: waiting …'
474 lines = msg_into_lines_of_width(info, self.window_width)
476 for i in range(len(lines)):
477 y = height_header + i
478 if y >= self.size.y - len(self.input_lines):
480 safe_addstr(y, self.window_width, lines[i])
483 y = self.size.y - len(self.input_lines)
484 for i in range(len(self.input_lines)):
485 safe_addstr(y, self.window_width, self.input_lines[i])
489 if not self.game.turn_complete:
491 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
494 help = "hit [%s] for help" % self.keys['help']
495 if self.mode.has_input_prompt:
496 help = "enter /help for help"
497 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
500 if not self.game.turn_complete:
503 map_content = self.game.map_content
504 if self.map_mode == 'control':
505 map_content = self.game.map_control_content
506 for y in range(self.game.map_geometry.size.y):
507 start = self.game.map_geometry.size.x * y
508 end = start + self.game.map_geometry.size.x
509 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
510 if self.map_mode == 'terrain':
511 for p in self.game.portals.keys():
512 map_lines_split[p.y][p.x] = 'P '
514 for t in self.game.things:
515 symbol = self.game.thing_types[t.type_]
517 if hasattr(t, 'player_char'):
518 meta_char = t.player_char
519 if t.position in used_positions:
521 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
522 used_positions += [t.position]
523 if self.mode.shows_info:
524 map_lines_split[self.explorer.y][self.explorer.x] = '??'
526 if type(self.game.map_geometry) == MapGeometryHex:
528 for line in map_lines_split:
529 map_lines += [indent*' ' + ''.join(line)]
530 indent = 0 if indent else 1
532 for line in map_lines_split:
533 map_lines += [''.join(line)]
534 window_center = YX(int(self.size.y / 2),
535 int(self.window_width / 2))
536 player = self.game.get_thing(self.game.player_id)
537 center = player.position
538 if self.mode.shows_info:
539 center = self.explorer
540 center = YX(center.y, center.x * 2)
541 offset = center - window_center
542 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
544 term_y = max(0, -offset.y)
545 term_x = max(0, -offset.x)
546 map_y = max(0, offset.y)
547 map_x = max(0, offset.x)
548 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
549 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
550 safe_addstr(term_y, term_x, to_draw)
555 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
556 self.mode.help_intro)
557 if self.mode == self.mode_play:
558 content += "Available actions:\n"
559 if 'MOVE' in self.game.tasks:
560 content += "[%s] – move player\n" % ','.join(self.movement_keys)
561 if 'PICK_UP' in self.game.tasks:
562 content += "[%s] – take thing under player\n" % self.keys['take_thing']
563 if 'DROP' in self.game.tasks:
564 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
565 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
566 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
567 content += '[%s] – teleport to other space\n' % self.keys['teleport']
568 content += 'Other modes available from here:\n'
569 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
570 content += '[%s] – study mode\n' % self.keys['switch_to_study']
571 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
572 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
573 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
574 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
575 elif self.mode == self.mode_study:
576 content += 'Available actions:\n'
577 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
578 content += '[%s] – toggle view between terrain, and password protection areas\n' % self.keys['toggle_map_mode']
579 content += '\n\nOther modes available from here:'
580 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
581 content += '[%s] – play mode\n' % self.keys['switch_to_play']
582 elif self.mode == self.mode_chat:
583 content += '/nick NAME – re-name yourself to NAME\n'
584 #content += '/msg USER TEXT – send TEXT to USER\n'
585 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
586 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
587 for i in range(self.size.y):
589 self.window_width * (not self.mode.has_input_prompt),
590 ' '*self.window_width)
592 for line in content.split('\n'):
593 lines += msg_into_lines_of_width(line, self.window_width)
594 for i in range(len(lines)):
598 self.window_width * (not self.mode.has_input_prompt),
603 if self.mode.has_input_prompt:
606 if self.mode.shows_info:
611 if not self.mode.is_intro:
617 curses.curs_set(False) # hide cursor
618 curses.use_default_colors();
621 self.explorer = YX(0, 0)
624 interval = datetime.timedelta(seconds=5)
625 last_ping = datetime.datetime.now() - interval
627 if self.disconnected and self.force_instant_connect:
628 self.force_instant_connect = False
630 now = datetime.datetime.now()
631 if now - last_ping > interval:
632 if self.disconnected:
642 self.do_refresh = False
645 msg = self.queue.get(block=False)
650 key = stdscr.getkey()
651 self.do_refresh = True
654 self.show_help = False
655 if key == 'KEY_RESIZE':
657 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
658 self.input_ = self.input_[:-1]
659 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
660 self.show_help = True
662 self.restore_input_values()
663 elif self.mode.has_input_prompt and key != '\n': # Return key
665 max_length = self.window_width * self.size.y - len(input_prompt) - 1
666 if len(self.input_) > max_length:
667 self.input_ = self.input_[:max_length]
668 elif key == self.keys['help'] and self.mode != self.mode_edit:
669 self.show_help = True
670 elif self.mode == self.mode_login and key == '\n':
671 self.login_name = self.input_
672 self.send('LOGIN ' + quote(self.input_))
674 elif self.mode == self.mode_password and key == '\n':
675 if self.input_ == '':
677 self.password = self.input_
679 self.switch_mode('play')
680 elif self.mode == self.mode_chat and key == '\n':
681 if self.input_ == '':
683 if self.input_[0] == '/': # FIXME fails on empty input
684 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
685 self.switch_mode('play')
686 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
687 self.switch_mode('study')
688 elif self.input_.startswith('/nick'):
689 tokens = self.input_.split(maxsplit=1)
691 self.send('NICK ' + quote(tokens[1]))
693 self.log_msg('? need login name')
695 self.log_msg('? unknown command')
697 self.send('ALL ' + quote(self.input_))
699 elif self.mode == self.mode_annotate and key == '\n':
700 if self.input_ == '':
702 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
703 quote(self.password)))
705 self.switch_mode('play')
706 elif self.mode == self.mode_portal and key == '\n':
707 if self.input_ == '':
709 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
710 quote(self.password)))
712 self.switch_mode('play')
713 elif self.mode == self.mode_study:
714 if key == self.keys['switch_to_chat']:
715 self.switch_mode('chat')
716 elif key == self.keys['switch_to_play']:
717 self.switch_mode('play')
718 elif key == self.keys['toggle_map_mode']:
719 if self.map_mode == 'terrain':
720 self.map_mode = 'control'
722 self.map_mode = 'terrain'
723 elif key in self.movement_keys:
724 move_explorer(self.movement_keys[key])
725 elif self.mode == self.mode_play:
726 if key == self.keys['switch_to_chat']:
727 self.switch_mode('chat')
728 elif key == self.keys['switch_to_study']:
729 self.switch_mode('study')
730 elif key == self.keys['switch_to_annotate']:
731 self.switch_mode('annotate')
732 elif key == self.keys['switch_to_portal']:
733 self.switch_mode('portal')
734 elif key == self.keys['switch_to_password']:
735 self.switch_mode('password')
736 if key == self.keys['switch_to_edit'] and\
737 'WRITE' in self.game.tasks:
738 self.switch_mode('edit')
739 elif key == self.keys['flatten'] and\
740 'FLATTEN_SURROUNDINGS' in self.game.tasks:
741 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
742 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
743 self.send('TASK:PICK_UP')
744 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
745 self.send('TASK:DROP')
746 elif key == self.keys['teleport']:
747 player = self.game.get_thing(self.game.player_id)
748 if player.position in self.game.portals:
749 self.host = self.game.portals[player.position]
753 self.log_msg('? not standing on portal')
754 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
755 self.send('TASK:MOVE ' + self.movement_keys[key])
756 elif self.mode == self.mode_edit:
757 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
758 self.switch_mode('play')
760 #TUI('localhost:5000')
761 TUI('wss://plomlompom.com/rogue_chat/')