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)
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 = []
294 curses.wrapper(self.loop)
301 def handle_recv(msg):
307 self.log_msg('@ attempting connect')
308 socket_client_class = PlomSocketClient
309 if self.host.startswith('ws://') or self.host.startswith('wss://'):
310 socket_client_class = WebSocketClient
312 self.socket = socket_client_class(handle_recv, self.host)
313 self.socket_thread = threading.Thread(target=self.socket.run)
314 self.socket_thread.start()
315 self.disconnected = False
316 self.game.thing_types = {}
317 self.game.terrains = {}
318 self.socket.send('TASKS')
319 self.socket.send('TERRAINS')
320 self.socket.send('THING_TYPES')
321 self.switch_mode('login')
322 except ConnectionRefusedError:
323 self.log_msg('@ server connect failure')
324 self.disconnected = True
325 self.switch_mode('waiting_for_server')
326 self.do_refresh = True
329 self.log_msg('@ attempting reconnect')
331 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
332 # conditions with ws4py, find out what exactly
333 self.switch_mode('waiting_for_server')
338 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
339 raise BrokenSocketConnection
340 self.socket.send(msg)
341 except (BrokenPipeError, BrokenSocketConnection):
342 self.log_msg('@ server disconnected :(')
343 self.disconnected = True
344 self.force_instant_connect = True
345 self.do_refresh = True
347 def log_msg(self, msg):
349 if len(self.log) > 100:
350 self.log = self.log[-100:]
352 def query_info(self):
353 self.send('GET_ANNOTATION ' + str(self.explorer))
355 def restore_input_values(self):
356 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
357 info = self.game.info_db[self.explorer]
360 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
361 self.input_ = self.game.portals[self.explorer]
362 elif self.mode.name == 'password':
363 self.input_ = self.password
365 def switch_mode(self, mode_name):
366 self.map_mode = 'terrain'
367 self.mode = getattr(self, 'mode_' + mode_name)
368 if self.mode.shows_info:
369 player = self.game.get_thing(self.game.player_id)
370 self.explorer = YX(player.position.y, player.position.x)
371 if self.mode.name == 'waiting_for_server':
372 self.log_msg('@ waiting for server …')
373 if self.mode.name == 'edit':
374 self.show_help = True
375 elif self.mode.name == 'login':
377 self.send('LOGIN ' + quote(self.login_name))
379 self.log_msg('@ enter username')
380 self.restore_input_values()
382 def loop(self, stdscr):
385 def safe_addstr(y, x, line):
386 if y < self.size.y - 1 or x + len(line) < self.size.x:
387 stdscr.addstr(y, x, line)
388 else: # workaround to <https://stackoverflow.com/q/7063128>
389 cut_i = self.size.x - x - 1
391 last_char = line[cut_i]
392 stdscr.addstr(y, self.size.x - 2, last_char)
393 stdscr.insstr(y, self.size.x - 2, ' ')
394 stdscr.addstr(y, x, cut)
396 def handle_input(msg):
397 command, args = self.parser.parse(msg)
400 def msg_into_lines_of_width(msg, width):
404 for i in range(len(msg)):
405 if x >= width or msg[i] == "\n":
415 def reset_screen_size():
416 self.size = YX(*stdscr.getmaxyx())
417 self.size = self.size - YX(self.size.y % 4, 0)
418 self.size = self.size - YX(0, self.size.x % 4)
419 self.window_width = int(self.size.x / 2)
421 def recalc_input_lines():
422 if not self.mode.has_input_prompt:
423 self.input_lines = []
425 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
428 def move_explorer(direction):
429 target = self.game.map_geometry.move(self.explorer, direction)
431 self.explorer = target
438 for line in self.log:
439 lines += msg_into_lines_of_width(line, self.window_width)
442 max_y = self.size.y - len(self.input_lines)
443 for i in range(len(lines)):
444 if (i >= max_y - height_header):
446 safe_addstr(max_y - i - 1, self.window_width, lines[i])
449 if not self.game.turn_complete:
451 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
452 info = 'outside field of view'
453 if self.game.fov[pos_i] == '.':
454 terrain_char = self.game.map_content[pos_i]
456 if terrain_char in self.game.terrains:
457 terrain_desc = self.game.terrains[terrain_char]
458 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
459 for t in self.game.things:
460 if t.position == self.explorer:
461 info += 'THING: %s / %s' % (t.type_,
462 self.game.thing_types[t.type_])
463 if hasattr(t, 'player_char'):
464 info += t.player_char
465 if hasattr(t, 'name'):
466 info += ' (%s)' % t.name
468 if self.explorer in self.game.portals:
469 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
471 info += 'PORTAL: (none)\n'
472 if self.explorer in self.game.info_db:
473 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
475 info += 'ANNOTATION: waiting …'
476 lines = msg_into_lines_of_width(info, self.window_width)
478 for i in range(len(lines)):
479 y = height_header + i
480 if y >= self.size.y - len(self.input_lines):
482 safe_addstr(y, self.window_width, lines[i])
485 y = self.size.y - len(self.input_lines)
486 for i in range(len(self.input_lines)):
487 safe_addstr(y, self.window_width, self.input_lines[i])
491 if not self.game.turn_complete:
493 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
496 help = "hit [%s] for help" % self.keys['help']
497 if self.mode.has_input_prompt:
498 help = "enter /help for help"
499 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
502 if not self.game.turn_complete:
505 map_content = self.game.map_content
506 if self.map_mode == 'control':
507 map_content = self.game.map_control_content
508 for y in range(self.game.map_geometry.size.y):
509 start = self.game.map_geometry.size.x * y
510 end = start + self.game.map_geometry.size.x
511 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
512 if self.map_mode == 'terrain':
513 for p in self.game.portals.keys():
514 map_lines_split[p.y][p.x] = 'P '
516 for t in self.game.things:
517 symbol = self.game.thing_types[t.type_]
519 if hasattr(t, 'player_char'):
520 meta_char = t.player_char
521 if t.position in used_positions:
523 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
524 used_positions += [t.position]
525 if self.mode.shows_info:
526 map_lines_split[self.explorer.y][self.explorer.x] = '??'
528 if type(self.game.map_geometry) == MapGeometryHex:
530 for line in map_lines_split:
531 map_lines += [indent*' ' + ''.join(line)]
532 indent = 0 if indent else 1
534 for line in map_lines_split:
535 map_lines += [''.join(line)]
536 window_center = YX(int(self.size.y / 2),
537 int(self.window_width / 2))
538 player = self.game.get_thing(self.game.player_id)
539 center = player.position
540 if self.mode.shows_info:
541 center = self.explorer
542 center = YX(center.y, center.x * 2)
543 offset = center - window_center
544 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
546 term_y = max(0, -offset.y)
547 term_x = max(0, -offset.x)
548 map_y = max(0, offset.y)
549 map_x = max(0, offset.x)
550 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
551 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
552 safe_addstr(term_y, term_x, to_draw)
557 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
558 self.mode.help_intro)
559 if self.mode == self.mode_play:
560 content += "Available actions:\n"
561 if 'MOVE' in self.game.tasks:
562 content += "[%s] – move player\n" % ','.join(self.movement_keys)
563 if 'PICK_UP' in self.game.tasks:
564 content += "[%s] – take thing under player\n" % self.keys['take_thing']
565 if 'DROP' in self.game.tasks:
566 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
567 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
568 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
569 content += '[%s] – teleport to other space\n' % self.keys['teleport']
570 content += 'Other modes available from here:\n'
571 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
572 content += '[%s] – study mode\n' % self.keys['switch_to_study']
573 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
574 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
575 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
576 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
577 elif self.mode == self.mode_study:
578 content += 'Available actions:\n'
579 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
580 content += '[%s] – toggle view between terrain, and password protection areas\n' % self.keys['toggle_map_mode']
581 content += '\n\nOther modes available from here:'
582 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
583 content += '[%s] – play mode\n' % self.keys['switch_to_play']
584 elif self.mode == self.mode_chat:
585 content += '/nick NAME – re-name yourself to NAME\n'
586 #content += '/msg USER TEXT – send TEXT to USER\n'
587 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
588 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
589 for i in range(self.size.y):
591 self.window_width * (not self.mode.has_input_prompt),
592 ' '*self.window_width)
594 for line in content.split('\n'):
595 lines += msg_into_lines_of_width(line, self.window_width)
596 for i in range(len(lines)):
600 self.window_width * (not self.mode.has_input_prompt),
605 if self.mode.has_input_prompt:
608 if self.mode.shows_info:
613 if not self.mode.is_intro:
619 curses.curs_set(False) # hide cursor
620 curses.use_default_colors();
623 self.explorer = YX(0, 0)
626 interval = datetime.timedelta(seconds=5)
627 last_ping = datetime.datetime.now() - interval
629 if self.disconnected and self.force_instant_connect:
630 self.force_instant_connect = False
632 now = datetime.datetime.now()
633 if now - last_ping > interval:
634 if self.disconnected:
641 self.do_refresh = False
644 msg = self.queue.get(block=False)
649 key = stdscr.getkey()
650 self.do_refresh = True
653 self.show_help = False
654 if key == 'KEY_RESIZE':
656 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
657 self.input_ = self.input_[:-1]
658 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
659 self.show_help = True
661 self.restore_input_values()
662 elif self.mode.has_input_prompt and key != '\n': # Return key
664 max_length = self.window_width * self.size.y - len(input_prompt) - 1
665 if len(self.input_) > max_length:
666 self.input_ = self.input_[:max_length]
667 elif key == self.keys['help'] and self.mode != self.mode_edit:
668 self.show_help = True
669 elif self.mode == self.mode_login and key == '\n':
670 self.login_name = self.input_
671 self.send('LOGIN ' + quote(self.input_))
673 elif self.mode == self.mode_password and key == '\n':
674 if self.input_ == '':
676 self.password = self.input_
678 self.switch_mode('play')
679 elif self.mode == self.mode_chat and key == '\n':
680 if self.input_ == '':
682 if self.input_[0] == '/': # FIXME fails on empty input
683 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
684 self.switch_mode('play')
685 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
686 self.switch_mode('study')
687 elif self.input_.startswith('/nick'):
688 tokens = self.input_.split(maxsplit=1)
690 self.send('NICK ' + quote(tokens[1]))
692 self.log_msg('? need login name')
694 self.log_msg('? unknown command')
696 self.send('ALL ' + quote(self.input_))
698 elif self.mode == self.mode_annotate and key == '\n':
699 if self.input_ == '':
701 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
702 quote(self.password)))
704 self.switch_mode('play')
705 elif self.mode == self.mode_portal and key == '\n':
706 if self.input_ == '':
708 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
709 quote(self.password)))
711 self.switch_mode('play')
712 elif self.mode == self.mode_study:
713 if key == self.keys['switch_to_chat']:
714 self.switch_mode('chat')
715 elif key == self.keys['switch_to_play']:
716 self.switch_mode('play')
717 elif key == self.keys['toggle_map_mode']:
718 if self.map_mode == 'terrain':
719 self.map_mode = 'control'
721 self.map_mode = 'terrain'
722 elif key in self.movement_keys:
723 move_explorer(self.movement_keys[key])
724 elif self.mode == self.mode_play:
725 if key == self.keys['switch_to_chat']:
726 self.switch_mode('chat')
727 elif key == self.keys['switch_to_study']:
728 self.switch_mode('study')
729 elif key == self.keys['switch_to_annotate']:
730 self.switch_mode('annotate')
731 elif key == self.keys['switch_to_portal']:
732 self.switch_mode('portal')
733 elif key == self.keys['switch_to_password']:
734 self.switch_mode('password')
735 if key == self.keys['switch_to_edit'] and\
736 'WRITE' in self.game.tasks:
737 self.switch_mode('edit')
738 elif key == self.keys['flatten'] and\
739 'FLATTEN_SURROUNDINGS' in self.game.tasks:
740 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
741 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
742 self.send('TASK:PICK_UP')
743 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
744 self.send('TASK:DROP')
745 elif key == self.keys['teleport']:
746 player = self.game.get_thing(self.game.player_id)
747 if player.position in self.game.portals:
748 self.host = self.game.portals[player.position]
752 self.log_msg('? not standing on portal')
753 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
754 self.send('TASK:MOVE ' + self.movement_keys[key])
755 elif self.mode == self.mode_edit:
756 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
757 self.switch_mode('play')
759 #TUI('localhost:5000')
760 TUI('wss://plomlompom.com/rogue_chat/')