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 game.tui.restore_input_values()
159 if game.tui.mode.shows_info:
160 game.tui.do_refresh = True
161 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
163 def cmd_TASKS(game, tasks_comma_separated):
164 game.tasks = tasks_comma_separated.split(',')
165 cmd_TASKS.argtypes = 'string'
167 def cmd_THING_TYPE(game, thing_type, symbol_hint):
168 game.thing_types[thing_type] = symbol_hint
169 cmd_THING_TYPE.argtypes = 'string char'
171 def cmd_TERRAIN(game, terrain_char, terrain_desc):
172 game.terrains[terrain_char] = terrain_desc
173 cmd_TERRAIN.argtypes = 'char string'
177 cmd_PONG.argtypes = ''
179 class Game(GameBase):
180 turn_complete = False
184 def __init__(self, *args, **kwargs):
185 super().__init__(*args, **kwargs)
186 self.register_command(cmd_LOGIN_OK)
187 self.register_command(cmd_PONG)
188 self.register_command(cmd_CHAT)
189 self.register_command(cmd_PLAYER_ID)
190 self.register_command(cmd_TURN)
191 self.register_command(cmd_THING)
192 self.register_command(cmd_THING_TYPE)
193 self.register_command(cmd_THING_NAME)
194 self.register_command(cmd_THING_CHAR)
195 self.register_command(cmd_TERRAIN)
196 self.register_command(cmd_MAP)
197 self.register_command(cmd_MAP_CONTROL)
198 self.register_command(cmd_PORTAL)
199 self.register_command(cmd_ANNOTATION)
200 self.register_command(cmd_GAME_STATE_COMPLETE)
201 self.register_command(cmd_ARGUMENT_ERROR)
202 self.register_command(cmd_GAME_ERROR)
203 self.register_command(cmd_PLAY_ERROR)
204 self.register_command(cmd_TASKS)
205 self.register_command(cmd_FOV)
206 self.map_content = ''
212 def get_string_options(self, string_option_type):
213 if string_option_type == 'map_geometry':
214 return ['Hex', 'Square']
215 elif string_option_type == 'thing_type':
216 return self.thing_types.keys()
219 def get_command(self, command_name):
220 from functools import partial
221 f = partial(self.commands[command_name], self)
222 f.argtypes = self.commands[command_name].argtypes
229 def __init__(self, name, help_intro, has_input_prompt=False,
230 shows_info=False, is_intro = False):
232 self.has_input_prompt = has_input_prompt
233 self.shows_info = shows_info
234 self.is_intro = is_intro
235 self.help_intro = help_intro
237 def __init__(self, host):
241 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
242 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)
243 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.')
244 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)
245 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)
246 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)
247 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
248 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
249 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
250 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)
253 self.parser = Parser(self.game)
255 self.do_refresh = True
256 self.queue = queue.Queue()
257 self.login_name = None
258 self.map_mode = 'terrain'
259 self.password = 'foo'
260 self.switch_mode('waiting_for_server')
262 'switch_to_chat': 't',
263 'switch_to_play': 'p',
264 'switch_to_password': 'P',
265 'switch_to_annotate': 'M',
266 'switch_to_portal': 'T',
267 'switch_to_study': '?',
268 'switch_to_edit': 'm',
273 'toggle_map_mode': 'M',
274 'hex_move_upleft': 'w',
275 'hex_move_upright': 'e',
276 'hex_move_right': 'd',
277 'hex_move_downright': 'x',
278 'hex_move_downleft': 'y',
279 'hex_move_left': 'a',
280 'square_move_up': 'w',
281 'square_move_left': 'a',
282 'square_move_down': 's',
283 'square_move_right': 'd',
285 if os.path.isfile('config.json'):
286 with open('config.json', 'r') as f:
287 keys_conf = json.loads(f.read())
289 self.keys[k] = keys_conf[k]
290 self.show_help = False
291 self.disconnected = True
292 self.force_instant_connect = True
293 self.input_lines = []
296 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)
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_yx(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:
644 self.do_refresh = False
647 msg = self.queue.get(block=False)
652 key = stdscr.getkey()
653 self.do_refresh = True
656 self.show_help = False
657 if key == 'KEY_RESIZE':
659 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
660 self.input_ = self.input_[:-1]
661 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
662 self.show_help = True
664 self.restore_input_values()
665 elif self.mode.has_input_prompt and key != '\n': # Return key
667 max_length = self.window_width * self.size.y - len(input_prompt) - 1
668 if len(self.input_) > max_length:
669 self.input_ = self.input_[:max_length]
670 elif key == self.keys['help'] and self.mode != self.mode_edit:
671 self.show_help = True
672 elif self.mode == self.mode_login and key == '\n':
673 self.login_name = self.input_
674 self.send('LOGIN ' + quote(self.input_))
676 elif self.mode == self.mode_password and key == '\n':
677 if self.input_ == '':
679 self.password = self.input_
681 self.switch_mode('play')
682 elif self.mode == self.mode_chat and key == '\n':
683 if self.input_ == '':
685 if self.input_[0] == '/': # FIXME fails on empty input
686 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
687 self.switch_mode('play')
688 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
689 self.switch_mode('study')
690 elif self.input_.startswith('/nick'):
691 tokens = self.input_.split(maxsplit=1)
693 self.send('NICK ' + quote(tokens[1]))
695 self.log_msg('? need login name')
697 self.log_msg('? unknown command')
699 self.send('ALL ' + quote(self.input_))
701 elif self.mode == self.mode_annotate and key == '\n':
702 if self.input_ == '':
704 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
705 quote(self.password)))
707 self.switch_mode('play')
708 elif self.mode == self.mode_portal and key == '\n':
709 if self.input_ == '':
711 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
712 quote(self.password)))
714 self.switch_mode('play')
715 elif self.mode == self.mode_study:
716 if key == self.keys['switch_to_chat']:
717 self.switch_mode('chat')
718 elif key == self.keys['switch_to_play']:
719 self.switch_mode('play')
720 elif key == self.keys['toggle_map_mode']:
721 if self.map_mode == 'terrain':
722 self.map_mode = 'control'
724 self.map_mode = 'terrain'
725 elif key in self.movement_keys:
726 move_explorer(self.movement_keys[key])
727 elif self.mode == self.mode_play:
728 if key == self.keys['switch_to_chat']:
729 self.switch_mode('chat')
730 elif key == self.keys['switch_to_study']:
731 self.switch_mode('study')
732 elif key == self.keys['switch_to_annotate']:
733 self.switch_mode('annotate')
734 elif key == self.keys['switch_to_portal']:
735 self.switch_mode('portal')
736 elif key == self.keys['switch_to_password']:
737 self.switch_mode('password')
738 if key == self.keys['switch_to_edit'] and\
739 'WRITE' in self.game.tasks:
740 self.switch_mode('edit')
741 elif key == self.keys['flatten'] and\
742 'FLATTEN_SURROUNDINGS' in self.game.tasks:
743 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
744 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
745 self.send('TASK:PICK_UP')
746 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
747 self.send('TASK:DROP')
748 elif key == self.keys['teleport']:
749 player = self.game.get_thing(self.game.player_id)
750 if player.position in self.game.portals:
751 self.host = self.game.portals[player.position]
755 self.log_msg('? not standing on portal')
756 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
757 self.send('TASK:MOVE ' + self.movement_keys[key])
758 elif self.mode == self.mode_edit:
759 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
760 self.switch_mode('play')
762 #TUI('localhost:5000')
763 TUI('wss://plomlompom.com/rogue_chat/')