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
16 'long': 'This mode allows you to interact with the map.'
20 'long': '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.'},
22 'short': 'terrain edit',
23 'long': '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.'
26 'short': 'change tile control password',
27 'long': 'This mode is the first of two steps to change the password for a tile control character. First enter the tile control character for which you want to change the password!'
31 'long': 'This mode is the second of two steps to change the password for a tile control character. Enter the new password for the tile control character you chose.'
34 'short': 'annotation',
35 'long': '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.'
38 'short': 'edit portal',
39 'long': '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.'
43 'long': '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:'
47 'long': 'Pick your player name.'
49 'waiting_for_server': {
51 'long': 'Waiting for a server response.'
55 'long': 'Waiting for a server response.'
58 'short': 'password input',
59 'long': '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.'
62 'short': 'become admin',
63 'long': 'This mode allows you to become admin if you know an admin password.'
67 from ws4py.client import WebSocketBaseClient
68 class WebSocketClient(WebSocketBaseClient):
70 def __init__(self, recv_handler, *args, **kwargs):
71 super().__init__(*args, **kwargs)
72 self.recv_handler = recv_handler
75 def received_message(self, message):
77 message = str(message)
78 self.recv_handler(message)
81 def plom_closed(self):
82 return self.client_terminated
84 from plomrogue.io_tcp import PlomSocket
85 class PlomSocketClient(PlomSocket):
87 def __init__(self, recv_handler, url):
89 self.recv_handler = recv_handler
90 host, port = url.split(':')
91 super().__init__(socket.create_connection((host, port)))
99 for msg in self.recv():
100 if msg == 'NEED_SSL':
101 self.socket = ssl.wrap_socket(self.socket)
103 self.recv_handler(msg)
104 except BrokenSocketConnection:
105 pass # we assume socket will be known as dead by now
107 def cmd_TURN(game, n):
113 game.turn_complete = False
114 cmd_TURN.argtypes = 'int:nonneg'
116 def cmd_LOGIN_OK(game):
117 game.tui.switch_mode('post_login_wait')
118 game.tui.send('GET_GAMESTATE')
119 game.tui.log_msg('@ welcome')
120 cmd_LOGIN_OK.argtypes = ''
122 def cmd_CHAT(game, msg):
123 game.tui.log_msg('# ' + msg)
124 game.tui.do_refresh = True
125 cmd_CHAT.argtypes = 'string'
127 def cmd_PLAYER_ID(game, player_id):
128 game.player_id = player_id
129 cmd_PLAYER_ID.argtypes = 'int:nonneg'
131 def cmd_THING(game, yx, thing_type, thing_id):
132 t = game.get_thing(thing_id)
134 t = ThingBase(game, thing_id)
138 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
140 def cmd_THING_NAME(game, thing_id, name):
141 t = game.get_thing(thing_id)
144 cmd_THING_NAME.argtypes = 'int:nonneg string'
146 def cmd_THING_CHAR(game, thing_id, c):
147 t = game.get_thing(thing_id)
150 cmd_THING_CHAR.argtypes = 'int:nonneg char'
152 def cmd_MAP(game, geometry, size, content):
153 map_geometry_class = globals()['MapGeometry' + geometry]
154 game.map_geometry = map_geometry_class(size)
155 game.map_content = content
156 if type(game.map_geometry) == MapGeometrySquare:
157 game.tui.movement_keys = {
158 game.tui.keys['square_move_up']: 'UP',
159 game.tui.keys['square_move_left']: 'LEFT',
160 game.tui.keys['square_move_down']: 'DOWN',
161 game.tui.keys['square_move_right']: 'RIGHT',
163 elif type(game.map_geometry) == MapGeometryHex:
164 game.tui.movement_keys = {
165 game.tui.keys['hex_move_upleft']: 'UPLEFT',
166 game.tui.keys['hex_move_upright']: 'UPRIGHT',
167 game.tui.keys['hex_move_right']: 'RIGHT',
168 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
169 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
170 game.tui.keys['hex_move_left']: 'LEFT',
172 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
174 def cmd_FOV(game, content):
176 cmd_FOV.argtypes = 'string'
178 def cmd_MAP_CONTROL(game, content):
179 game.map_control_content = content
180 cmd_MAP_CONTROL.argtypes = 'string'
182 def cmd_GAME_STATE_COMPLETE(game):
183 if game.tui.mode.name == 'post_login_wait':
184 game.tui.switch_mode('play')
185 if game.tui.mode.shows_info:
186 game.tui.query_info()
187 game.turn_complete = True
188 game.tui.do_refresh = True
189 cmd_GAME_STATE_COMPLETE.argtypes = ''
191 def cmd_PORTAL(game, position, msg):
192 game.portals[position] = msg
193 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
195 def cmd_PLAY_ERROR(game, msg):
196 game.tui.log_msg('? ' + msg)
197 game.tui.flash = True
198 game.tui.do_refresh = True
199 cmd_PLAY_ERROR.argtypes = 'string'
201 def cmd_GAME_ERROR(game, msg):
202 game.tui.log_msg('? game error: ' + msg)
203 game.tui.do_refresh = True
204 cmd_GAME_ERROR.argtypes = 'string'
206 def cmd_ARGUMENT_ERROR(game, msg):
207 game.tui.log_msg('? syntax error: ' + msg)
208 game.tui.do_refresh = True
209 cmd_ARGUMENT_ERROR.argtypes = 'string'
211 def cmd_ANNOTATION_HINT(game, position):
212 game.info_hints += [position]
213 cmd_ANNOTATION_HINT.argtypes = 'yx_tuple:nonneg'
215 def cmd_ANNOTATION(game, position, msg):
216 game.info_db[position] = msg
217 game.tui.restore_input_values()
218 if game.tui.mode.shows_info:
219 game.tui.do_refresh = True
220 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
222 def cmd_TASKS(game, tasks_comma_separated):
223 game.tasks = tasks_comma_separated.split(',')
224 game.tui.mode_edit.legal = 'WRITE' in game.tasks
225 cmd_TASKS.argtypes = 'string'
227 def cmd_THING_TYPE(game, thing_type, symbol_hint):
228 game.thing_types[thing_type] = symbol_hint
229 cmd_THING_TYPE.argtypes = 'string char'
231 def cmd_TERRAIN(game, terrain_char, terrain_desc):
232 game.terrains[terrain_char] = terrain_desc
233 cmd_TERRAIN.argtypes = 'char string'
237 cmd_PONG.argtypes = ''
239 class Game(GameBase):
240 turn_complete = False
244 def __init__(self, *args, **kwargs):
245 super().__init__(*args, **kwargs)
246 self.register_command(cmd_LOGIN_OK)
247 self.register_command(cmd_PONG)
248 self.register_command(cmd_CHAT)
249 self.register_command(cmd_PLAYER_ID)
250 self.register_command(cmd_TURN)
251 self.register_command(cmd_THING)
252 self.register_command(cmd_THING_TYPE)
253 self.register_command(cmd_THING_NAME)
254 self.register_command(cmd_THING_CHAR)
255 self.register_command(cmd_TERRAIN)
256 self.register_command(cmd_MAP)
257 self.register_command(cmd_MAP_CONTROL)
258 self.register_command(cmd_PORTAL)
259 self.register_command(cmd_ANNOTATION)
260 self.register_command(cmd_ANNOTATION_HINT)
261 self.register_command(cmd_GAME_STATE_COMPLETE)
262 self.register_command(cmd_ARGUMENT_ERROR)
263 self.register_command(cmd_GAME_ERROR)
264 self.register_command(cmd_PLAY_ERROR)
265 self.register_command(cmd_TASKS)
266 self.register_command(cmd_FOV)
267 self.map_content = ''
274 def get_string_options(self, string_option_type):
275 if string_option_type == 'map_geometry':
276 return ['Hex', 'Square']
277 elif string_option_type == 'thing_type':
278 return self.thing_types.keys()
281 def get_command(self, command_name):
282 from functools import partial
283 f = partial(self.commands[command_name], self)
284 f.argtypes = self.commands[command_name].argtypes
289 def __init__(self, name, has_input_prompt=False, shows_info=False,
290 is_intro=False, is_single_char_entry=False):
292 self.short_desc = mode_helps[name]['short']
293 self.available_modes = []
294 self.has_input_prompt = has_input_prompt
295 self.shows_info = shows_info
296 self.is_intro = is_intro
297 self.help_intro = mode_helps[name]['long']
298 self.is_single_char_entry = is_single_char_entry
301 def iter_available_modes(self, tui):
302 for mode_name in self.available_modes:
303 mode = getattr(tui, 'mode_' + mode_name)
306 key = tui.keys['switch_to_' + mode.name]
309 def list_available_modes(self, tui):
311 if len(self.available_modes) > 0:
312 msg = 'Other modes available from here:\n'
313 for mode, key in self.iter_available_modes(tui):
314 msg += '[%s] – %s\n' % (key, mode.short_desc)
317 def mode_switch_on_key(self, tui, key_pressed):
318 for mode, key in self.iter_available_modes(tui):
319 if key_pressed == key:
320 tui.switch_mode(mode.name)
325 mode_admin = Mode('admin', has_input_prompt=True)
326 mode_play = Mode('play')
327 mode_study = Mode('study', shows_info=True)
328 mode_edit = Mode('edit', is_single_char_entry=True)
329 mode_control_pw_type = Mode('control_pw_type', is_single_char_entry=True)
330 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
331 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
332 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
333 mode_chat = Mode('chat', has_input_prompt=True)
334 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
335 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
336 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
337 mode_password = Mode('password', has_input_prompt=True)
339 def __init__(self, host):
342 self.mode_play.available_modes = ["chat", "study", "edit",
343 "annotate", "portal",
346 self.mode_study.available_modes = ["chat", "play"]
350 self.parser = Parser(self.game)
352 self.do_refresh = True
353 self.queue = queue.Queue()
354 self.login_name = None
355 self.map_mode = 'terrain'
356 self.password = 'foo'
357 self.switch_mode('waiting_for_server')
359 'switch_to_chat': 't',
360 'switch_to_play': 'p',
361 'switch_to_password': 'P',
362 'switch_to_annotate': 'M',
363 'switch_to_portal': 'T',
364 'switch_to_study': '?',
365 'switch_to_edit': 'm',
366 'switch_to_admin': 'A',
367 'switch_to_control_pw_type': 'C',
373 'toggle_map_mode': 'M',
374 'hex_move_upleft': 'w',
375 'hex_move_upright': 'e',
376 'hex_move_right': 'd',
377 'hex_move_downright': 'x',
378 'hex_move_downleft': 'y',
379 'hex_move_left': 'a',
380 'square_move_up': 'w',
381 'square_move_left': 'a',
382 'square_move_down': 's',
383 'square_move_right': 'd',
385 if os.path.isfile('config.json'):
386 with open('config.json', 'r') as f:
387 keys_conf = json.loads(f.read())
389 self.keys[k] = keys_conf[k]
390 self.show_help = False
391 self.disconnected = True
392 self.force_instant_connect = True
393 self.input_lines = []
396 curses.wrapper(self.loop)
400 def handle_recv(msg):
406 self.log_msg('@ attempting connect')
407 socket_client_class = PlomSocketClient
408 if self.host.startswith('ws://') or self.host.startswith('wss://'):
409 socket_client_class = WebSocketClient
411 self.socket = socket_client_class(handle_recv, self.host)
412 self.socket_thread = threading.Thread(target=self.socket.run)
413 self.socket_thread.start()
414 self.disconnected = False
415 self.game.thing_types = {}
416 self.game.terrains = {}
417 self.socket.send('TASKS')
418 self.socket.send('TERRAINS')
419 self.socket.send('THING_TYPES')
420 self.switch_mode('login')
421 except ConnectionRefusedError:
422 self.log_msg('@ server connect failure')
423 self.disconnected = True
424 self.switch_mode('waiting_for_server')
425 self.do_refresh = True
428 self.log_msg('@ attempting reconnect')
430 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
431 # conditions with ws4py, find out what exactly
432 self.switch_mode('waiting_for_server')
437 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
438 raise BrokenSocketConnection
439 self.socket.send(msg)
440 except (BrokenPipeError, BrokenSocketConnection):
441 self.log_msg('@ server disconnected :(')
442 self.disconnected = True
443 self.force_instant_connect = True
444 self.do_refresh = True
446 def log_msg(self, msg):
448 if len(self.log) > 100:
449 self.log = self.log[-100:]
451 def query_info(self):
452 self.send('GET_ANNOTATION ' + str(self.explorer))
454 def restore_input_values(self):
455 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
456 info = self.game.info_db[self.explorer]
459 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
460 self.input_ = self.game.portals[self.explorer]
461 elif self.mode.name == 'password':
462 self.input_ = self.password
464 def switch_mode(self, mode_name):
465 self.map_mode = 'terrain'
466 self.mode = getattr(self, 'mode_' + mode_name)
467 if self.mode.shows_info:
468 player = self.game.get_thing(self.game.player_id)
469 self.explorer = YX(player.position.y, player.position.x)
471 if self.mode.is_single_char_entry:
472 self.show_help = True
473 if self.mode.name == 'waiting_for_server':
474 self.log_msg('@ waiting for server …')
475 elif self.mode.name == 'login':
477 self.send('LOGIN ' + quote(self.login_name))
479 self.log_msg('@ enter username')
480 elif self.mode.name == 'admin':
481 self.log_msg('@ enter admin password:')
482 elif self.mode.name == 'control_pw_pw':
483 self.log_msg('@ enter tile control password for "%s":' % self.tile_control_char)
484 self.restore_input_values()
486 def loop(self, stdscr):
489 def safe_addstr(y, x, line):
490 if y < self.size.y - 1 or x + len(line) < self.size.x:
491 stdscr.addstr(y, x, line)
492 else: # workaround to <https://stackoverflow.com/q/7063128>
493 cut_i = self.size.x - x - 1
495 last_char = line[cut_i]
496 stdscr.addstr(y, self.size.x - 2, last_char)
497 stdscr.insstr(y, self.size.x - 2, ' ')
498 stdscr.addstr(y, x, cut)
500 def handle_input(msg):
501 command, args = self.parser.parse(msg)
504 def msg_into_lines_of_width(msg, width):
508 for i in range(len(msg)):
509 if x >= width or msg[i] == "\n":
519 def reset_screen_size():
520 self.size = YX(*stdscr.getmaxyx())
521 self.size = self.size - YX(self.size.y % 4, 0)
522 self.size = self.size - YX(0, self.size.x % 4)
523 self.window_width = int(self.size.x / 2)
525 def recalc_input_lines():
526 if not self.mode.has_input_prompt:
527 self.input_lines = []
529 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
532 def move_explorer(direction):
533 target = self.game.map_geometry.move_yx(self.explorer, direction)
535 self.explorer = target
542 for line in self.log:
543 lines += msg_into_lines_of_width(line, self.window_width)
546 max_y = self.size.y - len(self.input_lines)
547 for i in range(len(lines)):
548 if (i >= max_y - height_header):
550 safe_addstr(max_y - i - 1, self.window_width, lines[i])
553 if not self.game.turn_complete:
555 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
556 info = 'outside field of view'
557 if self.game.fov[pos_i] == '.':
558 terrain_char = self.game.map_content[pos_i]
560 if terrain_char in self.game.terrains:
561 terrain_desc = self.game.terrains[terrain_char]
562 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
563 protection = self.game.map_control_content[pos_i]
564 if protection == '.':
565 protection = 'unprotected'
566 info = 'PROTECTION: %s\n' % protection
567 for t in self.game.things:
568 if t.position == self.explorer:
569 info += 'THING: %s / %s' % (t.type_,
570 self.game.thing_types[t.type_])
571 if hasattr(t, 'player_char'):
572 info += t.player_char
573 if hasattr(t, 'name'):
574 info += ' (%s)' % t.name
576 if self.explorer in self.game.portals:
577 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
579 info += 'PORTAL: (none)\n'
580 if self.explorer in self.game.info_db:
581 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
583 info += 'ANNOTATION: waiting …'
584 lines = msg_into_lines_of_width(info, self.window_width)
586 for i in range(len(lines)):
587 y = height_header + i
588 if y >= self.size.y - len(self.input_lines):
590 safe_addstr(y, self.window_width, lines[i])
593 y = self.size.y - len(self.input_lines)
594 for i in range(len(self.input_lines)):
595 safe_addstr(y, self.window_width, self.input_lines[i])
599 if not self.game.turn_complete:
601 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
604 help = "hit [%s] for help" % self.keys['help']
605 if self.mode.has_input_prompt:
606 help = "enter /help for help"
607 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
610 if not self.game.turn_complete:
613 map_content = self.game.map_content
614 if self.map_mode == 'control':
615 map_content = self.game.map_control_content
616 for y in range(self.game.map_geometry.size.y):
617 start = self.game.map_geometry.size.x * y
618 end = start + self.game.map_geometry.size.x
619 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
620 if self.map_mode == 'annotations':
621 for p in self.game.info_hints:
622 map_lines_split[p.y][p.x] = 'A '
623 elif self.map_mode == 'terrain':
624 for p in self.game.portals.keys():
625 map_lines_split[p.y][p.x] = 'P '
627 for t in self.game.things:
628 symbol = self.game.thing_types[t.type_]
630 if hasattr(t, 'player_char'):
631 meta_char = t.player_char
632 if t.position in used_positions:
634 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
635 used_positions += [t.position]
636 if self.mode.shows_info:
637 map_lines_split[self.explorer.y][self.explorer.x] = '??'
639 if type(self.game.map_geometry) == MapGeometryHex:
641 for line in map_lines_split:
642 map_lines += [indent*' ' + ''.join(line)]
643 indent = 0 if indent else 1
645 for line in map_lines_split:
646 map_lines += [''.join(line)]
647 window_center = YX(int(self.size.y / 2),
648 int(self.window_width / 2))
649 player = self.game.get_thing(self.game.player_id)
650 center = player.position
651 if self.mode.shows_info:
652 center = self.explorer
653 center = YX(center.y, center.x * 2)
654 offset = center - window_center
655 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
657 term_y = max(0, -offset.y)
658 term_x = max(0, -offset.x)
659 map_y = max(0, offset.y)
660 map_x = max(0, offset.x)
661 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
662 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
663 safe_addstr(term_y, term_x, to_draw)
668 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
669 self.mode.help_intro)
670 if self.mode.name == 'play':
671 content += "Available actions:\n"
672 if 'MOVE' in self.game.tasks:
673 content += "[%s] – move player\n" % ','.join(self.movement_keys)
674 if 'PICK_UP' in self.game.tasks:
675 content += "[%s] – take thing under player\n" % self.keys['take_thing']
676 if 'DROP' in self.game.tasks:
677 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
678 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
679 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
680 content += '[%s] – teleport to other space\n' % self.keys['teleport']
682 elif self.mode.name == 'study':
683 content += 'Available actions:\n'
684 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
685 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
687 elif self.mode.name == 'chat':
688 content += '/nick NAME – re-name yourself to NAME\n'
689 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
690 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
691 content += self.mode.list_available_modes(self)
692 for i in range(self.size.y):
694 self.window_width * (not self.mode.has_input_prompt),
695 ' '*self.window_width)
697 for line in content.split('\n'):
698 lines += msg_into_lines_of_width(line, self.window_width)
699 for i in range(len(lines)):
703 self.window_width * (not self.mode.has_input_prompt),
708 if self.mode.has_input_prompt:
711 if self.mode.shows_info:
716 if not self.mode.is_intro:
722 curses.curs_set(False) # hide cursor
723 curses.use_default_colors();
726 self.explorer = YX(0, 0)
729 interval = datetime.timedelta(seconds=5)
730 last_ping = datetime.datetime.now() - interval
732 if self.disconnected and self.force_instant_connect:
733 self.force_instant_connect = False
735 now = datetime.datetime.now()
736 if now - last_ping > interval:
737 if self.disconnected:
747 self.do_refresh = False
750 msg = self.queue.get(block=False)
755 key = stdscr.getkey()
756 self.do_refresh = True
759 self.show_help = False
760 if key == 'KEY_RESIZE':
762 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
763 self.input_ = self.input_[:-1]
764 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
765 self.show_help = True
767 self.restore_input_values()
768 elif self.mode.has_input_prompt and key != '\n': # Return key
770 max_length = self.window_width * self.size.y - len(input_prompt) - 1
771 if len(self.input_) > max_length:
772 self.input_ = self.input_[:max_length]
773 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
774 self.show_help = True
775 elif self.mode.name == 'login' and key == '\n':
776 self.login_name = self.input_
777 self.send('LOGIN ' + quote(self.input_))
779 elif self.mode.name == 'control_pw_pw' and key == '\n':
780 if self.input_ == '':
781 self.log_msg('@ aborted')
783 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
785 self.switch_mode('play')
786 elif self.mode.name == 'password' and key == '\n':
787 if self.input_ == '':
789 self.password = self.input_
791 self.switch_mode('play')
792 elif self.mode.name == 'admin' and key == '\n':
793 self.send('BECOME_ADMIN ' + quote(self.input_))
795 self.switch_mode('play')
796 elif self.mode.name == 'chat' and key == '\n':
797 if self.input_ == '':
799 if self.input_[0] == '/': # FIXME fails on empty input
800 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
801 self.switch_mode('play')
802 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
803 self.switch_mode('study')
804 elif self.input_.startswith('/nick'):
805 tokens = self.input_.split(maxsplit=1)
807 self.send('NICK ' + quote(tokens[1]))
809 self.log_msg('? need login name')
811 self.log_msg('? unknown command')
813 self.send('ALL ' + quote(self.input_))
815 elif self.mode.name == 'annotate' and key == '\n':
816 if self.input_ == '':
818 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
819 quote(self.password)))
821 self.switch_mode('play')
822 elif self.mode.name == 'portal' and key == '\n':
823 if self.input_ == '':
825 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
826 quote(self.password)))
828 self.switch_mode('play')
829 elif self.mode.name == 'study':
830 if self.mode.mode_switch_on_key(self, key):
832 elif key == self.keys['toggle_map_mode']:
833 if self.map_mode == 'terrain':
834 self.map_mode = 'annotations'
835 elif self.map_mode == 'annotations':
836 self.map_mode = 'control'
838 self.map_mode = 'terrain'
839 elif key in self.movement_keys:
840 move_explorer(self.movement_keys[key])
841 elif self.mode.name == 'play':
842 if self.mode.mode_switch_on_key(self, key):
844 if key == self.keys['flatten'] and\
845 'FLATTEN_SURROUNDINGS' in self.game.tasks:
846 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
847 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
848 self.send('TASK:PICK_UP')
849 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
850 self.send('TASK:DROP')
851 elif key == self.keys['teleport']:
852 player = self.game.get_thing(self.game.player_id)
853 if player.position in self.game.portals:
854 self.host = self.game.portals[player.position]
858 self.log_msg('? not standing on portal')
859 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
860 self.send('TASK:MOVE ' + self.movement_keys[key])
861 elif self.mode.name == 'edit':
862 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
863 self.switch_mode('play')
864 elif self.mode.name == 'control_pw_type':
865 self.tile_control_char = key
866 self.switch_mode('control_pw_pw')
868 #TUI('localhost:5000')
869 TUI('wss://plomlompom.com/rogue_chat/')