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):
59 game.turn_complete = False
60 cmd_TURN.argtypes = 'int:nonneg'
62 def cmd_LOGIN_OK(game):
63 game.tui.switch_mode('post_login_wait')
64 game.tui.send('GET_GAMESTATE')
65 game.tui.log_msg('@ welcome')
66 cmd_LOGIN_OK.argtypes = ''
68 def cmd_CHAT(game, msg):
69 game.tui.log_msg('# ' + msg)
70 game.tui.do_refresh = True
71 cmd_CHAT.argtypes = 'string'
73 def cmd_PLAYER_ID(game, player_id):
74 game.player_id = player_id
75 cmd_PLAYER_ID.argtypes = 'int:nonneg'
77 def cmd_THING(game, yx, thing_type, thing_id):
78 t = game.get_thing(thing_id)
80 t = ThingBase(game, thing_id)
84 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
86 def cmd_THING_NAME(game, thing_id, name):
87 t = game.get_thing(thing_id)
90 cmd_THING_NAME.argtypes = 'int:nonneg string'
92 def cmd_THING_CHAR(game, thing_id, c):
93 t = game.get_thing(thing_id)
96 cmd_THING_CHAR.argtypes = 'int:nonneg char'
98 def cmd_MAP(game, geometry, size, content):
99 map_geometry_class = globals()['MapGeometry' + geometry]
100 game.map_geometry = map_geometry_class(size)
101 game.map_content = content
102 if type(game.map_geometry) == MapGeometrySquare:
103 game.tui.movement_keys = {
104 game.tui.keys['square_move_up']: 'UP',
105 game.tui.keys['square_move_left']: 'LEFT',
106 game.tui.keys['square_move_down']: 'DOWN',
107 game.tui.keys['square_move_right']: 'RIGHT',
109 elif type(game.map_geometry) == MapGeometryHex:
110 game.tui.movement_keys = {
111 game.tui.keys['hex_move_upleft']: 'UPLEFT',
112 game.tui.keys['hex_move_upright']: 'UPRIGHT',
113 game.tui.keys['hex_move_right']: 'RIGHT',
114 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
115 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
116 game.tui.keys['hex_move_left']: 'LEFT',
118 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
120 def cmd_FOV(game, content):
122 cmd_FOV.argtypes = 'string'
124 def cmd_MAP_CONTROL(game, content):
125 game.map_control_content = content
126 cmd_MAP_CONTROL.argtypes = 'string'
128 def cmd_GAME_STATE_COMPLETE(game):
129 if game.tui.mode.name == 'post_login_wait':
130 game.tui.switch_mode('play')
131 if game.tui.mode.shows_info:
132 game.tui.query_info()
133 game.turn_complete = True
134 game.tui.do_refresh = True
135 cmd_GAME_STATE_COMPLETE.argtypes = ''
137 def cmd_PORTAL(game, position, msg):
138 game.portals[position] = msg
139 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
141 def cmd_PLAY_ERROR(game, msg):
142 game.tui.log_msg('? ' + msg)
143 game.tui.flash = True
144 game.tui.do_refresh = True
145 cmd_PLAY_ERROR.argtypes = 'string'
147 def cmd_GAME_ERROR(game, msg):
148 game.tui.log_msg('? game error: ' + msg)
149 game.tui.do_refresh = True
150 cmd_GAME_ERROR.argtypes = 'string'
152 def cmd_ARGUMENT_ERROR(game, msg):
153 game.tui.log_msg('? syntax error: ' + msg)
154 game.tui.do_refresh = True
155 cmd_ARGUMENT_ERROR.argtypes = 'string'
157 def cmd_ANNOTATION_HINT(game, position):
158 game.info_hints += [position]
159 cmd_ANNOTATION_HINT.argtypes = 'yx_tuple:nonneg'
161 def cmd_ANNOTATION(game, position, msg):
162 game.info_db[position] = msg
163 game.tui.restore_input_values()
164 if game.tui.mode.shows_info:
165 game.tui.do_refresh = True
166 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
168 def cmd_TASKS(game, tasks_comma_separated):
169 game.tasks = tasks_comma_separated.split(',')
170 cmd_TASKS.argtypes = 'string'
172 def cmd_THING_TYPE(game, thing_type, symbol_hint):
173 game.thing_types[thing_type] = symbol_hint
174 cmd_THING_TYPE.argtypes = 'string char'
176 def cmd_TERRAIN(game, terrain_char, terrain_desc):
177 game.terrains[terrain_char] = terrain_desc
178 cmd_TERRAIN.argtypes = 'char string'
182 cmd_PONG.argtypes = ''
184 class Game(GameBase):
185 turn_complete = False
189 def __init__(self, *args, **kwargs):
190 super().__init__(*args, **kwargs)
191 self.register_command(cmd_LOGIN_OK)
192 self.register_command(cmd_PONG)
193 self.register_command(cmd_CHAT)
194 self.register_command(cmd_PLAYER_ID)
195 self.register_command(cmd_TURN)
196 self.register_command(cmd_THING)
197 self.register_command(cmd_THING_TYPE)
198 self.register_command(cmd_THING_NAME)
199 self.register_command(cmd_THING_CHAR)
200 self.register_command(cmd_TERRAIN)
201 self.register_command(cmd_MAP)
202 self.register_command(cmd_MAP_CONTROL)
203 self.register_command(cmd_PORTAL)
204 self.register_command(cmd_ANNOTATION)
205 self.register_command(cmd_ANNOTATION_HINT)
206 self.register_command(cmd_GAME_STATE_COMPLETE)
207 self.register_command(cmd_ARGUMENT_ERROR)
208 self.register_command(cmd_GAME_ERROR)
209 self.register_command(cmd_PLAY_ERROR)
210 self.register_command(cmd_TASKS)
211 self.register_command(cmd_FOV)
212 self.map_content = ''
219 def get_string_options(self, string_option_type):
220 if string_option_type == 'map_geometry':
221 return ['Hex', 'Square']
222 elif string_option_type == 'thing_type':
223 return self.thing_types.keys()
226 def get_command(self, command_name):
227 from functools import partial
228 f = partial(self.commands[command_name], self)
229 f.argtypes = self.commands[command_name].argtypes
236 def __init__(self, name, help_intro, has_input_prompt=False,
237 shows_info=False, is_intro = False):
239 self.has_input_prompt = has_input_prompt
240 self.shows_info = shows_info
241 self.is_intro = is_intro
242 self.help_intro = help_intro
244 def __init__(self, host):
248 self.mode_play = self.Mode('play', 'This mode allows you to interact with the map.')
249 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)
250 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.')
251 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)
252 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)
253 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)
254 self.mode_waiting_for_server = self.Mode('waiting_for_server', 'Waiting for a server response.', is_intro=True)
255 self.mode_login = self.Mode('login', 'Pick your player name.', has_input_prompt=True, is_intro=True)
256 self.mode_post_login_wait = self.Mode('post_login_wait', 'Waiting for a server response.', is_intro=True)
257 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)
260 self.parser = Parser(self.game)
262 self.do_refresh = True
263 self.queue = queue.Queue()
264 self.login_name = None
265 self.map_mode = 'terrain'
266 self.password = 'foo'
267 self.switch_mode('waiting_for_server')
269 'switch_to_chat': 't',
270 'switch_to_play': 'p',
271 'switch_to_password': 'P',
272 'switch_to_annotate': 'M',
273 'switch_to_portal': 'T',
274 'switch_to_study': '?',
275 'switch_to_edit': 'm',
280 'toggle_map_mode': 'M',
281 'hex_move_upleft': 'w',
282 'hex_move_upright': 'e',
283 'hex_move_right': 'd',
284 'hex_move_downright': 'x',
285 'hex_move_downleft': 'y',
286 'hex_move_left': 'a',
287 'square_move_up': 'w',
288 'square_move_left': 'a',
289 'square_move_down': 's',
290 'square_move_right': 'd',
292 if os.path.isfile('config.json'):
293 with open('config.json', 'r') as f:
294 keys_conf = json.loads(f.read())
296 self.keys[k] = keys_conf[k]
297 self.show_help = False
298 self.disconnected = True
299 self.force_instant_connect = True
300 self.input_lines = []
303 curses.wrapper(self.loop)
307 def handle_recv(msg):
313 self.log_msg('@ attempting connect')
314 socket_client_class = PlomSocketClient
315 if self.host.startswith('ws://') or self.host.startswith('wss://'):
316 socket_client_class = WebSocketClient
318 self.socket = socket_client_class(handle_recv, self.host)
319 self.socket_thread = threading.Thread(target=self.socket.run)
320 self.socket_thread.start()
321 self.disconnected = False
322 self.game.thing_types = {}
323 self.game.terrains = {}
324 self.socket.send('TASKS')
325 self.socket.send('TERRAINS')
326 self.socket.send('THING_TYPES')
327 self.switch_mode('login')
328 except ConnectionRefusedError:
329 self.log_msg('@ server connect failure')
330 self.disconnected = True
331 self.switch_mode('waiting_for_server')
332 self.do_refresh = True
335 self.log_msg('@ attempting reconnect')
337 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
338 # conditions with ws4py, find out what exactly
339 self.switch_mode('waiting_for_server')
344 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
345 raise BrokenSocketConnection
346 self.socket.send(msg)
347 except (BrokenPipeError, BrokenSocketConnection):
348 self.log_msg('@ server disconnected :(')
349 self.disconnected = True
350 self.force_instant_connect = True
351 self.do_refresh = True
353 def log_msg(self, msg):
355 if len(self.log) > 100:
356 self.log = self.log[-100:]
358 def query_info(self):
359 self.send('GET_ANNOTATION ' + str(self.explorer))
361 def restore_input_values(self):
362 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
363 info = self.game.info_db[self.explorer]
366 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
367 self.input_ = self.game.portals[self.explorer]
368 elif self.mode.name == 'password':
369 self.input_ = self.password
371 def switch_mode(self, mode_name):
372 self.map_mode = 'terrain'
373 self.mode = getattr(self, 'mode_' + mode_name)
374 if self.mode.shows_info:
375 player = self.game.get_thing(self.game.player_id)
376 self.explorer = YX(player.position.y, player.position.x)
378 if self.mode.name == 'waiting_for_server':
379 self.log_msg('@ waiting for server …')
380 if self.mode.name == 'edit':
381 self.show_help = True
382 elif self.mode.name == 'login':
384 self.send('LOGIN ' + quote(self.login_name))
386 self.log_msg('@ enter username')
387 self.restore_input_values()
389 def loop(self, stdscr):
392 def safe_addstr(y, x, line):
393 if y < self.size.y - 1 or x + len(line) < self.size.x:
394 stdscr.addstr(y, x, line)
395 else: # workaround to <https://stackoverflow.com/q/7063128>
396 cut_i = self.size.x - x - 1
398 last_char = line[cut_i]
399 stdscr.addstr(y, self.size.x - 2, last_char)
400 stdscr.insstr(y, self.size.x - 2, ' ')
401 stdscr.addstr(y, x, cut)
403 def handle_input(msg):
404 command, args = self.parser.parse(msg)
407 def msg_into_lines_of_width(msg, width):
411 for i in range(len(msg)):
412 if x >= width or msg[i] == "\n":
422 def reset_screen_size():
423 self.size = YX(*stdscr.getmaxyx())
424 self.size = self.size - YX(self.size.y % 4, 0)
425 self.size = self.size - YX(0, self.size.x % 4)
426 self.window_width = int(self.size.x / 2)
428 def recalc_input_lines():
429 if not self.mode.has_input_prompt:
430 self.input_lines = []
432 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
435 def move_explorer(direction):
436 target = self.game.map_geometry.move_yx(self.explorer, direction)
438 self.explorer = target
445 for line in self.log:
446 lines += msg_into_lines_of_width(line, self.window_width)
449 max_y = self.size.y - len(self.input_lines)
450 for i in range(len(lines)):
451 if (i >= max_y - height_header):
453 safe_addstr(max_y - i - 1, self.window_width, lines[i])
456 if not self.game.turn_complete:
458 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
459 info = 'outside field of view'
460 if self.game.fov[pos_i] == '.':
461 terrain_char = self.game.map_content[pos_i]
463 if terrain_char in self.game.terrains:
464 terrain_desc = self.game.terrains[terrain_char]
465 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
466 for t in self.game.things:
467 if t.position == self.explorer:
468 info += 'THING: %s / %s' % (t.type_,
469 self.game.thing_types[t.type_])
470 if hasattr(t, 'player_char'):
471 info += t.player_char
472 if hasattr(t, 'name'):
473 info += ' (%s)' % t.name
475 if self.explorer in self.game.portals:
476 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
478 info += 'PORTAL: (none)\n'
479 if self.explorer in self.game.info_db:
480 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
482 info += 'ANNOTATION: waiting …'
483 lines = msg_into_lines_of_width(info, self.window_width)
485 for i in range(len(lines)):
486 y = height_header + i
487 if y >= self.size.y - len(self.input_lines):
489 safe_addstr(y, self.window_width, lines[i])
492 y = self.size.y - len(self.input_lines)
493 for i in range(len(self.input_lines)):
494 safe_addstr(y, self.window_width, self.input_lines[i])
498 if not self.game.turn_complete:
500 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
503 help = "hit [%s] for help" % self.keys['help']
504 if self.mode.has_input_prompt:
505 help = "enter /help for help"
506 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
509 if not self.game.turn_complete:
512 map_content = self.game.map_content
513 if self.map_mode == 'control':
514 map_content = self.game.map_control_content
515 for y in range(self.game.map_geometry.size.y):
516 start = self.game.map_geometry.size.x * y
517 end = start + self.game.map_geometry.size.x
518 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
519 if self.map_mode == 'annotations':
520 for p in self.game.info_hints:
521 map_lines_split[p.y][p.x] = 'A '
522 elif self.map_mode == 'terrain':
523 for p in self.game.portals.keys():
524 map_lines_split[p.y][p.x] = 'P '
526 for t in self.game.things:
527 symbol = self.game.thing_types[t.type_]
529 if hasattr(t, 'player_char'):
530 meta_char = t.player_char
531 if t.position in used_positions:
533 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
534 used_positions += [t.position]
535 if self.mode.shows_info:
536 map_lines_split[self.explorer.y][self.explorer.x] = '??'
538 if type(self.game.map_geometry) == MapGeometryHex:
540 for line in map_lines_split:
541 map_lines += [indent*' ' + ''.join(line)]
542 indent = 0 if indent else 1
544 for line in map_lines_split:
545 map_lines += [''.join(line)]
546 window_center = YX(int(self.size.y / 2),
547 int(self.window_width / 2))
548 player = self.game.get_thing(self.game.player_id)
549 center = player.position
550 if self.mode.shows_info:
551 center = self.explorer
552 center = YX(center.y, center.x * 2)
553 offset = center - window_center
554 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
556 term_y = max(0, -offset.y)
557 term_x = max(0, -offset.x)
558 map_y = max(0, offset.y)
559 map_x = max(0, offset.x)
560 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
561 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
562 safe_addstr(term_y, term_x, to_draw)
567 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
568 self.mode.help_intro)
569 if self.mode == self.mode_play:
570 content += "Available actions:\n"
571 if 'MOVE' in self.game.tasks:
572 content += "[%s] – move player\n" % ','.join(self.movement_keys)
573 if 'PICK_UP' in self.game.tasks:
574 content += "[%s] – take thing under player\n" % self.keys['take_thing']
575 if 'DROP' in self.game.tasks:
576 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
577 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
578 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
579 content += '[%s] – teleport to other space\n' % self.keys['teleport']
580 content += 'Other modes available from here:\n'
581 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
582 content += '[%s] – study mode\n' % self.keys['switch_to_study']
583 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
584 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
585 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
586 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
587 elif self.mode == self.mode_study:
588 content += 'Available actions:\n'
589 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
590 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
591 content += '\n\nOther modes available from here:'
592 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
593 content += '[%s] – play mode\n' % self.keys['switch_to_play']
594 elif self.mode == self.mode_chat:
595 content += '/nick NAME – re-name yourself to NAME\n'
596 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
597 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
598 for i in range(self.size.y):
600 self.window_width * (not self.mode.has_input_prompt),
601 ' '*self.window_width)
603 for line in content.split('\n'):
604 lines += msg_into_lines_of_width(line, self.window_width)
605 for i in range(len(lines)):
609 self.window_width * (not self.mode.has_input_prompt),
614 if self.mode.has_input_prompt:
617 if self.mode.shows_info:
622 if not self.mode.is_intro:
628 curses.curs_set(False) # hide cursor
629 curses.use_default_colors();
632 self.explorer = YX(0, 0)
635 interval = datetime.timedelta(seconds=5)
636 last_ping = datetime.datetime.now() - interval
638 if self.disconnected and self.force_instant_connect:
639 self.force_instant_connect = False
641 now = datetime.datetime.now()
642 if now - last_ping > interval:
643 if self.disconnected:
653 self.do_refresh = False
656 msg = self.queue.get(block=False)
661 key = stdscr.getkey()
662 self.do_refresh = True
665 self.show_help = False
666 if key == 'KEY_RESIZE':
668 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
669 self.input_ = self.input_[:-1]
670 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
671 self.show_help = True
673 self.restore_input_values()
674 elif self.mode.has_input_prompt and key != '\n': # Return key
676 max_length = self.window_width * self.size.y - len(input_prompt) - 1
677 if len(self.input_) > max_length:
678 self.input_ = self.input_[:max_length]
679 elif key == self.keys['help'] and self.mode != self.mode_edit:
680 self.show_help = True
681 elif self.mode == self.mode_login and key == '\n':
682 self.login_name = self.input_
683 self.send('LOGIN ' + quote(self.input_))
685 elif self.mode == self.mode_password and key == '\n':
686 if self.input_ == '':
688 self.password = self.input_
690 self.switch_mode('play')
691 elif self.mode == self.mode_chat and key == '\n':
692 if self.input_ == '':
694 if self.input_[0] == '/': # FIXME fails on empty input
695 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
696 self.switch_mode('play')
697 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
698 self.switch_mode('study')
699 elif self.input_.startswith('/nick'):
700 tokens = self.input_.split(maxsplit=1)
702 self.send('NICK ' + quote(tokens[1]))
704 self.log_msg('? need login name')
706 self.log_msg('? unknown command')
708 self.send('ALL ' + quote(self.input_))
710 elif self.mode == self.mode_annotate and key == '\n':
711 if self.input_ == '':
713 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
714 quote(self.password)))
716 self.switch_mode('play')
717 elif self.mode == self.mode_portal and key == '\n':
718 if self.input_ == '':
720 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
721 quote(self.password)))
723 self.switch_mode('play')
724 elif self.mode == self.mode_study:
725 if key == self.keys['switch_to_chat']:
726 self.switch_mode('chat')
727 elif key == self.keys['switch_to_play']:
728 self.switch_mode('play')
729 elif key == self.keys['toggle_map_mode']:
730 if self.map_mode == 'terrain':
731 self.map_mode = 'annotations'
732 elif self.map_mode == 'annotations':
733 self.map_mode = 'control'
735 self.map_mode = 'terrain'
736 elif key in self.movement_keys:
737 move_explorer(self.movement_keys[key])
738 elif self.mode == self.mode_play:
739 if key == self.keys['switch_to_chat']:
740 self.switch_mode('chat')
741 elif key == self.keys['switch_to_study']:
742 self.switch_mode('study')
743 elif key == self.keys['switch_to_annotate']:
744 self.switch_mode('annotate')
745 elif key == self.keys['switch_to_portal']:
746 self.switch_mode('portal')
747 elif key == self.keys['switch_to_password']:
748 self.switch_mode('password')
749 if key == self.keys['switch_to_edit'] and\
750 'WRITE' in self.game.tasks:
751 self.switch_mode('edit')
752 elif key == self.keys['flatten'] and\
753 'FLATTEN_SURROUNDINGS' in self.game.tasks:
754 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
755 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
756 self.send('TASK:PICK_UP')
757 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
758 self.send('TASK:DROP')
759 elif key == self.keys['teleport']:
760 player = self.game.get_thing(self.game.player_id)
761 if player.position in self.game.portals:
762 self.host = self.game.portals[player.position]
766 self.log_msg('? not standing on portal')
767 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
768 self.send('TASK:MOVE ' + self.movement_keys[key])
769 elif self.mode == self.mode_edit:
770 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
771 self.switch_mode('play')
773 #TUI('localhost:5000')
774 TUI('wss://plomlompom.com/rogue_chat/')