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 protection = self.game.map_control_content[pos_i]
467 if protection == '.':
468 protection = 'unprotected'
469 info = 'PROTECTION: %s\n' % protection
470 for t in self.game.things:
471 if t.position == self.explorer:
472 info += 'THING: %s / %s' % (t.type_,
473 self.game.thing_types[t.type_])
474 if hasattr(t, 'player_char'):
475 info += t.player_char
476 if hasattr(t, 'name'):
477 info += ' (%s)' % t.name
479 if self.explorer in self.game.portals:
480 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
482 info += 'PORTAL: (none)\n'
483 if self.explorer in self.game.info_db:
484 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
486 info += 'ANNOTATION: waiting …'
487 lines = msg_into_lines_of_width(info, self.window_width)
489 for i in range(len(lines)):
490 y = height_header + i
491 if y >= self.size.y - len(self.input_lines):
493 safe_addstr(y, self.window_width, lines[i])
496 y = self.size.y - len(self.input_lines)
497 for i in range(len(self.input_lines)):
498 safe_addstr(y, self.window_width, self.input_lines[i])
502 if not self.game.turn_complete:
504 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
507 help = "hit [%s] for help" % self.keys['help']
508 if self.mode.has_input_prompt:
509 help = "enter /help for help"
510 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
513 if not self.game.turn_complete:
516 map_content = self.game.map_content
517 if self.map_mode == 'control':
518 map_content = self.game.map_control_content
519 for y in range(self.game.map_geometry.size.y):
520 start = self.game.map_geometry.size.x * y
521 end = start + self.game.map_geometry.size.x
522 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
523 if self.map_mode == 'annotations':
524 for p in self.game.info_hints:
525 map_lines_split[p.y][p.x] = 'A '
526 elif self.map_mode == 'terrain':
527 for p in self.game.portals.keys():
528 map_lines_split[p.y][p.x] = 'P '
530 for t in self.game.things:
531 symbol = self.game.thing_types[t.type_]
533 if hasattr(t, 'player_char'):
534 meta_char = t.player_char
535 if t.position in used_positions:
537 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
538 used_positions += [t.position]
539 if self.mode.shows_info:
540 map_lines_split[self.explorer.y][self.explorer.x] = '??'
542 if type(self.game.map_geometry) == MapGeometryHex:
544 for line in map_lines_split:
545 map_lines += [indent*' ' + ''.join(line)]
546 indent = 0 if indent else 1
548 for line in map_lines_split:
549 map_lines += [''.join(line)]
550 window_center = YX(int(self.size.y / 2),
551 int(self.window_width / 2))
552 player = self.game.get_thing(self.game.player_id)
553 center = player.position
554 if self.mode.shows_info:
555 center = self.explorer
556 center = YX(center.y, center.x * 2)
557 offset = center - window_center
558 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
560 term_y = max(0, -offset.y)
561 term_x = max(0, -offset.x)
562 map_y = max(0, offset.y)
563 map_x = max(0, offset.x)
564 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
565 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
566 safe_addstr(term_y, term_x, to_draw)
571 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
572 self.mode.help_intro)
573 if self.mode == self.mode_play:
574 content += "Available actions:\n"
575 if 'MOVE' in self.game.tasks:
576 content += "[%s] – move player\n" % ','.join(self.movement_keys)
577 if 'PICK_UP' in self.game.tasks:
578 content += "[%s] – take thing under player\n" % self.keys['take_thing']
579 if 'DROP' in self.game.tasks:
580 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
581 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
582 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
583 content += '[%s] – teleport to other space\n' % self.keys['teleport']
584 content += 'Other modes available from here:\n'
585 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
586 content += '[%s] – study mode\n' % self.keys['switch_to_study']
587 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
588 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
589 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
590 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
591 elif self.mode == self.mode_study:
592 content += 'Available actions:\n'
593 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
594 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
595 content += '\n\nOther modes available from here:'
596 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
597 content += '[%s] – play mode\n' % self.keys['switch_to_play']
598 elif self.mode == self.mode_chat:
599 content += '/nick NAME – re-name yourself to NAME\n'
600 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
601 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
602 for i in range(self.size.y):
604 self.window_width * (not self.mode.has_input_prompt),
605 ' '*self.window_width)
607 for line in content.split('\n'):
608 lines += msg_into_lines_of_width(line, self.window_width)
609 for i in range(len(lines)):
613 self.window_width * (not self.mode.has_input_prompt),
618 if self.mode.has_input_prompt:
621 if self.mode.shows_info:
626 if not self.mode.is_intro:
632 curses.curs_set(False) # hide cursor
633 curses.use_default_colors();
636 self.explorer = YX(0, 0)
639 interval = datetime.timedelta(seconds=5)
640 last_ping = datetime.datetime.now() - interval
642 if self.disconnected and self.force_instant_connect:
643 self.force_instant_connect = False
645 now = datetime.datetime.now()
646 if now - last_ping > interval:
647 if self.disconnected:
657 self.do_refresh = False
660 msg = self.queue.get(block=False)
665 key = stdscr.getkey()
666 self.do_refresh = True
669 self.show_help = False
670 if key == 'KEY_RESIZE':
672 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
673 self.input_ = self.input_[:-1]
674 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
675 self.show_help = True
677 self.restore_input_values()
678 elif self.mode.has_input_prompt and key != '\n': # Return key
680 max_length = self.window_width * self.size.y - len(input_prompt) - 1
681 if len(self.input_) > max_length:
682 self.input_ = self.input_[:max_length]
683 elif key == self.keys['help'] and self.mode != self.mode_edit:
684 self.show_help = True
685 elif self.mode == self.mode_login and key == '\n':
686 self.login_name = self.input_
687 self.send('LOGIN ' + quote(self.input_))
689 elif self.mode == self.mode_password and key == '\n':
690 if self.input_ == '':
692 self.password = self.input_
694 self.switch_mode('play')
695 elif self.mode == self.mode_chat and key == '\n':
696 if self.input_ == '':
698 if self.input_[0] == '/': # FIXME fails on empty input
699 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
700 self.switch_mode('play')
701 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
702 self.switch_mode('study')
703 elif self.input_.startswith('/nick'):
704 tokens = self.input_.split(maxsplit=1)
706 self.send('NICK ' + quote(tokens[1]))
708 self.log_msg('? need login name')
710 self.log_msg('? unknown command')
712 self.send('ALL ' + quote(self.input_))
714 elif self.mode == self.mode_annotate and key == '\n':
715 if self.input_ == '':
717 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
718 quote(self.password)))
720 self.switch_mode('play')
721 elif self.mode == self.mode_portal and key == '\n':
722 if self.input_ == '':
724 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
725 quote(self.password)))
727 self.switch_mode('play')
728 elif self.mode == self.mode_study:
729 if key == self.keys['switch_to_chat']:
730 self.switch_mode('chat')
731 elif key == self.keys['switch_to_play']:
732 self.switch_mode('play')
733 elif key == self.keys['toggle_map_mode']:
734 if self.map_mode == 'terrain':
735 self.map_mode = 'annotations'
736 elif self.map_mode == 'annotations':
737 self.map_mode = 'control'
739 self.map_mode = 'terrain'
740 elif key in self.movement_keys:
741 move_explorer(self.movement_keys[key])
742 elif self.mode == self.mode_play:
743 if key == self.keys['switch_to_chat']:
744 self.switch_mode('chat')
745 elif key == self.keys['switch_to_study']:
746 self.switch_mode('study')
747 elif key == self.keys['switch_to_annotate']:
748 self.switch_mode('annotate')
749 elif key == self.keys['switch_to_portal']:
750 self.switch_mode('portal')
751 elif key == self.keys['switch_to_password']:
752 self.switch_mode('password')
753 if key == self.keys['switch_to_edit'] and\
754 'WRITE' in self.game.tasks:
755 self.switch_mode('edit')
756 elif key == self.keys['flatten'] and\
757 'FLATTEN_SURROUNDINGS' in self.game.tasks:
758 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
759 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
760 self.send('TASK:PICK_UP')
761 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
762 self.send('TASK:DROP')
763 elif key == self.keys['teleport']:
764 player = self.game.get_thing(self.game.player_id)
765 if player.position in self.game.portals:
766 self.host = self.game.portals[player.position]
770 self.log_msg('? not standing on portal')
771 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
772 self.send('TASK:MOVE ' + self.movement_keys[key])
773 elif self.mode == self.mode_edit:
774 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
775 self.switch_mode('play')
777 #TUI('localhost:5000')
778 TUI('wss://plomlompom.com/rogue_chat/')