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
14 'play': 'This mode allows you to interact with the map.',
15 '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.',
16 '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.',
17 'control_pw_type': '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!',
18 'control_pw_pw': '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.',
19 '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.',
20 '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.',
21 '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:',
22 'login': 'Pick your player name.',
23 'waiting_for_server': 'Waiting for a server response.',
24 'post_login_wait': 'Waiting for a server response.',
25 '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.',
26 'admin': 'This mode allows you to become admin if you know an admin password.'
29 from ws4py.client import WebSocketBaseClient
30 class WebSocketClient(WebSocketBaseClient):
32 def __init__(self, recv_handler, *args, **kwargs):
33 super().__init__(*args, **kwargs)
34 self.recv_handler = recv_handler
37 def received_message(self, message):
39 message = str(message)
40 self.recv_handler(message)
43 def plom_closed(self):
44 return self.client_terminated
46 from plomrogue.io_tcp import PlomSocket
47 class PlomSocketClient(PlomSocket):
49 def __init__(self, recv_handler, url):
51 self.recv_handler = recv_handler
52 host, port = url.split(':')
53 super().__init__(socket.create_connection((host, port)))
61 for msg in self.recv():
63 self.socket = ssl.wrap_socket(self.socket)
65 self.recv_handler(msg)
66 except BrokenSocketConnection:
67 pass # we assume socket will be known as dead by now
69 def cmd_TURN(game, n):
75 game.turn_complete = False
76 cmd_TURN.argtypes = 'int:nonneg'
78 def cmd_LOGIN_OK(game):
79 game.tui.switch_mode('post_login_wait')
80 game.tui.send('GET_GAMESTATE')
81 game.tui.log_msg('@ welcome')
82 cmd_LOGIN_OK.argtypes = ''
84 def cmd_CHAT(game, msg):
85 game.tui.log_msg('# ' + msg)
86 game.tui.do_refresh = True
87 cmd_CHAT.argtypes = 'string'
89 def cmd_PLAYER_ID(game, player_id):
90 game.player_id = player_id
91 cmd_PLAYER_ID.argtypes = 'int:nonneg'
93 def cmd_THING(game, yx, thing_type, thing_id):
94 t = game.get_thing(thing_id)
96 t = ThingBase(game, thing_id)
100 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
102 def cmd_THING_NAME(game, thing_id, name):
103 t = game.get_thing(thing_id)
106 cmd_THING_NAME.argtypes = 'int:nonneg string'
108 def cmd_THING_CHAR(game, thing_id, c):
109 t = game.get_thing(thing_id)
112 cmd_THING_CHAR.argtypes = 'int:nonneg char'
114 def cmd_MAP(game, geometry, size, content):
115 map_geometry_class = globals()['MapGeometry' + geometry]
116 game.map_geometry = map_geometry_class(size)
117 game.map_content = content
118 if type(game.map_geometry) == MapGeometrySquare:
119 game.tui.movement_keys = {
120 game.tui.keys['square_move_up']: 'UP',
121 game.tui.keys['square_move_left']: 'LEFT',
122 game.tui.keys['square_move_down']: 'DOWN',
123 game.tui.keys['square_move_right']: 'RIGHT',
125 elif type(game.map_geometry) == MapGeometryHex:
126 game.tui.movement_keys = {
127 game.tui.keys['hex_move_upleft']: 'UPLEFT',
128 game.tui.keys['hex_move_upright']: 'UPRIGHT',
129 game.tui.keys['hex_move_right']: 'RIGHT',
130 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
131 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
132 game.tui.keys['hex_move_left']: 'LEFT',
134 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
136 def cmd_FOV(game, content):
138 cmd_FOV.argtypes = 'string'
140 def cmd_MAP_CONTROL(game, content):
141 game.map_control_content = content
142 cmd_MAP_CONTROL.argtypes = 'string'
144 def cmd_GAME_STATE_COMPLETE(game):
145 if game.tui.mode.name == 'post_login_wait':
146 game.tui.switch_mode('play')
147 if game.tui.mode.shows_info:
148 game.tui.query_info()
149 game.turn_complete = True
150 game.tui.do_refresh = True
151 cmd_GAME_STATE_COMPLETE.argtypes = ''
153 def cmd_PORTAL(game, position, msg):
154 game.portals[position] = msg
155 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
157 def cmd_PLAY_ERROR(game, msg):
158 game.tui.log_msg('? ' + msg)
159 game.tui.flash = True
160 game.tui.do_refresh = True
161 cmd_PLAY_ERROR.argtypes = 'string'
163 def cmd_GAME_ERROR(game, msg):
164 game.tui.log_msg('? game error: ' + msg)
165 game.tui.do_refresh = True
166 cmd_GAME_ERROR.argtypes = 'string'
168 def cmd_ARGUMENT_ERROR(game, msg):
169 game.tui.log_msg('? syntax error: ' + msg)
170 game.tui.do_refresh = True
171 cmd_ARGUMENT_ERROR.argtypes = 'string'
173 def cmd_ANNOTATION_HINT(game, position):
174 game.info_hints += [position]
175 cmd_ANNOTATION_HINT.argtypes = 'yx_tuple:nonneg'
177 def cmd_ANNOTATION(game, position, msg):
178 game.info_db[position] = msg
179 game.tui.restore_input_values()
180 if game.tui.mode.shows_info:
181 game.tui.do_refresh = True
182 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
184 def cmd_TASKS(game, tasks_comma_separated):
185 game.tasks = tasks_comma_separated.split(',')
186 cmd_TASKS.argtypes = 'string'
188 def cmd_THING_TYPE(game, thing_type, symbol_hint):
189 game.thing_types[thing_type] = symbol_hint
190 cmd_THING_TYPE.argtypes = 'string char'
192 def cmd_TERRAIN(game, terrain_char, terrain_desc):
193 game.terrains[terrain_char] = terrain_desc
194 cmd_TERRAIN.argtypes = 'char string'
198 cmd_PONG.argtypes = ''
200 class Game(GameBase):
201 turn_complete = False
205 def __init__(self, *args, **kwargs):
206 super().__init__(*args, **kwargs)
207 self.register_command(cmd_LOGIN_OK)
208 self.register_command(cmd_PONG)
209 self.register_command(cmd_CHAT)
210 self.register_command(cmd_PLAYER_ID)
211 self.register_command(cmd_TURN)
212 self.register_command(cmd_THING)
213 self.register_command(cmd_THING_TYPE)
214 self.register_command(cmd_THING_NAME)
215 self.register_command(cmd_THING_CHAR)
216 self.register_command(cmd_TERRAIN)
217 self.register_command(cmd_MAP)
218 self.register_command(cmd_MAP_CONTROL)
219 self.register_command(cmd_PORTAL)
220 self.register_command(cmd_ANNOTATION)
221 self.register_command(cmd_ANNOTATION_HINT)
222 self.register_command(cmd_GAME_STATE_COMPLETE)
223 self.register_command(cmd_ARGUMENT_ERROR)
224 self.register_command(cmd_GAME_ERROR)
225 self.register_command(cmd_PLAY_ERROR)
226 self.register_command(cmd_TASKS)
227 self.register_command(cmd_FOV)
228 self.map_content = ''
235 def get_string_options(self, string_option_type):
236 if string_option_type == 'map_geometry':
237 return ['Hex', 'Square']
238 elif string_option_type == 'thing_type':
239 return self.thing_types.keys()
242 def get_command(self, command_name):
243 from functools import partial
244 f = partial(self.commands[command_name], self)
245 f.argtypes = self.commands[command_name].argtypes
252 def __init__(self, name, has_input_prompt=False,
253 shows_info=False, is_intro = False,
254 is_single_char_entry=False):
256 self.has_input_prompt = has_input_prompt
257 self.shows_info = shows_info
258 self.is_intro = is_intro
259 self.help_intro = mode_helps[name]
260 self.is_single_char_entry = is_single_char_entry
262 def __init__(self, host):
266 self.mode_play = self.Mode('play')
267 self.mode_study = self.Mode('study', shows_info=True)
268 self.mode_edit = self.Mode('edit', is_single_char_entry=True)
269 self.mode_control_pw_type = self.Mode('control_pw_type',
270 is_single_char_entry=True)
271 self.mode_control_pw_pw = self.Mode('control_pw_pw',
272 has_input_prompt=True)
273 self.mode_annotate = self.Mode('annotate', has_input_prompt=True,
275 self.mode_portal = self.Mode('portal', has_input_prompt=True,
277 self.mode_chat = self.Mode('chat', has_input_prompt=True)
278 self.mode_waiting_for_server = self.Mode('waiting_for_server',
280 self.mode_login = self.Mode('login', has_input_prompt=True,
282 self.mode_post_login_wait = self.Mode('post_login_wait',
284 self.mode_password = self.Mode('password', has_input_prompt=True)
285 self.mode_admin = self.Mode('admin', has_input_prompt=True)
288 self.parser = Parser(self.game)
290 self.do_refresh = True
291 self.queue = queue.Queue()
292 self.login_name = None
293 self.map_mode = 'terrain'
294 self.password = 'foo'
295 self.switch_mode('waiting_for_server')
297 'switch_to_chat': 't',
298 'switch_to_play': 'p',
299 'switch_to_password': 'P',
300 'switch_to_annotate': 'M',
301 'switch_to_portal': 'T',
302 'switch_to_study': '?',
303 'switch_to_edit': 'm',
304 'switch_to_admin': 'A',
305 'switch_to_control_pw': 'C',
311 'toggle_map_mode': 'M',
312 'hex_move_upleft': 'w',
313 'hex_move_upright': 'e',
314 'hex_move_right': 'd',
315 'hex_move_downright': 'x',
316 'hex_move_downleft': 'y',
317 'hex_move_left': 'a',
318 'square_move_up': 'w',
319 'square_move_left': 'a',
320 'square_move_down': 's',
321 'square_move_right': 'd',
323 if os.path.isfile('config.json'):
324 with open('config.json', 'r') as f:
325 keys_conf = json.loads(f.read())
327 self.keys[k] = keys_conf[k]
328 self.show_help = False
329 self.disconnected = True
330 self.force_instant_connect = True
331 self.input_lines = []
334 curses.wrapper(self.loop)
338 def handle_recv(msg):
344 self.log_msg('@ attempting connect')
345 socket_client_class = PlomSocketClient
346 if self.host.startswith('ws://') or self.host.startswith('wss://'):
347 socket_client_class = WebSocketClient
349 self.socket = socket_client_class(handle_recv, self.host)
350 self.socket_thread = threading.Thread(target=self.socket.run)
351 self.socket_thread.start()
352 self.disconnected = False
353 self.game.thing_types = {}
354 self.game.terrains = {}
355 self.socket.send('TASKS')
356 self.socket.send('TERRAINS')
357 self.socket.send('THING_TYPES')
358 self.switch_mode('login')
359 except ConnectionRefusedError:
360 self.log_msg('@ server connect failure')
361 self.disconnected = True
362 self.switch_mode('waiting_for_server')
363 self.do_refresh = True
366 self.log_msg('@ attempting reconnect')
368 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
369 # conditions with ws4py, find out what exactly
370 self.switch_mode('waiting_for_server')
375 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
376 raise BrokenSocketConnection
377 self.socket.send(msg)
378 except (BrokenPipeError, BrokenSocketConnection):
379 self.log_msg('@ server disconnected :(')
380 self.disconnected = True
381 self.force_instant_connect = True
382 self.do_refresh = True
384 def log_msg(self, msg):
386 if len(self.log) > 100:
387 self.log = self.log[-100:]
389 def query_info(self):
390 self.send('GET_ANNOTATION ' + str(self.explorer))
392 def restore_input_values(self):
393 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
394 info = self.game.info_db[self.explorer]
397 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
398 self.input_ = self.game.portals[self.explorer]
399 elif self.mode.name == 'password':
400 self.input_ = self.password
402 def switch_mode(self, mode_name):
403 self.map_mode = 'terrain'
404 self.mode = getattr(self, 'mode_' + mode_name)
405 if self.mode.shows_info:
406 player = self.game.get_thing(self.game.player_id)
407 self.explorer = YX(player.position.y, player.position.x)
409 if self.mode.is_single_char_entry:
410 self.show_help = True
411 if self.mode.name == 'waiting_for_server':
412 self.log_msg('@ waiting for server …')
413 elif self.mode.name == 'login':
415 self.send('LOGIN ' + quote(self.login_name))
417 self.log_msg('@ enter username')
418 elif self.mode.name == 'admin':
419 self.log_msg('@ enter admin password:')
420 elif self.mode.name == 'control_pw_pw':
421 self.log_msg('@ enter tile control password for "%s":' % self.tile_control_char)
422 self.restore_input_values()
424 def loop(self, stdscr):
427 def safe_addstr(y, x, line):
428 if y < self.size.y - 1 or x + len(line) < self.size.x:
429 stdscr.addstr(y, x, line)
430 else: # workaround to <https://stackoverflow.com/q/7063128>
431 cut_i = self.size.x - x - 1
433 last_char = line[cut_i]
434 stdscr.addstr(y, self.size.x - 2, last_char)
435 stdscr.insstr(y, self.size.x - 2, ' ')
436 stdscr.addstr(y, x, cut)
438 def handle_input(msg):
439 command, args = self.parser.parse(msg)
442 def msg_into_lines_of_width(msg, width):
446 for i in range(len(msg)):
447 if x >= width or msg[i] == "\n":
457 def reset_screen_size():
458 self.size = YX(*stdscr.getmaxyx())
459 self.size = self.size - YX(self.size.y % 4, 0)
460 self.size = self.size - YX(0, self.size.x % 4)
461 self.window_width = int(self.size.x / 2)
463 def recalc_input_lines():
464 if not self.mode.has_input_prompt:
465 self.input_lines = []
467 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
470 def move_explorer(direction):
471 target = self.game.map_geometry.move_yx(self.explorer, direction)
473 self.explorer = target
480 for line in self.log:
481 lines += msg_into_lines_of_width(line, self.window_width)
484 max_y = self.size.y - len(self.input_lines)
485 for i in range(len(lines)):
486 if (i >= max_y - height_header):
488 safe_addstr(max_y - i - 1, self.window_width, lines[i])
491 if not self.game.turn_complete:
493 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
494 info = 'outside field of view'
495 if self.game.fov[pos_i] == '.':
496 terrain_char = self.game.map_content[pos_i]
498 if terrain_char in self.game.terrains:
499 terrain_desc = self.game.terrains[terrain_char]
500 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
501 protection = self.game.map_control_content[pos_i]
502 if protection == '.':
503 protection = 'unprotected'
504 info = 'PROTECTION: %s\n' % protection
505 for t in self.game.things:
506 if t.position == self.explorer:
507 info += 'THING: %s / %s' % (t.type_,
508 self.game.thing_types[t.type_])
509 if hasattr(t, 'player_char'):
510 info += t.player_char
511 if hasattr(t, 'name'):
512 info += ' (%s)' % t.name
514 if self.explorer in self.game.portals:
515 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
517 info += 'PORTAL: (none)\n'
518 if self.explorer in self.game.info_db:
519 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
521 info += 'ANNOTATION: waiting …'
522 lines = msg_into_lines_of_width(info, self.window_width)
524 for i in range(len(lines)):
525 y = height_header + i
526 if y >= self.size.y - len(self.input_lines):
528 safe_addstr(y, self.window_width, lines[i])
531 y = self.size.y - len(self.input_lines)
532 for i in range(len(self.input_lines)):
533 safe_addstr(y, self.window_width, self.input_lines[i])
537 if not self.game.turn_complete:
539 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
542 help = "hit [%s] for help" % self.keys['help']
543 if self.mode.has_input_prompt:
544 help = "enter /help for help"
545 safe_addstr(1, self.window_width, 'MODE: %s – %s' % (self.mode.name, help))
548 if not self.game.turn_complete:
551 map_content = self.game.map_content
552 if self.map_mode == 'control':
553 map_content = self.game.map_control_content
554 for y in range(self.game.map_geometry.size.y):
555 start = self.game.map_geometry.size.x * y
556 end = start + self.game.map_geometry.size.x
557 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
558 if self.map_mode == 'annotations':
559 for p in self.game.info_hints:
560 map_lines_split[p.y][p.x] = 'A '
561 elif self.map_mode == 'terrain':
562 for p in self.game.portals.keys():
563 map_lines_split[p.y][p.x] = 'P '
565 for t in self.game.things:
566 symbol = self.game.thing_types[t.type_]
568 if hasattr(t, 'player_char'):
569 meta_char = t.player_char
570 if t.position in used_positions:
572 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
573 used_positions += [t.position]
574 if self.mode.shows_info:
575 map_lines_split[self.explorer.y][self.explorer.x] = '??'
577 if type(self.game.map_geometry) == MapGeometryHex:
579 for line in map_lines_split:
580 map_lines += [indent*' ' + ''.join(line)]
581 indent = 0 if indent else 1
583 for line in map_lines_split:
584 map_lines += [''.join(line)]
585 window_center = YX(int(self.size.y / 2),
586 int(self.window_width / 2))
587 player = self.game.get_thing(self.game.player_id)
588 center = player.position
589 if self.mode.shows_info:
590 center = self.explorer
591 center = YX(center.y, center.x * 2)
592 offset = center - window_center
593 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
595 term_y = max(0, -offset.y)
596 term_x = max(0, -offset.x)
597 map_y = max(0, offset.y)
598 map_x = max(0, offset.x)
599 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
600 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
601 safe_addstr(term_y, term_x, to_draw)
606 content = "%s mode help\n\n%s\n\n" % (self.mode.name,
607 self.mode.help_intro)
608 if self.mode.name == 'play':
609 content += "Available actions:\n"
610 if 'MOVE' in self.game.tasks:
611 content += "[%s] – move player\n" % ','.join(self.movement_keys)
612 if 'PICK_UP' in self.game.tasks:
613 content += "[%s] – take thing under player\n" % self.keys['take_thing']
614 if 'DROP' in self.game.tasks:
615 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
616 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
617 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
618 content += '[%s] – teleport to other space\n' % self.keys['teleport']
619 content += 'Other modes available from here:\n'
620 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
621 content += '[%s] – study mode\n' % self.keys['switch_to_study']
622 content += '[%s] – terrain edit mode\n' % self.keys['switch_to_edit']
623 content += '[%s] – portal edit mode\n' % self.keys['switch_to_portal']
624 content += '[%s] – annotation mode\n' % self.keys['switch_to_annotate']
625 content += '[%s] – password input mode\n' % self.keys['switch_to_password']
626 content += '[%s] – become admin\n' % self.keys['switch_to_admin']
627 content += '[%s] – change tile control password' % self.keys['switch_to_control_pw']
629 elif self.mode.name == 'study':
630 content += 'Available actions:\n'
631 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
632 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
633 content += '\n\nOther modes available from here:'
634 content += '[%s] – chat mode\n' % self.keys['switch_to_chat']
635 content += '[%s] – play mode\n' % self.keys['switch_to_play']
636 elif self.mode.name == 'chat':
637 content += '/nick NAME – re-name yourself to NAME\n'
638 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
639 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
640 for i in range(self.size.y):
642 self.window_width * (not self.mode.has_input_prompt),
643 ' '*self.window_width)
645 for line in content.split('\n'):
646 lines += msg_into_lines_of_width(line, self.window_width)
647 for i in range(len(lines)):
651 self.window_width * (not self.mode.has_input_prompt),
656 if self.mode.has_input_prompt:
659 if self.mode.shows_info:
664 if not self.mode.is_intro:
670 curses.curs_set(False) # hide cursor
671 curses.use_default_colors();
674 self.explorer = YX(0, 0)
677 interval = datetime.timedelta(seconds=5)
678 last_ping = datetime.datetime.now() - interval
680 if self.disconnected and self.force_instant_connect:
681 self.force_instant_connect = False
683 now = datetime.datetime.now()
684 if now - last_ping > interval:
685 if self.disconnected:
695 self.do_refresh = False
698 msg = self.queue.get(block=False)
703 key = stdscr.getkey()
704 self.do_refresh = True
707 self.show_help = False
708 if key == 'KEY_RESIZE':
710 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
711 self.input_ = self.input_[:-1]
712 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
713 self.show_help = True
715 self.restore_input_values()
716 elif self.mode.has_input_prompt and key != '\n': # Return key
718 max_length = self.window_width * self.size.y - len(input_prompt) - 1
719 if len(self.input_) > max_length:
720 self.input_ = self.input_[:max_length]
721 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
722 self.show_help = True
723 elif self.mode.name == 'login' and key == '\n':
724 self.login_name = self.input_
725 self.send('LOGIN ' + quote(self.input_))
727 elif self.mode.name == 'control_pw_pw' and key == '\n':
728 if self.input_ == '':
729 self.log_msg('@ aborted')
731 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
733 self.switch_mode('play')
734 elif self.mode.name == 'password' and key == '\n':
735 if self.input_ == '':
737 self.password = self.input_
739 self.switch_mode('play')
740 elif self.mode.name == 'admin' and key == '\n':
741 self.send('BECOME_ADMIN ' + quote(self.input_))
743 self.switch_mode('play')
744 elif self.mode.name == 'chat' and key == '\n':
745 if self.input_ == '':
747 if self.input_[0] == '/': # FIXME fails on empty input
748 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
749 self.switch_mode('play')
750 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
751 self.switch_mode('study')
752 elif self.input_.startswith('/nick'):
753 tokens = self.input_.split(maxsplit=1)
755 self.send('NICK ' + quote(tokens[1]))
757 self.log_msg('? need login name')
759 self.log_msg('? unknown command')
761 self.send('ALL ' + quote(self.input_))
763 elif self.mode.name == 'annotate' and key == '\n':
764 if self.input_ == '':
766 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
767 quote(self.password)))
769 self.switch_mode('play')
770 elif self.mode.name == 'portal' and key == '\n':
771 if self.input_ == '':
773 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
774 quote(self.password)))
776 self.switch_mode('play')
777 elif self.mode.name == 'study':
778 if key == self.keys['switch_to_chat']:
779 self.switch_mode('chat')
780 elif key == self.keys['switch_to_play']:
781 self.switch_mode('play')
782 elif key == self.keys['toggle_map_mode']:
783 if self.map_mode == 'terrain':
784 self.map_mode = 'annotations'
785 elif self.map_mode == 'annotations':
786 self.map_mode = 'control'
788 self.map_mode = 'terrain'
789 elif key in self.movement_keys:
790 move_explorer(self.movement_keys[key])
791 elif self.mode.name == 'play':
792 if key == self.keys['switch_to_chat']:
793 self.switch_mode('chat')
794 elif key == self.keys['switch_to_study']:
795 self.switch_mode('study')
796 elif key == self.keys['switch_to_annotate']:
797 self.switch_mode('annotate')
798 elif key == self.keys['switch_to_portal']:
799 self.switch_mode('portal')
800 elif key == self.keys['switch_to_password']:
801 self.switch_mode('password')
802 elif key == self.keys['switch_to_admin']:
803 self.switch_mode('admin')
804 elif key == self.keys['switch_to_control_pw']:
805 self.switch_mode('control_pw_type')
806 if key == self.keys['switch_to_edit'] and\
807 'WRITE' in self.game.tasks:
808 self.switch_mode('edit')
809 elif key == self.keys['flatten'] and\
810 'FLATTEN_SURROUNDINGS' in self.game.tasks:
811 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
812 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
813 self.send('TASK:PICK_UP')
814 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
815 self.send('TASK:DROP')
816 elif key == self.keys['teleport']:
817 player = self.game.get_thing(self.game.player_id)
818 if player.position in self.game.portals:
819 self.host = self.game.portals[player.position]
823 self.log_msg('? not standing on portal')
824 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
825 self.send('TASK:MOVE ' + self.movement_keys[key])
826 elif self.mode.name == 'edit':
827 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
828 self.switch_mode('play')
829 elif self.mode.name == 'control_pw_type':
830 self.tile_control_char = key
831 self.switch_mode('control_pw_pw')
833 #TUI('localhost:5000')
834 TUI('wss://plomlompom.com/rogue_chat/')